| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Accessibility component for exporting menu.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import H from '../../Core/Globals.js';
- import U from '../../Core/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 removeElement = HTMLUtilities.removeElement, getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * Get the wrapped export button element of a chart.
- *
- * @private
- * @param {Highcharts.Chart} chart
- * @returns {Highcharts.SVGElement}
- */
- function getExportMenuButtonElement(chart) {
- return chart.exportSVGElements && chart.exportSVGElements[0];
- }
- /**
- * Show the export menu and focus the first item (if exists).
- *
- * @private
- * @function Highcharts.Chart#showExportMenu
- */
- H.Chart.prototype.showExportMenu = function () {
- var exportButton = getExportMenuButtonElement(this);
- if (exportButton) {
- var el = exportButton.element;
- if (el.onclick) {
- el.onclick(getFakeMouseEvent('click'));
- }
- }
- };
- /**
- * @private
- * @function Highcharts.Chart#hideExportMenu
- */
- H.Chart.prototype.hideExportMenu = function () {
- var chart = this, exportList = chart.exportDivElements;
- if (exportList && chart.exportContextMenu) {
- // Reset hover states etc.
- exportList.forEach(function (el) {
- if (el.className === 'highcharts-menu-item' && el.onmouseout) {
- el.onmouseout(getFakeMouseEvent('mouseout'));
- }
- });
- chart.highlightedExportItemIx = 0;
- // Hide the menu div
- chart.exportContextMenu.hideMenu();
- // Make sure the chart has focus and can capture keyboard events
- chart.container.focus();
- }
- };
- /**
- * Highlight export menu item by index.
- *
- * @private
- * @function Highcharts.Chart#highlightExportItem
- *
- * @param {number} ix
- *
- * @return {boolean}
- */
- H.Chart.prototype.highlightExportItem = function (ix) {
- var listItem = this.exportDivElements && this.exportDivElements[ix], curHighlighted = this.exportDivElements &&
- this.exportDivElements[this.highlightedExportItemIx], hasSVGFocusSupport;
- if (listItem &&
- listItem.tagName === 'LI' &&
- !(listItem.children && listItem.children.length)) {
- // Test if we have focus support for SVG elements
- hasSVGFocusSupport = !!(this.renderTo.getElementsByTagName('g')[0] || {}).focus;
- // Only focus if we can set focus back to the elements after
- // destroying the menu (#7422)
- if (listItem.focus && hasSVGFocusSupport) {
- listItem.focus();
- }
- if (curHighlighted && curHighlighted.onmouseout) {
- curHighlighted.onmouseout(getFakeMouseEvent('mouseout'));
- }
- if (listItem.onmouseover) {
- listItem.onmouseover(getFakeMouseEvent('mouseover'));
- }
- this.highlightedExportItemIx = ix;
- return true;
- }
- return false;
- };
- /**
- * Try to highlight the last valid export menu item.
- *
- * @private
- * @function Highcharts.Chart#highlightLastExportItem
- * @return {boolean}
- */
- H.Chart.prototype.highlightLastExportItem = function () {
- var chart = this, i;
- if (chart.exportDivElements) {
- i = chart.exportDivElements.length;
- while (i--) {
- if (chart.highlightExportItem(i)) {
- return true;
- }
- }
- }
- return false;
- };
- /**
- * @private
- * @param {Highcharts.Chart} chart
- */
- function exportingShouldHaveA11y(chart) {
- var exportingOpts = chart.options.exporting, exportButton = getExportMenuButtonElement(chart);
- return !!(exportingOpts &&
- exportingOpts.enabled !== false &&
- exportingOpts.accessibility &&
- exportingOpts.accessibility.enabled &&
- exportButton &&
- exportButton.element);
- }
- /**
- * The MenuComponent class
- *
- * @private
- * @class
- * @name Highcharts.MenuComponent
- */
- var MenuComponent = function () { };
- MenuComponent.prototype = new AccessibilityComponent();
- extend(MenuComponent.prototype, /** @lends Highcharts.MenuComponent */ {
- /**
- * Init the component
- */
- init: function () {
- var chart = this.chart, component = this;
- this.addEvent(chart, 'exportMenuShown', function () {
- component.onMenuShown();
- });
- this.addEvent(chart, 'exportMenuHidden', function () {
- component.onMenuHidden();
- });
- },
- /**
- * @private
- */
- onMenuHidden: function () {
- var menu = this.chart.exportContextMenu;
- if (menu) {
- menu.setAttribute('aria-hidden', 'true');
- }
- this.isExportMenuShown = false;
- this.setExportButtonExpandedState('false');
- },
- /**
- * @private
- */
- onMenuShown: function () {
- var chart = this.chart, menu = chart.exportContextMenu;
- if (menu) {
- this.addAccessibleContextMenuAttribs();
- unhideChartElementFromAT(chart, menu);
- }
- this.isExportMenuShown = true;
- this.setExportButtonExpandedState('true');
- },
- /**
- * @private
- * @param {string} stateStr
- */
- setExportButtonExpandedState: function (stateStr) {
- var button = this.exportButtonProxy;
- if (button) {
- button.setAttribute('aria-expanded', stateStr);
- }
- },
- /**
- * Called on each render of the chart. We need to update positioning of the
- * proxy overlay.
- */
- onChartRender: function () {
- var chart = this.chart, a11yOptions = chart.options.accessibility;
- // Always start with a clean slate
- removeElement(this.exportProxyGroup);
- // Set screen reader properties on export menu
- if (exportingShouldHaveA11y(chart)) {
- // Proxy button and group
- this.exportProxyGroup = this.addProxyGroup(
- // Wrap in a region div if verbosity is high
- a11yOptions.landmarkVerbosity === 'all' ? {
- 'aria-label': chart.langFormat('accessibility.exporting.exportRegionLabel', { chart: chart }),
- 'role': 'region'
- } : {});
- var button = getExportMenuButtonElement(this.chart);
- this.exportButtonProxy = this.createProxyButton(button, this.exportProxyGroup, {
- 'aria-label': chart.langFormat('accessibility.exporting.menuButtonLabel', { chart: chart }),
- 'aria-expanded': 'false'
- });
- }
- },
- /**
- * @private
- */
- addAccessibleContextMenuAttribs: function () {
- var chart = this.chart, exportList = chart.exportDivElements;
- if (exportList && exportList.length) {
- // Set tabindex on the menu items to allow focusing by script
- // Set role to give screen readers a chance to pick up the contents
- exportList.forEach(function (item) {
- if (item.tagName === 'LI' &&
- !(item.children && item.children.length)) {
- item.setAttribute('tabindex', -1);
- }
- else {
- item.setAttribute('aria-hidden', 'true');
- }
- });
- // Set accessibility properties on parent div
- var parentDiv = exportList[0].parentNode;
- parentDiv.removeAttribute('aria-hidden');
- parentDiv.setAttribute('aria-label', chart.langFormat('accessibility.exporting.chartMenuLabel', { chart: chart }));
- }
- },
- /**
- * Get keyboard navigation handler for this component.
- * @return {Highcharts.KeyboardNavigationHandler}
- */
- getKeyboardNavigation: function () {
- var keys = this.keyCodes, chart = this.chart, component = this;
- return new KeyboardNavigationHandler(chart, {
- keyCodeMap: [
- // Arrow prev handler
- [
- [keys.left, keys.up],
- function () {
- return component.onKbdPrevious(this);
- }
- ],
- // Arrow next handler
- [
- [keys.right, keys.down],
- function () {
- return component.onKbdNext(this);
- }
- ],
- // Click handler
- [
- [keys.enter, keys.space],
- function () {
- return component.onKbdClick(this);
- }
- ]
- ],
- // Only run exporting navigation if exporting support exists and is
- // enabled on chart
- validate: function () {
- return chart.exportChart &&
- chart.options.exporting.enabled !== false &&
- chart.options.exporting.accessibility.enabled !==
- false;
- },
- // Focus export menu button
- init: function () {
- var exportBtn = component.exportButtonProxy, exportGroup = chart.exportingGroup;
- if (exportGroup && exportBtn) {
- chart.setFocusToElement(exportGroup, exportBtn);
- }
- },
- // Hide the menu
- terminate: function () {
- chart.hideExportMenu();
- }
- });
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdPrevious: function (keyboardNavigationHandler) {
- var chart = this.chart, a11yOptions = chart.options.accessibility, response = keyboardNavigationHandler.response, i = chart.highlightedExportItemIx || 0;
- // Try to highlight prev item in list. Highlighting e.g.
- // separators will fail.
- while (i--) {
- if (chart.highlightExportItem(i)) {
- return response.success;
- }
- }
- // We failed, so wrap around or move to prev module
- if (a11yOptions.keyboardNavigation.wrapAround) {
- chart.highlightLastExportItem();
- return response.success;
- }
- return response.prev;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdNext: function (keyboardNavigationHandler) {
- var chart = this.chart, a11yOptions = chart.options.accessibility, response = keyboardNavigationHandler.response, i = (chart.highlightedExportItemIx || 0) + 1;
- // Try to highlight next item in list. Highlighting e.g.
- // separators will fail.
- for (; i < chart.exportDivElements.length; ++i) {
- if (chart.highlightExportItem(i)) {
- return response.success;
- }
- }
- // We failed, so wrap around or move to next module
- if (a11yOptions.keyboardNavigation.wrapAround) {
- chart.highlightExportItem(0);
- return response.success;
- }
- return response.next;
- },
- /**
- * @private
- * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
- * @return {number}
- * Response code
- */
- onKbdClick: function (keyboardNavigationHandler) {
- var chart = this.chart, curHighlightedItem = chart.exportDivElements[chart.highlightedExportItemIx], exportButtonElement = getExportMenuButtonElement(chart).element;
- if (this.isExportMenuShown) {
- this.fakeClickEvent(curHighlightedItem);
- }
- else {
- this.fakeClickEvent(exportButtonElement);
- chart.highlightExportItem(0);
- }
- return keyboardNavigationHandler.response.success;
- }
- });
- export default MenuComponent;
|