266 lines
9.4 KiB
JavaScript
266 lines
9.4 KiB
JavaScript
|
/* *
|
||
|
*
|
||
|
* (c) 2009-2020 Øystein Moseng
|
||
|
*
|
||
|
* Accessibility component for the range selector.
|
||
|
*
|
||
|
* License: www.highcharts.com/license
|
||
|
*
|
||
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||
|
*
|
||
|
* */
|
||
|
'use strict';
|
||
|
import H from '../../../parts/Globals.js';
|
||
|
import U from '../../../parts/Utilities.js';
|
||
|
var extend = U.extend;
|
||
|
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||
|
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
||
|
import ChartUtilities from '../utils/chartUtilities.js';
|
||
|
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
|
||
|
import HTMLUtilities from '../utils/htmlUtilities.js';
|
||
|
var setElAttrs = HTMLUtilities.setElAttrs;
|
||
|
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
function shouldRunInputNavigation(chart) {
|
||
|
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 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, curSelectedIx = this.highlightedRangeSelectorItemIx;
|
||
|
// Deselect old
|
||
|
if (typeof curSelectedIx !== 'undefined' && buttons[curSelectedIx]) {
|
||
|
buttons[curSelectedIx].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
|
||
|
*/
|
||
|
var RangeSelectorComponent = function () { };
|
||
|
RangeSelectorComponent.prototype = new AccessibilityComponent();
|
||
|
extend(RangeSelectorComponent.prototype, /** @lends Highcharts.RangeSelectorComponent */ {
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
if (rangeSelector.buttons && rangeSelector.buttons.length) {
|
||
|
rangeSelector.buttons.forEach(function (button) {
|
||
|
unhideChartElementFromAT(chart, button.element);
|
||
|
component.setRangeButtonAttrs(button);
|
||
|
});
|
||
|
}
|
||
|
// Make sure input boxes are accessible and focusable
|
||
|
if (rangeSelector.maxInput && rangeSelector.minInput) {
|
||
|
['minInput', 'maxInput'].forEach(function (key, i) {
|
||
|
var input = rangeSelector[key];
|
||
|
if (input) {
|
||
|
unhideChartElementFromAT(chart, input);
|
||
|
component.setRangeInputAttrs(input, 'accessibility.rangeSelector.' + (i ? 'max' : 'min') +
|
||
|
'InputLabel');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
* @param {Highcharts.SVGElement} button
|
||
|
*/
|
||
|
setRangeButtonAttrs: function (button) {
|
||
|
var chart = this.chart, label = chart.langFormat('accessibility.rangeSelector.buttonText', {
|
||
|
chart: chart,
|
||
|
buttonText: button.text && button.text.textStr
|
||
|
});
|
||
|
setElAttrs(button.element, {
|
||
|
tabindex: -1,
|
||
|
role: 'button',
|
||
|
'aria-label': label
|
||
|
});
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
setRangeInputAttrs: function (input, langKey) {
|
||
|
var chart = this.chart;
|
||
|
setElAttrs(input, {
|
||
|
tabindex: -1,
|
||
|
role: 'textbox',
|
||
|
'aria-label': chart.langFormat(langKey, { 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, component = this;
|
||
|
return new KeyboardNavigationHandler(chart, {
|
||
|
keyCodeMap: [
|
||
|
[
|
||
|
[keys.left, keys.right, keys.up, keys.down],
|
||
|
function (keyCode) {
|
||
|
return component.onButtonNavKbdArrowKey(this, keyCode);
|
||
|
}
|
||
|
],
|
||
|
[
|
||
|
[keys.enter, keys.space],
|
||
|
function () {
|
||
|
return component.onButtonNavKbdClick(this);
|
||
|
}
|
||
|
]
|
||
|
],
|
||
|
validate: function () {
|
||
|
var hasRangeSelector = chart.rangeSelector &&
|
||
|
chart.rangeSelector.buttons &&
|
||
|
chart.rangeSelector.buttons.length;
|
||
|
return hasRangeSelector;
|
||
|
},
|
||
|
init: function (direction) {
|
||
|
var lastButtonIx = (chart.rangeSelector.buttons.length - 1);
|
||
|
chart.highlightRangeSelectorButton(direction > 0 ? 0 : lastButtonIx);
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||
|
* @param {number} keyCode
|
||
|
* @return {number} Response code
|
||
|
*/
|
||
|
onButtonNavKbdArrowKey: function (keyboardNavigationHandler, keyCode) {
|
||
|
var response = keyboardNavigationHandler.response, keys = this.keyCodes, chart = this.chart, wrapAround = chart.options.accessibility
|
||
|
.keyboardNavigation.wrapAround, direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1, didHighlight = chart.highlightRangeSelectorButton(chart.highlightedRangeSelectorItemIx + direction);
|
||
|
if (!didHighlight) {
|
||
|
if (wrapAround) {
|
||
|
keyboardNavigationHandler.init(direction);
|
||
|
return response.success;
|
||
|
}
|
||
|
return response[direction > 0 ? 'next' : 'prev'];
|
||
|
}
|
||
|
return response.success;
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
onButtonNavKbdClick: function (keyboardNavigationHandler) {
|
||
|
var response = keyboardNavigationHandler.response, chart = this.chart, wasDisabled = chart.oldRangeSelectorItemState === 3;
|
||
|
if (!wasDisabled) {
|
||
|
this.fakeClickEvent(chart.rangeSelector.buttons[chart.highlightedRangeSelectorItemIx].element);
|
||
|
}
|
||
|
return response.success;
|
||
|
},
|
||
|
/**
|
||
|
* Get navigation for the range selector input boxes.
|
||
|
* @private
|
||
|
* @return {Highcharts.KeyboardNavigationHandler}
|
||
|
* The module object.
|
||
|
*/
|
||
|
getRangeSelectorInputNavigation: function () {
|
||
|
var chart = this.chart, keys = this.keyCodes, component = this;
|
||
|
return new KeyboardNavigationHandler(chart, {
|
||
|
keyCodeMap: [
|
||
|
[
|
||
|
[
|
||
|
keys.tab, keys.up, keys.down
|
||
|
], function (keyCode, e) {
|
||
|
var direction = (keyCode === keys.tab && e.shiftKey ||
|
||
|
keyCode === keys.up) ? -1 : 1;
|
||
|
return component.onInputKbdMove(this, direction);
|
||
|
}
|
||
|
]
|
||
|
],
|
||
|
validate: function () {
|
||
|
return shouldRunInputNavigation(chart);
|
||
|
},
|
||
|
init: function (direction) {
|
||
|
component.onInputNavInit(direction);
|
||
|
},
|
||
|
terminate: function () {
|
||
|
component.onInputNavTerminate();
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||
|
* @param {number} direction
|
||
|
* @return {number} Response code
|
||
|
*/
|
||
|
onInputKbdMove: function (keyboardNavigationHandler, direction) {
|
||
|
var chart = this.chart, response = keyboardNavigationHandler.response, newIx = chart.highlightedInputRangeIx =
|
||
|
chart.highlightedInputRangeIx + direction, newIxOutOfRange = newIx > 1 || newIx < 0;
|
||
|
if (newIxOutOfRange) {
|
||
|
return response[direction > 0 ? 'next' : 'prev'];
|
||
|
}
|
||
|
chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
|
||
|
return response.success;
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
* @param {number} direction
|
||
|
*/
|
||
|
onInputNavInit: function (direction) {
|
||
|
var chart = this.chart, buttonIxToHighlight = direction > 0 ? 0 : 1;
|
||
|
chart.highlightedInputRangeIx = buttonIxToHighlight;
|
||
|
chart.rangeSelector[buttonIxToHighlight ? 'maxInput' : 'minInput'].focus();
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
onInputNavTerminate: function () {
|
||
|
var rangeSel = (this.chart.rangeSelector || {});
|
||
|
if (rangeSel.maxInput) {
|
||
|
rangeSel.hideInput('max');
|
||
|
}
|
||
|
if (rangeSel.minInput) {
|
||
|
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;
|