Carga
Carga
This commit is contained in:
@@ -0,0 +1,486 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2021 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for chart legend.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
import A from '../../Core/Animation/AnimationUtilities.js';
|
||||
var animObject = A.animObject;
|
||||
import H from '../../Core/Globals.js';
|
||||
var doc = H.doc;
|
||||
import Legend from '../../Core/Legend/Legend.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, fireEvent = U.fireEvent, isNumber = U.isNumber, pick = U.pick, syncTimeout = U.syncTimeout;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
||||
import CU from '../Utils/ChartUtilities.js';
|
||||
var getChartTitle = CU.getChartTitle;
|
||||
import HU from '../Utils/HTMLUtilities.js';
|
||||
var stripHTMLTags = HU.stripHTMLTagsFromString, addClass = HU.addClass, removeClass = HU.removeClass;
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function scrollLegendToItem(legend, itemIx) {
|
||||
var itemPage = legend.allItems[itemIx].pageIx, curPage = legend.currentPage;
|
||||
if (typeof itemPage !== 'undefined' && itemPage + 1 !== curPage) {
|
||||
legend.scroll(1 + itemPage - curPage);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function shouldDoLegendA11y(chart) {
|
||||
var items = chart.legend && chart.legend.allItems, legendA11yOptions = (chart.options.legend.accessibility || {}), unsupportedColorAxis = chart.colorAxis && chart.colorAxis.some(function (c) { return !c.dataClasses || !c.dataClasses.length; });
|
||||
return !!(items && items.length &&
|
||||
!unsupportedColorAxis &&
|
||||
legendA11yOptions.enabled !== false);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function setLegendItemHoverState(hoverActive, legendItem) {
|
||||
legendItem.setState(hoverActive ? 'hover' : '', true);
|
||||
['legendGroup', 'legendItem', 'legendSymbol'].forEach(function (i) {
|
||||
var obj = legendItem[i];
|
||||
var el = obj && obj.element || obj;
|
||||
if (el) {
|
||||
fireEvent(el, hoverActive ? 'mouseover' : 'mouseout');
|
||||
}
|
||||
});
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Class
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* The LegendComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.LegendComponent
|
||||
*/
|
||||
var LegendComponent = /** @class */ (function (_super) {
|
||||
__extends(LegendComponent, _super);
|
||||
function LegendComponent() {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this.highlightedLegendItemIx = NaN;
|
||||
_this.proxyGroup = null;
|
||||
return _this;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Init the component
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.init = function () {
|
||||
var component = this;
|
||||
this.recreateProxies();
|
||||
// Note: Chart could create legend dynamically, so events cannot be
|
||||
// tied to the component's chart's current legend.
|
||||
// @todo 1. attach component to created legends
|
||||
// @todo 2. move listeners to composition and access `this.component`
|
||||
this.addEvent(Legend, 'afterScroll', function () {
|
||||
if (this.chart === component.chart) {
|
||||
component.proxyProvider.updateGroupProxyElementPositions('legend');
|
||||
component.updateLegendItemProxyVisibility();
|
||||
if (component.highlightedLegendItemIx > -1) {
|
||||
this.chart.highlightLegendItem(component.highlightedLegendItemIx);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.addEvent(Legend, 'afterPositionItem', function (e) {
|
||||
if (this.chart === component.chart && this.chart.renderer) {
|
||||
component.updateProxyPositionForItem(e.item);
|
||||
}
|
||||
});
|
||||
this.addEvent(Legend, 'afterRender', function () {
|
||||
if (this.chart === component.chart &&
|
||||
this.chart.renderer &&
|
||||
component.recreateProxies()) {
|
||||
syncTimeout(function () { return component.proxyProvider
|
||||
.updateGroupProxyElementPositions('legend'); }, animObject(pick(this.chart.renderer.globalAnimation, true)).duration);
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Update visibility of legend items when using paged legend
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.updateLegendItemProxyVisibility = function () {
|
||||
var chart = this.chart;
|
||||
var legend = chart.legend;
|
||||
var items = legend.allItems || [];
|
||||
var curPage = legend.currentPage || 1;
|
||||
var clipHeight = legend.clipHeight || 0;
|
||||
items.forEach(function (item) {
|
||||
if (item.a11yProxyElement) {
|
||||
var hasPages = legend.pages && legend.pages.length;
|
||||
var proxyEl = item.a11yProxyElement.element;
|
||||
var hide = false;
|
||||
if (hasPages) {
|
||||
var itemPage = item.pageIx || 0;
|
||||
var y = item._legendItemPos ? item._legendItemPos[1] : 0;
|
||||
var h = item.legendItem ?
|
||||
Math.round(item.legendItem.getBBox().height) :
|
||||
0;
|
||||
hide = y + h - legend.pages[itemPage] > clipHeight ||
|
||||
itemPage !== curPage - 1;
|
||||
}
|
||||
if (hide) {
|
||||
if (chart.styledMode) {
|
||||
addClass(proxyEl, 'highcharts-a11y-invisible');
|
||||
}
|
||||
else {
|
||||
proxyEl.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
else {
|
||||
removeClass(proxyEl, 'highcharts-a11y-invisible');
|
||||
proxyEl.style.visibility = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.onChartRender = function () {
|
||||
if (!shouldDoLegendA11y(this.chart)) {
|
||||
this.removeProxies();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.highlightAdjacentLegendPage = function (direction) {
|
||||
var chart = this.chart;
|
||||
var legend = chart.legend;
|
||||
var curPageIx = legend.currentPage || 1;
|
||||
var newPageIx = curPageIx + direction;
|
||||
var pages = legend.pages || [];
|
||||
if (newPageIx > 0 && newPageIx <= pages.length) {
|
||||
var len = legend.allItems.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (legend.allItems[i].pageIx + 1 === newPageIx) {
|
||||
var res = chart.highlightLegendItem(i);
|
||||
if (res) {
|
||||
this.highlightedLegendItemIx = i;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.updateProxyPositionForItem = function (item) {
|
||||
if (item.a11yProxyElement) {
|
||||
item.a11yProxyElement.refreshPosition();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Returns false if legend a11y is disabled and proxies were not created,
|
||||
* true otherwise.
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.recreateProxies = function () {
|
||||
var focusedElement = doc.activeElement;
|
||||
var proxyGroup = this.proxyGroup;
|
||||
var shouldRestoreFocus = focusedElement && proxyGroup &&
|
||||
proxyGroup.contains(focusedElement);
|
||||
this.removeProxies();
|
||||
if (shouldDoLegendA11y(this.chart)) {
|
||||
this.addLegendProxyGroup();
|
||||
this.proxyLegendItems();
|
||||
this.updateLegendItemProxyVisibility();
|
||||
this.updateLegendTitle();
|
||||
if (shouldRestoreFocus) {
|
||||
this.chart.highlightLegendItem(this.highlightedLegendItemIx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.removeProxies = function () {
|
||||
this.proxyProvider.removeGroup('legend');
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.updateLegendTitle = function () {
|
||||
var chart = this.chart;
|
||||
var legendTitle = stripHTMLTags((chart.legend &&
|
||||
chart.legend.options.title &&
|
||||
chart.legend.options.title.text ||
|
||||
'').replace(/<br ?\/?>/g, ' '));
|
||||
var legendLabel = chart.langFormat('accessibility.legend.legendLabel' + (legendTitle ? '' : 'NoTitle'), {
|
||||
chart: chart,
|
||||
legendTitle: legendTitle,
|
||||
chartTitle: getChartTitle(chart)
|
||||
});
|
||||
this.proxyProvider.updateGroupAttrs('legend', {
|
||||
'aria-label': legendLabel
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.addLegendProxyGroup = function () {
|
||||
var a11yOptions = this.chart.options.accessibility;
|
||||
var groupRole = a11yOptions.landmarkVerbosity === 'all' ?
|
||||
'region' : null;
|
||||
this.proxyGroup = this.proxyProvider.addGroup('legend', 'ul', {
|
||||
// Filled by updateLegendTitle, to keep up to date without
|
||||
// recreating group
|
||||
'aria-label': '_placeholder_',
|
||||
role: groupRole
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.proxyLegendItems = function () {
|
||||
var component = this, items = (this.chart.legend &&
|
||||
this.chart.legend.allItems || []);
|
||||
items.forEach(function (item) {
|
||||
if (item.legendItem && item.legendItem.element) {
|
||||
component.proxyLegendItem(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.BubbleLegendItem|Point|Highcharts.Series} item
|
||||
*/
|
||||
LegendComponent.prototype.proxyLegendItem = function (item) {
|
||||
if (!item.legendItem || !item.legendGroup) {
|
||||
return;
|
||||
}
|
||||
var itemLabel = this.chart.langFormat('accessibility.legend.legendItem', {
|
||||
chart: this.chart,
|
||||
itemName: stripHTMLTags(item.name),
|
||||
item: item
|
||||
});
|
||||
var attribs = {
|
||||
tabindex: -1,
|
||||
'aria-pressed': item.visible,
|
||||
'aria-label': itemLabel
|
||||
};
|
||||
// Considers useHTML
|
||||
var proxyPositioningElement = item.legendGroup.div ?
|
||||
item.legendItem :
|
||||
item.legendGroup;
|
||||
item.a11yProxyElement = this.proxyProvider.addProxyElement('legend', {
|
||||
click: item.legendItem,
|
||||
visual: proxyPositioningElement.element
|
||||
}, attribs);
|
||||
};
|
||||
/**
|
||||
* Get keyboard navigation handler for this component.
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.getKeyboardNavigation = function () {
|
||||
var keys = this.keyCodes, component = this, chart = this.chart;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[
|
||||
[keys.left, keys.right, keys.up, keys.down],
|
||||
function (keyCode) {
|
||||
return component.onKbdArrowKey(this, keyCode);
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.enter, keys.space],
|
||||
function (keyCode) {
|
||||
if (H.isFirefox && keyCode === keys.space) { // #15520
|
||||
return this.response.success;
|
||||
}
|
||||
return component.onKbdClick(this);
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.pageDown, keys.pageUp],
|
||||
function (keyCode) {
|
||||
var direction = keyCode === keys.pageDown ? 1 : -1;
|
||||
component.highlightAdjacentLegendPage(direction);
|
||||
return this.response.success;
|
||||
}
|
||||
]
|
||||
],
|
||||
validate: function () {
|
||||
return component.shouldHaveLegendNavigation();
|
||||
},
|
||||
init: function () {
|
||||
chart.highlightLegendItem(0);
|
||||
component.highlightedLegendItemIx = 0;
|
||||
},
|
||||
terminate: function () {
|
||||
component.highlightedLegendItemIx = -1;
|
||||
chart.legend.allItems.forEach(function (item) { return setLegendItemHoverState(false, item); });
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Arrow key navigation
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.onKbdArrowKey = function (keyboardNavigationHandler, keyCode) {
|
||||
var keys = this.keyCodes, response = keyboardNavigationHandler.response, chart = this.chart, a11yOptions = chart.options.accessibility, numItems = chart.legend.allItems.length, direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1;
|
||||
var res = chart.highlightLegendItem(this.highlightedLegendItemIx + direction);
|
||||
if (res) {
|
||||
this.highlightedLegendItemIx += direction;
|
||||
return response.success;
|
||||
}
|
||||
if (numItems > 1 &&
|
||||
a11yOptions.keyboardNavigation.wrapAround) {
|
||||
keyboardNavigationHandler.init(direction);
|
||||
return response.success;
|
||||
}
|
||||
return response.success;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @return {number} Response code
|
||||
*/
|
||||
LegendComponent.prototype.onKbdClick = function (keyboardNavigationHandler) {
|
||||
var legendItem = this.chart.legend.allItems[this.highlightedLegendItemIx];
|
||||
if (legendItem && legendItem.a11yProxyElement) {
|
||||
legendItem.a11yProxyElement.click();
|
||||
}
|
||||
return keyboardNavigationHandler.response.success;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LegendComponent.prototype.shouldHaveLegendNavigation = function () {
|
||||
if (!shouldDoLegendA11y(this.chart)) {
|
||||
return false;
|
||||
}
|
||||
var chart = this.chart, legendOptions = chart.options.legend || {}, legendA11yOptions = (legendOptions.accessibility || {});
|
||||
return !!(chart.legend.display &&
|
||||
legendA11yOptions.keyboardNavigation &&
|
||||
legendA11yOptions.keyboardNavigation.enabled);
|
||||
};
|
||||
return LegendComponent;
|
||||
}(AccessibilityComponent));
|
||||
/* *
|
||||
*
|
||||
* Class Namespace
|
||||
*
|
||||
* */
|
||||
(function (LegendComponent) {
|
||||
/* *
|
||||
*
|
||||
* Declarations
|
||||
*
|
||||
* */
|
||||
/* *
|
||||
*
|
||||
* Constants
|
||||
*
|
||||
* */
|
||||
var composedClasses = [];
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Highlight legend item by index.
|
||||
* @private
|
||||
*/
|
||||
function chartHighlightLegendItem(ix) {
|
||||
var items = this.legend.allItems;
|
||||
var oldIx = this.accessibility &&
|
||||
this.accessibility.components.legend.highlightedLegendItemIx;
|
||||
var itemToHighlight = items[ix];
|
||||
if (itemToHighlight) {
|
||||
if (isNumber(oldIx) && items[oldIx]) {
|
||||
setLegendItemHoverState(false, items[oldIx]);
|
||||
}
|
||||
scrollLegendToItem(this.legend, ix);
|
||||
var legendItemProp = itemToHighlight.legendItem;
|
||||
var proxyBtn = itemToHighlight.a11yProxyElement &&
|
||||
itemToHighlight.a11yProxyElement.buttonElement;
|
||||
if (legendItemProp && legendItemProp.element && proxyBtn) {
|
||||
this.setFocusToElement(legendItemProp, proxyBtn);
|
||||
}
|
||||
setLegendItemHoverState(true, itemToHighlight);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function compose(ChartClass, LegendClass) {
|
||||
if (composedClasses.indexOf(ChartClass) === -1) {
|
||||
composedClasses.push(ChartClass);
|
||||
var chartProto = ChartClass.prototype;
|
||||
chartProto.highlightLegendItem = chartHighlightLegendItem;
|
||||
}
|
||||
if (composedClasses.indexOf(LegendClass) === -1) {
|
||||
composedClasses.push(LegendClass);
|
||||
addEvent(LegendClass, 'afterColorizeItem', legendOnAfterColorizeItem);
|
||||
}
|
||||
}
|
||||
LegendComponent.compose = compose;
|
||||
/**
|
||||
* Keep track of pressed state for legend items.
|
||||
* @private
|
||||
*/
|
||||
function legendOnAfterColorizeItem(e) {
|
||||
var chart = this.chart, a11yOptions = chart.options.accessibility, legendItem = e.item;
|
||||
if (a11yOptions.enabled && legendItem && legendItem.a11yProxyElement) {
|
||||
legendItem.a11yProxyElement.buttonElement.setAttribute('aria-pressed', e.visible ? 'true' : 'false');
|
||||
}
|
||||
}
|
||||
})(LegendComponent || (LegendComponent = {}));
|
||||
/* *
|
||||
*
|
||||
* Default Export
|
||||
*
|
||||
* */
|
||||
export default LegendComponent;
|
||||
Reference in New Issue
Block a user