RangeSelectorComponent.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /* *
  2. *
  3. * (c) 2009-2020 Øystein Moseng
  4. *
  5. * Accessibility component for the range selector.
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import H from '../../Core/Globals.js';
  14. import U from '../../Core/Utilities.js';
  15. var extend = U.extend;
  16. import AccessibilityComponent from '../AccessibilityComponent.js';
  17. import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
  18. import ChartUtilities from '../Utils/ChartUtilities.js';
  19. var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
  20. import HTMLUtilities from '../Utils/HTMLUtilities.js';
  21. var setElAttrs = HTMLUtilities.setElAttrs;
  22. /* eslint-disable no-invalid-this, valid-jsdoc */
  23. /**
  24. * @private
  25. */
  26. function shouldRunInputNavigation(chart) {
  27. var inputVisible = (chart.rangeSelector &&
  28. chart.rangeSelector.inputGroup &&
  29. chart.rangeSelector.inputGroup.element
  30. .getAttribute('visibility') !== 'hidden');
  31. return (inputVisible &&
  32. chart.options.rangeSelector.inputEnabled !== false &&
  33. chart.rangeSelector.minInput &&
  34. chart.rangeSelector.maxInput);
  35. }
  36. /**
  37. * Highlight range selector button by index.
  38. *
  39. * @private
  40. * @function Highcharts.Chart#highlightRangeSelectorButton
  41. *
  42. * @param {number} ix
  43. *
  44. * @return {boolean}
  45. */
  46. H.Chart.prototype.highlightRangeSelectorButton = function (ix) {
  47. var buttons = this.rangeSelector.buttons, curSelectedIx = this.highlightedRangeSelectorItemIx;
  48. // Deselect old
  49. if (typeof curSelectedIx !== 'undefined' && buttons[curSelectedIx]) {
  50. buttons[curSelectedIx].setState(this.oldRangeSelectorItemState || 0);
  51. }
  52. // Select new
  53. this.highlightedRangeSelectorItemIx = ix;
  54. if (buttons[ix]) {
  55. this.setFocusToElement(buttons[ix].box, buttons[ix].element);
  56. this.oldRangeSelectorItemState = buttons[ix].state;
  57. buttons[ix].setState(2);
  58. return true;
  59. }
  60. return false;
  61. };
  62. /**
  63. * The RangeSelectorComponent class
  64. *
  65. * @private
  66. * @class
  67. * @name Highcharts.RangeSelectorComponent
  68. */
  69. var RangeSelectorComponent = function () { };
  70. RangeSelectorComponent.prototype = new AccessibilityComponent();
  71. extend(RangeSelectorComponent.prototype, /** @lends Highcharts.RangeSelectorComponent */ {
  72. /**
  73. * Called on first render/updates to the chart, including options changes.
  74. */
  75. onChartUpdate: function () {
  76. var chart = this.chart, component = this, rangeSelector = chart.rangeSelector;
  77. if (!rangeSelector) {
  78. return;
  79. }
  80. if (rangeSelector.buttons && rangeSelector.buttons.length) {
  81. rangeSelector.buttons.forEach(function (button) {
  82. unhideChartElementFromAT(chart, button.element);
  83. component.setRangeButtonAttrs(button);
  84. });
  85. }
  86. // Make sure input boxes are accessible and focusable
  87. if (rangeSelector.maxInput && rangeSelector.minInput) {
  88. ['minInput', 'maxInput'].forEach(function (key, i) {
  89. var input = rangeSelector[key];
  90. if (input) {
  91. unhideChartElementFromAT(chart, input);
  92. component.setRangeInputAttrs(input, 'accessibility.rangeSelector.' + (i ? 'max' : 'min') +
  93. 'InputLabel');
  94. }
  95. });
  96. }
  97. },
  98. /**
  99. * @private
  100. * @param {Highcharts.SVGElement} button
  101. */
  102. setRangeButtonAttrs: function (button) {
  103. var chart = this.chart, label = chart.langFormat('accessibility.rangeSelector.buttonText', {
  104. chart: chart,
  105. buttonText: button.text && button.text.textStr
  106. });
  107. setElAttrs(button.element, {
  108. tabindex: -1,
  109. role: 'button',
  110. 'aria-label': label
  111. });
  112. },
  113. /**
  114. * @private
  115. */
  116. setRangeInputAttrs: function (input, langKey) {
  117. var chart = this.chart;
  118. setElAttrs(input, {
  119. tabindex: -1,
  120. role: 'textbox',
  121. 'aria-label': chart.langFormat(langKey, { chart: chart })
  122. });
  123. },
  124. /**
  125. * Get navigation for the range selector buttons.
  126. * @private
  127. * @return {Highcharts.KeyboardNavigationHandler} The module object.
  128. */
  129. getRangeSelectorButtonNavigation: function () {
  130. var chart = this.chart, keys = this.keyCodes, component = this;
  131. return new KeyboardNavigationHandler(chart, {
  132. keyCodeMap: [
  133. [
  134. [keys.left, keys.right, keys.up, keys.down],
  135. function (keyCode) {
  136. return component.onButtonNavKbdArrowKey(this, keyCode);
  137. }
  138. ],
  139. [
  140. [keys.enter, keys.space],
  141. function () {
  142. return component.onButtonNavKbdClick(this);
  143. }
  144. ]
  145. ],
  146. validate: function () {
  147. var hasRangeSelector = chart.rangeSelector &&
  148. chart.rangeSelector.buttons &&
  149. chart.rangeSelector.buttons.length;
  150. return hasRangeSelector;
  151. },
  152. init: function (direction) {
  153. var lastButtonIx = (chart.rangeSelector.buttons.length - 1);
  154. chart.highlightRangeSelectorButton(direction > 0 ? 0 : lastButtonIx);
  155. }
  156. });
  157. },
  158. /**
  159. * @private
  160. * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
  161. * @param {number} keyCode
  162. * @return {number} Response code
  163. */
  164. onButtonNavKbdArrowKey: function (keyboardNavigationHandler, keyCode) {
  165. var response = keyboardNavigationHandler.response, keys = this.keyCodes, chart = this.chart, wrapAround = chart.options.accessibility
  166. .keyboardNavigation.wrapAround, direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1, didHighlight = chart.highlightRangeSelectorButton(chart.highlightedRangeSelectorItemIx + direction);
  167. if (!didHighlight) {
  168. if (wrapAround) {
  169. keyboardNavigationHandler.init(direction);
  170. return response.success;
  171. }
  172. return response[direction > 0 ? 'next' : 'prev'];
  173. }
  174. return response.success;
  175. },
  176. /**
  177. * @private
  178. */
  179. onButtonNavKbdClick: function (keyboardNavigationHandler) {
  180. var response = keyboardNavigationHandler.response, chart = this.chart, wasDisabled = chart.oldRangeSelectorItemState === 3;
  181. if (!wasDisabled) {
  182. this.fakeClickEvent(chart.rangeSelector.buttons[chart.highlightedRangeSelectorItemIx].element);
  183. }
  184. return response.success;
  185. },
  186. /**
  187. * Get navigation for the range selector input boxes.
  188. * @private
  189. * @return {Highcharts.KeyboardNavigationHandler}
  190. * The module object.
  191. */
  192. getRangeSelectorInputNavigation: function () {
  193. var chart = this.chart, keys = this.keyCodes, component = this;
  194. return new KeyboardNavigationHandler(chart, {
  195. keyCodeMap: [
  196. [
  197. [
  198. keys.tab, keys.up, keys.down
  199. ],
  200. function (keyCode, e) {
  201. var direction = (keyCode === keys.tab && e.shiftKey ||
  202. keyCode === keys.up) ? -1 : 1;
  203. return component.onInputKbdMove(this, direction);
  204. }
  205. ]
  206. ],
  207. validate: function () {
  208. return shouldRunInputNavigation(chart);
  209. },
  210. init: function (direction) {
  211. component.onInputNavInit(direction);
  212. },
  213. terminate: function () {
  214. component.onInputNavTerminate();
  215. }
  216. });
  217. },
  218. /**
  219. * @private
  220. * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
  221. * @param {number} direction
  222. * @return {number} Response code
  223. */
  224. onInputKbdMove: function (keyboardNavigationHandler, direction) {
  225. var chart = this.chart, response = keyboardNavigationHandler.response, newIx = chart.highlightedInputRangeIx =
  226. chart.highlightedInputRangeIx + direction, newIxOutOfRange = newIx > 1 || newIx < 0;
  227. if (newIxOutOfRange) {
  228. return response[direction > 0 ? 'next' : 'prev'];
  229. }
  230. chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
  231. return response.success;
  232. },
  233. /**
  234. * @private
  235. * @param {number} direction
  236. */
  237. onInputNavInit: function (direction) {
  238. var chart = this.chart, buttonIxToHighlight = direction > 0 ? 0 : 1;
  239. chart.highlightedInputRangeIx = buttonIxToHighlight;
  240. chart.rangeSelector[buttonIxToHighlight ? 'maxInput' : 'minInput'].focus();
  241. },
  242. /**
  243. * @private
  244. */
  245. onInputNavTerminate: function () {
  246. var rangeSel = (this.chart.rangeSelector || {});
  247. if (rangeSel.maxInput) {
  248. rangeSel.hideInput('max');
  249. }
  250. if (rangeSel.minInput) {
  251. rangeSel.hideInput('min');
  252. }
  253. },
  254. /**
  255. * Get keyboard navigation handlers for this component.
  256. * @return {Array<Highcharts.KeyboardNavigationHandler>}
  257. * List of module objects.
  258. */
  259. getKeyboardNavigation: function () {
  260. return [
  261. this.getRangeSelectorButtonNavigation(),
  262. this.getRangeSelectorInputNavigation()
  263. ];
  264. }
  265. });
  266. export default RangeSelectorComponent;