235 lines
6.5 KiB
JavaScript
235 lines
6.5 KiB
JavaScript
/* *
|
|
*
|
|
* (c) 2009-2019 Øystein Moseng
|
|
*
|
|
* Accessibility component for chart legend.
|
|
*
|
|
* License: www.highcharts.com/license
|
|
*
|
|
* */
|
|
|
|
'use strict';
|
|
|
|
import H from '../../../parts/Globals.js';
|
|
import AccessibilityComponent from '../AccessibilityComponent.js';
|
|
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
|
|
|
|
|
/**
|
|
* Highlight legend item by index.
|
|
*
|
|
* @private
|
|
* @function Highcharts.Chart#highlightLegendItem
|
|
*
|
|
* @param {number} ix
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
H.Chart.prototype.highlightLegendItem = function (ix) {
|
|
var items = this.legend.allItems,
|
|
oldIx = this.highlightedLegendItemIx;
|
|
|
|
if (items[ix]) {
|
|
if (items[oldIx]) {
|
|
H.fireEvent(
|
|
items[oldIx].legendGroup.element,
|
|
'mouseout'
|
|
);
|
|
}
|
|
// Scroll if we have to
|
|
if (items[ix].pageIx !== undefined &&
|
|
items[ix].pageIx + 1 !== this.legend.currentPage) {
|
|
this.legend.scroll(1 + items[ix].pageIx - this.legend.currentPage);
|
|
}
|
|
// Focus
|
|
this.setFocusToElement(
|
|
items[ix].legendItem, items[ix].a11yProxyElement
|
|
);
|
|
H.fireEvent(items[ix].legendGroup.element, 'mouseover');
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Keep track of pressed state for legend items
|
|
H.addEvent(H.Legend, 'afterColorizeItem', function (e) {
|
|
var chart = this.chart,
|
|
a11yOptions = chart.options.accessibility,
|
|
legendItem = e.item;
|
|
if (a11yOptions.enabled && legendItem && legendItem.a11yProxyElement) {
|
|
legendItem.a11yProxyElement.setAttribute(
|
|
'aria-pressed', e.visible ? 'false' : 'true'
|
|
);
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* The LegendComponent class
|
|
*
|
|
* @private
|
|
* @class
|
|
* @name Highcharts.LegendComponent
|
|
* @param {Highcharts.Chart} chart
|
|
* Chart object
|
|
*/
|
|
var LegendComponent = function (chart) {
|
|
this.initBase(chart);
|
|
};
|
|
LegendComponent.prototype = new AccessibilityComponent();
|
|
H.extend(LegendComponent.prototype, /** @lends Highcharts.LegendComponent */ {
|
|
|
|
/**
|
|
* The legend needs updates on every render, in order to update positioning
|
|
* of the proxy overlays.
|
|
*/
|
|
onChartRender: function () {
|
|
var chart = this.chart,
|
|
a11yOptions = chart.options.accessibility,
|
|
items = chart.legend && chart.legend.allItems,
|
|
component = this;
|
|
|
|
// Ignore render after proxy clicked. No need to destroy it, and
|
|
// destroying also kills focus.
|
|
if (component.legendProxyButtonClicked) {
|
|
delete component.legendProxyButtonClicked;
|
|
return;
|
|
}
|
|
|
|
// Always Remove group if exists
|
|
this.removeElement(this.legendProxyGroup);
|
|
|
|
// Skip everything if we do not have legend items, or if we have a
|
|
// color axis
|
|
if (
|
|
!items || !items.length ||
|
|
chart.colorAxis && chart.colorAxis.length ||
|
|
!chart.options.legend.accessibility.enabled
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Add proxy group
|
|
this.legendProxyGroup = this.addProxyGroup({
|
|
'aria-label': chart.langFormat(
|
|
'accessibility.legendLabel'
|
|
),
|
|
'role': a11yOptions.landmarkVerbosity === 'all' ?
|
|
'region' : null
|
|
});
|
|
|
|
// Proxy the legend items
|
|
items.forEach(function (item) {
|
|
if (item.legendItem && item.legendItem.element) {
|
|
item.a11yProxyElement = component.createProxyButton(
|
|
item.legendItem,
|
|
component.legendProxyGroup,
|
|
{
|
|
tabindex: -1,
|
|
'aria-pressed': !item.visible,
|
|
'aria-label': chart.langFormat(
|
|
'accessibility.legendItem',
|
|
{
|
|
chart: chart,
|
|
itemName: component.stripTags(item.name)
|
|
}
|
|
)
|
|
},
|
|
// Consider useHTML
|
|
item.legendGroup.div ? item.legendItem : item.legendGroup,
|
|
// Additional click event (fires first)
|
|
function () {
|
|
// Keep track of when we should ignore next render
|
|
component.legendProxyButtonClicked = true;
|
|
}
|
|
);
|
|
}
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* Get keyboard navigation handler for this component.
|
|
* @return {Highcharts.KeyboardNavigationHandler}
|
|
*/
|
|
getKeyboardNavigation: function () {
|
|
var keys = this.keyCodes,
|
|
component = this,
|
|
chart = this.chart,
|
|
a11yOptions = chart.options.accessibility;
|
|
return new KeyboardNavigationHandler(chart, {
|
|
keyCodeMap: [
|
|
// Arrow key handling
|
|
[[
|
|
keys.left, keys.right, keys.up, keys.down
|
|
], function (keyCode) {
|
|
var direction = (
|
|
keyCode === keys.left || keyCode === keys.up
|
|
) ? -1 : 1;
|
|
|
|
// Try to highlight next/prev legend item
|
|
var res = chart.highlightLegendItem(
|
|
component.highlightedLegendItemIx + direction
|
|
);
|
|
if (res) {
|
|
component.highlightedLegendItemIx += direction;
|
|
return this.response.success;
|
|
}
|
|
|
|
// Failed, can we wrap around?
|
|
if (
|
|
chart.legend.allItems.length > 1 &&
|
|
a11yOptions.keyboardNavigation.wrapAround
|
|
) {
|
|
// Wrap around if we failed and have more than 1 item
|
|
this.init(direction);
|
|
return this.response.success;
|
|
}
|
|
|
|
// No wrap, move
|
|
return this.response[direction > 0 ? 'next' : 'prev'];
|
|
}],
|
|
|
|
// Click item
|
|
[[
|
|
keys.enter, keys.space
|
|
], function () {
|
|
var legendItem = chart.legend.allItems[
|
|
component.highlightedLegendItemIx
|
|
];
|
|
if (legendItem && legendItem.a11yProxyElement) {
|
|
H.fireEvent(legendItem.a11yProxyElement, 'click');
|
|
}
|
|
return this.response.success;
|
|
}]
|
|
],
|
|
|
|
// Only run this module if we have at least one legend - wait for
|
|
// it - item. Don't run if the legend is populated by a colorAxis.
|
|
// Don't run if legend navigation is disabled.
|
|
validate: function () {
|
|
var legendOptions = chart.options.legend;
|
|
return chart.legend && chart.legend.allItems &&
|
|
chart.legend.display &&
|
|
!(chart.colorAxis && chart.colorAxis.length) &&
|
|
legendOptions &&
|
|
legendOptions.accessibility &&
|
|
legendOptions.accessibility.enabled &&
|
|
legendOptions.accessibility.keyboardNavigation &&
|
|
legendOptions.accessibility.keyboardNavigation.enabled;
|
|
},
|
|
|
|
|
|
// Focus first/last item
|
|
init: function (direction) {
|
|
var ix = direction > 0 ? 0 : chart.legend.allItems.length - 1;
|
|
chart.highlightLegendItem(ix);
|
|
component.highlightedLegendItemIx = ix;
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
export default LegendComponent;
|