/** * @license Highcharts JS v8.1.0 (2020-05-05) * * Accessibility module * * (c) 2010-2019 Highsoft AS * Author: Oystein Moseng * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/modules/accessibility', ['highcharts'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); } } _registerModule(_modules, 'modules/accessibility/utils/htmlUtilities.js', [_modules['parts/Utilities.js'], _modules['parts/Globals.js']], function (U, H) { /* * * * (c) 2009-2020 Øystein Moseng * * Utility functions for accessibility module. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var merge = U.merge; var win = H.win, doc = win.document; /* eslint-disable valid-jsdoc */ /** * @private * @param {Highcharts.HTMLDOMElement} el * @param {string} className * @return {void} */ function addClass(el, className) { if (el.classList) { el.classList.add(className); } else if (el.className.indexOf(className) < 0) { // Note: Dumb check for class name exists, should be fine for practical // use cases, but will return false positives if the element has a class // that contains the className. el.className += className; } } /** * @private * @param {string} str * @return {string} */ function escapeStringForHTML(str) { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/\//g, '/'); } /** * Get an element by ID * @param {string} id * @private * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|null} */ function getElement(id) { return doc.getElementById(id); } /** * Get a fake mouse event of a given type * @param {string} type * @private * @return {global.MouseEvent} */ function getFakeMouseEvent(type) { if (typeof win.MouseEvent === 'function') { return new win.MouseEvent(type); } // No MouseEvent support, try using initMouseEvent if (doc.createEvent) { var evt = doc.createEvent('MouseEvent'); if (evt.initMouseEvent) { evt.initMouseEvent(type, true, // Bubble true, // Cancel win, // View type === 'click' ? 1 : 0, // Detail // Coords 0, 0, 0, 0, // Pressed keys false, false, false, false, 0, // button null // related target ); return evt; } } return { type: type }; } /** * Remove an element from the DOM. * @private * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} [element] * @return {void} */ function removeElement(element) { if (element && element.parentNode) { element.parentNode.removeChild(element); } } /** * Utility function. Reverses child nodes of a DOM element. * @private * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} node * @return {void} */ function reverseChildNodes(node) { var i = node.childNodes.length; while (i--) { node.appendChild(node.childNodes[i]); } } /** * Set attributes on element. Set to null to remove attribute. * @private * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el * @param {Highcharts.HTMLAttributes|Highcharts.SVGAttributes} attrs * @return {void} */ function setElAttrs(el, attrs) { Object.keys(attrs).forEach(function (attr) { var val = attrs[attr]; if (val === null) { el.removeAttribute(attr); } else { var cleanedVal = escapeStringForHTML('' + val); el.setAttribute(attr, cleanedVal); } }); } /** * Used for aria-label attributes, painting on a canvas will fail if the * text contains tags. * @private * @param {string} str * @return {string} */ function stripHTMLTagsFromString(str) { return typeof str === 'string' ? str.replace(/<\/?[^>]+(>|$)/g, '') : str; } /** * Utility function for hiding an element visually, but still keeping it * available to screen reader users. * @private * @param {Highcharts.HTMLDOMElement} element * @return {void} */ function visuallyHideElement(element) { var hiddenStyle = { position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', whiteSpace: 'nowrap', clip: 'rect(1px, 1px, 1px, 1px)', marginTop: '-3px', '-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)', filter: 'alpha(opacity=1)', opacity: '0.01' }; merge(true, element.style, hiddenStyle); } var HTMLUtilities = { addClass: addClass, escapeStringForHTML: escapeStringForHTML, getElement: getElement, getFakeMouseEvent: getFakeMouseEvent, removeElement: removeElement, reverseChildNodes: reverseChildNodes, setElAttrs: setElAttrs, stripHTMLTagsFromString: stripHTMLTagsFromString, visuallyHideElement: visuallyHideElement }; return HTMLUtilities; }); _registerModule(_modules, 'modules/accessibility/utils/chartUtilities.js', [_modules['modules/accessibility/utils/htmlUtilities.js'], _modules['parts/Utilities.js']], function (HTMLUtilities, U) { /* * * * (c) 2009-2020 Øystein Moseng * * Utils for dealing with charts. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString; var defined = U.defined, find = U.find; /* eslint-disable valid-jsdoc */ /** * @return {string} */ function getChartTitle(chart) { return stripHTMLTags(chart.options.title.text || chart.langFormat('accessibility.defaultChartTitle', { chart: chart })); } /** * @param {Highcharts.Axis} axis * @return {string} */ function getAxisDescription(axis) { return stripHTMLTags(axis && (axis.userOptions && axis.userOptions.accessibility && axis.userOptions.accessibility.description || axis.axisTitle && axis.axisTitle.textStr || axis.options.id || axis.categories && 'categories' || axis.dateTime && 'Time' || 'values')); } /** * Get the DOM element for the first point in the series. * @private * @param {Highcharts.Series} series * The series to get element for. * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined} * The DOM element for the point. */ function getSeriesFirstPointElement(series) { if (series.points && series.points.length && series.points[0].graphic) { return series.points[0].graphic.element; } } /** * Get the DOM element for the series that we put accessibility info on. * @private * @param {Highcharts.Series} series * The series to get element for. * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined} * The DOM element for the series */ function getSeriesA11yElement(series) { var firstPointEl = getSeriesFirstPointElement(series); return (firstPointEl && firstPointEl.parentNode || series.graph && series.graph.element || series.group && series.group.element); // Could be tracker series depending on series type } /** * Remove aria-hidden from element. Also unhides parents of the element, and * hides siblings that are not explicitly unhidden. * @private * @param {Highcharts.Chart} chart * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element * @return {void} */ function unhideChartElementFromAT(chart, element) { element.setAttribute('aria-hidden', false); if (element === chart.renderTo || !element.parentNode) { return; } // Hide siblings unless their hidden state is already explicitly set Array.prototype.forEach.call(element.parentNode.childNodes, function (node) { if (!node.hasAttribute('aria-hidden')) { node.setAttribute('aria-hidden', true); } }); // Repeat for parent unhideChartElementFromAT(chart, element.parentNode); } /** * Hide series from screen readers. * @private * @param {Highcharts.Series} series * The series to hide * @return {void} */ function hideSeriesFromAT(series) { var seriesEl = getSeriesA11yElement(series); if (seriesEl) { seriesEl.setAttribute('aria-hidden', true); } } /** * Get series objects by series name. * @private * @param {Highcharts.Chart} chart * @param {string} name * @return {Array} */ function getSeriesFromName(chart, name) { if (!name) { return chart.series; } return (chart.series || []).filter(function (s) { return s.name === name; }); } /** * Get point in a series from x/y values. * @private * @param {Array} series * @param {number} x * @param {number} y * @return {Highcharts.Point|undefined} */ function getPointFromXY(series, x, y) { var i = series.length, res; while (i--) { res = find(series[i].points || [], function (p) { return p.x === x && p.y === y; }); if (res) { return res; } } } /** * Get relative position of point on an x/y axis from 0 to 1. * @private * @param {Highcharts.Axis} axis * @param {Highcharts.Point} point * @return {number} */ function getRelativePointAxisPosition(axis, point) { if (!defined(axis.dataMin) || !defined(axis.dataMax)) { return 0; } var axisStart = axis.toPixels(axis.dataMin); var axisEnd = axis.toPixels(axis.dataMax); // We have to use pixel position because of axis breaks, log axis etc. var positionProp = axis.coll === 'xAxis' ? 'x' : 'y'; var pointPos = axis.toPixels(point[positionProp] || 0); return (pointPos - axisStart) / (axisEnd - axisStart); } /** * Get relative position of point on an x/y axis from 0 to 1. * @private * @param {Highcharts.Point} point */ function scrollToPoint(point) { var xAxis = point.series.xAxis; var yAxis = point.series.yAxis; var axis = (xAxis === null || xAxis === void 0 ? void 0 : xAxis.scrollbar) ? xAxis : yAxis; var scrollbar = axis === null || axis === void 0 ? void 0 : axis.scrollbar; if (scrollbar && defined(scrollbar.to) && defined(scrollbar.from)) { var range = scrollbar.to - scrollbar.from; var pos = getRelativePointAxisPosition(axis, point); scrollbar.updatePosition(pos - range / 2, pos + range / 2); Highcharts.fireEvent(scrollbar, 'changed', { from: scrollbar.from, to: scrollbar.to, trigger: 'scrollbar', DOMEvent: null }); } } var ChartUtilities = { getChartTitle: getChartTitle, getAxisDescription: getAxisDescription, getPointFromXY: getPointFromXY, getSeriesFirstPointElement: getSeriesFirstPointElement, getSeriesFromName: getSeriesFromName, getSeriesA11yElement: getSeriesA11yElement, unhideChartElementFromAT: unhideChartElementFromAT, hideSeriesFromAT: hideSeriesFromAT, scrollToPoint: scrollToPoint }; return ChartUtilities; }); _registerModule(_modules, 'modules/accessibility/KeyboardNavigationHandler.js', [_modules['parts/Utilities.js']], function (U) { /* * * * (c) 2009-2020 Øystein Moseng * * Keyboard navigation handler base class definition * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var find = U.find; /** * Options for the keyboard navigation handler. * * @interface Highcharts.KeyboardNavigationHandlerOptionsObject */ /** * An array containing pairs of an array of keycodes, mapped to a handler * function. When the keycode is received, the handler is called with the * keycode as parameter. * @name Highcharts.KeyboardNavigationHandlerOptionsObject#keyCodeMap * @type {Array, Function>>} */ /** * Function to run on initialization of module. * @name Highcharts.KeyboardNavigationHandlerOptionsObject#init * @type {Function} */ /** * Function to run before moving to next/prev module. Receives moving direction * as parameter: +1 for next, -1 for previous. * @name Highcharts.KeyboardNavigationHandlerOptionsObject#terminate * @type {Function|undefined} */ /** * Function to run to validate module. Should return false if module should not * run, true otherwise. Receives chart as parameter. * @name Highcharts.KeyboardNavigationHandlerOptionsObject#validate * @type {Function|undefined} */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Define a keyboard navigation handler for use with a * Highcharts.AccessibilityComponent instance. This functions as an abstraction * layer for keyboard navigation, and defines a map of keyCodes to handler * functions. * * @requires module:modules/accessibility * * @sample highcharts/accessibility/custom-component * Custom accessibility component * * @class * @name Highcharts.KeyboardNavigationHandler * * @param {Highcharts.Chart} chart * The chart this module should act on. * * @param {Highcharts.KeyboardNavigationHandlerOptionsObject} options * Options for the keyboard navigation handler. */ function KeyboardNavigationHandler(chart, options) { this.chart = chart; this.keyCodeMap = options.keyCodeMap || []; this.validate = options.validate; this.init = options.init; this.terminate = options.terminate; // Response enum this.response = { success: 1, prev: 2, next: 3, noHandler: 4, fail: 5 // Handler failed }; } KeyboardNavigationHandler.prototype = { /** * Find handler function(s) for key code in the keyCodeMap and run it. * * @function KeyboardNavigationHandler#run * @param {global.KeyboardEvent} e * @return {number} Returns a response code indicating whether the run was * a success/fail/unhandled, or if we should move to next/prev module. */ run: function (e) { var keyCode = e.which || e.keyCode, response = this.response.noHandler, handlerCodeSet = find(this.keyCodeMap, function (codeSet) { return codeSet[0].indexOf(keyCode) > -1; }); if (handlerCodeSet) { response = handlerCodeSet[1].call(this, keyCode, e); } else if (keyCode === 9) { // Default tab handler, move to next/prev module response = this.response[e.shiftKey ? 'prev' : 'next']; } return response; } }; return KeyboardNavigationHandler; }); _registerModule(_modules, 'modules/accessibility/utils/EventProvider.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2009-2020 Øystein Moseng * * Class that can keep track of events added, and clean them up on destroy. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, extend = U.extend; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private * @class */ var EventProvider = function () { this.eventRemovers = []; }; extend(EventProvider.prototype, { /** * Add an event to an element and keep track of it for later removal. * Same args as Highcharts.addEvent. * @private * @return {Function} */ addEvent: function () { var remover = addEvent.apply(H, arguments); this.eventRemovers.push(remover); return remover; }, /** * Remove all added events. * @private * @return {void} */ removeAddedEvents: function () { this.eventRemovers.forEach(function (remover) { remover(); }); this.eventRemovers = []; } }); return EventProvider; }); _registerModule(_modules, 'modules/accessibility/utils/DOMElementProvider.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, U, HTMLUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Class that can keep track of elements added to DOM and clean them up on * destroy. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var doc = H.win.document; var extend = U.extend; var removeElement = HTMLUtilities.removeElement; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private * @class */ var DOMElementProvider = function () { this.elements = []; }; extend(DOMElementProvider.prototype, { /** * Create an element and keep track of it for later removal. * Same args as document.createElement * @private */ createElement: function () { var el = doc.createElement.apply(doc, arguments); this.elements.push(el); return el; }, /** * Destroy all created elements, removing them from the DOM. * @private */ destroyCreatedElements: function () { this.elements.forEach(function (element) { removeElement(element); }); this.elements = []; } }); return DOMElementProvider; }); _registerModule(_modules, 'modules/accessibility/AccessibilityComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/utils/EventProvider.js'], _modules['modules/accessibility/utils/DOMElementProvider.js']], function (H, U, HTMLUtilities, ChartUtilities, EventProvider, DOMElementProvider) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component class definition * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var win = H.win, doc = win.document; var extend = U.extend, fireEvent = U.fireEvent, merge = U.merge; var removeElement = HTMLUtilities.removeElement, getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent; var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; /* eslint-disable valid-jsdoc */ /** @lends Highcharts.AccessibilityComponent */ var functionsToOverrideByDerivedClasses = { /** * Called on component initialization. */ init: function () { }, /** * Get keyboard navigation handler for this component. * @return {Highcharts.KeyboardNavigationHandler} */ getKeyboardNavigation: function () { }, /** * Called on updates to the chart, including options changes. * Note that this is also called on first render of chart. */ onChartUpdate: function () { }, /** * Called on every chart render. */ onChartRender: function () { }, /** * Called when accessibility is disabled or chart is destroyed. */ destroy: function () { } }; /** * The AccessibilityComponent base class, representing a part of the chart that * has accessibility logic connected to it. This class can be inherited from to * create a custom accessibility component for a chart. * * Components should take care to destroy added elements and unregister event * handlers on destroy. This is handled automatically if using this.addEvent and * this.createElement. * * @sample highcharts/accessibility/custom-component * Custom accessibility component * * @requires module:modules/accessibility * @class * @name Highcharts.AccessibilityComponent */ function AccessibilityComponent() { } /** * @lends Highcharts.AccessibilityComponent */ AccessibilityComponent.prototype = { /** * Initialize the class * @private * @param {Highcharts.Chart} chart * Chart object */ initBase: function (chart) { this.chart = chart; this.eventProvider = new EventProvider(); this.domElementProvider = new DOMElementProvider(); // Key code enum for common keys this.keyCodes = { left: 37, right: 39, up: 38, down: 40, enter: 13, space: 32, esc: 27, tab: 9 }; }, /** * Add an event to an element and keep track of it for later removal. * See EventProvider for details. * @private */ addEvent: function () { return this.eventProvider.addEvent .apply(this.eventProvider, arguments); }, /** * Create an element and keep track of it for later removal. * See DOMElementProvider for details. * @private */ createElement: function () { return this.domElementProvider.createElement.apply(this.domElementProvider, arguments); }, /** * Fire an event on an element that is either wrapped by Highcharts, * or a DOM element * @private * @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement| * Highcharts.SVGDOMElement|Highcharts.SVGElement} el * @param {Event} eventObject */ fireEventOnWrappedOrUnwrappedElement: function (el, eventObject) { var type = eventObject.type; if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) { if (el.dispatchEvent) { el.dispatchEvent(eventObject); } else { el.fireEvent(type, eventObject); } } else { fireEvent(el, type, eventObject); } }, /** * Utility function to attempt to fake a click event on an element. * @private * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element */ fakeClickEvent: function (element) { if (element) { var fakeEventObject = getFakeMouseEvent('click'); this.fireEventOnWrappedOrUnwrappedElement(element, fakeEventObject); } }, /** * Add a new proxy group to the proxy container. Creates the proxy container * if it does not exist. * @private * @param {Highcharts.HTMLAttributes} [attrs] * The attributes to set on the new group div. * @return {Highcharts.HTMLDOMElement} * The new proxy group element. */ addProxyGroup: function (attrs) { this.createOrUpdateProxyContainer(); var groupDiv = this.createElement('div'); Object.keys(attrs || {}).forEach(function (prop) { if (attrs[prop] !== null) { groupDiv.setAttribute(prop, attrs[prop]); } }); this.chart.a11yProxyContainer.appendChild(groupDiv); return groupDiv; }, /** * Creates and updates DOM position of proxy container * @private */ createOrUpdateProxyContainer: function () { var chart = this.chart, rendererSVGEl = chart.renderer.box; chart.a11yProxyContainer = chart.a11yProxyContainer || this.createProxyContainerElement(); if (rendererSVGEl.nextSibling !== chart.a11yProxyContainer) { chart.container.insertBefore(chart.a11yProxyContainer, rendererSVGEl.nextSibling); } }, /** * @private * @return {Highcharts.HTMLDOMElement} element */ createProxyContainerElement: function () { var pc = doc.createElement('div'); pc.className = 'highcharts-a11y-proxy-container'; return pc; }, /** * Create an invisible proxy HTML button in the same position as an SVG * element * @private * @param {Highcharts.SVGElement} svgElement * The wrapped svg el to proxy. * @param {Highcharts.HTMLDOMElement} parentGroup * The proxy group element in the proxy container to add this button to. * @param {Highcharts.SVGAttributes} [attributes] * Additional attributes to set. * @param {Highcharts.SVGElement} [posElement] * Element to use for positioning instead of svgElement. * @param {Function} [preClickEvent] * Function to call before click event fires. * * @return {Highcharts.HTMLDOMElement} The proxy button. */ createProxyButton: function (svgElement, parentGroup, attributes, posElement, preClickEvent) { var svgEl = svgElement.element, proxy = this.createElement('button'), attrs = merge({ 'aria-label': svgEl.getAttribute('aria-label') }, attributes), bBox = this.getElementPosition(posElement || svgElement); Object.keys(attrs).forEach(function (prop) { if (attrs[prop] !== null) { proxy.setAttribute(prop, attrs[prop]); } }); proxy.className = 'highcharts-a11y-proxy-button'; if (preClickEvent) { this.addEvent(proxy, 'click', preClickEvent); } this.setProxyButtonStyle(proxy, bBox); this.proxyMouseEventsForButton(svgEl, proxy); // Add to chart div and unhide from screen readers parentGroup.appendChild(proxy); if (!attrs['aria-hidden']) { unhideChartElementFromAT(this.chart, proxy); } return proxy; }, /** * Get the position relative to chart container for a wrapped SVG element. * @private * @param {Highcharts.SVGElement} element * The element to calculate position for. * @return {Highcharts.BBoxObject} * Object with x and y props for the position. */ getElementPosition: function (element) { var el = element.element, div = this.chart.renderTo; if (div && el && el.getBoundingClientRect) { var rectEl = el.getBoundingClientRect(), rectDiv = div.getBoundingClientRect(); return { x: rectEl.left - rectDiv.left, y: rectEl.top - rectDiv.top, width: rectEl.right - rectEl.left, height: rectEl.bottom - rectEl.top }; } return { x: 0, y: 0, width: 1, height: 1 }; }, /** * @private * @param {Highcharts.HTMLElement} button * @param {Highcharts.BBoxObject} bBox */ setProxyButtonStyle: function (button, bBox) { merge(true, button.style, { 'border-width': 0, 'background-color': 'transparent', cursor: 'pointer', outline: 'none', opacity: 0.001, filter: 'alpha(opacity=1)', '-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)', zIndex: 999, overflow: 'hidden', padding: 0, margin: 0, display: 'block', position: 'absolute', width: (bBox.width || 1) + 'px', height: (bBox.height || 1) + 'px', left: (bBox.x || 0) + 'px', top: (bBox.y || 0) + 'px' }); }, /** * @private * @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement| * Highcharts.SVGDOMElement|Highcharts.SVGElement} source * @param {Highcharts.HTMLElement} button */ proxyMouseEventsForButton: function (source, button) { var component = this; [ 'click', 'touchstart', 'touchend', 'touchcancel', 'touchmove', 'mouseover', 'mouseenter', 'mouseleave', 'mouseout' ].forEach(function (evtType) { component.addEvent(button, evtType, function (e) { var clonedEvent = component.cloneMouseEvent(e); if (source) { component.fireEventOnWrappedOrUnwrappedElement(source, clonedEvent); } e.stopPropagation(); e.preventDefault(); }); }); }, /** * Utility function to clone a mouse event for re-dispatching. * @private * @param {global.MouseEvent} e The event to clone. * @return {global.MouseEvent} The cloned event */ cloneMouseEvent: function (e) { if (typeof win.MouseEvent === 'function') { return new win.MouseEvent(e.type, e); } // No MouseEvent support, try using initMouseEvent if (doc.createEvent) { var evt = doc.createEvent('MouseEvent'); if (evt.initMouseEvent) { evt.initMouseEvent(e.type, e.bubbles, // #10561, #12161 e.cancelable, e.view || win, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); return evt; } } return getFakeMouseEvent(e.type); }, /** * Remove traces of the component. * @private */ destroyBase: function () { removeElement(this.chart.a11yProxyContainer); this.domElementProvider.destroyCreatedElements(); this.eventProvider.removeAddedEvents(); } }; extend(AccessibilityComponent.prototype, functionsToOverrideByDerivedClasses); return AccessibilityComponent; }); _registerModule(_modules, 'modules/accessibility/KeyboardNavigation.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js'], _modules['modules/accessibility/utils/EventProvider.js']], function (H, U, HTMLUtilities, EventProvider) { /* * * * (c) 2009-2020 Øystein Moseng * * Main keyboard navigation handling. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var win = H.win, doc = win.document; var addEvent = U.addEvent, fireEvent = U.fireEvent; var getElement = HTMLUtilities.getElement; /* eslint-disable valid-jsdoc */ // Add event listener to document to detect ESC key press and dismiss // hover/popup content. addEvent(doc, 'keydown', function (e) { var keycode = e.which || e.keyCode; var esc = 27; if (keycode === esc && H.charts) { H.charts.forEach(function (chart) { if (chart && chart.dismissPopupContent) { chart.dismissPopupContent(); } }); } }); /** * Dismiss popup content in chart, including export menu and tooltip. */ H.Chart.prototype.dismissPopupContent = function () { var chart = this; fireEvent(this, 'dismissPopupContent', {}, function () { if (chart.tooltip) { chart.tooltip.hide(0); } chart.hideExportMenu(); }); }; /** * The KeyboardNavigation class, containing the overall keyboard navigation * logic for the chart. * * @requires module:modules/accessibility * * @private * @class * @param {Highcharts.Chart} chart * Chart object * @param {object} components * Map of component names to AccessibilityComponent objects. * @name Highcharts.KeyboardNavigation */ function KeyboardNavigation(chart, components) { this.init(chart, components); } KeyboardNavigation.prototype = { /** * Initialize the class * @private * @param {Highcharts.Chart} chart * Chart object * @param {object} components * Map of component names to AccessibilityComponent objects. */ init: function (chart, components) { var _this = this; var ep = this.eventProvider = new EventProvider(); this.chart = chart; this.components = components; this.modules = []; this.currentModuleIx = 0; ep.addEvent(chart.renderTo, 'keydown', function (e) { return _this.onKeydown(e); }); ep.addEvent(chart.container, 'focus', function (e) { return _this.onFocus(e); }); ep.addEvent(doc, 'mouseup', function () { return _this.onMouseUp(); }); ep.addEvent(chart.renderTo, 'mousedown', function () { _this.isClickingChart = true; }); ep.addEvent(chart.renderTo, 'mouseover', function () { _this.pointerIsOverChart = true; }); ep.addEvent(chart.renderTo, 'mouseout', function () { _this.pointerIsOverChart = false; }); // Run an update to get all modules this.update(); // Init first module if (this.modules.length) { this.modules[0].init(1); } }, /** * Update the modules for the keyboard navigation. * @param {Array} [order] * Array specifying the tab order of the components. */ update: function (order) { var a11yOptions = this.chart.options.accessibility, keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation, components = this.components; this.updateContainerTabindex(); if (keyboardOptions && keyboardOptions.enabled && order && order.length) { // We (still) have keyboard navigation. Update module list this.modules = order.reduce(function (modules, componentName) { var navModules = components[componentName].getKeyboardNavigation(); return modules.concat(navModules); }, []); this.updateExitAnchor(); } else { this.modules = []; this.currentModuleIx = 0; this.removeExitAnchor(); } }, /** * Function to run on container focus * @private * @param {global.FocusEvent} e Browser focus event. */ onFocus: function (e) { var _a; var chart = this.chart; var focusComesFromChart = (e.relatedTarget && chart.container.contains(e.relatedTarget)); // Init keyboard nav if tabbing into chart if (!this.isClickingChart && !focusComesFromChart) { (_a = this.modules[0]) === null || _a === void 0 ? void 0 : _a.init(1); } }, /** * Reset chart navigation state if we click outside the chart and it's * not already reset. * @private */ onMouseUp: function () { delete this.isClickingChart; if (!this.keyboardReset && !this.pointerIsOverChart) { var chart = this.chart, curMod = this.modules && this.modules[this.currentModuleIx || 0]; if (curMod && curMod.terminate) { curMod.terminate(); } if (chart.focusElement) { chart.focusElement.removeFocusBorder(); } this.currentModuleIx = 0; this.keyboardReset = true; } }, /** * Function to run on keydown * @private * @param {global.KeyboardEvent} ev Browser keydown event. */ onKeydown: function (ev) { var e = ev || win.event, preventDefault, curNavModule = this.modules && this.modules.length && this.modules[this.currentModuleIx]; // Used for resetting nav state when clicking outside chart this.keyboardReset = false; // If there is a nav module for the current index, run it. // Otherwise, we are outside of the chart in some direction. if (curNavModule) { var response = curNavModule.run(e); if (response === curNavModule.response.success) { preventDefault = true; } else if (response === curNavModule.response.prev) { preventDefault = this.prev(); } else if (response === curNavModule.response.next) { preventDefault = this.next(); } if (preventDefault) { e.preventDefault(); e.stopPropagation(); } } }, /** * Go to previous module. * @private */ prev: function () { return this.move(-1); }, /** * Go to next module. * @private */ next: function () { return this.move(1); }, /** * Move to prev/next module. * @private * @param {number} direction * Direction to move. +1 for next, -1 for prev. * @return {boolean} * True if there was a valid module in direction. */ move: function (direction) { var curModule = this.modules && this.modules[this.currentModuleIx]; if (curModule && curModule.terminate) { curModule.terminate(direction); } // Remove existing focus border if any if (this.chart.focusElement) { this.chart.focusElement.removeFocusBorder(); } this.currentModuleIx += direction; var newModule = this.modules && this.modules[this.currentModuleIx]; if (newModule) { if (newModule.validate && !newModule.validate()) { return this.move(direction); // Invalid module, recurse } if (newModule.init) { newModule.init(direction); // Valid module, init it return true; } } // No module this.currentModuleIx = 0; // Reset counter // Set focus to chart or exit anchor depending on direction if (direction > 0) { this.exiting = true; this.exitAnchor.focus(); } else { this.chart.container.focus(); } return false; }, /** * We use an exit anchor to move focus out of chart whenever we want, by * setting focus to this div and not preventing the default tab action. We * also use this when users come back into the chart by tabbing back, in * order to navigate from the end of the chart. * @private */ updateExitAnchor: function () { var endMarkerId = 'highcharts-end-of-chart-marker-' + this.chart.index, endMarker = getElement(endMarkerId); this.removeExitAnchor(); if (endMarker) { this.makeElementAnExitAnchor(endMarker); this.exitAnchor = endMarker; } else { this.createExitAnchor(); } }, /** * Chart container should have tabindex if navigation is enabled. * @private */ updateContainerTabindex: function () { var a11yOptions = this.chart.options.accessibility, keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation, shouldHaveTabindex = !(keyboardOptions && keyboardOptions.enabled === false), container = this.chart.container, curTabindex = container.getAttribute('tabIndex'); if (shouldHaveTabindex && !curTabindex) { container.setAttribute('tabindex', '0'); } else if (!shouldHaveTabindex && curTabindex === '0') { container.removeAttribute('tabindex'); } }, /** * @private */ makeElementAnExitAnchor: function (el) { el.setAttribute('class', 'highcharts-exit-anchor'); el.setAttribute('tabindex', '0'); el.setAttribute('aria-hidden', false); // Handle focus this.addExitAnchorEventsToEl(el); }, /** * Add new exit anchor to the chart. * * @private */ createExitAnchor: function () { var chart = this.chart, exitAnchor = this.exitAnchor = doc.createElement('div'); chart.renderTo.appendChild(exitAnchor); this.makeElementAnExitAnchor(exitAnchor); }, /** * @private */ removeExitAnchor: function () { if (this.exitAnchor && this.exitAnchor.parentNode) { this.exitAnchor.parentNode .removeChild(this.exitAnchor); delete this.exitAnchor; } }, /** * @private */ addExitAnchorEventsToEl: function (element) { var chart = this.chart, keyboardNavigation = this; this.eventProvider.addEvent(element, 'focus', function (ev) { var e = ev || win.event, curModule, focusComesFromChart = (e.relatedTarget && chart.container.contains(e.relatedTarget)), comingInBackwards = !(focusComesFromChart || keyboardNavigation.exiting); if (comingInBackwards) { chart.renderTo.focus(); e.preventDefault(); // Move to last valid keyboard nav module // Note the we don't run it, just set the index if (keyboardNavigation.modules && keyboardNavigation.modules.length) { keyboardNavigation.currentModuleIx = keyboardNavigation.modules.length - 1; curModule = keyboardNavigation.modules[keyboardNavigation.currentModuleIx]; // Validate the module if (curModule && curModule.validate && !curModule.validate()) { // Invalid. Try moving backwards to find next valid. keyboardNavigation.prev(); } else if (curModule) { // We have a valid module, init it curModule.init(-1); } } } else { // Don't skip the next focus, we only skip once. keyboardNavigation.exiting = false; } }); }, /** * Remove all traces of keyboard navigation. * @private */ destroy: function () { this.removeExitAnchor(); this.eventProvider.removeAddedEvents(); if (this.chart.container.getAttribute('tabindex') === '0') { this.chart.container.removeAttribute('tabindex'); } } }; return KeyboardNavigation; }); _registerModule(_modules, 'modules/accessibility/components/LegendComponent.js', [_modules['parts/Globals.js'], _modules['parts/Legend.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/AccessibilityComponent.js'], _modules['modules/accessibility/KeyboardNavigationHandler.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, Legend, U, AccessibilityComponent, KeyboardNavigationHandler, HTMLUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component for chart legend. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, extend = U.extend, fireEvent = U.fireEvent; var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString, removeElement = HTMLUtilities.removeElement; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private */ function scrollLegendToItem(legend, itemIx) { var itemPage = legend.allItems[itemIx].pageIx, curPage = legend.currentPage; if (typeof itemPage !== 'undefined' && itemPage + 1 !== curPage) { legend.scroll(1 + itemPage - curPage); } } /** * @private */ function shouldDoLegendA11y(chart) { var items = chart.legend && chart.legend.allItems, legendA11yOptions = (chart.options.legend.accessibility || {}); return !!(items && items.length && !(chart.colorAxis && chart.colorAxis.length) && legendA11yOptions.enabled !== false); } /** * 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]) { fireEvent(items[oldIx].legendGroup.element, 'mouseout'); } scrollLegendToItem(this.legend, ix); this.setFocusToElement(items[ix].legendItem, items[ix].a11yProxyElement); fireEvent(items[ix].legendGroup.element, 'mouseover'); return true; } return false; }; // Keep track of pressed state for legend items addEvent(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 */ var LegendComponent = function () { }; LegendComponent.prototype = new AccessibilityComponent(); extend(LegendComponent.prototype, /** @lends Highcharts.LegendComponent */ { /** * Init the component * @private */ init: function () { var component = this; this.addEvent(Legend, 'afterScroll', function () { if (this.chart === component.chart) { component.updateProxies(); } }); }, /** * @private */ updateLegendItemProxyVisibility: function () { var legend = this.chart.legend, items = legend.allItems || [], curPage = legend.currentPage || 1, clipHeight = legend.clipHeight || 0; items.forEach(function (item) { var itemPage = item.pageIx || 0, y = item._legendItemPos ? item._legendItemPos[1] : 0, h = item.legendItem ? Math.round(item.legendItem.getBBox().height) : 0, hide = y + h - legend.pages[itemPage] > clipHeight || itemPage !== curPage - 1; if (item.a11yProxyElement) { item.a11yProxyElement.style.visibility = hide ? 'hidden' : 'visible'; } }); }, /** * The legend needs updates on every render, in order to update positioning * of the proxy overlays. */ onChartRender: function () { var component = this; // Ignore render after proxy clicked. No need to destroy it, and // destroying also kills focus. if (this.legendProxyButtonClicked) { delete component.legendProxyButtonClicked; return; } this.updateProxies(); }, /** * @private */ updateProxies: function () { removeElement(this.legendProxyGroup); if (shouldDoLegendA11y(this.chart)) { this.addLegendProxyGroup(); this.proxyLegendItems(); this.updateLegendItemProxyVisibility(); } }, /** * @private */ addLegendProxyGroup: function () { var a11yOptions = this.chart.options.accessibility, groupLabel = this.chart.langFormat('accessibility.legend.legendLabel', {}), groupRole = a11yOptions.landmarkVerbosity === 'all' ? 'region' : null; this.legendProxyGroup = this.addProxyGroup({ 'aria-label': groupLabel, 'role': groupRole }); }, /** * @private */ proxyLegendItems: function () { var component = this, items = (this.chart.legend && this.chart.legend.allItems || []); items.forEach(function (item) { if (item.legendItem && item.legendItem.element) { component.proxyLegendItem(item); } }); }, /** * @private * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item */ proxyLegendItem: function (item) { var component = this, itemLabel = this.chart.langFormat('accessibility.legend.legendItem', { chart: this.chart, itemName: stripHTMLTags(item.name) }), attribs = { tabindex: -1, 'aria-pressed': !item.visible, 'aria-label': itemLabel }, // Keep track of when we should ignore next render preClickEvent = function () { component.legendProxyButtonClicked = true; }, // Considers useHTML proxyPositioningElement = item.legendGroup.div ? item.legendItem : item.legendGroup; item.a11yProxyElement = this.createProxyButton(item.legendItem, this.legendProxyGroup, attribs, proxyPositioningElement, preClickEvent); }, /** * Get keyboard navigation handler for this component. * @return {Highcharts.KeyboardNavigationHandler} */ getKeyboardNavigation: function () { var keys = this.keyCodes, component = this, chart = this.chart; return new KeyboardNavigationHandler(chart, { keyCodeMap: [ [ [keys.left, keys.right, keys.up, keys.down], function (keyCode) { return component.onKbdArrowKey(this, keyCode); } ], [ [keys.enter, keys.space], function () { return component.onKbdClick(this); } ] ], validate: function () { return component.shouldHaveLegendNavigation(); }, init: function (direction) { return component.onKbdNavigationInit(direction); } }); }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler * @param {number} keyCode * @return {number} * Response code */ onKbdArrowKey: function (keyboardNavigationHandler, keyCode) { var keys = this.keyCodes, response = keyboardNavigationHandler.response, chart = this.chart, a11yOptions = chart.options.accessibility, numItems = chart.legend.allItems.length, direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1; var res = chart.highlightLegendItem(this.highlightedLegendItemIx + direction); if (res) { this.highlightedLegendItemIx += direction; return response.success; } if (numItems > 1 && a11yOptions.keyboardNavigation.wrapAround) { keyboardNavigationHandler.init(direction); return response.success; } // No wrap, move return response[direction > 0 ? 'next' : 'prev']; }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler * @return {number} * Response code */ onKbdClick: function (keyboardNavigationHandler) { var legendItem = this.chart.legend.allItems[this.highlightedLegendItemIx]; if (legendItem && legendItem.a11yProxyElement) { fireEvent(legendItem.a11yProxyElement, 'click'); } return keyboardNavigationHandler.response.success; }, /** * @private * @return {boolean|undefined} */ shouldHaveLegendNavigation: function () { var chart = this.chart, legendOptions = chart.options.legend || {}, hasLegend = chart.legend && chart.legend.allItems, hasColorAxis = chart.colorAxis && chart.colorAxis.length, legendA11yOptions = (legendOptions.accessibility || {}); return !!(hasLegend && chart.legend.display && !hasColorAxis && legendA11yOptions.enabled && legendA11yOptions.keyboardNavigation && legendA11yOptions.keyboardNavigation.enabled); }, /** * @private * @param {number} direction */ onKbdNavigationInit: function (direction) { var chart = this.chart, lastIx = chart.legend.allItems.length - 1, ixToHighlight = direction > 0 ? 0 : lastIx; chart.highlightLegendItem(ixToHighlight); this.highlightedLegendItemIx = ixToHighlight; } }); return LegendComponent; }); _registerModule(_modules, 'modules/accessibility/components/MenuComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/AccessibilityComponent.js'], _modules['modules/accessibility/KeyboardNavigationHandler.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, U, AccessibilityComponent, KeyboardNavigationHandler, ChartUtilities, HTMLUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component for exporting menu. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var extend = U.extend; var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; 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); } ], // ESC handler [ [keys.esc], function () { return this.response.prev; } ] ], // 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; } }); return MenuComponent; }); _registerModule(_modules, 'modules/accessibility/components/SeriesComponent/SeriesKeyboardNavigation.js', [_modules['parts/Globals.js'], _modules['parts/Point.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/KeyboardNavigationHandler.js'], _modules['modules/accessibility/utils/EventProvider.js'], _modules['modules/accessibility/utils/chartUtilities.js']], function (H, Point, U, KeyboardNavigationHandler, EventProvider, ChartUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Handle keyboard navigation for series. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var extend = U.extend, defined = U.defined; var getPointFromXY = ChartUtilities.getPointFromXY, getSeriesFromName = ChartUtilities.getSeriesFromName, scrollToPoint = ChartUtilities.scrollToPoint; /* eslint-disable no-invalid-this, valid-jsdoc */ /* * Set for which series types it makes sense to move to the closest point with * up/down arrows, and which series types should just move to next series. */ H.Series.prototype.keyboardMoveVertical = true; ['column', 'pie'].forEach(function (type) { if (H.seriesTypes[type]) { H.seriesTypes[type].prototype.keyboardMoveVertical = false; } }); /** * Get the index of a point in a series. This is needed when using e.g. data * grouping. * * @private * @function getPointIndex * * @param {Highcharts.AccessibilityPoint} point * The point to find index of. * * @return {number|undefined} * The index in the series.points array of the point. */ function getPointIndex(point) { var index = point.index, points = point.series.points, i = points.length; if (points[index] !== point) { while (i--) { if (points[i] === point) { return i; } } } else { return index; } } /** * Determine if series navigation should be skipped * * @private * @function isSkipSeries * * @param {Highcharts.Series} series * * @return {boolean|number|undefined} */ function isSkipSeries(series) { var a11yOptions = series.chart.options.accessibility, seriesNavOptions = a11yOptions.keyboardNavigation.seriesNavigation, seriesA11yOptions = series.options.accessibility || {}, seriesKbdNavOptions = seriesA11yOptions.keyboardNavigation; return seriesKbdNavOptions && seriesKbdNavOptions.enabled === false || seriesA11yOptions.enabled === false || series.options.enableMouseTracking === false || // #8440 !series.visible || // Skip all points in a series where pointNavigationEnabledThreshold is // reached (seriesNavOptions.pointNavigationEnabledThreshold && seriesNavOptions.pointNavigationEnabledThreshold <= series.points.length); } /** * Determine if navigation for a point should be skipped * * @private * @function isSkipPoint * * @param {Highcharts.Point} point * * @return {boolean|number|undefined} */ function isSkipPoint(point) { var a11yOptions = point.series.chart.options.accessibility; return point.isNull && a11yOptions.keyboardNavigation.seriesNavigation.skipNullPoints || point.visible === false || isSkipSeries(point.series); } /** * Get the point in a series that is closest (in pixel distance) to a reference * point. Optionally supply weight factors for x and y directions. * * @private * @function getClosestPoint * * @param {Highcharts.Point} point * @param {Highcharts.Series} series * @param {number} [xWeight] * @param {number} [yWeight] * * @return {Highcharts.Point|undefined} */ function getClosestPoint(point, series, xWeight, yWeight) { var minDistance = Infinity, dPoint, minIx, distance, i = series.points.length, hasUndefinedPosition = function (point) { return !(defined(point.plotX) && defined(point.plotY)); }; if (hasUndefinedPosition(point)) { return; } while (i--) { dPoint = series.points[i]; if (hasUndefinedPosition(dPoint)) { continue; } distance = (point.plotX - dPoint.plotX) * (point.plotX - dPoint.plotX) * (xWeight || 1) + (point.plotY - dPoint.plotY) * (point.plotY - dPoint.plotY) * (yWeight || 1); if (distance < minDistance) { minDistance = distance; minIx = i; } } return defined(minIx) ? series.points[minIx] : void 0; } /** * Highlights a point (show tooltip and display hover state). * * @private * @function Highcharts.Point#highlight * * @return {Highcharts.Point} * This highlighted point. */ Point.prototype.highlight = function () { var chart = this.series.chart; if (!this.isNull) { this.onMouseOver(); // Show the hover marker and tooltip } else { if (chart.tooltip) { chart.tooltip.hide(0); } // Don't call blur on the element, as it messes up the chart div's focus } scrollToPoint(this); // We focus only after calling onMouseOver because the state change can // change z-index and mess up the element. if (this.graphic) { chart.setFocusToElement(this.graphic); } chart.highlightedPoint = this; return this; }; /** * Function to highlight next/previous point in chart. * * @private * @function Highcharts.Chart#highlightAdjacentPoint * * @param {boolean} next * Flag for the direction. * * @return {Highcharts.Point|boolean} * Returns highlighted point on success, false on failure (no adjacent * point to highlight in chosen direction). */ H.Chart.prototype.highlightAdjacentPoint = function (next) { var chart = this, series = chart.series, curPoint = chart.highlightedPoint, curPointIndex = curPoint && getPointIndex(curPoint) || 0, curPoints = (curPoint && curPoint.series.points), lastSeries = chart.series && chart.series[chart.series.length - 1], lastPoint = lastSeries && lastSeries.points && lastSeries.points[lastSeries.points.length - 1], newSeries, newPoint; // If no points, return false if (!series[0] || !series[0].points) { return false; } if (!curPoint) { // No point is highlighted yet. Try first/last point depending on move // direction newPoint = next ? series[0].points[0] : lastPoint; } else { // We have a highlighted point. // Grab next/prev point & series newSeries = series[curPoint.series.index + (next ? 1 : -1)]; newPoint = curPoints[curPointIndex + (next ? 1 : -1)]; if (!newPoint && newSeries) { // Done with this series, try next one newPoint = newSeries.points[next ? 0 : newSeries.points.length - 1]; } // If there is no adjacent point, we return false if (!newPoint) { return false; } } // Recursively skip points if (isSkipPoint(newPoint)) { // If we skip this whole series, move to the end of the series before we // recurse, just to optimize newSeries = newPoint.series; if (isSkipSeries(newSeries)) { chart.highlightedPoint = next ? newSeries.points[newSeries.points.length - 1] : newSeries.points[0]; } else { // Otherwise, just move one point chart.highlightedPoint = newPoint; } // Retry return chart.highlightAdjacentPoint(next); } // There is an adjacent point, highlight it return newPoint.highlight(); }; /** * Highlight first valid point in a series. Returns the point if successfully * highlighted, otherwise false. If there is a highlighted point in the series, * use that as starting point. * * @private * @function Highcharts.Series#highlightFirstValidPoint * * @return {boolean|Highcharts.Point} */ H.Series.prototype.highlightFirstValidPoint = function () { var curPoint = this.chart.highlightedPoint, start = (curPoint && curPoint.series) === this ? getPointIndex(curPoint) : 0, points = this.points, len = points.length; if (points && len) { for (var i = start; i < len; ++i) { if (!isSkipPoint(points[i])) { return points[i].highlight(); } } for (var j = start; j >= 0; --j) { if (!isSkipPoint(points[j])) { return points[j].highlight(); } } } return false; }; /** * Highlight next/previous series in chart. Returns false if no adjacent series * in the direction, otherwise returns new highlighted point. * * @private * @function Highcharts.Chart#highlightAdjacentSeries * * @param {boolean} down * * @return {Highcharts.Point|boolean} */ H.Chart.prototype.highlightAdjacentSeries = function (down) { var chart = this, newSeries, newPoint, adjacentNewPoint, curPoint = chart.highlightedPoint, lastSeries = chart.series && chart.series[chart.series.length - 1], lastPoint = lastSeries && lastSeries.points && lastSeries.points[lastSeries.points.length - 1]; // If no point is highlighted, highlight the first/last point if (!chart.highlightedPoint) { newSeries = down ? (chart.series && chart.series[0]) : lastSeries; newPoint = down ? (newSeries && newSeries.points && newSeries.points[0]) : lastPoint; return newPoint ? newPoint.highlight() : false; } newSeries = chart.series[curPoint.series.index + (down ? -1 : 1)]; if (!newSeries) { return false; } // We have a new series in this direction, find the right point // Weigh xDistance as counting much higher than Y distance newPoint = getClosestPoint(curPoint, newSeries, 4); if (!newPoint) { return false; } // New series and point exists, but we might want to skip it if (isSkipSeries(newSeries)) { // Skip the series newPoint.highlight(); adjacentNewPoint = chart.highlightAdjacentSeries(down); // Try recurse if (!adjacentNewPoint) { // Recurse failed curPoint.highlight(); return false; } // Recurse succeeded return adjacentNewPoint; } // Highlight the new point or any first valid point back or forwards from it newPoint.highlight(); return newPoint.series.highlightFirstValidPoint(); }; /** * Highlight the closest point vertically. * * @private * @function Highcharts.Chart#highlightAdjacentPointVertical * * @param {boolean} down * * @return {Highcharts.Point|boolean} */ H.Chart.prototype.highlightAdjacentPointVertical = function (down) { var curPoint = this.highlightedPoint, minDistance = Infinity, bestPoint; if (!defined(curPoint.plotX) || !defined(curPoint.plotY)) { return false; } this.series.forEach(function (series) { if (isSkipSeries(series)) { return; } series.points.forEach(function (point) { if (!defined(point.plotY) || !defined(point.plotX) || point === curPoint) { return; } var yDistance = point.plotY - curPoint.plotY, width = Math.abs(point.plotX - curPoint.plotX), distance = Math.abs(yDistance) * Math.abs(yDistance) + width * width * 4; // Weigh horizontal distance highly // Reverse distance number if axis is reversed if (series.yAxis && series.yAxis.reversed) { yDistance *= -1; } if (yDistance <= 0 && down || yDistance >= 0 && !down || // Chk dir distance < 5 || // Points in same spot => infinite loop isSkipPoint(point)) { return; } if (distance < minDistance) { minDistance = distance; bestPoint = point; } }); }); return bestPoint ? bestPoint.highlight() : false; }; /** * @private * @param {Highcharts.Chart} chart * @return {Highcharts.Point|boolean} */ function highlightFirstValidPointInChart(chart) { var res = false; delete chart.highlightedPoint; res = chart.series.reduce(function (acc, cur) { return acc || cur.highlightFirstValidPoint(); }, false); return res; } /** * @private * @param {Highcharts.Chart} chart * @return {Highcharts.Point|boolean} */ function highlightLastValidPointInChart(chart) { var numSeries = chart.series.length, i = numSeries, res = false; while (i--) { chart.highlightedPoint = chart.series[i].points[chart.series[i].points.length - 1]; // Highlight first valid point in the series will also // look backwards. It always starts from currently // highlighted point. res = chart.series[i].highlightFirstValidPoint(); if (res) { break; } } return res; } /** * @private * @param {Highcharts.Chart} chart */ function updateChartFocusAfterDrilling(chart) { highlightFirstValidPointInChart(chart); if (chart.focusElement) { chart.focusElement.removeFocusBorder(); } } /** * @private * @class * @name Highcharts.SeriesKeyboardNavigation */ function SeriesKeyboardNavigation(chart, keyCodes) { this.keyCodes = keyCodes; this.chart = chart; } extend(SeriesKeyboardNavigation.prototype, /** @lends Highcharts.SeriesKeyboardNavigation */ { /** * Init the keyboard navigation */ init: function () { var keyboardNavigation = this, chart = this.chart, e = this.eventProvider = new EventProvider(); e.addEvent(H.Series, 'destroy', function () { return keyboardNavigation.onSeriesDestroy(this); }); e.addEvent(chart, 'afterDrilldown', function () { updateChartFocusAfterDrilling(this); }); e.addEvent(chart, 'drilldown', function (e) { var point = e.point, series = point.series; keyboardNavigation.lastDrilledDownPoint = { x: point.x, y: point.y, seriesName: series ? series.name : '' }; }); e.addEvent(chart, 'drillupall', function () { setTimeout(function () { keyboardNavigation.onDrillupAll(); }, 10); }); }, onDrillupAll: function () { // After drillup we want to find the point that was drilled down to and // highlight it. var last = this.lastDrilledDownPoint, chart = this.chart, series = last && getSeriesFromName(chart, last.seriesName), point; if (last && series && defined(last.x) && defined(last.y)) { point = getPointFromXY(series, last.x, last.y); } // Container focus can be lost on drillup due to deleted elements. if (chart.container) { chart.container.focus(); } if (point && point.highlight) { point.highlight(); } if (chart.focusElement) { chart.focusElement.removeFocusBorder(); } }, /** * @return {Highcharts.KeyboardNavigationHandler} */ getKeyboardNavigationHandler: function () { var keyboardNavigation = this, keys = this.keyCodes, chart = this.chart, inverted = chart.inverted; return new KeyboardNavigationHandler(chart, { keyCodeMap: [ [inverted ? [keys.up, keys.down] : [keys.left, keys.right], function (keyCode) { return keyboardNavigation.onKbdSideways(this, keyCode); }], [inverted ? [keys.left, keys.right] : [keys.up, keys.down], function (keyCode) { return keyboardNavigation.onKbdVertical(this, keyCode); }], [[keys.enter, keys.space], function () { if (chart.highlightedPoint) { chart.highlightedPoint.firePointEvent('click'); } return this.response.success; }] ], init: function (dir) { return keyboardNavigation.onHandlerInit(this, dir); }, terminate: function () { return keyboardNavigation.onHandlerTerminate(); } }); }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} handler * @param {number} keyCode * @return {number} * response */ onKbdSideways: function (handler, keyCode) { var keys = this.keyCodes, isNext = keyCode === keys.right || keyCode === keys.down; return this.attemptHighlightAdjacentPoint(handler, isNext); }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} handler * @param {number} keyCode * @return {number} * response */ onKbdVertical: function (handler, keyCode) { var chart = this.chart, keys = this.keyCodes, isNext = keyCode === keys.down || keyCode === keys.right, navOptions = chart.options.accessibility.keyboardNavigation .seriesNavigation; // Handle serialized mode, act like left/right if (navOptions.mode && navOptions.mode === 'serialize') { return this.attemptHighlightAdjacentPoint(handler, isNext); } // Normal mode, move between series var highlightMethod = (chart.highlightedPoint && chart.highlightedPoint.series.keyboardMoveVertical) ? 'highlightAdjacentPointVertical' : 'highlightAdjacentSeries'; chart[highlightMethod](isNext); return handler.response.success; }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} handler * @param {number} initDirection * @return {number} * response */ onHandlerInit: function (handler, initDirection) { var chart = this.chart; if (initDirection > 0) { highlightFirstValidPointInChart(chart); } else { highlightLastValidPointInChart(chart); } return handler.response.success; }, /** * @private */ onHandlerTerminate: function () { var _a, _b; var chart = this.chart; var curPoint = chart.highlightedPoint; (_a = chart.tooltip) === null || _a === void 0 ? void 0 : _a.hide(0); (_b = curPoint === null || curPoint === void 0 ? void 0 : curPoint.onMouseOut) === null || _b === void 0 ? void 0 : _b.call(curPoint); delete chart.highlightedPoint; }, /** * Function that attempts to highlight next/prev point. Handles wrap around. * @private * @param {Highcharts.KeyboardNavigationHandler} handler * @param {boolean} directionIsNext * @return {number} * response */ attemptHighlightAdjacentPoint: function (handler, directionIsNext) { var chart = this.chart, wrapAround = chart.options.accessibility.keyboardNavigation .wrapAround, highlightSuccessful = chart.highlightAdjacentPoint(directionIsNext); if (!highlightSuccessful) { if (wrapAround) { return handler.init(directionIsNext ? 1 : -1); } return handler.response[directionIsNext ? 'next' : 'prev']; } return handler.response.success; }, /** * @private */ onSeriesDestroy: function (series) { var chart = this.chart, currentHighlightedPointDestroyed = chart.highlightedPoint && chart.highlightedPoint.series === series; if (currentHighlightedPointDestroyed) { delete chart.highlightedPoint; if (chart.focusElement) { chart.focusElement.removeFocusBorder(); } } }, /** * @private */ destroy: function () { this.eventProvider.removeAddedEvents(); } }); return SeriesKeyboardNavigation; }); _registerModule(_modules, 'modules/accessibility/components/AnnotationsA11y.js', [_modules['parts/Utilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (U, HTMLUtilities) { /* * * * (c) 2009-2019 Øystein Moseng * * Annotations accessibility code. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var inArray = U.inArray; var escapeStringForHTML = HTMLUtilities.escapeStringForHTML, stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString; /** * Get list of all annotation labels in the chart. * * @private * @param {Highcharts.Chart} chart The chart to get annotation info on. * @return {Array} The labels, or empty array if none. */ function getChartAnnotationLabels(chart) { var annotations = chart.annotations || []; return annotations.reduce(function (acc, cur) { var _a; if (((_a = cur.options) === null || _a === void 0 ? void 0 : _a.visible) !== false) { acc = acc.concat(cur.labels); } return acc; }, []); } /** * Get the text of an annotation label. * * @private * @param {object} label The annotation label object * @return {string} The text in the label. */ function getLabelText(label) { var _a, _b, _c, _d; var a11yDesc = (_b = (_a = label.options) === null || _a === void 0 ? void 0 : _a.accessibility) === null || _b === void 0 ? void 0 : _b.description; return a11yDesc ? a11yDesc : ((_d = (_c = label.graphic) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.textStr) || ''; } /** * Describe an annotation label. * * @private * @param {object} label The annotation label object to describe * @return {string} The description for the label. */ function getAnnotationLabelDescription(label) { var _a, _b; var a11yDesc = (_b = (_a = label.options) === null || _a === void 0 ? void 0 : _a.accessibility) === null || _b === void 0 ? void 0 : _b.description; if (a11yDesc) { return a11yDesc; } var chart = label.chart; var labelText = getLabelText(label); var points = label.points; var getAriaLabel = function (point) { var _a, _b; return ((_b = (_a = point === null || point === void 0 ? void 0 : point.graphic) === null || _a === void 0 ? void 0 : _a.element) === null || _b === void 0 ? void 0 : _b.getAttribute('aria-label')) || ''; }; var getValueDesc = function (point) { var _a; var valDesc = ((_a = point === null || point === void 0 ? void 0 : point.accessibility) === null || _a === void 0 ? void 0 : _a.valueDescription) || getAriaLabel(point); var seriesName = (point === null || point === void 0 ? void 0 : point.series.name) || ''; return (seriesName ? seriesName + ', ' : '') + 'data point ' + valDesc; }; var pointValueDescriptions = points .filter(function (p) { return !!p.graphic; }) // Filter out mock points .map(getValueDesc) .filter(function (desc) { return !!desc; }); // Filter out points we can't describe var numPoints = pointValueDescriptions.length; var pointsSelector = numPoints > 1 ? 'MultiplePoints' : numPoints ? 'SinglePoint' : 'NoPoints'; var langFormatStr = 'accessibility.screenReaderSection.annotations.description' + pointsSelector; var context = { annotationText: labelText, numPoints: numPoints, annotationPoint: pointValueDescriptions[0], additionalAnnotationPoints: pointValueDescriptions.slice(1) }; return chart.langFormat(langFormatStr, context); } /** * Return array of HTML strings for each annotation label in the chart. * * @private * @param {Highcharts.Chart} chart The chart to get annotation info on. * @return {Array} Array of strings with HTML content for each annotation label. */ function getAnnotationListItems(chart) { var labels = getChartAnnotationLabels(chart); return labels.map(function (label) { var desc = escapeStringForHTML(stripHTMLTagsFromString(getAnnotationLabelDescription(label))); return desc ? "
  • " + desc + "
  • " : ''; }); } /** * Return the annotation info for a chart as string. * * @private * @param {Highcharts.Chart} chart The chart to get annotation info on. * @return {string} String with HTML content or empty string if no annotations. */ function getAnnotationsInfoHTML(chart) { var annotations = chart.annotations; if (!(annotations && annotations.length)) { return ''; } var annotationItems = getAnnotationListItems(chart); return "
      " + annotationItems.join(' ') + "
    "; } /** * Return the texts for the annotation(s) connected to a point, or empty array * if none. * * @private * @param {Highcharts.Point} point The data point to get the annotation info from. * @return {Array} Annotation texts */ function getPointAnnotationTexts(point) { var labels = getChartAnnotationLabels(point.series.chart); var pointLabels = labels .filter(function (label) { return inArray(point, label.points) > -1; }); if (!pointLabels.length) { return []; } return pointLabels.map(function (label) { return "" + getLabelText(label); }); } var AnnotationsA11y = { getAnnotationsInfoHTML: getAnnotationsInfoHTML, getAnnotationLabelDescription: getAnnotationLabelDescription, getAnnotationListItems: getAnnotationListItems, getPointAnnotationTexts: getPointAnnotationTexts }; return AnnotationsA11y; }); _registerModule(_modules, 'modules/accessibility/components/SeriesComponent/SeriesDescriber.js', [_modules['parts/Utilities.js'], _modules['modules/accessibility/components/AnnotationsA11y.js'], _modules['modules/accessibility/utils/htmlUtilities.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['parts/Tooltip.js']], function (U, AnnotationsA11y, HTMLUtilities, ChartUtilities, Tooltip) { /* * * * (c) 2009-2020 Øystein Moseng * * Place desriptions on a series and its points. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var find = U.find, format = U.format, isNumber = U.isNumber, numberFormat = U.numberFormat, pick = U.pick, defined = U.defined; var getPointAnnotationTexts = AnnotationsA11y.getPointAnnotationTexts; var escapeStringForHTML = HTMLUtilities.escapeStringForHTML, reverseChildNodes = HTMLUtilities.reverseChildNodes, stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString; var getAxisDescription = ChartUtilities.getAxisDescription, getSeriesFirstPointElement = ChartUtilities.getSeriesFirstPointElement, getSeriesA11yElement = ChartUtilities.getSeriesA11yElement, unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; /* eslint-disable valid-jsdoc */ /** * @private */ function findFirstPointWithGraphic(point) { var sourcePointIndex = point.index; if (!point.series || !point.series.data || !defined(sourcePointIndex)) { return null; } return find(point.series.data, function (p) { return !!(p && typeof p.index !== 'undefined' && p.index > sourcePointIndex && p.graphic && p.graphic.element); }) || null; } /** * @private */ function shouldAddDummyPoint(point) { // Note: Sunburst series use isNull for hidden points on drilldown. // Ignore these. var isSunburst = point.series && point.series.is('sunburst'), isNull = point.isNull; return isNull && !isSunburst; } /** * @private */ function makeDummyElement(point, pos) { var renderer = point.series.chart.renderer, dummy = renderer.rect(pos.x, pos.y, 1, 1); dummy.attr({ 'class': 'highcharts-a11y-dummy-point', fill: 'none', opacity: 0, 'fill-opacity': 0, 'stroke-opacity': 0 }); return dummy; } /** * @private * @param {Highcharts.Point} point * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined} */ function addDummyPointElement(point) { var series = point.series, firstPointWithGraphic = findFirstPointWithGraphic(point), firstGraphic = firstPointWithGraphic && firstPointWithGraphic.graphic, parentGroup = firstGraphic ? firstGraphic.parentGroup : series.graph || series.group, dummyPos = firstPointWithGraphic ? { x: pick(point.plotX, firstPointWithGraphic.plotX, 0), y: pick(point.plotY, firstPointWithGraphic.plotY, 0) } : { x: pick(point.plotX, 0), y: pick(point.plotY, 0) }, dummyElement = makeDummyElement(point, dummyPos); if (parentGroup && parentGroup.element) { point.graphic = dummyElement; point.hasDummyGraphic = true; dummyElement.add(parentGroup); // Move to correct pos in DOM parentGroup.element.insertBefore(dummyElement.element, firstGraphic ? firstGraphic.element : null); return dummyElement.element; } } /** * @private * @param {Highcharts.Series} series * @return {boolean} */ function hasMorePointsThanDescriptionThreshold(series) { var chartA11yOptions = series.chart.options.accessibility, threshold = (chartA11yOptions.series.pointDescriptionEnabledThreshold); return !!(threshold !== false && series.points && series.points.length >= threshold); } /** * @private * @param {Highcharts.Series} series * @return {boolean} */ function shouldSetScreenReaderPropsOnPoints(series) { var seriesA11yOptions = series.options.accessibility || {}; return !hasMorePointsThanDescriptionThreshold(series) && !seriesA11yOptions.exposeAsGroupOnly; } /** * @private * @param {Highcharts.Series} series * @return {boolean} */ function shouldSetKeyboardNavPropsOnPoints(series) { var chartA11yOptions = series.chart.options.accessibility, seriesNavOptions = chartA11yOptions.keyboardNavigation.seriesNavigation; return !!(series.points && (series.points.length < seriesNavOptions.pointNavigationEnabledThreshold || seriesNavOptions.pointNavigationEnabledThreshold === false)); } /** * @private * @param {Highcharts.Series} series * @return {boolean} */ function shouldDescribeSeriesElement(series) { var chart = series.chart, chartOptions = chart.options.chart || {}, chartHas3d = chartOptions.options3d && chartOptions.options3d.enabled, hasMultipleSeries = chart.series.length > 1, describeSingleSeriesOption = chart.options.accessibility.series.describeSingleSeries, exposeAsGroupOnlyOption = (series.options.accessibility || {}).exposeAsGroupOnly, noDescribe3D = chartHas3d && hasMultipleSeries; return !noDescribe3D && (hasMultipleSeries || describeSingleSeriesOption || exposeAsGroupOnlyOption || hasMorePointsThanDescriptionThreshold(series)); } /** * @private * @param {Highcharts.Point} point * @param {number} value * @return {string} */ function pointNumberToString(point, value) { var chart = point.series.chart, a11yPointOptions = chart.options.accessibility.point || {}, tooltipOptions = point.series.tooltipOptions || {}, lang = chart.options.lang; if (isNumber(value)) { return numberFormat(value, a11yPointOptions.valueDecimals || tooltipOptions.valueDecimals || -1, lang.decimalPoint, lang.accessibility.thousandsSep || lang.thousandsSep); } return value; } /** * @private * @param {Highcharts.Series} series * @return {string} */ function getSeriesDescriptionText(series) { var seriesA11yOptions = series.options.accessibility || {}, descOpt = seriesA11yOptions.description; return descOpt && series.chart.langFormat('accessibility.series.description', { description: descOpt, series: series }) || ''; } /** * @private * @param {Highcharts.series} series * @param {string} axisCollection * @return {string} */ function getSeriesAxisDescriptionText(series, axisCollection) { var axis = series[axisCollection]; return series.chart.langFormat('accessibility.series.' + axisCollection + 'Description', { name: getAxisDescription(axis), series: series }); } /** * Get accessible time description for a point on a datetime axis. * * @private * @function Highcharts.Point#getTimeDescription * @param {Highcharts.Point} point * @return {string|undefined} * The description as string. */ function getPointA11yTimeDescription(point) { var series = point.series, chart = series.chart, a11yOptions = chart.options.accessibility.point || {}, hasDateXAxis = series.xAxis && series.xAxis.dateTime; if (hasDateXAxis) { var tooltipDateFormat = Tooltip.prototype.getXDateFormat.call({ getDateFormat: Tooltip.prototype.getDateFormat, chart: chart }, point, chart.options.tooltip, series.xAxis), dateFormat = a11yOptions.dateFormatter && a11yOptions.dateFormatter(point) || a11yOptions.dateFormat || tooltipDateFormat; return chart.time.dateFormat(dateFormat, point.x, void 0); } } /** * @private * @param {Highcharts.Point} point * @return {string} */ function getPointXDescription(point) { var timeDesc = getPointA11yTimeDescription(point), xAxis = point.series.xAxis || {}, pointCategory = xAxis.categories && defined(point.category) && ('' + point.category).replace('
    ', ' '), canUseId = point.id && point.id.indexOf('highcharts-') < 0, fallback = 'x, ' + point.x; return point.name || timeDesc || pointCategory || (canUseId ? point.id : fallback); } /** * @private * @param {Highcharts.Point} point * @param {string} prefix * @param {string} suffix * @return {string} */ function getPointArrayMapValueDescription(point, prefix, suffix) { var pre = prefix || '', suf = suffix || '', keyToValStr = function (key) { var num = pointNumberToString(point, pick(point[key], point.options[key])); return key + ': ' + pre + num + suf; }, pointArrayMap = point.series.pointArrayMap; return pointArrayMap.reduce(function (desc, key) { return desc + (desc.length ? ', ' : '') + keyToValStr(key); }, ''); } /** * @private * @param {Highcharts.Point} point * @return {string} */ function getPointValue(point) { var series = point.series, a11yPointOpts = series.chart.options.accessibility.point || {}, tooltipOptions = series.tooltipOptions || {}, valuePrefix = a11yPointOpts.valuePrefix || tooltipOptions.valuePrefix || '', valueSuffix = a11yPointOpts.valueSuffix || tooltipOptions.valueSuffix || '', fallbackKey = (typeof point.value !== 'undefined' ? 'value' : 'y'), fallbackDesc = pointNumberToString(point, point[fallbackKey]); if (point.isNull) { return series.chart.langFormat('accessibility.series.nullPointValue', { point: point }); } if (series.pointArrayMap) { return getPointArrayMapValueDescription(point, valuePrefix, valueSuffix); } return valuePrefix + fallbackDesc + valueSuffix; } /** * Return the description for the annotation(s) connected to a point, or empty * string if none. * * @private * @param {Highcharts.Point} point The data point to get the annotation info from. * @return {string} Annotation description */ function getPointAnnotationDescription(point) { var chart = point.series.chart; var langKey = 'accessibility.series.pointAnnotationsDescription'; var annotations = getPointAnnotationTexts(point); var context = { point: point, annotations: annotations }; return annotations.length ? chart.langFormat(langKey, context) : ''; } /** * Return string with information about point. * @private * @return {string} */ function getPointValueDescription(point) { var series = point.series, chart = series.chart, pointValueDescriptionFormat = chart.options.accessibility .point.valueDescriptionFormat, showXDescription = pick(series.xAxis && series.xAxis.options.accessibility && series.xAxis.options.accessibility.enabled, !chart.angular), xDesc = showXDescription ? getPointXDescription(point) : '', context = { point: point, index: defined(point.index) ? (point.index + 1) : '', xDescription: xDesc, value: getPointValue(point), separator: showXDescription ? ', ' : '' }; return format(pointValueDescriptionFormat, context, chart); } /** * Return string with information about point. * @private * @return {string} */ function defaultPointDescriptionFormatter(point) { var series = point.series, chart = series.chart, valText = getPointValueDescription(point), description = point.options && point.options.accessibility && point.options.accessibility.description, userDescText = description ? ' ' + description : '', seriesNameText = chart.series.length > 1 && series.name ? ' ' + series.name + '.' : '', annotationsDesc = getPointAnnotationDescription(point), pointAnnotationsText = annotationsDesc ? ' ' + annotationsDesc : ''; point.accessibility = point.accessibility || {}; point.accessibility.valueDescription = valText; return valText + userDescText + seriesNameText + pointAnnotationsText; } /** * Set a11y props on a point element * @private * @param {Highcharts.Point} point * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} pointElement */ function setPointScreenReaderAttribs(point, pointElement) { var series = point.series, a11yPointOptions = series.chart.options.accessibility.point || {}, seriesA11yOptions = series.options.accessibility || {}, label = escapeStringForHTML(stripHTMLTags(seriesA11yOptions.pointDescriptionFormatter && seriesA11yOptions.pointDescriptionFormatter(point) || a11yPointOptions.descriptionFormatter && a11yPointOptions.descriptionFormatter(point) || defaultPointDescriptionFormatter(point))); pointElement.setAttribute('role', 'img'); pointElement.setAttribute('aria-label', label); } /** * Add accessible info to individual point elements of a series * @private * @param {Highcharts.Series} series */ function describePointsInSeries(series) { var setScreenReaderProps = shouldSetScreenReaderPropsOnPoints(series), setKeyboardProps = shouldSetKeyboardNavPropsOnPoints(series); if (setScreenReaderProps || setKeyboardProps) { series.points.forEach(function (point) { var pointEl = point.graphic && point.graphic.element || shouldAddDummyPoint(point) && addDummyPointElement(point); if (pointEl) { // We always set tabindex, as long as we are setting props. // When setting tabindex, also remove default outline to // avoid ugly border on click. pointEl.setAttribute('tabindex', '-1'); pointEl.style.outline = '0'; if (setScreenReaderProps) { setPointScreenReaderAttribs(point, pointEl); } else { pointEl.setAttribute('aria-hidden', true); } } }); } } /** * Return string with information about series. * @private * @return {string} */ function defaultSeriesDescriptionFormatter(series) { var chart = series.chart, chartTypes = chart.types || [], description = getSeriesDescriptionText(series), shouldDescribeAxis = function (coll) { return chart[coll] && chart[coll].length > 1 && series[coll]; }, xAxisInfo = getSeriesAxisDescriptionText(series, 'xAxis'), yAxisInfo = getSeriesAxisDescriptionText(series, 'yAxis'), summaryContext = { name: series.name || '', ix: series.index + 1, numSeries: chart.series && chart.series.length, numPoints: series.points && series.points.length, series: series }, combinationSuffix = chartTypes.length > 1 ? 'Combination' : '', summary = chart.langFormat('accessibility.series.summary.' + series.type + combinationSuffix, summaryContext) || chart.langFormat('accessibility.series.summary.default' + combinationSuffix, summaryContext); return summary + (description ? ' ' + description : '') + (shouldDescribeAxis('yAxis') ? ' ' + yAxisInfo : '') + (shouldDescribeAxis('xAxis') ? ' ' + xAxisInfo : ''); } /** * Set a11y props on a series element * @private * @param {Highcharts.Series} series * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} seriesElement */ function describeSeriesElement(series, seriesElement) { var seriesA11yOptions = series.options.accessibility || {}, a11yOptions = series.chart.options.accessibility, landmarkVerbosity = a11yOptions.landmarkVerbosity; // Handle role attribute if (seriesA11yOptions.exposeAsGroupOnly) { seriesElement.setAttribute('role', 'img'); } else if (landmarkVerbosity === 'all') { seriesElement.setAttribute('role', 'region'); } /* else do not add role */ seriesElement.setAttribute('tabindex', '-1'); seriesElement.style.outline = '0'; // Don't show browser outline on click, despite tabindex seriesElement.setAttribute('aria-label', escapeStringForHTML(stripHTMLTags(a11yOptions.series.descriptionFormatter && a11yOptions.series.descriptionFormatter(series) || defaultSeriesDescriptionFormatter(series)))); } /** * Put accessible info on series and points of a series. * @param {Highcharts.Series} series The series to add info on. */ function describeSeries(series) { var chart = series.chart, firstPointEl = getSeriesFirstPointElement(series), seriesEl = getSeriesA11yElement(series), is3d = chart.is3d && chart.is3d(); if (seriesEl) { // For some series types the order of elements do not match the // order of points in series. In that case we have to reverse them // in order for AT to read them out in an understandable order. // Due to z-index issues we can not do this for 3D charts. if (seriesEl.lastChild === firstPointEl && !is3d) { reverseChildNodes(seriesEl); } describePointsInSeries(series); unhideChartElementFromAT(chart, seriesEl); if (shouldDescribeSeriesElement(series)) { describeSeriesElement(series, seriesEl); } else { seriesEl.setAttribute('aria-label', ''); } } } var SeriesDescriber = { describeSeries: describeSeries, defaultPointDescriptionFormatter: defaultPointDescriptionFormatter, defaultSeriesDescriptionFormatter: defaultSeriesDescriptionFormatter, getPointA11yTimeDescription: getPointA11yTimeDescription, getPointXDescription: getPointXDescription, getPointValue: getPointValue, getPointValueDescription: getPointValueDescription }; return SeriesDescriber; }); _registerModule(_modules, 'modules/accessibility/utils/Announcer.js', [_modules['parts/Globals.js'], _modules['modules/accessibility/utils/DOMElementProvider.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, DOMElementProvider, HTMLUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Create announcer to speak messages to screen readers and other AT. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var visuallyHideElement = HTMLUtilities.visuallyHideElement; var Announcer = /** @class */ (function () { function Announcer(chart, type) { this.chart = chart; this.domElementProvider = new DOMElementProvider(); this.announceRegion = this.addAnnounceRegion(type); } Announcer.prototype.destroy = function () { this.domElementProvider.destroyCreatedElements(); }; Announcer.prototype.announce = function (message) { var _this = this; this.announceRegion.innerHTML = message; // Delete contents after a little while to avoid user finding the live // region in the DOM. if (this.clearAnnouncementRegionTimer) { clearTimeout(this.clearAnnouncementRegionTimer); } this.clearAnnouncementRegionTimer = setTimeout(function () { _this.announceRegion.innerHTML = ''; delete _this.clearAnnouncementRegionTimer; }, 1000); }; Announcer.prototype.addAnnounceRegion = function (type) { var chartContainer = this.chart.renderTo; var div = this.domElementProvider.createElement('div'); div.setAttribute('aria-hidden', false); div.setAttribute('aria-live', type); visuallyHideElement(div); chartContainer.insertBefore(div, chartContainer.firstChild); return div; }; return Announcer; }()); H.Announcer = Announcer; return Announcer; }); _registerModule(_modules, 'modules/accessibility/components/SeriesComponent/NewDataAnnouncer.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/components/SeriesComponent/SeriesDescriber.js'], _modules['modules/accessibility/utils/Announcer.js'], _modules['modules/accessibility/utils/EventProvider.js']], function (H, U, ChartUtilities, SeriesDescriber, Announcer, EventProvider) { /* * * * (c) 2009-2020 Øystein Moseng * * Handle announcing new data for a chart. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var extend = U.extend, defined = U.defined; var getChartTitle = ChartUtilities.getChartTitle; var defaultPointDescriptionFormatter = SeriesDescriber .defaultPointDescriptionFormatter, defaultSeriesDescriptionFormatter = SeriesDescriber .defaultSeriesDescriptionFormatter; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private */ function chartHasAnnounceEnabled(chart) { return !!chart.options.accessibility.announceNewData.enabled; } /** * @private */ function findPointInDataArray(point) { var candidates = point.series.data.filter(function (candidate) { return point.x === candidate.x && point.y === candidate.y; }); return candidates.length === 1 ? candidates[0] : point; } /** * Get array of unique series from two arrays * @private */ function getUniqueSeries(arrayA, arrayB) { var uniqueSeries = (arrayA || []).concat(arrayB || []) .reduce(function (acc, cur) { acc[cur.name + cur.index] = cur; return acc; }, {}); return Object.keys(uniqueSeries).map(function (ix) { return uniqueSeries[ix]; }); } /** * @private * @class */ var NewDataAnnouncer = function (chart) { this.chart = chart; }; extend(NewDataAnnouncer.prototype, { /** * Initialize the new data announcer. * @private */ init: function () { var chart = this.chart; var announceOptions = chart.options.accessibility.announceNewData; var announceType = announceOptions.interruptUser ? 'assertive' : 'polite'; this.lastAnnouncementTime = 0; this.dirty = { allSeries: {} }; this.eventProvider = new EventProvider(); this.announcer = new Announcer(chart, announceType); this.addEventListeners(); }, /** * Remove traces of announcer. * @private */ destroy: function () { this.eventProvider.removeAddedEvents(); this.announcer.destroy(); }, /** * Add event listeners for the announcer * @private */ addEventListeners: function () { var announcer = this, chart = this.chart, e = this.eventProvider; e.addEvent(chart, 'afterDrilldown', function () { announcer.lastAnnouncementTime = 0; }); e.addEvent(H.Series, 'updatedData', function () { announcer.onSeriesUpdatedData(this); }); e.addEvent(chart, 'afterAddSeries', function (e) { announcer.onSeriesAdded(e.series); }); e.addEvent(H.Series, 'addPoint', function (e) { announcer.onPointAdded(e.point); }); e.addEvent(chart, 'redraw', function () { announcer.announceDirtyData(); }); }, /** * On new data in the series, make sure we add it to the dirty list. * @private * @param {Highcharts.Series} series */ onSeriesUpdatedData: function (series) { var chart = this.chart; if (series.chart === chart && chartHasAnnounceEnabled(chart)) { this.dirty.hasDirty = true; this.dirty.allSeries[series.name + series.index] = series; } }, /** * On new data series added, update dirty list. * @private * @param {Highcharts.Series} series */ onSeriesAdded: function (series) { if (chartHasAnnounceEnabled(this.chart)) { this.dirty.hasDirty = true; this.dirty.allSeries[series.name + series.index] = series; // Add it to newSeries storage unless we already have one this.dirty.newSeries = defined(this.dirty.newSeries) ? void 0 : series; } }, /** * On new point added, update dirty list. * @private * @param {Highcharts.Point} point */ onPointAdded: function (point) { var chart = point.series.chart; if (this.chart === chart && chartHasAnnounceEnabled(chart)) { // Add it to newPoint storage unless we already have one this.dirty.newPoint = defined(this.dirty.newPoint) ? void 0 : point; } }, /** * Gather what we know and announce the data to user. * @private */ announceDirtyData: function () { var chart = this.chart, announcer = this; if (chart.options.accessibility.announceNewData && this.dirty.hasDirty) { var newPoint = this.dirty.newPoint; // If we have a single new point, see if we can find it in the // data array. Otherwise we can only pass through options to // the description builder, and it is a bit sparse in info. if (newPoint) { newPoint = findPointInDataArray(newPoint); } this.queueAnnouncement(Object.keys(this.dirty.allSeries).map(function (ix) { return announcer.dirty.allSeries[ix]; }), this.dirty.newSeries, newPoint); // Reset this.dirty = { allSeries: {} }; } }, /** * Announce to user that there is new data. * @private * @param {Array} dirtySeries * Array of series with new data. * @param {Highcharts.Series} [newSeries] * If a single new series was added, a reference to this series. * @param {Highcharts.Point} [newPoint] * If a single point was added, a reference to this point. */ queueAnnouncement: function (dirtySeries, newSeries, newPoint) { var _this = this; var chart = this.chart; var annOptions = chart.options.accessibility.announceNewData; if (annOptions.enabled) { var now = +new Date(); var dTime = now - this.lastAnnouncementTime; var time = Math.max(0, annOptions.minAnnounceInterval - dTime); // Add series from previously queued announcement. var allSeries = getUniqueSeries(this.queuedAnnouncement && this.queuedAnnouncement.series, dirtySeries); // Build message and announce var message = this.buildAnnouncementMessage(allSeries, newSeries, newPoint); if (message) { // Is there already one queued? if (this.queuedAnnouncement) { clearTimeout(this.queuedAnnouncementTimer); } // Build the announcement this.queuedAnnouncement = { time: now, message: message, series: allSeries }; // Queue the announcement this.queuedAnnouncementTimer = setTimeout(function () { if (_this && _this.announcer) { _this.lastAnnouncementTime = +new Date(); _this.announcer.announce(_this.queuedAnnouncement.message); delete _this.queuedAnnouncement; delete _this.queuedAnnouncementTimer; } }, time); } } }, /** * Get announcement message for new data. * @private * @param {Array} dirtySeries * Array of series with new data. * @param {Highcharts.Series} [newSeries] * If a single new series was added, a reference to this series. * @param {Highcharts.Point} [newPoint] * If a single point was added, a reference to this point. * * @return {string|null} * The announcement message to give to user. */ buildAnnouncementMessage: function (dirtySeries, newSeries, newPoint) { var chart = this.chart, annOptions = chart.options.accessibility.announceNewData; // User supplied formatter? if (annOptions.announcementFormatter) { var formatterRes = annOptions.announcementFormatter(dirtySeries, newSeries, newPoint); if (formatterRes !== false) { return formatterRes.length ? formatterRes : null; } } // Default formatter - use lang options var multiple = H.charts && H.charts.length > 1 ? 'Multiple' : 'Single', langKey = newSeries ? 'newSeriesAnnounce' + multiple : newPoint ? 'newPointAnnounce' + multiple : 'newDataAnnounce', chartTitle = getChartTitle(chart); return chart.langFormat('accessibility.announceNewData.' + langKey, { chartTitle: chartTitle, seriesDesc: newSeries ? defaultSeriesDescriptionFormatter(newSeries) : null, pointDesc: newPoint ? defaultPointDescriptionFormatter(newPoint) : null, point: newPoint, series: newSeries }); } }); return NewDataAnnouncer; }); _registerModule(_modules, 'modules/accessibility/components/SeriesComponent/forcedMarkers.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2009-2020 Øystein Moseng * * Handle forcing series markers. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, merge = U.merge; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private */ function isWithinDescriptionThreshold(series) { var a11yOptions = series.chart.options.accessibility; return series.points.length < a11yOptions.series.pointDescriptionEnabledThreshold || a11yOptions.series.pointDescriptionEnabledThreshold === false; } /** * @private */ function isWithinNavigationThreshold(series) { var navOptions = series.chart.options.accessibility .keyboardNavigation.seriesNavigation; return series.points.length < navOptions.pointNavigationEnabledThreshold || navOptions.pointNavigationEnabledThreshold === false; } /** * @private */ function shouldForceMarkers(series) { var chartA11yEnabled = series.chart.options.accessibility.enabled, seriesA11yEnabled = (series.options.accessibility && series.options.accessibility.enabled) !== false, withinDescriptionThreshold = isWithinDescriptionThreshold(series), withinNavigationThreshold = isWithinNavigationThreshold(series); return chartA11yEnabled && seriesA11yEnabled && (withinDescriptionThreshold || withinNavigationThreshold); } /** * @private */ function unforceMarkerOptions(series) { var resetMarkerOptions = series.resetA11yMarkerOptions; merge(true, series.options, { marker: { enabled: resetMarkerOptions.enabled, states: { normal: { opacity: resetMarkerOptions.states && resetMarkerOptions.states.normal && resetMarkerOptions.states.normal.opacity } } } }); } /** * @private */ function forceZeroOpacityMarkerOptions(options) { merge(true, options, { marker: { enabled: true, states: { normal: { opacity: 0 } } } }); } /** * @private */ function getPointMarkerOpacity(pointOptions) { return pointOptions.marker.states && pointOptions.marker.states.normal && pointOptions.marker.states.normal.opacity || 1; } /** * @private */ function forceDisplayPointMarker(pointOptions) { merge(true, pointOptions.marker, { states: { normal: { opacity: getPointMarkerOpacity(pointOptions) } } }); } /** * @private */ function handleForcePointMarkers(points) { var i = points.length; while (i--) { var pointOptions = points[i].options; if (pointOptions.marker) { if (pointOptions.marker.enabled) { forceDisplayPointMarker(pointOptions); } else { forceZeroOpacityMarkerOptions(pointOptions); } } } } /** * @private */ function addForceMarkersEvents() { /** * Keep track of forcing markers. * @private */ addEvent(H.Series, 'render', function () { var series = this, options = series.options; if (shouldForceMarkers(series)) { if (options.marker && options.marker.enabled === false) { series.a11yMarkersForced = true; forceZeroOpacityMarkerOptions(series.options); } if (series._hasPointMarkers && series.points && series.points.length) { handleForcePointMarkers(series.points); } } else if (series.a11yMarkersForced && series.resetMarkerOptions) { delete series.a11yMarkersForced; unforceMarkerOptions(series); } }); /** * Keep track of options to reset markers to if no longer forced. * @private */ addEvent(H.Series, 'afterSetOptions', function (e) { this.resetA11yMarkerOptions = merge(e.options.marker || {}, this.userOptions.marker || {}); }); } return addForceMarkersEvents; }); _registerModule(_modules, 'modules/accessibility/components/SeriesComponent/SeriesComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/AccessibilityComponent.js'], _modules['modules/accessibility/components/SeriesComponent/SeriesKeyboardNavigation.js'], _modules['modules/accessibility/components/SeriesComponent/NewDataAnnouncer.js'], _modules['modules/accessibility/components/SeriesComponent/forcedMarkers.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/components/SeriesComponent/SeriesDescriber.js'], _modules['parts/Tooltip.js']], function (H, U, AccessibilityComponent, SeriesKeyboardNavigation, NewDataAnnouncer, addForceMarkersEvents, ChartUtilities, SeriesDescriber, Tooltip) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component for series and points. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var extend = U.extend; var hideSeriesFromAT = ChartUtilities.hideSeriesFromAT; var describeSeries = SeriesDescriber.describeSeries; // Expose functionality to users H.SeriesAccessibilityDescriber = SeriesDescriber; // Handle forcing markers addForceMarkersEvents(); /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The SeriesComponent class * * @private * @class * @name Highcharts.SeriesComponent */ var SeriesComponent = function () { }; SeriesComponent.prototype = new AccessibilityComponent(); extend(SeriesComponent.prototype, /** @lends Highcharts.SeriesComponent */ { /** * Init the component. */ init: function () { this.newDataAnnouncer = new NewDataAnnouncer(this.chart); this.newDataAnnouncer.init(); this.keyboardNavigation = new SeriesKeyboardNavigation(this.chart, this.keyCodes); this.keyboardNavigation.init(); this.hideTooltipFromATWhenShown(); this.hideSeriesLabelsFromATWhenShown(); }, /** * @private */ hideTooltipFromATWhenShown: function () { var component = this; this.addEvent(Tooltip, 'refresh', function () { if (this.chart === component.chart && this.label && this.label.element) { this.label.element.setAttribute('aria-hidden', true); } }); }, /** * @private */ hideSeriesLabelsFromATWhenShown: function () { this.addEvent(this.chart, 'afterDrawSeriesLabels', function () { this.series.forEach(function (series) { if (series.labelBySeries) { series.labelBySeries.attr('aria-hidden', true); } }); }); }, /** * Called on chart render. It is necessary to do this for render in case * markers change on zoom/pixel density. */ onChartRender: function () { var chart = this.chart; chart.series.forEach(function (series) { var shouldDescribeSeries = (series.options.accessibility && series.options.accessibility.enabled) !== false && series.visible; if (shouldDescribeSeries) { describeSeries(series); } else { hideSeriesFromAT(series); } }); }, /** * Get keyboard navigation handler for this component. * @return {Highcharts.KeyboardNavigationHandler} */ getKeyboardNavigation: function () { return this.keyboardNavigation.getKeyboardNavigationHandler(); }, /** * Remove traces */ destroy: function () { this.newDataAnnouncer.destroy(); this.keyboardNavigation.destroy(); } }); return SeriesComponent; }); _registerModule(_modules, 'modules/accessibility/components/ZoomComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/AccessibilityComponent.js'], _modules['modules/accessibility/KeyboardNavigationHandler.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, U, AccessibilityComponent, KeyboardNavigationHandler, ChartUtilities, HTMLUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component for chart zoom. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var extend = U.extend, pick = U.pick; var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; var setElAttrs = HTMLUtilities.setElAttrs, removeElement = HTMLUtilities.removeElement; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private */ function chartHasMapZoom(chart) { return !!(chart.mapZoom && chart.mapNavButtons && chart.mapNavButtons.length); } /** * Pan along axis in a direction (1 or -1), optionally with a defined * granularity (number of steps it takes to walk across current view) * * @private * @function Highcharts.Axis#panStep * * @param {number} direction * @param {number} [granularity] */ H.Axis.prototype.panStep = function (direction, granularity) { var gran = granularity || 3, extremes = this.getExtremes(), step = (extremes.max - extremes.min) / gran * direction, newMax = extremes.max + step, newMin = extremes.min + step, size = newMax - newMin; if (direction < 0 && newMin < extremes.dataMin) { newMin = extremes.dataMin; newMax = newMin + size; } else if (direction > 0 && newMax > extremes.dataMax) { newMax = extremes.dataMax; newMin = newMax - size; } this.setExtremes(newMin, newMax); }; /** * The ZoomComponent class * * @private * @class * @name Highcharts.ZoomComponent */ var ZoomComponent = function () { }; ZoomComponent.prototype = new AccessibilityComponent(); extend(ZoomComponent.prototype, /** @lends Highcharts.ZoomComponent */ { /** * Initialize the component */ init: function () { var component = this, chart = this.chart; [ 'afterShowResetZoom', 'afterDrilldown', 'drillupall' ].forEach(function (eventType) { component.addEvent(chart, eventType, function () { component.updateProxyOverlays(); }); }); }, /** * Called when chart is updated */ onChartUpdate: function () { var chart = this.chart, component = this; // Make map zoom buttons accessible if (chart.mapNavButtons) { chart.mapNavButtons.forEach(function (button, i) { unhideChartElementFromAT(chart, button.element); component.setMapNavButtonAttrs(button.element, 'accessibility.zoom.mapZoom' + (i ? 'Out' : 'In')); }); } }, /** * @private * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} button * @param {string} labelFormatKey */ setMapNavButtonAttrs: function (button, labelFormatKey) { var chart = this.chart, label = chart.langFormat(labelFormatKey, { chart: chart }); setElAttrs(button, { tabindex: -1, role: 'button', 'aria-label': label }); }, /** * Update the proxy overlays on every new render to ensure positions are * correct. */ onChartRender: function () { this.updateProxyOverlays(); }, /** * Update proxy overlays, recreating the buttons. */ updateProxyOverlays: function () { var chart = this.chart; // Always start with a clean slate removeElement(this.drillUpProxyGroup); removeElement(this.resetZoomProxyGroup); if (chart.resetZoomButton) { this.recreateProxyButtonAndGroup(chart.resetZoomButton, 'resetZoomProxyButton', 'resetZoomProxyGroup', chart.langFormat('accessibility.zoom.resetZoomButton', { chart: chart })); } if (chart.drillUpButton) { this.recreateProxyButtonAndGroup(chart.drillUpButton, 'drillUpProxyButton', 'drillUpProxyGroup', chart.langFormat('accessibility.drillUpButton', { chart: chart, buttonText: chart.getDrilldownBackText() })); } }, /** * @private * @param {Highcharts.SVGElement} buttonEl * @param {string} buttonProp * @param {string} groupProp * @param {string} label */ recreateProxyButtonAndGroup: function (buttonEl, buttonProp, groupProp, label) { removeElement(this[groupProp]); this[groupProp] = this.addProxyGroup(); this[buttonProp] = this.createProxyButton(buttonEl, this[groupProp], { 'aria-label': label, tabindex: -1 }); }, /** * Get keyboard navigation handler for map zoom. * @private * @return {Highcharts.KeyboardNavigationHandler} The module object */ getMapZoomNavigation: function () { var keys = this.keyCodes, chart = this.chart, component = this; return new KeyboardNavigationHandler(chart, { keyCodeMap: [ [ [keys.up, keys.down, keys.left, keys.right], function (keyCode) { return component.onMapKbdArrow(this, keyCode); } ], [ [keys.tab], function (_keyCode, e) { return component.onMapKbdTab(this, e); } ], [ [keys.space, keys.enter], function () { return component.onMapKbdClick(this); } ] ], validate: function () { return chartHasMapZoom(chart); }, init: function (direction) { return component.onMapNavInit(direction); } }); }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler * @param {number} keyCode * @return {number} Response code */ onMapKbdArrow: function (keyboardNavigationHandler, keyCode) { var keys = this.keyCodes, panAxis = (keyCode === keys.up || keyCode === keys.down) ? 'yAxis' : 'xAxis', stepDirection = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1; this.chart[panAxis][0].panStep(stepDirection); return keyboardNavigationHandler.response.success; }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler * @param {global.KeyboardEvent} event * @return {number} Response code */ onMapKbdTab: function (keyboardNavigationHandler, event) { var button, chart = this.chart, response = keyboardNavigationHandler.response, isBackwards = event.shiftKey, isMoveOutOfRange = isBackwards && !this.focusedMapNavButtonIx || !isBackwards && this.focusedMapNavButtonIx; // Deselect old chart.mapNavButtons[this.focusedMapNavButtonIx].setState(0); if (isMoveOutOfRange) { chart.mapZoom(); // Reset zoom return response[isBackwards ? 'prev' : 'next']; } // Select other button this.focusedMapNavButtonIx += isBackwards ? -1 : 1; button = chart.mapNavButtons[this.focusedMapNavButtonIx]; chart.setFocusToElement(button.box, button.element); button.setState(2); return response.success; }, /** * @private * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler * @return {number} Response code */ onMapKbdClick: function (keyboardNavigationHandler) { this.fakeClickEvent(this.chart.mapNavButtons[this.focusedMapNavButtonIx] .element); return keyboardNavigationHandler.response.success; }, /** * @private * @param {number} direction */ onMapNavInit: function (direction) { var chart = this.chart, zoomIn = chart.mapNavButtons[0], zoomOut = chart.mapNavButtons[1], initialButton = direction > 0 ? zoomIn : zoomOut; chart.setFocusToElement(initialButton.box, initialButton.element); initialButton.setState(2); this.focusedMapNavButtonIx = direction > 0 ? 0 : 1; }, /** * Get keyboard navigation handler for a simple chart button. Provide the * button reference for the chart, and a function to call on click. * * @private * @param {string} buttonProp The property on chart referencing the button. * @return {Highcharts.KeyboardNavigationHandler} The module object */ simpleButtonNavigation: function (buttonProp, proxyProp, onClick) { var keys = this.keyCodes, component = this, chart = this.chart; return new KeyboardNavigationHandler(chart, { keyCodeMap: [ [ [keys.tab, keys.up, keys.down, keys.left, keys.right], function (keyCode, e) { var isBackwards = keyCode === keys.tab && e.shiftKey || keyCode === keys.left || keyCode === keys.up; // Arrow/tab => just move return this.response[isBackwards ? 'prev' : 'next']; } ], [ [keys.space, keys.enter], function () { var res = onClick(this, chart); return pick(res, this.response.success); } ] ], validate: function () { var hasButton = (chart[buttonProp] && chart[buttonProp].box && component[proxyProp]); return hasButton; }, init: function () { chart.setFocusToElement(chart[buttonProp].box, component[proxyProp]); } }); }, /** * Get keyboard navigation handlers for this component. * @return {Array} * List of module objects */ getKeyboardNavigation: function () { return [ this.simpleButtonNavigation('resetZoomButton', 'resetZoomProxyButton', function (_handler, chart) { chart.zoomOut(); }), this.simpleButtonNavigation('drillUpButton', 'drillUpProxyButton', function (handler, chart) { chart.drillUp(); return handler.response.prev; }), this.getMapZoomNavigation() ]; } }); return ZoomComponent; }); _registerModule(_modules, 'modules/accessibility/components/RangeSelectorComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/AccessibilityComponent.js'], _modules['modules/accessibility/KeyboardNavigationHandler.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, U, AccessibilityComponent, KeyboardNavigationHandler, ChartUtilities, HTMLUtilities) { /* * * * (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. !!!!!!! * * */ var extend = U.extend; var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; 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} * List of module objects. */ getKeyboardNavigation: function () { return [ this.getRangeSelectorButtonNavigation(), this.getRangeSelectorInputNavigation() ]; } }); return RangeSelectorComponent; }); _registerModule(_modules, 'modules/accessibility/components/InfoRegionsComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/AccessibilityComponent.js'], _modules['modules/accessibility/utils/Announcer.js'], _modules['modules/accessibility/components/AnnotationsA11y.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js']], function (H, U, AccessibilityComponent, Announcer, AnnotationsA11y, ChartUtilities, HTMLUtilities) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component for chart info region and table. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var doc = H.win.document; var extend = U.extend, format = U.format, pick = U.pick; var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML; var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, getChartTitle = ChartUtilities.getChartTitle, getAxisDescription = ChartUtilities.getAxisDescription; var addClass = HTMLUtilities.addClass, setElAttrs = HTMLUtilities.setElAttrs, escapeStringForHTML = HTMLUtilities.escapeStringForHTML, stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString, getElement = HTMLUtilities.getElement, visuallyHideElement = HTMLUtilities.visuallyHideElement; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * @private */ function getTypeDescForMapChart(chart, formatContext) { return formatContext.mapTitle ? chart.langFormat('accessibility.chartTypes.mapTypeDescription', formatContext) : chart.langFormat('accessibility.chartTypes.unknownMap', formatContext); } /** * @private */ function getTypeDescForCombinationChart(chart, formatContext) { return chart.langFormat('accessibility.chartTypes.combinationChart', formatContext); } /** * @private */ function getTypeDescForEmptyChart(chart, formatContext) { return chart.langFormat('accessibility.chartTypes.emptyChart', formatContext); } /** * @private */ function buildTypeDescriptionFromSeries(chart, types, context) { var firstType = types[0], typeExplaination = chart.langFormat('accessibility.seriesTypeDescriptions.' + firstType, context), multi = chart.series && chart.series.length < 2 ? 'Single' : 'Multiple'; return (chart.langFormat('accessibility.chartTypes.' + firstType + multi, context) || chart.langFormat('accessibility.chartTypes.default' + multi, context)) + (typeExplaination ? ' ' + typeExplaination : ''); } /** * @private */ function getTableSummary(chart) { return chart.langFormat('accessibility.table.tableSummary', { chart: chart }); } /** * @private */ function stripEmptyHTMLTags(str) { return str.replace(/<(\w+)[^>]*?>\s*<\/\1>/g, ''); } /** * @private */ function enableSimpleHTML(str) { return str .replace(/<(h[1-7]|p|div|ul|ol|li)>/g, '<$1>') .replace(/</(h[1-7]|p|div|ul|ol|li|a|button)>/g, '') .replace(/<(div|a|button) id="([a-zA-Z\-0-9#]*?)">/g, '<$1 id="$2">'); } /** * @private */ function stringToSimpleHTML(str) { return stripEmptyHTMLTags(enableSimpleHTML(escapeStringForHTML(str))); } /** * Return simplified explaination of chart type. Some types will not be familiar * to most users, but in those cases we try to add an explaination of the type. * * @private * @function Highcharts.Chart#getTypeDescription * @param {Array} types The series types in this chart. * @return {string} The text description of the chart type. */ H.Chart.prototype.getTypeDescription = function (types) { var firstType = types[0], firstSeries = this.series && this.series[0] || {}, formatContext = { numSeries: this.series.length, numPoints: firstSeries.points && firstSeries.points.length, chart: this, mapTitle: firstSeries.mapTitle }; if (!firstType) { return getTypeDescForEmptyChart(this, formatContext); } if (firstType === 'map') { return getTypeDescForMapChart(this, formatContext); } if (this.types.length > 1) { return getTypeDescForCombinationChart(this, formatContext); } return buildTypeDescriptionFromSeries(this, types, formatContext); }; /** * The InfoRegionsComponent class * * @private * @class * @name Highcharts.InfoRegionsComponent */ var InfoRegionsComponent = function () { }; InfoRegionsComponent.prototype = new AccessibilityComponent(); extend(InfoRegionsComponent.prototype, /** @lends Highcharts.InfoRegionsComponent */ { /** * Init the component * @private */ init: function () { var chart = this.chart; var component = this; this.initRegionsDefinitions(); this.addEvent(chart, 'afterGetTable', function (e) { component.onDataTableCreated(e); }); this.addEvent(chart, 'afterViewData', function (tableDiv) { component.dataTableDiv = tableDiv; // Use small delay to give browsers & AT time to register new table setTimeout(function () { component.focusDataTable(); }, 300); }); this.announcer = new Announcer(chart, 'assertive'); }, /** * @private */ initRegionsDefinitions: function () { var component = this; this.screenReaderSections = { before: { element: null, buildContent: function (chart) { var formatter = chart.options.accessibility .screenReaderSection.beforeChartFormatter; return formatter ? formatter(chart) : component.defaultBeforeChartFormatter(chart); }, insertIntoDOM: function (el, chart) { chart.renderTo.insertBefore(el, chart.renderTo.firstChild); }, afterInserted: function () { if (typeof component.sonifyButtonId !== 'undefined') { component.initSonifyButton(component.sonifyButtonId); } if (typeof component.dataTableButtonId !== 'undefined') { component.initDataTableButton(component.dataTableButtonId); } } }, after: { element: null, buildContent: function (chart) { var formatter = chart.options.accessibility.screenReaderSection .afterChartFormatter; return formatter ? formatter(chart) : component.defaultAfterChartFormatter(); }, insertIntoDOM: function (el, chart) { chart.renderTo.insertBefore(el, chart.container.nextSibling); } } }; }, /** * Called on chart render. Have to update the sections on render, in order * to get a11y info from series. */ onChartRender: function () { var component = this; this.linkedDescriptionElement = this.getLinkedDescriptionElement(); this.setLinkedDescriptionAttrs(); Object.keys(this.screenReaderSections).forEach(function (regionKey) { component.updateScreenReaderSection(regionKey); }); }, /** * @private */ getLinkedDescriptionElement: function () { var chartOptions = this.chart.options, linkedDescOption = chartOptions.accessibility.linkedDescription; if (!linkedDescOption) { return; } if (typeof linkedDescOption !== 'string') { return linkedDescOption; } var query = format(linkedDescOption, this.chart), queryMatch = doc.querySelectorAll(query); if (queryMatch.length === 1) { return queryMatch[0]; } }, /** * @private */ setLinkedDescriptionAttrs: function () { var el = this.linkedDescriptionElement; if (el) { el.setAttribute('aria-hidden', 'true'); addClass(el, 'highcharts-linked-description'); } }, /** * @private * @param {string} regionKey The name/key of the region to update */ updateScreenReaderSection: function (regionKey) { var chart = this.chart, region = this.screenReaderSections[regionKey], content = region.buildContent(chart), sectionDiv = region.element = (region.element || this.createElement('div')), hiddenDiv = (sectionDiv.firstChild || this.createElement('div')); this.setScreenReaderSectionAttribs(sectionDiv, regionKey); hiddenDiv.innerHTML = content; sectionDiv.appendChild(hiddenDiv); region.insertIntoDOM(sectionDiv, chart); visuallyHideElement(hiddenDiv); unhideChartElementFromAT(chart, hiddenDiv); if (region.afterInserted) { region.afterInserted(); } }, /** * @private * @param {Highcharts.HTMLDOMElement} sectionDiv The section element * @param {string} regionKey Name/key of the region we are setting attrs for */ setScreenReaderSectionAttribs: function (sectionDiv, regionKey) { var labelLangKey = ('accessibility.screenReaderSection.' + regionKey + 'RegionLabel'), chart = this.chart, labelText = chart.langFormat(labelLangKey, { chart: chart }), sectionId = 'highcharts-screen-reader-region-' + regionKey + '-' + chart.index; setElAttrs(sectionDiv, { id: sectionId, 'aria-label': labelText }); // Sections are wrapped to be positioned relatively to chart in case // elements inside are tabbed to. sectionDiv.style.position = 'relative'; if (chart.options.accessibility.landmarkVerbosity === 'all' && labelText) { sectionDiv.setAttribute('role', 'region'); } }, /** * @private * @return {string} */ defaultBeforeChartFormatter: function () { var _a; var chart = this.chart, format = chart.options.accessibility .screenReaderSection.beforeChartFormat, axesDesc = this.getAxesDescription(), shouldHaveSonifyBtn = chart.sonify && ((_a = chart.options.sonification) === null || _a === void 0 ? void 0 : _a.enabled), sonifyButtonId = 'highcharts-a11y-sonify-data-btn-' + chart.index, dataTableButtonId = 'hc-linkto-highcharts-data-table-' + chart.index, annotationsList = getAnnotationsInfoHTML(chart), annotationsTitleStr = chart.langFormat('accessibility.screenReaderSection.annotations.heading', { chart: chart }), context = { chartTitle: getChartTitle(chart), typeDescription: this.getTypeDescriptionText(), chartSubtitle: this.getSubtitleText(), chartLongdesc: this.getLongdescText(), xAxisDescription: axesDesc.xAxis, yAxisDescription: axesDesc.yAxis, playAsSoundButton: shouldHaveSonifyBtn ? this.getSonifyButtonText(sonifyButtonId) : '', viewTableButton: chart.getCSV ? this.getDataTableButtonText(dataTableButtonId) : '', annotationsTitle: annotationsList ? annotationsTitleStr : '', annotationsList: annotationsList }, formattedString = H.i18nFormat(format, context, chart); this.dataTableButtonId = dataTableButtonId; this.sonifyButtonId = sonifyButtonId; return stringToSimpleHTML(formattedString); }, /** * @private * @return {string} */ defaultAfterChartFormatter: function () { var chart = this.chart, format = chart.options.accessibility .screenReaderSection.afterChartFormat, context = { endOfChartMarker: this.getEndOfChartMarkerText() }, formattedString = H.i18nFormat(format, context, chart); return stringToSimpleHTML(formattedString); }, /** * @private * @return {string} */ getLinkedDescription: function () { var el = this.linkedDescriptionElement, content = el && el.innerHTML || ''; return stripHTMLTagsFromString(content); }, /** * @private * @return {string} */ getLongdescText: function () { var chartOptions = this.chart.options, captionOptions = chartOptions.caption, captionText = captionOptions && captionOptions.text, linkedDescription = this.getLinkedDescription(); return (chartOptions.accessibility.description || linkedDescription || captionText || ''); }, /** * @private * @return {string} */ getTypeDescriptionText: function () { var chart = this.chart; return chart.types ? chart.options.accessibility.typeDescription || chart.getTypeDescription(chart.types) : ''; }, /** * @private * @param {string} buttonId * @return {string} */ getDataTableButtonText: function (buttonId) { var chart = this.chart, buttonText = chart.langFormat('accessibility.table.viewAsDataTableButtonText', { chart: chart, chartTitle: getChartTitle(chart) }); return '' + buttonText + ''; }, /** * @private * @param {string} buttonId * @return {string} */ getSonifyButtonText: function (buttonId) { var _a; var chart = this.chart; if (((_a = chart.options.sonification) === null || _a === void 0 ? void 0 : _a.enabled) === false) { return ''; } var buttonText = chart.langFormat('accessibility.sonification.playAsSoundButtonText', { chart: chart, chartTitle: getChartTitle(chart) }); return ''; }, /** * @private * @return {string} */ getSubtitleText: function () { var subtitle = (this.chart.options.subtitle); return stripHTMLTagsFromString(subtitle && subtitle.text || ''); }, /** * @private * @return {string} */ getEndOfChartMarkerText: function () { var chart = this.chart, markerText = chart.langFormat('accessibility.screenReaderSection.endOfChartMarker', { chart: chart }), id = 'highcharts-end-of-chart-marker-' + chart.index; return '
    ' + markerText + '
    '; }, /** * @private * @param {Highcharts.Dictionary} e */ onDataTableCreated: function (e) { var chart = this.chart; if (chart.options.accessibility.enabled) { if (this.viewDataTableButton) { this.viewDataTableButton.setAttribute('aria-expanded', 'true'); } e.html = e.html.replace('} */ getAxesDescription: function () { var chart = this.chart, shouldDescribeColl = function (collectionKey, defaultCondition) { var axes = chart[collectionKey]; return axes.length > 1 || axes[0] && pick(axes[0].options.accessibility && axes[0].options.accessibility.enabled, defaultCondition); }, hasNoMap = !!chart.types && chart.types.indexOf('map') < 0, hasCartesian = !!chart.hasCartesianSeries, showXAxes = shouldDescribeColl('xAxis', !chart.angular && hasCartesian && hasNoMap), showYAxes = shouldDescribeColl('yAxis', hasCartesian && hasNoMap), desc = {}; if (showXAxes) { desc.xAxis = this.getAxisDescriptionText('xAxis'); } if (showYAxes) { desc.yAxis = this.getAxisDescriptionText('yAxis'); } return desc; }, /** * @private * @param {string} collectionKey * @return {string} */ getAxisDescriptionText: function (collectionKey) { var component = this, chart = this.chart, axes = chart[collectionKey]; return chart.langFormat('accessibility.axis.' + collectionKey + 'Description' + (axes.length > 1 ? 'Plural' : 'Singular'), { chart: chart, names: axes.map(function (axis) { return getAxisDescription(axis); }), ranges: axes.map(function (axis) { return component.getAxisRangeDescription(axis); }), numAxes: axes.length }); }, /** * Return string with text description of the axis range. * @private * @param {Highcharts.Axis} axis The axis to get range desc of. * @return {string} A string with the range description for the axis. */ getAxisRangeDescription: function (axis) { var axisOptions = axis.options || {}; // Handle overridden range description if (axisOptions.accessibility && typeof axisOptions.accessibility.rangeDescription !== 'undefined') { return axisOptions.accessibility.rangeDescription; } // Handle category axes if (axis.categories) { return this.getCategoryAxisRangeDesc(axis); } // Use time range, not from-to? if (axis.dateTime && (axis.min === 0 || axis.dataMin === 0)) { return this.getAxisTimeLengthDesc(axis); } // Just use from and to. // We have the range and the unit to use, find the desc format return this.getAxisFromToDescription(axis); }, /** * @private * @param {Highcharts.Axis} axis * @return {string} */ getCategoryAxisRangeDesc: function (axis) { var chart = this.chart; if (axis.dataMax && axis.dataMin) { return chart.langFormat('accessibility.axis.rangeCategories', { chart: chart, axis: axis, numCategories: axis.dataMax - axis.dataMin + 1 }); } return ''; }, /** * @private * @param {Highcharts.Axis} axis * @return {string} */ getAxisTimeLengthDesc: function (axis) { var chart = this.chart, range = {}, rangeUnit = 'Seconds'; range.Seconds = ((axis.max || 0) - (axis.min || 0)) / 1000; range.Minutes = range.Seconds / 60; range.Hours = range.Minutes / 60; range.Days = range.Hours / 24; ['Minutes', 'Hours', 'Days'].forEach(function (unit) { if (range[unit] > 2) { rangeUnit = unit; } }); var rangeValue = range[rangeUnit].toFixed(rangeUnit !== 'Seconds' && rangeUnit !== 'Minutes' ? 1 : 0 // Use decimals for days/hours ); // We have the range and the unit to use, find the desc format return chart.langFormat('accessibility.axis.timeRange' + rangeUnit, { chart: chart, axis: axis, range: rangeValue.replace('.0', '') }); }, /** * @private * @param {Highcharts.Axis} axis * @return {string} */ getAxisFromToDescription: function (axis) { var chart = this.chart, dateRangeFormat = chart.options.accessibility .screenReaderSection.axisRangeDateFormat, format = function (axisKey) { return axis.dateTime ? chart.time.dateFormat(dateRangeFormat, axis[axisKey]) : axis[axisKey]; }; return chart.langFormat('accessibility.axis.rangeFromTo', { chart: chart, axis: axis, rangeFrom: format('min'), rangeTo: format('max') }); }, /** * Remove component traces */ destroy: function () { var _a; (_a = this.announcer) === null || _a === void 0 ? void 0 : _a.destroy(); } }); return InfoRegionsComponent; }); _registerModule(_modules, 'modules/accessibility/components/ContainerComponent.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/accessibility/utils/htmlUtilities.js'], _modules['modules/accessibility/utils/chartUtilities.js'], _modules['modules/accessibility/AccessibilityComponent.js']], function (H, U, HTMLUtilities, ChartUtilities, AccessibilityComponent) { /* * * * (c) 2009-2020 Øystein Moseng * * Accessibility component for chart container. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var doc = H.win.document; var extend = U.extend; var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString; var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, getChartTitle = ChartUtilities.getChartTitle; /* eslint-disable valid-jsdoc */ /** * The ContainerComponent class * * @private * @class * @name Highcharts.ContainerComponent */ var ContainerComponent = function () { }; ContainerComponent.prototype = new AccessibilityComponent(); extend(ContainerComponent.prototype, /** @lends Highcharts.ContainerComponent */ { /** * Called on first render/updates to the chart, including options changes. */ onChartUpdate: function () { this.handleSVGTitleElement(); this.setSVGContainerLabel(); this.setGraphicContainerAttrs(); this.setRenderToAttrs(); this.makeCreditsAccessible(); }, /** * @private */ handleSVGTitleElement: function () { var chart = this.chart, titleId = 'highcharts-title-' + chart.index, titleContents = stripHTMLTags(chart.langFormat('accessibility.svgContainerTitle', { chartTitle: getChartTitle(chart) })); if (titleContents.length) { var titleElement = this.svgTitleElement = this.svgTitleElement || doc.createElementNS('http://www.w3.org/2000/svg', 'title'); titleElement.textContent = titleContents; titleElement.id = titleId; chart.renderTo.insertBefore(titleElement, chart.renderTo.firstChild); } }, /** * @private */ setSVGContainerLabel: function () { var chart = this.chart, svgContainerLabel = stripHTMLTags(chart.langFormat('accessibility.svgContainerLabel', { chartTitle: getChartTitle(chart) })); if (chart.renderer.box && svgContainerLabel.length) { chart.renderer.box.setAttribute('aria-label', svgContainerLabel); } }, /** * @private */ setGraphicContainerAttrs: function () { var chart = this.chart, label = chart.langFormat('accessibility.graphicContainerLabel', { chartTitle: getChartTitle(chart) }); if (label.length) { chart.container.setAttribute('aria-label', label); } }, /** * @private */ setRenderToAttrs: function () { var chart = this.chart; if (chart.options.accessibility.landmarkVerbosity !== 'disabled') { chart.renderTo.setAttribute('role', 'region'); } else { chart.renderTo.removeAttribute('role'); } chart.renderTo.setAttribute('aria-label', chart.langFormat('accessibility.chartContainerLabel', { title: getChartTitle(chart), chart: chart })); }, /** * @private */ makeCreditsAccessible: function () { var chart = this.chart, credits = chart.credits; if (credits) { if (credits.textStr) { credits.element.setAttribute('aria-label', stripHTMLTags(chart.langFormat('accessibility.credits', { creditsStr: credits.textStr }))); } unhideChartElementFromAT(chart, credits.element); } }, /** * Accessibility disabled/chart destroyed. */ destroy: function () { this.chart.renderTo.setAttribute('aria-hidden', true); } }); return ContainerComponent; }); _registerModule(_modules, 'modules/accessibility/high-contrast-mode.js', [_modules['parts/Globals.js']], function (H) { /* * * * (c) 2009-2020 Øystein Moseng * * Handling for Windows High Contrast Mode. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var isMS = H.isMS, win = H.win, doc = win.document; var whcm = { /** * Detect WHCM in the browser. * * @function Highcharts#isHighContrastModeActive * @private * @return {boolean} Returns true if the browser is in High Contrast mode. */ isHighContrastModeActive: function () { // Use media query on Edge, but not on IE var isEdge = /(Edg)/.test(win.navigator.userAgent); if (win.matchMedia && isEdge) { return win.matchMedia('(-ms-high-contrast: active)').matches; } // Test BG image for IE if (isMS && win.getComputedStyle) { var testDiv = doc.createElement('div'); testDiv.style.backgroundImage = 'url(#)'; doc.body.appendChild(testDiv); var bi = (testDiv.currentStyle || win.getComputedStyle(testDiv)).backgroundImage; doc.body.removeChild(testDiv); return bi === 'none'; } // Not used for other browsers return false; }, /** * Force high contrast theme for the chart. The default theme is defined in * a separate file. * * @function Highcharts#setHighContrastTheme * @private * @param {Highcharts.AccessibilityChart} chart The chart to set the theme of. * @return {void} */ setHighContrastTheme: function (chart) { // We might want to add additional functionality here in the future for // storing the old state so that we can reset the theme if HC mode is // disabled. For now, the user will have to reload the page. chart.highContrastModeActive = true; // Apply theme to chart var theme = (chart.options.accessibility.highContrastTheme); chart.update(theme, false); // Force series colors (plotOptions is not enough) chart.series.forEach(function (s) { var plotOpts = theme.plotOptions[s.type] || {}; s.update({ color: plotOpts.color || 'windowText', colors: [plotOpts.color || 'windowText'], borderColor: plotOpts.borderColor || 'window' }); // Force point colors if existing s.points.forEach(function (p) { if (p.options && p.options.color) { p.update({ color: plotOpts.color || 'windowText', borderColor: plotOpts.borderColor || 'window' }, false); } }); }); // The redraw for each series and after is required for 3D pie // (workaround) chart.redraw(); } }; return whcm; }); _registerModule(_modules, 'modules/accessibility/high-contrast-theme.js', [], function () { /* * * * (c) 2009-2020 Øystein Moseng * * Default theme for Windows High Contrast Mode. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var theme = { chart: { backgroundColor: 'window' }, title: { style: { color: 'windowText' } }, subtitle: { style: { color: 'windowText' } }, colorAxis: { minColor: 'windowText', maxColor: 'windowText', stops: [] }, colors: ['windowText'], xAxis: { gridLineColor: 'windowText', labels: { style: { color: 'windowText' } }, lineColor: 'windowText', minorGridLineColor: 'windowText', tickColor: 'windowText', title: { style: { color: 'windowText' } } }, yAxis: { gridLineColor: 'windowText', labels: { style: { color: 'windowText' } }, lineColor: 'windowText', minorGridLineColor: 'windowText', tickColor: 'windowText', title: { style: { color: 'windowText' } } }, tooltip: { backgroundColor: 'window', borderColor: 'windowText', style: { color: 'windowText' } }, plotOptions: { series: { lineColor: 'windowText', fillColor: 'window', borderColor: 'windowText', edgeColor: 'windowText', borderWidth: 1, dataLabels: { connectorColor: 'windowText', color: 'windowText', style: { color: 'windowText', textOutline: 'none' } }, marker: { lineColor: 'windowText', fillColor: 'windowText' } }, pie: { color: 'window', colors: ['window'], borderColor: 'windowText', borderWidth: 1 }, boxplot: { fillColor: 'window' }, candlestick: { lineColor: 'windowText', fillColor: 'window' }, errorbar: { fillColor: 'window' } }, legend: { backgroundColor: 'window', itemStyle: { color: 'windowText' }, itemHoverStyle: { color: 'windowText' }, itemHiddenStyle: { color: '#555' }, title: { style: { color: 'windowText' } } }, credits: { style: { color: 'windowText' } }, labels: { style: { color: 'windowText' } }, drilldown: { activeAxisLabelStyle: { color: 'windowText' }, activeDataLabelStyle: { color: 'windowText' } }, navigation: { buttonOptions: { symbolStroke: 'windowText', theme: { fill: 'window' } } }, rangeSelector: { buttonTheme: { fill: 'window', stroke: 'windowText', style: { color: 'windowText' }, states: { hover: { fill: 'window', stroke: 'windowText', style: { color: 'windowText' } }, select: { fill: '#444', stroke: 'windowText', style: { color: 'windowText' } } } }, inputBoxBorderColor: 'windowText', inputStyle: { backgroundColor: 'window', color: 'windowText' }, labelStyle: { color: 'windowText' } }, navigator: { handles: { backgroundColor: 'window', borderColor: 'windowText' }, outlineColor: 'windowText', maskFill: 'transparent', series: { color: 'windowText', lineColor: 'windowText' }, xAxis: { gridLineColor: 'windowText' } }, scrollbar: { barBackgroundColor: '#444', barBorderColor: 'windowText', buttonArrowColor: 'windowText', buttonBackgroundColor: 'window', buttonBorderColor: 'windowText', rifleColor: 'windowText', trackBackgroundColor: 'window', trackBorderColor: 'windowText' } }; return theme; }); _registerModule(_modules, 'modules/accessibility/options/options.js', [], function () { /* * * * (c) 2009-2020 Øystein Moseng * * Default options for accessibility. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /** * Formatter callback for the accessibility announcement. * * @callback Highcharts.AccessibilityAnnouncementFormatter * * @param {Array} updatedSeries * Array of all series that received updates. If an announcement is already * queued, the series that received updates for that announcement are also * included in this array. * * @param {Highcharts.Series} [addedSeries] * This is provided if {@link Highcharts.Chart#addSeries} was called, and there * is a new series. In that case, this argument is a reference to the new * series. * * @param {Highcharts.Point} [addedPoint] * This is provided if {@link Highcharts.Series#addPoint} was called, and there * is a new point. In that case, this argument is a reference to the new point. * * @return {false|string} * The function should return a string with the text to announce to the user. * Return empty string to not announce anything. Return `false` to use the * default announcement format. */ /** * @interface Highcharts.PointAccessibilityOptionsObject */ /** * Provide a description of the data point, announced to screen readers. * @name Highcharts.PointAccessibilityOptionsObject#description * @type {string|undefined} * @requires modules/accessibility * @since 7.1.0 */ /* * * @interface Highcharts.PointOptionsObject in parts/Point.ts */ /** * @name Highcharts.PointOptionsObject#accessibility * @type {Highcharts.PointAccessibilityOptionsObject|undefined} * @requires modules/accessibility * @since 7.1.0 */ /** * @callback Highcharts.ScreenReaderClickCallbackFunction * * @param {global.MouseEvent} evt * Mouse click event * * @return {void} */ /** * Creates a formatted string for the screen reader module. * * @callback Highcharts.ScreenReaderFormatterCallbackFunction * * @param {T} context * Context to format * * @return {string} * Formatted string for the screen reader module. */ var options = { /** * Options for configuring accessibility for the chart. Requires the * [accessibility module](https://code.highcharts.com/modules/accessibility.js) * to be loaded. For a description of the module and information * on its features, see * [Highcharts Accessibility](https://www.highcharts.com/docs/chart-concepts/accessibility). * * @since 5.0.0 * @requires modules/accessibility * @optionparent accessibility */ accessibility: { /** * Enable accessibility functionality for the chart. * * @since 5.0.0 */ enabled: true, /** * Accessibility options for the screen reader information sections * added before and after the chart. * * @since 8.0.0 */ screenReaderSection: { /** * Function to run upon clicking the "View as Data Table" link in * the screen reader region. * * By default Highcharts will insert and set focus to a data table * representation of the chart. * * @type {Highcharts.ScreenReaderClickCallbackFunction} * @since 8.0.0 * @apioption accessibility.screenReaderSection.onViewDataTableClick */ /** * Function to run upon clicking the "Play as sound" button in * the screen reader region. * * By default Highcharts will call the `chart.sonify` function. * * @type {Highcharts.ScreenReaderClickCallbackFunction} * @since 8.0.1 * @apioption accessibility.screenReaderSection.onPlayAsSoundClick */ /** * A formatter function to create the HTML contents of the hidden * screen reader information region before the chart. Receives one * argument, `chart`, referring to the chart object. Should return a * string with the HTML content of the region. By default this * returns an automatic description of the chart based on * [beforeChartFormat](#accessibility.screenReaderSection.beforeChartFormat). * * @type {Highcharts.ScreenReaderFormatterCallbackFunction} * @since 8.0.0 * @apioption accessibility.screenReaderSection.beforeChartFormatter */ /** * Format for the screen reader information region before the chart. * Supported HTML tags are ``, `

    `, `

    `, ``, `