highcharts
Version:
JavaScript charting framework
271 lines (239 loc) • 9.05 kB
JavaScript
/* *
*
* (c) 2009-2019 Øystein Moseng
*
* Accessibility component for the range selector.
*
* License: www.highcharts.com/license
*
* */
'use strict';
import H from '../../../parts/Globals.js';
import AccessibilityComponent from '../AccessibilityComponent.js';
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
/**
* Highlight range selector button by index.
*
* @private
* @function Highcharts.Chart#highlightRangeSelectorButton
*
* @param {number} ix
*
* @return {boolean}
*/
H.Chart.prototype.highlightRangeSelectorButton = function (ix) {
var buttons = this.rangeSelector.buttons;
// Deselect old
if (buttons[this.highlightedRangeSelectorItemIx]) {
buttons[this.highlightedRangeSelectorItemIx].setState(
this.oldRangeSelectorItemState || 0
);
}
// Select new
this.highlightedRangeSelectorItemIx = ix;
if (buttons[ix]) {
this.setFocusToElement(buttons[ix].box, buttons[ix].element);
this.oldRangeSelectorItemState = buttons[ix].state;
buttons[ix].setState(2);
return true;
}
return false;
};
/**
* The RangeSelectorComponent class
*
* @private
* @class
* @name Highcharts.RangeSelectorComponent
* @param {Highcharts.Chart} chart
* Chart object
*/
var RangeSelectorComponent = function (chart) {
this.initBase(chart);
};
RangeSelectorComponent.prototype = new AccessibilityComponent();
H.extend(RangeSelectorComponent.prototype, /** @lends Highcharts.RangeSelectorComponent */ { // eslint-disable-line
/**
* Called on first render/updates to the chart, including options changes.
*/
onChartUpdate: function () {
var chart = this.chart,
component = this,
rangeSelector = chart.rangeSelector;
if (!rangeSelector) {
return;
}
// Make sure buttons are accessible and focusable
if (rangeSelector.buttons && rangeSelector.buttons.length) {
rangeSelector.buttons.forEach(function (button) {
component.unhideElementFromScreenReaders(button.element);
button.element.setAttribute('tabindex', '-1');
button.element.setAttribute('role', 'button');
button.element.setAttribute(
'aria-label',
chart.langFormat(
'accessibility.rangeSelectorButton',
{
chart: chart,
buttonText: button.text && button.text.textStr
}
)
);
});
}
// Make sure input boxes are accessible and focusable
if (rangeSelector.maxInput && rangeSelector.minInput) {
['minInput', 'maxInput'].forEach(function (key, i) {
if (rangeSelector[key]) {
component.unhideElementFromScreenReaders(
rangeSelector[key]
);
rangeSelector[key].setAttribute('tabindex', '-1');
rangeSelector[key].setAttribute('role', 'textbox');
rangeSelector[key].setAttribute(
'aria-label',
chart.langFormat(
'accessibility.rangeSelector' +
(i ? 'MaxInput' : 'MinInput'), { chart: chart }
)
);
}
});
}
},
/**
* Get navigation for the range selector buttons.
* @private
* @return {Highcharts.KeyboardNavigationHandler} The module object.
*/
getRangeSelectorButtonNavigation: function () {
var chart = this.chart,
keys = this.keyCodes,
a11yOptions = chart.options.accessibility,
component = this;
return new KeyboardNavigationHandler(chart, {
keyCodeMap: [
// Left/Right/Up/Down
[[
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 button
if (
!chart.highlightRangeSelectorButton(
chart.highlightedRangeSelectorItemIx + direction
)
) {
// If we failed, handle wrap around/move
if (a11yOptions.keyboardNavigation.wrapAround) {
this.init(direction);
return this.response.success;
}
return this.response[direction > 0 ? 'next' : 'prev'];
}
}],
// Enter/Spacebar
[[
keys.enter, keys.space
], function () {
// Don't allow click if button used to be disabled
if (chart.oldRangeSelectorItemState !== 3) {
component.fakeClickEvent(
chart.rangeSelector.buttons[
chart.highlightedRangeSelectorItemIx
].element
);
}
}]
],
// Only run this module if we have range selector
validate: function () {
return chart.rangeSelector && chart.rangeSelector.buttons &&
chart.rangeSelector.buttons.length;
},
// Focus first/last button
init: function (direction) {
chart.highlightRangeSelectorButton(
direction > 0 ? 0 : chart.rangeSelector.buttons.length - 1
);
}
});
},
/**
* Get navigation for the range selector input boxes.
* @private
* @return {Highcharts.KeyboardNavigationHandler}
* The module object.
*/
getRangeSelectorInputNavigation: function () {
var chart = this.chart,
keys = this.keyCodes;
return new KeyboardNavigationHandler(chart, {
keyCodeMap: [
// Tab/Up/Down
[[
keys.tab, keys.up, keys.down
], function (keyCode, e) {
var direction = (
keyCode === keys.tab && e.shiftKey ||
keyCode === keys.up
) ? -1 : 1,
newIx = chart.highlightedInputRangeIx =
chart.highlightedInputRangeIx + direction;
// Try to highlight next/prev item in list.
if (newIx > 1 || newIx < 0) { // Out of range
return this.response[direction > 0 ? 'next' : 'prev'];
}
chart.rangeSelector[
newIx ? 'maxInput' : 'minInput'
].focus();
return this.response.success;
}]
],
// Only run if we have range selector with input boxes
validate: function () {
var inputVisible = (
chart.rangeSelector &&
chart.rangeSelector.inputGroup &&
chart.rangeSelector.inputGroup.element
.getAttribute('visibility') !== 'hidden'
);
return (
inputVisible &&
chart.options.rangeSelector.inputEnabled !== false &&
chart.rangeSelector.minInput &&
chart.rangeSelector.maxInput
);
},
// Highlight first/last input box
init: function (direction) {
chart.highlightedInputRangeIx = direction > 0 ? 0 : 1;
chart.rangeSelector[
chart.highlightedInputRangeIx ? 'maxInput' : 'minInput'
].focus();
},
// Hide HTML element when leaving boxes
terminate: function () {
var rangeSel = chart.rangeSelector;
if (rangeSel && rangeSel.maxInput && rangeSel.minInput) {
rangeSel.hideInput('max');
rangeSel.hideInput('min');
}
}
});
},
/**
* Get keyboard navigation handlers for this component.
* @return {Array<Highcharts.KeyboardNavigationHandler>}
* List of module objects.
*/
getKeyboardNavigation: function () {
return [
this.getRangeSelectorButtonNavigation(),
this.getRangeSelectorInputNavigation()
];
}
});
export default RangeSelectorComponent;