diff --git a/frontend/static/frontend/js/modules/accessibility.src.js b/frontend/static/frontend/js/modules/accessibility.src.js new file mode 100644 index 0000000..e1011e7 --- /dev/null +++ b/frontend/static/frontend/js/modules/accessibility.src.js @@ -0,0 +1,12077 @@ +/** + * @license Highcharts JS v10.2.0 (2022-07-05) + * + * Accessibility module + * + * (c) 2010-2021 Highsoft AS + * Author: Oystein Moseng + * + * License: www.highcharts.com/license + */ +(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) { + 'use strict'; + var _modules = Highcharts ? Highcharts._modules : {}; + function _registerModule(obj, path, args, fn) { + if (!obj.hasOwnProperty(path)) { + obj[path] = fn.apply(null, args); + + if (typeof CustomEvent === 'function') { + window.dispatchEvent( + new CustomEvent( + 'HighchartsModuleLoaded', + { detail: { path: path, module: obj[path] } + }) + ); + } + } + } + _registerModule(_modules, 'Accessibility/A11yI18n.js', [_modules['Core/FormatUtilities.js'], _modules['Core/Utilities.js']], function (F, U) { + /* * + * + * Accessibility module - internationalization support + * + * (c) 2010-2021 Highsoft AS + * Author: Øystein Moseng + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var format = F.format; + var getNestedProperty = U.getNestedProperty, + pick = U.pick; + /* * + * + * Composition + * + * */ + var A11yI18nComposition; + (function (A11yI18nComposition) { + /* * + * + * Declarations + * + * */ + /* * + * + * Constants + * + * */ + var composedClasses = []; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + function compose(ChartClass) { + if (composedClasses.indexOf(ChartClass) === -1) { + composedClasses.push(ChartClass); + var chartProto = ChartClass.prototype; + chartProto.langFormat = langFormat; + } + return ChartClass; + } + A11yI18nComposition.compose = compose; + /** + * i18n utility function. Format a single array or plural statement in a + * format string. If the statement is not an array or plural statement, + * returns the statement within brackets. Invalid array statements return + * an empty string. + * + * @private + * @function formatExtendedStatement + * @param {string} statement + * @param {Highcharts.Dictionary<*>} ctx + * Context to apply to the format string. + */ + function formatExtendedStatement(statement, ctx) { + var eachStart = statement.indexOf('#each('), pluralStart = statement.indexOf('#plural('), indexStart = statement.indexOf('['), indexEnd = statement.indexOf(']'); + var arr, + result; + // Dealing with an each-function? + if (eachStart > -1) { + var eachEnd = statement.slice(eachStart).indexOf(')') + eachStart, preEach = statement.substring(0, eachStart), postEach = statement.substring(eachEnd + 1), eachStatement = statement.substring(eachStart + 6, eachEnd), eachArguments = eachStatement.split(','); + var lenArg = Number(eachArguments[1]), + len = void 0; + result = ''; + arr = getNestedProperty(eachArguments[0], ctx); + if (arr) { + lenArg = isNaN(lenArg) ? arr.length : lenArg; + len = lenArg < 0 ? + arr.length + lenArg : + Math.min(lenArg, arr.length); // Overshoot + // Run through the array for the specified length + for (var i = 0; i < len; ++i) { + result += preEach + arr[i] + postEach; + } + } + return result.length ? result : ''; + } + // Dealing with a plural-function? + if (pluralStart > -1) { + var pluralEnd = (statement.slice(pluralStart).indexOf(')') + pluralStart), pluralStatement = statement.substring(pluralStart + 8, pluralEnd), pluralArguments = pluralStatement.split(','), num = Number(getNestedProperty(pluralArguments[0], ctx)); + switch (num) { + case 0: + result = pick(pluralArguments[4], pluralArguments[1]); + break; + case 1: + result = pick(pluralArguments[2], pluralArguments[1]); + break; + case 2: + result = pick(pluralArguments[3], pluralArguments[1]); + break; + default: + result = pluralArguments[1]; + } + return result ? stringTrim(result) : ''; + } + // Array index + if (indexStart > -1) { + var arrayName = statement.substring(0, + indexStart), + ix = Number(statement.substring(indexStart + 1, + indexEnd)); + var val = void 0; + arr = getNestedProperty(arrayName, ctx); + if (!isNaN(ix) && arr) { + if (ix < 0) { + val = arr[arr.length + ix]; + // Handle negative overshoot + if (typeof val === 'undefined') { + val = arr[0]; + } + } + else { + val = arr[ix]; + // Handle positive overshoot + if (typeof val === 'undefined') { + val = arr[arr.length - 1]; + } + } + } + return typeof val !== 'undefined' ? val : ''; + } + // Standard substitution, delegate to format or similar + return '{' + statement + '}'; + } + /* eslint-disable max-len */ + /** + * i18n formatting function. Extends Highcharts.format() functionality by + * also handling arrays and plural conditionals. Arrays can be indexed as + * follows: + * + * - Format: 'This is the first index: {myArray[0]}. The last: {myArray[-1]}.' + * + * - Context: { myArray: [0, 1, 2, 3, 4, 5] } + * + * - Result: 'This is the first index: 0. The last: 5.' + * + * + * They can also be iterated using the #each() function. This will repeat + * the contents of the bracket expression for each element. Example: + * + * - Format: 'List contains: {#each(myArray)cm }' + * + * - Context: { myArray: [0, 1, 2] } + * + * - Result: 'List contains: 0cm 1cm 2cm ' + * + * + * The #each() function optionally takes a length parameter. If positive, + * this parameter specifies the max number of elements to iterate through. + * If negative, the function will subtract the number from the length of the + * array. Use this to stop iterating before the array ends. Example: + * + * - Format: 'List contains: {#each(myArray, -1) }and {myArray[-1]}.' + * + * - Context: { myArray: [0, 1, 2, 3] } + * + * - Result: 'List contains: 0, 1, 2, and 3.' + * + * + * Use the #plural() function to pick a string depending on whether or not a + * context object is 1. Arguments are #plural(obj, plural, singular). + * Example: + * + * - Format: 'Has {numPoints} {#plural(numPoints, points, point}.' + * + * - Context: { numPoints: 5 } + * + * - Result: 'Has 5 points.' + * + * + * Optionally there are additional parameters for dual and none: + * #plural(obj, plural, singular, dual, none). Example: + * + * - Format: 'Has {#plural(numPoints, many points, one point, two points, + * none}.' + * + * - Context: { numPoints: 2 } + * + * - Result: 'Has two points.' + * + * + * The dual or none parameters will take precedence if they are supplied. + * + * @requires modules/accessibility + * + * @function Highcharts.i18nFormat + * + * @param {string} formatString + * The string to format. + * + * @param {Highcharts.Dictionary<*>} context + * Context to apply to the format string. + * + * @param {Highcharts.Chart} chart + * A `Chart` instance with a time object and numberFormatter, passed on to + * format(). + * + * @return {string} + * The formatted string. + */ + function i18nFormat(formatString, context, chart) { + var getFirstBracketStatement = function (sourceStr, offset) { + var str = sourceStr.slice(offset || 0), startBracket = str.indexOf('{'), endBracket = str.indexOf('}'); + if (startBracket > -1 && endBracket > startBracket) { + return { + statement: str.substring(startBracket + 1, endBracket), + begin: offset + startBracket + 1, + end: offset + endBracket + }; + } + }, tokens = []; + var bracketRes, + constRes, + cursor = 0; + // Tokenize format string into bracket statements and constants + do { + bracketRes = getFirstBracketStatement(formatString, cursor); + constRes = formatString.substring(cursor, bracketRes && bracketRes.begin - 1); + // If we have constant content before this bracket statement, add it + if (constRes.length) { + tokens.push({ + value: constRes, + type: 'constant' + }); + } + // Add the bracket statement + if (bracketRes) { + tokens.push({ + value: bracketRes.statement, + type: 'statement' + }); + } + cursor = bracketRes ? bracketRes.end + 1 : cursor + 1; + } while (bracketRes); + // Perform the formatting. The formatArrayStatement function returns + // the statement in brackets if it is not an array statement, which + // means it gets picked up by format below. + tokens.forEach(function (token) { + if (token.type === 'statement') { + token.value = formatExtendedStatement(token.value, context); + } + }); + // Join string back together and pass to format to pick up non-array + // statements. + return format(tokens.reduce(function (acc, cur) { return acc + cur.value; }, ''), context, chart); + } + A11yI18nComposition.i18nFormat = i18nFormat; + /* eslint-enable max-len */ + /** + * Apply context to a format string from lang options of the chart. + * + * @requires modules/accessibility + * + * @function Highcharts.Chart#langFormat + * + * @param {string} langKey + * Key (using dot notation) into lang option structure. + * + * @param {Highcharts.Dictionary<*>} context + * Context to apply to the format string. + * + * @return {string} + * The formatted string. + */ + function langFormat(langKey, context) { + var keys = langKey.split('.'); + var formatString = this.options.lang, + i = 0; + for (; i < keys.length; ++i) { + formatString = formatString && formatString[keys[i]]; + } + return typeof formatString === 'string' ? + i18nFormat(formatString, context, this) : ''; + } + /** + * String trim that works for IE6-8 as well. + * + * @private + * @function stringTrim + * + * @param {string} str + * The input string + * + * @return {string} + * The trimmed string + */ + function stringTrim(str) { + return str.trim && str.trim() || str.replace(/^\s+|\s+$/g, ''); + } + })(A11yI18nComposition || (A11yI18nComposition = {})); + /* * + * + * Default Export + * + * */ + + return A11yI18nComposition; + }); + _registerModule(_modules, 'Accessibility/Utils/HTMLUtilities.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Utility functions for accessibility module. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc, + win = H.win; + var css = U.css; + /* * + * + * Functions + * + * */ + /* 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 {Highcharts.HTMLDOMElement} el + * @param {string} className + * @return {void} + */ + function removeClass(el, className) { + if (el.classList) { + el.classList.remove(className); + } + else { + // Note: Dumb logic that will break if the element has a class name that + // consists of className plus something else. + el.className = el.className.replace(new RegExp(className, 'g'), ''); + } + } + /** + * Utility function to clone a mouse event for re-dispatching. + * @private + */ + function cloneMouseEvent(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); + } + /** + * Utility function to clone a touch event for re-dispatching. + * @private + */ + function cloneTouchEvent(e) { + var touchListToTouchArray = function (l) { + var touchArray = []; + for (var i = 0; i < l.length; ++i) { + var item = l.item(i); + if (item) { + touchArray.push(item); + } + } + return touchArray; + }; + if (typeof win.TouchEvent === 'function') { + var newEvent = new win.TouchEvent(e.type, { + touches: touchListToTouchArray(e.touches), + targetTouches: touchListToTouchArray(e.targetTouches), + changedTouches: touchListToTouchArray(e.changedTouches), + ctrlKey: e.ctrlKey, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + bubbles: e.bubbles, + cancelable: e.cancelable, + composed: e.composed, + detail: e.detail, + view: e.view + }); + if (e.defaultPrevented) { + newEvent.preventDefault(); + } + return newEvent; + } + var fakeEvt = cloneMouseEvent(e); + fakeEvt.touches = e.touches; + fakeEvt.changedTouches = e.changedTouches; + fakeEvt.targetTouches = e.targetTouches; + return fakeEvt; + } + /** + * @private + */ + function escapeStringForHTML(str) { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\//g, '/'); + } + /** + * Get an element by ID + * @private + */ + function getElement(id) { + return doc.getElementById(id); + } + /** + * Get a fake mouse event of a given type + * @private + */ + function getFakeMouseEvent(type, position) { + var pos = position || { + x: 0, + y: 0 + }; + if (typeof win.MouseEvent === 'function') { + return new win.MouseEvent(type, { + bubbles: true, + cancelable: true, + composed: true, + view: win, + detail: type === 'click' ? 1 : 0, + screenX: pos.x, + screenY: pos.y, + clientX: pos.x, + clientY: pos.y + }); + } + // 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 + pos.x, pos.y, pos.x, pos.y, + // Pressed keys + false, false, false, false, 0, // button + null // related target + ); + return evt; + } + } + return { type: type }; + } + /** + * Get an appropriate heading level for an element. Corresponds to the + * heading level below the previous heading in the DOM. + * + * Note: Only detects previous headings in the DOM that are siblings, + * ancestors, or previous siblings of ancestors. Headings that are nested below + * siblings of ancestors (cousins et.al) are not picked up. This is because it + * is ambiguous whether or not the nesting is for layout purposes or indicates a + * separate section. + * + * @private + * @param {Highcharts.HTMLDOMElement} [element] + * @return {string} The heading tag name (h1, h2 etc). + * If no nearest heading is found, "p" is returned. + */ + function getHeadingTagNameForElement(element) { + var getIncreasedHeadingLevel = function (tagName) { + var headingLevel = parseInt(tagName.slice(1), 10), + newLevel = Math.min(6, + headingLevel + 1); + return 'h' + newLevel; + }; + var isHeading = function (tagName) { return /H[1-6]/.test(tagName); }; + var getPreviousSiblingsHeading = function (el) { + var sibling = el; + while (sibling = sibling.previousSibling) { // eslint-disable-line + var tagName = sibling.tagName || ''; + if (isHeading(tagName)) { + return tagName; + } + } + return ''; + }; + var getHeadingRecursive = function (el) { + var prevSiblingsHeading = getPreviousSiblingsHeading(el); + if (prevSiblingsHeading) { + return getIncreasedHeadingLevel(prevSiblingsHeading); + } + // No previous siblings are headings, try parent node + var parent = el.parentElement; + if (!parent) { + return 'p'; + } + var parentTagName = parent.tagName; + if (isHeading(parentTagName)) { + return getIncreasedHeadingLevel(parentTagName); + } + return getHeadingRecursive(parent); + }; + return getHeadingRecursive(element); + } + /** + * 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); + } + } + /** + * Remove all child nodes from an element. + * @private + * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} [element] + * @return {void} + */ + function removeChildNodes(element) { + while (element.lastChild) { + element.removeChild(element.lastChild); + } + } + /** + * Utility function. Reverses child nodes of a DOM element. + * @private + */ + function reverseChildNodes(node) { + var i = node.childNodes.length; + while (i--) { + node.appendChild(node.childNodes[i]); + } + } + /** + * Used for aria-label attributes, painting on a canvas will fail if the + * text contains tags. + * @private + */ + 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 + */ + function visuallyHideElement(element) { + css(element, { + 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 + }); + } + /* * + * + * Default Export + * + * */ + var HTMLUtilities = { + addClass: addClass, + cloneMouseEvent: cloneMouseEvent, + cloneTouchEvent: cloneTouchEvent, + escapeStringForHTML: escapeStringForHTML, + getElement: getElement, + getFakeMouseEvent: getFakeMouseEvent, + getHeadingTagNameForElement: getHeadingTagNameForElement, + removeChildNodes: removeChildNodes, + removeClass: removeClass, + removeElement: removeElement, + reverseChildNodes: reverseChildNodes, + stripHTMLTagsFromString: stripHTMLTagsFromString, + visuallyHideElement: visuallyHideElement + }; + + return HTMLUtilities; + }); + _registerModule(_modules, 'Accessibility/Utils/ChartUtilities.js', [_modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (H, HU, U) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Utils for dealing with charts. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc; + var stripHTMLTags = HU.stripHTMLTagsFromString; + var defined = U.defined, + find = U.find, + fireEvent = U.fireEvent; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Fire an event on an element that is either wrapped by Highcharts, + * or a DOM element. + * @private + */ + function fireEventOnWrappedOrUnwrappedElement(el, eventObject) { + var type = eventObject.type; + var hcEvents = el.hcEvents; + if ((doc.createEvent) && + (el.dispatchEvent || el.fireEvent)) { + if (el.dispatchEvent) { + el.dispatchEvent(eventObject); + } + else { + el.fireEvent(type, eventObject); + } + } + else if (hcEvents && hcEvents[type]) { + fireEvent(el, type, eventObject); + } + else if (el.element) { + fireEventOnWrappedOrUnwrappedElement(el.element, eventObject); + } + } + /** + * @private + */ + function getChartTitle(chart) { + return stripHTMLTags(chart.options.title.text || + chart.langFormat('accessibility.defaultChartTitle', { chart: chart })); + } + /** + * Return string with the axis name/title. + * @private + */ + function getAxisDescription(axis) { + return 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'); + } + /** + * 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. + */ + function getAxisRangeDescription(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 getCategoryAxisRangeDesc(axis); + } + // Use time range, not from-to? + if (axis.dateTime && (axis.min === 0 || axis.dataMin === 0)) { + return getAxisTimeLengthDesc(axis); + } + // Just use from and to. + // We have the range and the unit to use, find the desc format + return getAxisFromToDescription(axis); + } + /** + * Describe the range of a category axis. + * @private + */ + function getCategoryAxisRangeDesc(axis) { + var chart = axis.chart; + if (axis.dataMax && axis.dataMin) { + return chart.langFormat('accessibility.axis.rangeCategories', { + chart: chart, + axis: axis, + numCategories: axis.dataMax - axis.dataMin + 1 + }); + } + return ''; + } + /** + * Describe the length of the time window shown on an axis. + * @private + */ + function getAxisTimeLengthDesc(axis) { + var chart = axis.chart, + range = {}, + min = axis.dataMin || axis.min || 0, + max = axis.dataMax || axis.max || 0; + var rangeUnit = 'Seconds'; + range.Seconds = (max - min) / 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', '') + }); + } + /** + * Describe an axis from-to range. + * @private + */ + function getAxisFromToDescription(axis) { + var chart = axis.chart, + options = chart.options, + dateRangeFormat = (options && + options.accessibility && + options.accessibility.screenReaderSection.axisRangeDateFormat || + ''), + extremes = { + min: axis.dataMin || axis.min || 0, + max: axis.dataMax || axis.max || 0 + }, + format = function (key) { + return axis.dateTime ? + chart.time.dateFormat(dateRangeFormat, + extremes[key]) : + extremes[key].toString(); + }; + return chart.langFormat('accessibility.axis.rangeFromTo', { + chart: chart, + axis: axis, + rangeFrom: format('min'), + rangeTo: format('max') + }); + } + /** + * 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) { + var firstPointWithGraphic = find(series.points, + function (p) { return !!p.graphic; }); + return (firstPointWithGraphic && + firstPointWithGraphic.graphic && + firstPointWithGraphic.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 + */ + function unhideChartElementFromAT(chart, element) { + element.setAttribute('aria-hidden', false); + if (element === chart.renderTo || + !element.parentNode || + element.parentNode === doc.body // #16126: Full screen printing + ) { + 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 + */ + function hideSeriesFromAT(series) { + var seriesEl = getSeriesA11yElement(series); + if (seriesEl) { + seriesEl.setAttribute('aria-hidden', true); + } + } + /** + * Get series objects by series name. + * @private + */ + 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 + */ + 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 + */ + function getRelativePointAxisPosition(axis, point) { + if (!defined(axis.dataMin) || !defined(axis.dataMax)) { + return 0; + } + var axisStart = axis.toPixels(axis.dataMin), + axisEnd = axis.toPixels(axis.dataMax), + // We have to use pixel position because of axis breaks, log axis etc. + positionProp = axis.coll === 'xAxis' ? 'x' : 'y', + 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 + */ + function scrollToPoint(point) { + var xAxis = point.series.xAxis, + yAxis = point.series.yAxis, + axis = (xAxis && xAxis.scrollbar ? xAxis : yAxis), + scrollbar = (axis && 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); + fireEvent(scrollbar, 'changed', { + from: scrollbar.from, + to: scrollbar.to, + trigger: 'scrollbar', + DOMEvent: null + }); + } + } + /* * + * + * Default Export + * + * */ + var ChartUtilities = { + fireEventOnWrappedOrUnwrappedElement: fireEventOnWrappedOrUnwrappedElement, + getChartTitle: getChartTitle, + getAxisDescription: getAxisDescription, + getAxisRangeDescription: getAxisRangeDescription, + getPointFromXY: getPointFromXY, + getSeriesFirstPointElement: getSeriesFirstPointElement, + getSeriesFromName: getSeriesFromName, + getSeriesA11yElement: getSeriesA11yElement, + unhideChartElementFromAT: unhideChartElementFromAT, + hideSeriesFromAT: hideSeriesFromAT, + scrollToPoint: scrollToPoint + }; + + return ChartUtilities; + }); + _registerModule(_modules, 'Accessibility/Utils/DOMElementProvider.js', [_modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (H, HU) { + /* * + * + * (c) 2009-2021 Ø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.doc; + var removeElement = HU.removeElement; + /* * + * + * Class + * + * */ + /** + * @private + */ + var DOMElementProvider = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function DOMElementProvider() { + this.elements = []; + } + /** + * Create an element and keep track of it for later removal. + * Same args as document.createElement + * @private + */ + DOMElementProvider.prototype.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 + */ + DOMElementProvider.prototype.destroyCreatedElements = function () { + this.elements.forEach(function (element) { + removeElement(element); + }); + this.elements = []; + }; + return DOMElementProvider; + }()); + /* * + * + * Default Export + * + * */ + + return DOMElementProvider; + }); + _registerModule(_modules, 'Accessibility/Utils/EventProvider.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { + /* * + * + * (c) 2009-2021 Ø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; + /* * + * + * Class + * + * */ + /** + * @private + */ + var EventProvider = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function EventProvider() { + this.eventRemovers = []; + } + /** + * Add an event to an element and keep track of it for later removal. + * Same args as Highcharts.addEvent. + * @private + */ + EventProvider.prototype.addEvent = function () { + var remover = addEvent.apply(H, + arguments); + this.eventRemovers.push(remover); + return remover; + }; + /** + * Remove all added events. + * @private + */ + EventProvider.prototype.removeAddedEvents = function () { + this.eventRemovers.forEach(function (remover) { return remover(); }); + this.eventRemovers = []; + }; + return EventProvider; + }()); + /* * + * + * Default Export + * + * */ + + return EventProvider; + }); + _registerModule(_modules, 'Accessibility/AccessibilityComponent.js', [_modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/DOMElementProvider.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (CU, DOMElementProvider, EventProvider, HU, U) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component class definition + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var fireEventOnWrappedOrUnwrappedElement = CU.fireEventOnWrappedOrUnwrappedElement; + var getFakeMouseEvent = HU.getFakeMouseEvent; + var extend = U.extend; + /* * + * + * Class + * + * */ + /** + * 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 + */ + var AccessibilityComponent = /** @class */ (function () { + function AccessibilityComponent() { + /* * + * + * Properties + * + * */ + this.chart = void 0; + this.domElementProvider = void 0; + this.eventProvider = void 0; + this.keyCodes = void 0; + this.proxyProvider = void 0; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Initialize the class + * @private + * @param {Highcharts.Chart} chart The chart object + * @param {Highcharts.ProxyProvider} proxyProvider The proxy provider of the accessibility module + */ + AccessibilityComponent.prototype.initBase = function (chart, proxyProvider) { + this.chart = chart; + this.eventProvider = new EventProvider(); + this.domElementProvider = new DOMElementProvider(); + this.proxyProvider = proxyProvider; + // Key code enum for common keys + this.keyCodes = { + left: 37, + right: 39, + up: 38, + down: 40, + enter: 13, + space: 32, + esc: 27, + tab: 9, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36 + }; + }; + /** + * Add an event to an element and keep track of it for later removal. + * See EventProvider for details. + * @private + */ + AccessibilityComponent.prototype.addEvent = function (el, type, fn, options) { + return this.eventProvider.addEvent(el, type, fn, options); + }; + /** + * Create an element and keep track of it for later removal. + * See DOMElementProvider for details. + * @private + */ + AccessibilityComponent.prototype.createElement = function (tagName, options) { + return this.domElementProvider.createElement(tagName, options); + }; + /** + * Fire a fake click event on an element. It is useful to have this on + * AccessibilityComponent for users of custom components. + */ + AccessibilityComponent.prototype.fakeClickEvent = function (el) { + var fakeEvent = getFakeMouseEvent('click'); + fireEventOnWrappedOrUnwrappedElement(el, fakeEvent); + }; + /** + * Remove traces of the component. + * @private + */ + AccessibilityComponent.prototype.destroyBase = function () { + this.domElementProvider.destroyCreatedElements(); + this.eventProvider.removeAddedEvents(); + }; + return AccessibilityComponent; + }()); + extend(AccessibilityComponent.prototype, + /** @lends Highcharts.AccessibilityComponent */ + { + /** + * Called on component initialization. + */ + init: function () { }, + /** + * Get keyboard navigation handler for this component. + * @private + */ + 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 () { } + }); + /* * + * + * Default Export + * + * */ + + return AccessibilityComponent; + }); + _registerModule(_modules, 'Accessibility/KeyboardNavigationHandler.js', [_modules['Core/Utilities.js']], function (U) { + /* * + * + * (c) 2009-2021 Ø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; + /* * + * + * Class + * + * */ + /** + * 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. + */ + var KeyboardNavigationHandler = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + 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 + }; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * 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. + */ + KeyboardNavigationHandler.prototype.run = function (e) { + var keyCode = e.which || e.keyCode; + var response = this.response.noHandler; + var 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; + }()); + /* * + * + * Default Export + * + * */ + /* * + * + * API Declarations + * + * */ + /** + * 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} + */ + (''); // keeps doclets above in JS file + + return KeyboardNavigationHandler; + }); + _registerModule(_modules, 'Accessibility/Components/ContainerComponent.js', [_modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (AccessibilityComponent, KeyboardNavigationHandler, CU, H, HU) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component for chart container. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var unhideChartElementFromAT = CU.unhideChartElementFromAT, + getChartTitle = CU.getChartTitle; + var doc = H.doc; + var stripHTMLTags = HU.stripHTMLTagsFromString; + /** + * The ContainerComponent class + * + * @private + * @class + * @name Highcharts.ContainerComponent + */ + var ContainerComponent = /** @class */ (function (_super) { + __extends(ContainerComponent, _super); + function ContainerComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Called on first render/updates to the chart, including options changes. + */ + ContainerComponent.prototype.onChartUpdate = function () { + this.handleSVGTitleElement(); + this.setSVGContainerLabel(); + this.setGraphicContainerAttrs(); + this.setRenderToAttrs(); + this.makeCreditsAccessible(); + }; + /** + * @private + */ + ContainerComponent.prototype.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 + */ + ContainerComponent.prototype.setSVGContainerLabel = function () { + var chart = this.chart, + svgContainerLabel = chart.langFormat('accessibility.svgContainerLabel', { + chartTitle: getChartTitle(chart) + }); + if (chart.renderer.box && svgContainerLabel.length) { + chart.renderer.box.setAttribute('aria-label', svgContainerLabel); + } + }; + /** + * @private + */ + ContainerComponent.prototype.setGraphicContainerAttrs = function () { + var chart = this.chart, + label = chart.langFormat('accessibility.graphicContainerLabel', { + chartTitle: getChartTitle(chart) + }); + if (label.length) { + chart.container.setAttribute('aria-label', label); + } + }; + /** + * Set attributes on the chart container element. + * @private + */ + ContainerComponent.prototype.setRenderToAttrs = function () { + var chart = this.chart, shouldHaveLandmark = chart.options.accessibility + .landmarkVerbosity !== 'disabled', containerLabel = chart.langFormat('accessibility.chartContainerLabel', { + title: getChartTitle(chart), + chart: chart + }); + if (containerLabel) { + chart.renderTo.setAttribute('role', shouldHaveLandmark ? 'region' : 'group'); + chart.renderTo.setAttribute('aria-label', containerLabel); + } + }; + /** + * @private + */ + ContainerComponent.prototype.makeCreditsAccessible = function () { + var chart = this.chart, + credits = chart.credits; + if (credits) { + if (credits.textStr) { + credits.element.setAttribute('aria-label', chart.langFormat('accessibility.credits', { creditsStr: stripHTMLTags(credits.textStr) })); + } + unhideChartElementFromAT(chart, credits.element); + } + }; + /** + * Empty handler to just set focus on chart + * @private + */ + ContainerComponent.prototype.getKeyboardNavigation = function () { + var chart = this.chart; + return new KeyboardNavigationHandler(chart, { + keyCodeMap: [], + validate: function () { + return true; + }, + init: function () { + var a11y = chart.accessibility; + if (a11y) { + a11y.keyboardNavigation.tabindexContainer.focus(); + } + } + }); + }; + /** + * Accessibility disabled/chart destroyed. + */ + ContainerComponent.prototype.destroy = function () { + this.chart.renderTo.setAttribute('aria-hidden', true); + }; + return ContainerComponent; + }(AccessibilityComponent)); + /* * + * + * Default Export + * + * */ + + return ContainerComponent; + }); + _registerModule(_modules, 'Accessibility/FocusBorder.js', [_modules['Core/Renderer/SVG/SVGLabel.js'], _modules['Core/Utilities.js']], function (SVGLabel, U) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Extend SVG and Chart classes with focus border capabilities. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent, + pick = U.pick; + /* * + * + * Composition + * + * */ + var FocusBorderComposition; + (function (FocusBorderComposition) { + /* * + * + * Declarations + * + * */ + /* * + * + * Constants + * + * */ + var composedClasses = []; + // Attributes that trigger a focus border update + var svgElementBorderUpdateTriggers = [ + 'x', 'y', 'transform', 'width', 'height', 'r', 'd', 'stroke-width' + ]; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + function compose(ChartClass, SVGElementClass) { + if (composedClasses.indexOf(ChartClass) === -1) { + composedClasses.push(ChartClass); + var chartProto = ChartClass.prototype; + chartProto.renderFocusBorder = chartRenderFocusBorder; + chartProto.setFocusToElement = chartSetFocusToElement; + } + if (composedClasses.indexOf(SVGElementClass) === -1) { + composedClasses.push(SVGElementClass); + var svgElementProto = SVGElementClass.prototype; + svgElementProto.addFocusBorder = svgElementAddFocusBorder; + svgElementProto.removeFocusBorder = svgElementRemoveFocusBorder; + } + } + FocusBorderComposition.compose = compose; + /** + * Redraws the focus border on the currently focused element. + * + * @private + * @function Highcharts.Chart#renderFocusBorder + */ + function chartRenderFocusBorder() { + var focusElement = this.focusElement, + focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder; + if (focusElement) { + focusElement.removeFocusBorder(); + if (focusBorderOptions.enabled) { + focusElement.addFocusBorder(focusBorderOptions.margin, { + stroke: focusBorderOptions.style.color, + strokeWidth: focusBorderOptions.style.lineWidth, + r: focusBorderOptions.style.borderRadius + }); + } + } + } + /** + * Set chart's focus to an SVGElement. Calls focus() on it, and draws the + * focus border. This is used by multiple components. + * + * @private + * @function Highcharts.Chart#setFocusToElement + * + * @param {Highcharts.SVGElement} svgElement + * Element to draw the border around. + * + * @param {SVGDOMElement|HTMLDOMElement} [focusElement] + * If supplied, it draws the border around svgElement and sets the focus to + * focusElement. + */ + function chartSetFocusToElement(svgElement, focusElement) { + var focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder, + browserFocusElement = focusElement || svgElement.element; + // Set browser focus if possible + if (browserFocusElement && + browserFocusElement.focus) { + // If there is no focusin-listener, add one to work around Edge + // where Narrator is not reading out points despite calling focus(). + if (!(browserFocusElement.hcEvents && + browserFocusElement.hcEvents.focusin)) { + addEvent(browserFocusElement, 'focusin', function () { }); + } + browserFocusElement.focus(); + // Hide default focus ring + if (focusBorderOptions.hideBrowserFocusOutline) { + browserFocusElement.style.outline = 'none'; + } + } + if (this.focusElement) { + this.focusElement.removeFocusBorder(); + } + this.focusElement = svgElement; + this.renderFocusBorder(); + } + /** + * Add hook to destroy focus border if SVG element is destroyed, unless + * hook already exists. + * @private + * @param el Element to add destroy hook to + */ + function svgElementAddDestroyFocusBorderHook(el) { + if (el.focusBorderDestroyHook) { + return; + } + var origDestroy = el.destroy; + el.destroy = function () { + if (el.focusBorder && el.focusBorder.destroy) { + el.focusBorder.destroy(); + } + return origDestroy.apply(el, arguments); + }; + el.focusBorderDestroyHook = origDestroy; + } + /** + * Add focus border functionality to SVGElements. Draws a new rect on top of + * element around its bounding box. This is used by multiple components. + * + * @private + * @function Highcharts.SVGElement#addFocusBorder + * + * @param {number} margin + * + * @param {SVGAttributes} attribs + */ + function svgElementAddFocusBorder(margin, attribs) { + // Allow updating by just adding new border + if (this.focusBorder) { + this.removeFocusBorder(); + } + // Add the border rect + var bb = this.getBBox(), + pad = pick(margin, 3), + parent = this.parentGroup, + scaleX = this.scaleX || parent && parent.scaleX, + scaleY = this.scaleY || parent && parent.scaleY, + oneDefined = scaleX ? !scaleY : scaleY, + scaleBoth = oneDefined ? Math.abs(scaleX || scaleY || 1) : + (Math.abs(scaleX || 1) + Math.abs(scaleY || 1)) / 2; + bb.x += this.translateX ? this.translateX : 0; + bb.y += this.translateY ? this.translateY : 0; + var borderPosX = bb.x - pad, + borderPosY = bb.y - pad, + borderWidth = bb.width + 2 * pad, + borderHeight = bb.height + 2 * pad; + /** + * For text elements, apply x and y offset, #11397. + * @private + */ + function getTextAnchorCorrection(text) { + var posXCorrection = 0, + posYCorrection = 0; + if (text.attr('text-anchor') === 'middle') { + posXCorrection = posYCorrection = 0.5; + } + else if (!text.rotation) { + posYCorrection = 0.75; + } + else { + posXCorrection = 0.25; + } + return { + x: posXCorrection, + y: posYCorrection + }; + } + var isLabel = this instanceof SVGLabel; + if (this.element.nodeName === 'text' || isLabel) { + var isRotated = !!this.rotation; + var correction = !isLabel ? getTextAnchorCorrection(this) : + { + x: isRotated ? 1 : 0, + y: 0 + }; + var attrX = +this.attr('x'); + var attrY = +this.attr('y'); + if (!isNaN(attrX)) { + borderPosX = attrX - (bb.width * correction.x) - pad; + } + if (!isNaN(attrY)) { + borderPosY = attrY - (bb.height * correction.y) - pad; + } + if (isLabel && isRotated) { + var temp = borderWidth; + borderWidth = borderHeight; + borderHeight = temp; + if (!isNaN(attrX)) { + borderPosX = attrX - (bb.height * correction.x) - pad; + } + if (!isNaN(attrY)) { + borderPosY = attrY - (bb.width * correction.y) - pad; + } + } + } + this.focusBorder = this.renderer.rect(borderPosX, borderPosY, borderWidth, borderHeight, parseInt((attribs && attribs.r || 0).toString(), 10) / scaleBoth) + .addClass('highcharts-focus-border') + .attr({ + zIndex: 99 + }) + .add(parent); + if (!this.renderer.styledMode) { + this.focusBorder.attr({ + stroke: attribs && attribs.stroke, + 'stroke-width': (attribs && attribs.strokeWidth || 0) / scaleBoth + }); + } + avgElementAddUpdateFocusBorderHooks(this, margin, attribs); + svgElementAddDestroyFocusBorderHook(this); + } + /** + * Add hooks to update the focus border of an element when the element + * size/position is updated, unless already added. + * @private + * @param el Element to add update hooks to + * @param updateParams Parameters to pass through to addFocusBorder when updating. + */ + function avgElementAddUpdateFocusBorderHooks(el) { + var updateParams = []; + for (var _i = 1; _i < arguments.length; _i++) { + updateParams[_i - 1] = arguments[_i]; + } + if (el.focusBorderUpdateHooks) { + return; + } + el.focusBorderUpdateHooks = {}; + svgElementBorderUpdateTriggers.forEach(function (trigger) { + var setterKey = trigger + 'Setter'; + var origSetter = el[setterKey] || el._defaultSetter; + el.focusBorderUpdateHooks[setterKey] = origSetter; + el[setterKey] = function () { + var ret = origSetter.apply(el, + arguments); + el.addFocusBorder.apply(el, updateParams); + return ret; + }; + }); + } + /** + * Remove hook from SVG element added by addDestroyFocusBorderHook, if + * existing. + * @private + * @param el Element to remove destroy hook from + */ + function svgElementRemoveDestroyFocusBorderHook(el) { + if (!el.focusBorderDestroyHook) { + return; + } + el.destroy = el.focusBorderDestroyHook; + delete el.focusBorderDestroyHook; + } + /** + * Add focus border functionality to SVGElements. Draws a new rect on top of + * element around its bounding box. This is used by multiple components. + * @private + * @function Highcharts.SVGElement#removeFocusBorder + */ + function svgElementRemoveFocusBorder() { + svgElementRemoveUpdateFocusBorderHooks(this); + svgElementRemoveDestroyFocusBorderHook(this); + if (this.focusBorder) { + this.focusBorder.destroy(); + delete this.focusBorder; + } + } + /** + * Remove hooks from SVG element added by addUpdateFocusBorderHooks, if + * existing. + * @private + * @param el Element to remove update hooks from + */ + function svgElementRemoveUpdateFocusBorderHooks(el) { + if (!el.focusBorderUpdateHooks) { + return; + } + Object.keys(el.focusBorderUpdateHooks).forEach(function (setterKey) { + var origSetter = el.focusBorderUpdateHooks[setterKey]; + if (origSetter === el._defaultSetter) { + delete el[setterKey]; + } + else { + el[setterKey] = origSetter; + } + }); + delete el.focusBorderUpdateHooks; + } + })(FocusBorderComposition || (FocusBorderComposition = {})); + /* * + * + * Default Export + * + * */ + + return FocusBorderComposition; + }); + _registerModule(_modules, 'Accessibility/Utils/Announcer.js', [_modules['Core/Renderer/HTML/AST.js'], _modules['Accessibility/Utils/DOMElementProvider.js'], _modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (AST, DOMElementProvider, H, HU, U) { + /* * + * + * (c) 2009-2021 Ø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 doc = H.doc; + var addClass = HU.addClass, + visuallyHideElement = HU.visuallyHideElement; + var attr = U.attr; + /* * + * + * Class + * + * */ + var Announcer = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function Announcer(chart, type) { + this.chart = chart; + this.domElementProvider = new DOMElementProvider(); + this.announceRegion = this.addAnnounceRegion(type); + } + /* * + * + * Functions + * + * */ + Announcer.prototype.destroy = function () { + this.domElementProvider.destroyCreatedElements(); + }; + Announcer.prototype.announce = function (message) { + var _this = this; + AST.setElementHTML(this.announceRegion, 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 = AST.emptyHTML; + delete _this.clearAnnouncementRegionTimer; + }, 1000); + }; + Announcer.prototype.addAnnounceRegion = function (type) { + var chartContainer = (this.chart.announcerContainer || this.createAnnouncerContainer()), + div = this.domElementProvider.createElement('div'); + attr(div, { + 'aria-hidden': false, + 'aria-live': type + }); + if (this.chart.styledMode) { + addClass(div, 'highcharts-visually-hidden'); + } + else { + visuallyHideElement(div); + } + chartContainer.appendChild(div); + return div; + }; + Announcer.prototype.createAnnouncerContainer = function () { + var chart = this.chart, + container = doc.createElement('div'); + attr(container, { + 'aria-hidden': false, + 'class': 'highcharts-announcer-container' + }); + container.style.position = 'relative'; + chart.renderTo.insertBefore(container, chart.renderTo.firstChild); + chart.announcerContainer = container; + return container; + }; + return Announcer; + }()); + /* * + * + * Default Export + * + * */ + + return Announcer; + }); + _registerModule(_modules, 'Accessibility/Components/AnnotationsA11y.js', [_modules['Accessibility/Utils/HTMLUtilities.js']], function (HTMLUtilities) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Annotations accessibility code. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var escapeStringForHTML = HTMLUtilities.escapeStringForHTML, + stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString; + /* * + * + * Functions + * + * */ + /** + * 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) { + if (cur.options && + cur.options.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) { + return ((label.options && + label.options.accessibility && + label.options.accessibility.description) || + (label.graphic && + label.graphic.text && + label.graphic.text.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 a11yDesc = (label.options && + label.options.accessibility && + label.options.accessibility.description); + if (a11yDesc) { + return a11yDesc; + } + var chart = label.chart; + var labelText = getLabelText(label); + var points = label.points; + var getAriaLabel = function (point) { return (point.graphic && + point.graphic.element && + point.graphic.element.getAttribute('aria-label') || + ''); }; + var getValueDesc = function (point) { + var valDesc = (point.accessibility && + point.accessibility.valueDescription || + getAriaLabel(point)); + var seriesName = (point && + 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 out points we can't describe + .filter(function (desc) { return !!desc; }); + var numPoints = pointValueDescriptions.length; + var pointsSelector = numPoints > 1 ? + 'MultiplePoints' : numPoints ? + 'SinglePoint' : 'NoPoints'; + var langFormatStr = ('accessibility.screenReaderSection.annotations.description' + + pointsSelector); + var context = { + annotationText: labelText, + annotation: label, + 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 ? "
  • ".concat(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 "
      ".concat(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 label.points.indexOf(point) > -1; }); + if (!pointLabels.length) { + return []; + } + return pointLabels.map(function (label) { return "".concat(getLabelText(label)); }); + } + /* * + * + * Default Export + * + * */ + var AnnotationsA11y = { + getAnnotationsInfoHTML: getAnnotationsInfoHTML, + getAnnotationLabelDescription: getAnnotationLabelDescription, + getAnnotationListItems: getAnnotationListItems, + getPointAnnotationTexts: getPointAnnotationTexts + }; + + return AnnotationsA11y; + }); + _registerModule(_modules, 'Accessibility/Components/InfoRegionsComponent.js', [_modules['Accessibility/A11yI18n.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/Announcer.js'], _modules['Accessibility/Components/AnnotationsA11y.js'], _modules['Core/Renderer/HTML/AST.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (A11yI18n, AccessibilityComponent, Announcer, AnnotationsA11y, AST, CU, F, H, HU, U) { + /* * + * + * (c) 2009-2021 Ø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 __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML; + var getAxisDescription = CU.getAxisDescription, + getAxisRangeDescription = CU.getAxisRangeDescription, + getChartTitle = CU.getChartTitle, + unhideChartElementFromAT = CU.unhideChartElementFromAT; + var format = F.format; + var doc = H.doc; + var addClass = HU.addClass, + getElement = HU.getElement, + getHeadingTagNameForElement = HU.getHeadingTagNameForElement, + stripHTMLTagsFromString = HU.stripHTMLTagsFromString, + visuallyHideElement = HU.visuallyHideElement; + var attr = U.attr, + pick = U.pick; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + function getTableSummary(chart) { + return chart.langFormat('accessibility.table.tableSummary', { chart: chart }); + } + /** + * @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 : ''); + } + /** + * 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. + */ + function getTypeDescription(chart, types) { + var firstType = types[0], + firstSeries = chart.series && chart.series[0] || {}, + mapTitle = chart.mapView && chart.mapView.geoMap && + chart.mapView.geoMap.title, + formatContext = { + numSeries: chart.series.length, + numPoints: firstSeries.points && firstSeries.points.length, + chart: chart, + mapTitle: mapTitle + }; + if (!firstType) { + return getTypeDescForEmptyChart(chart, formatContext); + } + if (firstType === 'map') { + return getTypeDescForMapChart(chart, formatContext); + } + if (chart.types.length > 1) { + return getTypeDescForCombinationChart(chart, formatContext); + } + return buildTypeDescriptionFromSeries(chart, types, formatContext); + } + /** + * @private + */ + function stripEmptyHTMLTags(str) { + return str.replace(/<(\w+)[^>]*?>\s*<\/\1>/g, ''); + } + /* * + * + * Class + * + * */ + /** + * The InfoRegionsComponent class + * + * @private + * @class + * @name Highcharts.InfoRegionsComponent + */ + var InfoRegionsComponent = /** @class */ (function (_super) { + __extends(InfoRegionsComponent, _super); + function InfoRegionsComponent() { + /* * + * + * Properties + * + * */ + var _this = _super !== null && _super.apply(this, + arguments) || this; + _this.announcer = void 0; + _this.screenReaderSections = {}; + return _this; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Init the component + * @private + */ + InfoRegionsComponent.prototype.init = function () { + var chart = this.chart; + var component = this; + this.initRegionsDefinitions(); + this.addEvent(chart, 'aftergetTableAST', 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 + */ + InfoRegionsComponent.prototype.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); + }, + afterInserted: function () { + if (component.chart.accessibility) { + component.chart.accessibility + .keyboardNavigation.updateExitAnchor(); // #15986 + } + } + } + }; + }; + /** + * Called on chart render. Have to update the sections on render, in order + * to get a11y info from series. + */ + InfoRegionsComponent.prototype.onChartRender = function () { + var component = this; + this.linkedDescriptionElement = this.getLinkedDescriptionElement(); + this.setLinkedDescriptionAttrs(); + Object.keys(this.screenReaderSections).forEach(function (regionKey) { + component.updateScreenReaderSection(regionKey); + }); + }; + /** + * @private + */ + InfoRegionsComponent.prototype.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 + */ + InfoRegionsComponent.prototype.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 + */ + InfoRegionsComponent.prototype.updateScreenReaderSection = function (regionKey) { + var chart = this.chart; + var region = this.screenReaderSections[regionKey]; + var content = region.buildContent(chart); + var sectionDiv = region.element = (region.element || this.createElement('div')); + var hiddenDiv = (sectionDiv.firstChild || this.createElement('div')); + if (content) { + this.setScreenReaderSectionAttribs(sectionDiv, regionKey); + AST.setElementHTML(hiddenDiv, content); + sectionDiv.appendChild(hiddenDiv); + region.insertIntoDOM(sectionDiv, chart); + if (chart.styledMode) { + addClass(hiddenDiv, 'highcharts-visually-hidden'); + } + else { + visuallyHideElement(hiddenDiv); + } + unhideChartElementFromAT(chart, hiddenDiv); + if (region.afterInserted) { + region.afterInserted(); + } + } + else { + if (sectionDiv.parentNode) { + sectionDiv.parentNode.removeChild(sectionDiv); + } + region.element = null; + } + }; + /** + * Apply a11y attributes to a screen reader info section + * @private + * @param {Highcharts.HTMLDOMElement} sectionDiv The section element + * @param {string} regionKey Name/key of the region we are setting attrs for + */ + InfoRegionsComponent.prototype.setScreenReaderSectionAttribs = function (sectionDiv, regionKey) { + var chart = this.chart, labelText = chart.langFormat('accessibility.screenReaderSection.' + regionKey + + 'RegionLabel', { chart: chart, chartTitle: getChartTitle(chart) }), sectionId = "highcharts-screen-reader-region-".concat(regionKey, "-").concat(chart.index); + attr(sectionDiv, { + id: sectionId, + 'aria-label': labelText || void 0 + }); + // Sections are wrapped to be positioned relatively to chart in case + // elements inside are tabbed to. + sectionDiv.style.position = 'relative'; + if (labelText) { + sectionDiv.setAttribute('role', chart.options.accessibility.landmarkVerbosity === 'all' ? + 'region' : 'group'); + } + }; + /** + * @private + */ + InfoRegionsComponent.prototype.defaultBeforeChartFormatter = function () { + var chart = this.chart, + format = chart.options.accessibility.screenReaderSection + .beforeChartFormat; + if (!format) { + return ''; + } + var axesDesc = this.getAxesDescription(), + shouldHaveSonifyBtn = (chart.sonify && + chart.options.sonification && + chart.options.sonification.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 = { + headingTagName: getHeadingTagNameForElement(chart.renderTo), + 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 = A11yI18n.i18nFormat(format, + context, + chart); + this.dataTableButtonId = dataTableButtonId; + this.sonifyButtonId = sonifyButtonId; + return stripEmptyHTMLTags(formattedString); + }; + /** + * @private + */ + InfoRegionsComponent.prototype.defaultAfterChartFormatter = function () { + var chart = this.chart; + var format = chart.options.accessibility.screenReaderSection + .afterChartFormat; + if (!format) { + return ''; + } + var context = { endOfChartMarker: this.getEndOfChartMarkerText() }; + var formattedString = A11yI18n.i18nFormat(format, + context, + chart); + return stripEmptyHTMLTags(formattedString); + }; + /** + * @private + */ + InfoRegionsComponent.prototype.getLinkedDescription = function () { + var el = this.linkedDescriptionElement, + content = el && el.innerHTML || ''; + return stripHTMLTagsFromString(content); + }; + /** + * @private + */ + InfoRegionsComponent.prototype.getLongdescText = function () { + var chartOptions = this.chart.options, + captionOptions = chartOptions.caption, + captionText = captionOptions && captionOptions.text, + linkedDescription = this.getLinkedDescription(); + return (chartOptions.accessibility.description || + linkedDescription || + captionText || + ''); + }; + /** + * @private + */ + InfoRegionsComponent.prototype.getTypeDescriptionText = function () { + var chart = this.chart; + return chart.types ? + chart.options.accessibility.typeDescription || + getTypeDescription(chart, chart.types) : ''; + }; + /** + * @private + */ + InfoRegionsComponent.prototype.getDataTableButtonText = function (buttonId) { + var chart = this.chart, + buttonText = chart.langFormat('accessibility.table.viewAsDataTableButtonText', { chart: chart, + chartTitle: getChartTitle(chart) }); + return ''; + }; + /** + * @private + */ + InfoRegionsComponent.prototype.getSonifyButtonText = function (buttonId) { + var chart = this.chart; + if (chart.options.sonification && + chart.options.sonification.enabled === false) { + return ''; + } + var buttonText = chart.langFormat('accessibility.sonification.playAsSoundButtonText', { chart: chart, + chartTitle: getChartTitle(chart) }); + return ''; + }; + /** + * @private + */ + InfoRegionsComponent.prototype.getSubtitleText = function () { + var subtitle = (this.chart.options.subtitle); + return stripHTMLTagsFromString(subtitle && subtitle.text || ''); + }; + /** + * @private + */ + InfoRegionsComponent.prototype.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 + */ + InfoRegionsComponent.prototype.onDataTableCreated = function (e) { + var chart = this.chart; + if (chart.options.accessibility.enabled) { + if (this.viewDataTableButton) { + this.viewDataTableButton.setAttribute('aria-expanded', 'true'); + } + var attributes = e.tree.attributes || {}; + attributes.tabindex = -1; + attributes.summary = getTableSummary(chart); + e.tree.attributes = attributes; + } + }; + /** + * @private + */ + InfoRegionsComponent.prototype.focusDataTable = function () { + var tableDiv = this.dataTableDiv, + table = tableDiv && tableDiv.getElementsByTagName('table')[0]; + if (table && table.focus) { + table.focus(); + } + }; + /** + * @private + * @param {string} sonifyButtonId + */ + InfoRegionsComponent.prototype.initSonifyButton = function (sonifyButtonId) { + var _this = this; + var el = this.sonifyButton = getElement(sonifyButtonId); + var chart = this.chart; + var defaultHandler = function (e) { + if (el) { + el.setAttribute('aria-hidden', 'true'); + el.setAttribute('aria-label', ''); + } + e.preventDefault(); + e.stopPropagation(); + var announceMsg = chart.langFormat('accessibility.sonification.playAsSoundClickAnnouncement', { chart: chart }); + _this.announcer.announce(announceMsg); + setTimeout(function () { + if (el) { + el.removeAttribute('aria-hidden'); + el.removeAttribute('aria-label'); + } + if (chart.sonify) { + chart.sonify(); + } + }, 1000); // Delay to let screen reader speak the button press + }; + if (el && chart) { + el.setAttribute('tabindex', -1); + el.onclick = function (e) { + var onPlayAsSoundClick = (chart.options.accessibility && + chart.options.accessibility.screenReaderSection + .onPlayAsSoundClick); + (onPlayAsSoundClick || defaultHandler).call(this, e, chart); + }; + } + }; + /** + * Set attribs and handlers for default viewAsDataTable button if exists. + * @private + * @param {string} tableButtonId + */ + InfoRegionsComponent.prototype.initDataTableButton = function (tableButtonId) { + var el = this.viewDataTableButton = getElement(tableButtonId), chart = this.chart, tableId = tableButtonId.replace('hc-linkto-', ''); + if (el) { + attr(el, { + tabindex: -1, + 'aria-expanded': !!getElement(tableId) + }); + el.onclick = chart.options.accessibility + .screenReaderSection.onViewDataTableClick || + function () { + chart.viewData(); + }; + } + }; + /** + * Return object with text description of each of the chart's axes. + * @private + */ + InfoRegionsComponent.prototype.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 && + chart.types.indexOf('treemap') < 0 && + chart.types.indexOf('tilemap') < 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 + */ + InfoRegionsComponent.prototype.getAxisDescriptionText = function (collectionKey) { + var chart = this.chart; + var 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 getAxisRangeDescription(axis); + }), + numAxes: axes.length + }); + }; + /** + * Remove component traces + */ + InfoRegionsComponent.prototype.destroy = function () { + if (this.announcer) { + this.announcer.destroy(); + } + }; + return InfoRegionsComponent; + }(AccessibilityComponent)); + /* * + * + * Default Export + * + * */ + + return InfoRegionsComponent; + }); + _registerModule(_modules, 'Accessibility/Components/MenuComponent.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (Chart, U, AccessibilityComponent, KeyboardNavigationHandler, ChartUtilities, HTMLUtilities) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component for exporting menu. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var attr = U.attr; + var getChartTitle = ChartUtilities.getChartTitle, + unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; + var getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Get the wrapped export button element of a chart. + * @private + */ + function getExportMenuButtonElement(chart) { + return chart.exportSVGElements && chart.exportSVGElements[0]; + } + /** + * @private + */ + function exportingShouldHaveA11y(chart) { + var exportingOpts = chart.options.exporting, + exportButton = getExportMenuButtonElement(chart); + return !!(exportingOpts && + exportingOpts.enabled !== false && + exportingOpts.accessibility && + exportingOpts.accessibility.enabled && + exportButton && + exportButton.element); + } + /* * + * + * Class + * + * */ + /** + * The MenuComponent class + * + * @private + * @class + * @name Highcharts.MenuComponent + */ + var MenuComponent = /** @class */ (function (_super) { + __extends(MenuComponent, _super); + function MenuComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Init the component + */ + MenuComponent.prototype.init = function () { + var chart = this.chart, + component = this; + this.addEvent(chart, 'exportMenuShown', function () { + component.onMenuShown(); + }); + this.addEvent(chart, 'exportMenuHidden', function () { + component.onMenuHidden(); + }); + this.createProxyGroup(); + }; + /** + * @private + */ + MenuComponent.prototype.onMenuHidden = function () { + var menu = this.chart.exportContextMenu; + if (menu) { + menu.setAttribute('aria-hidden', 'true'); + } + this.setExportButtonExpandedState('false'); + }; + /** + * @private + */ + MenuComponent.prototype.onMenuShown = function () { + var chart = this.chart, + menu = chart.exportContextMenu; + if (menu) { + this.addAccessibleContextMenuAttribs(); + unhideChartElementFromAT(chart, menu); + } + this.setExportButtonExpandedState('true'); + }; + /** + * @private + * @param {string} stateStr + */ + MenuComponent.prototype.setExportButtonExpandedState = function (stateStr) { + if (this.exportButtonProxy) { + this.exportButtonProxy.buttonElement.setAttribute('aria-expanded', stateStr); + } + }; + /** + * Called on each render of the chart. We need to update positioning of the + * proxy overlay. + */ + MenuComponent.prototype.onChartRender = function () { + var chart = this.chart, + focusEl = chart.focusElement, + a11y = chart.accessibility; + this.proxyProvider.clearGroup('chartMenu'); + this.proxyMenuButton(); + if (this.exportButtonProxy && + focusEl && + focusEl === chart.exportingGroup) { + if (focusEl.focusBorder) { + chart.setFocusToElement(focusEl, this.exportButtonProxy.buttonElement); + } + else if (a11y) { + a11y.keyboardNavigation.tabindexContainer.focus(); + } + } + }; + /** + * @private + */ + MenuComponent.prototype.proxyMenuButton = function () { + var chart = this.chart; + var proxyProvider = this.proxyProvider; + var buttonEl = getExportMenuButtonElement(chart); + if (exportingShouldHaveA11y(chart) && buttonEl) { + this.exportButtonProxy = proxyProvider.addProxyElement('chartMenu', { click: buttonEl }, { + 'aria-label': chart.langFormat('accessibility.exporting.menuButtonLabel', { + chart: chart, + chartTitle: getChartTitle(chart) + }), + 'aria-expanded': false, + title: chart.options.lang.contextButtonTitle || null + }); + } + }; + /** + * @private + */ + MenuComponent.prototype.createProxyGroup = function () { + var chart = this.chart; + if (chart && this.proxyProvider) { + this.proxyProvider.addGroup('chartMenu', 'div'); + } + }; + /** + * @private + */ + MenuComponent.prototype.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) { + 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] && exportList[0].parentNode); + if (parentDiv) { + attr(parentDiv, { + 'aria-hidden': void 0, + 'aria-label': chart.langFormat('accessibility.exporting.chartMenuLabel', { chart: chart }), + role: 'list' // Needed for webkit/VO + }); + } + } + }; + /** + * Get keyboard navigation handler for this component. + * @private + */ + MenuComponent.prototype.getKeyboardNavigation = function () { + var keys = this.keyCodes, + chart = this.chart, + component = this; + return new KeyboardNavigationHandler(chart, { + keyCodeMap: [ + // Arrow prev handler + [ + [keys.left, keys.up], + function () { + return component.onKbdPrevious(this); + } + ], + // Arrow next handler + [ + [keys.right, keys.down], + function () { + return component.onKbdNext(this); + } + ], + // Click handler + [ + [keys.enter, keys.space], + function () { + return component.onKbdClick(this); + } + ] + ], + // Only run exporting navigation if exporting support exists and is + // enabled on chart + validate: function () { + return !!chart.exporting && + chart.options.exporting.enabled !== false && + chart.options.exporting.accessibility.enabled !== + false; + }, + // Focus export menu button + init: function () { + var proxy = component.exportButtonProxy; + var svgEl = component.chart.exportingGroup; + if (proxy && svgEl) { + chart.setFocusToElement(svgEl, proxy.buttonElement); + } + }, + // Hide the menu + terminate: function () { + chart.hideExportMenu(); + } + }); + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler + * @return {number} Response code + */ + MenuComponent.prototype.onKbdPrevious = function (keyboardNavigationHandler) { + var chart = this.chart; + var a11yOptions = chart.options.accessibility; + var response = keyboardNavigationHandler.response; + // Try to highlight prev item in list. Highlighting e.g. + // separators will fail. + var i = chart.highlightedExportItemIx || 0; + 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 + */ + MenuComponent.prototype.onKbdNext = function (keyboardNavigationHandler) { + var chart = this.chart; + var a11yOptions = chart.options.accessibility; + var response = keyboardNavigationHandler.response; + // Try to highlight next item in list. Highlighting e.g. + // separators will fail. + for (var i = (chart.highlightedExportItemIx || 0) + 1; 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 + */ + MenuComponent.prototype.onKbdClick = function (keyboardNavigationHandler) { + var chart = this.chart; + var curHighlightedItem = chart.exportDivElements[chart.highlightedExportItemIx]; + var exportButtonElement = getExportMenuButtonElement(chart).element; + if (chart.openMenu) { + this.fakeClickEvent(curHighlightedItem); + } + else { + this.fakeClickEvent(exportButtonElement); + chart.highlightExportItem(0); + } + return keyboardNavigationHandler.response.success; + }; + return MenuComponent; + }(AccessibilityComponent)); + /* * + * + * Class Namespace + * + * */ + (function (MenuComponent) { + /* * + * + * Declarations + * + * */ + /* * + * + * Constants + * + * */ + var composedClasses = []; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + function compose(ChartClass) { + if (composedClasses.indexOf(ChartClass) === -1) { + composedClasses.push(ChartClass); + var chartProto = Chart.prototype; + chartProto.hideExportMenu = chartHideExportMenu; + chartProto.highlightExportItem = chartHighlightExportItem; + chartProto.highlightLastExportItem = chartHighlightLastExportItem; + chartProto.showExportMenu = chartShowExportMenu; + } + } + MenuComponent.compose = compose; + /** + * Show the export menu and focus the first item (if exists). + * + * @private + * @function Highcharts.Chart#showExportMenu + */ + function chartShowExportMenu() { + var exportButton = getExportMenuButtonElement(this); + if (exportButton) { + var el = exportButton.element; + if (el.onclick) { + el.onclick(getFakeMouseEvent('click')); + } + } + } + /** + * @private + * @function Highcharts.Chart#hideExportMenu + */ + function chartHideExportMenu() { + var chart = this, + exportList = chart.exportDivElements; + if (exportList && chart.exportContextMenu && chart.openMenu) { + // Reset hover states etc. + exportList.forEach(function (el) { + if (el && + 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 + */ + function chartHighlightExportItem(ix) { + var listItem = this.exportDivElements && this.exportDivElements[ix]; + var curHighlighted = this.exportDivElements && + this.exportDivElements[this.highlightedExportItemIx]; + if (listItem && + listItem.tagName === 'LI' && + !(listItem.children && listItem.children.length)) { + // Test if we have focus support for SVG elements + var 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 + */ + function chartHighlightLastExportItem() { + var chart = this; + if (chart.exportDivElements) { + var i = chart.exportDivElements.length; + while (i--) { + if (chart.highlightExportItem(i)) { + return true; + } + } + } + return false; + } + })(MenuComponent || (MenuComponent = {})); + /* * + * + * Default Export + * + * */ + + return MenuComponent; + }); + _registerModule(_modules, 'Accessibility/KeyboardNavigation.js', [_modules['Core/Globals.js'], _modules['Accessibility/Components/MenuComponent.js'], _modules['Core/Utilities.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (H, MenuComponent, U, EventProvider, HTMLUtilities) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Main keyboard navigation handling. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc, + win = H.win; + var addEvent = U.addEvent, + fireEvent = U.fireEvent; + var getElement = HTMLUtilities.getElement; + /* * + * + * Class + * + * */ + /** + * 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 + */ + var KeyboardNavigation = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function KeyboardNavigation(chart, components) { + /* * + * + * Properties + * + * */ + this.chart = void 0; + this.components = void 0; + this.currentModuleIx = NaN; + this.eventProvider = void 0; + this.exitAnchor = void 0; + this.modules = []; + this.tabindexContainer = void 0; + this.init(chart, components); + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Initialize the class + * @private + * @param {Highcharts.Chart} chart + * Chart object + * @param {Object} components + * Map of component names to AccessibilityComponent objects. + */ + KeyboardNavigation.prototype.init = function (chart, components) { + var _this = this; + var ep = this.eventProvider = new EventProvider(); + this.chart = chart; + this.components = components; + this.modules = []; + this.currentModuleIx = 0; + this.update(); + ep.addEvent(this.tabindexContainer, 'keydown', function (e) { return _this.onKeydown(e); }); + ep.addEvent(this.tabindexContainer, 'focus', function (e) { return _this.onFocus(e); }); + ['mouseup', 'touchend'].forEach(function (eventName) { + return ep.addEvent(doc, eventName, function () { return _this.onMouseUp(); }); + }); + ['mousedown', 'touchstart'].forEach(function (eventName) { + return ep.addEvent(chart.renderTo, eventName, function () { + _this.isClickingChart = true; + }); + }); + ep.addEvent(chart.renderTo, 'mouseover', function () { + _this.pointerIsOverChart = true; + }); + ep.addEvent(chart.renderTo, 'mouseout', function () { + _this.pointerIsOverChart = false; + }); + }; + /** + * Update the modules for the keyboard navigation. + * @param {Array} [order] + * Array specifying the tab order of the components. + */ + KeyboardNavigation.prototype.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(); + } + }; + /** + * 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 + */ + KeyboardNavigation.prototype.updateExitAnchor = function () { + var endMarkerId = "highcharts-end-of-chart-marker-".concat(this.chart.index), + endMarker = getElement(endMarkerId); + this.removeExitAnchor(); + if (endMarker) { + this.makeElementAnExitAnchor(endMarker); + this.exitAnchor = endMarker; + } + else { + this.createExitAnchor(); + } + }; + /** + * 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. + */ + KeyboardNavigation.prototype.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 + this.exiting = true; + if (direction > 0) { + this.exitAnchor && this.exitAnchor.focus(); + } + else { + this.tabindexContainer.focus(); + } + return false; + }; + /** + * Function to run on container focus + * @private + * @param {global.FocusEvent} e Browser focus event. + */ + KeyboardNavigation.prototype.onFocus = function (e) { + var chart = this.chart; + var focusComesFromChart = (e.relatedTarget && + chart.container.contains(e.relatedTarget)); + // Init keyboard nav if tabbing into chart + if (!this.exiting && + !this.tabbingInBackwards && + !this.isClickingChart && + !focusComesFromChart) { + var ix = this.getFirstValidModuleIx(); + if (ix !== null) { + this.currentModuleIx = ix; + this.modules[ix].init(1); + } + } + this.exiting = false; + }; + /** + * Reset chart navigation state if we mouse click and it's not already + * reset. Reset fully if outside the chart, otherwise just hide focus + * indicator. + * @private + */ + KeyboardNavigation.prototype.onMouseUp = function () { + delete this.isClickingChart; + if (!this.keyboardReset) { + var chart = this.chart; + if (!this.pointerIsOverChart) { + var curMod = this.modules && + this.modules[this.currentModuleIx || 0]; + if (curMod && curMod.terminate) { + curMod.terminate(); + } + this.currentModuleIx = 0; + } + if (chart.focusElement) { + chart.focusElement.removeFocusBorder(); + delete chart.focusElement; + } + this.keyboardReset = true; + } + }; + /** + * Function to run on keydown + * @private + * @param {global.KeyboardEvent} ev Browser keydown event. + */ + KeyboardNavigation.prototype.onKeydown = function (ev) { + var e = ev || win.event, + curNavModule = (this.modules && + this.modules.length && + this.modules[this.currentModuleIx]); + var preventDefault; + // Used for resetting nav state when clicking outside chart + this.keyboardReset = false; + // Used for sending focus out of the chart by the modules. + this.exiting = 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.move(-1); + } + else if (response === curNavModule.response.next) { + preventDefault = this.move(1); + } + if (preventDefault) { + e.preventDefault(); + e.stopPropagation(); + } + } + }; + /** + * Chart container should have tabindex if navigation is enabled. + * @private + */ + KeyboardNavigation.prototype.updateContainerTabindex = function () { + var a11yOptions = this.chart.options.accessibility, + keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation, + shouldHaveTabindex = !(keyboardOptions && keyboardOptions.enabled === false), + chart = this.chart, + container = chart.container; + var tabindexContainer; + if (chart.renderTo.hasAttribute('tabindex')) { + container.removeAttribute('tabindex'); + tabindexContainer = chart.renderTo; + } + else { + tabindexContainer = container; + } + this.tabindexContainer = tabindexContainer; + var curTabindex = tabindexContainer.getAttribute('tabindex'); + if (shouldHaveTabindex && !curTabindex) { + tabindexContainer.setAttribute('tabindex', '0'); + } + else if (!shouldHaveTabindex) { + chart.container.removeAttribute('tabindex'); + } + }; + /** + * Add new exit anchor to the chart. + * @private + */ + KeyboardNavigation.prototype.createExitAnchor = function () { + var chart = this.chart, + exitAnchor = this.exitAnchor = doc.createElement('div'); + chart.renderTo.appendChild(exitAnchor); + this.makeElementAnExitAnchor(exitAnchor); + }; + /** + * Add attributes and events to an element to make it function as an + * exit anchor. + * @private + */ + KeyboardNavigation.prototype.makeElementAnExitAnchor = function (el) { + var chartTabindex = this.tabindexContainer.getAttribute('tabindex') || 0; + el.setAttribute('class', 'highcharts-exit-anchor'); + el.setAttribute('tabindex', chartTabindex); + el.setAttribute('aria-hidden', false); + // Handle focus + this.addExitAnchorEventsToEl(el); + }; + /** + * Destroy the exit anchor and remove from DOM. + * @private + */ + KeyboardNavigation.prototype.removeExitAnchor = function () { + if (this.exitAnchor && this.exitAnchor.parentNode) { + this.exitAnchor.parentNode.removeChild(this.exitAnchor); + delete this.exitAnchor; + } + }; + /** + * Add focus handler to exit anchor element. + * @private + */ + KeyboardNavigation.prototype.addExitAnchorEventsToEl = function (element) { + var chart = this.chart, + keyboardNavigation = this; + this.eventProvider.addEvent(element, 'focus', function (ev) { + var e = ev || win.event, + focusComesFromChart = (e.relatedTarget && + chart.container.contains(e.relatedTarget)), + comingInBackwards = !(focusComesFromChart || keyboardNavigation.exiting); + if (chart.focusElement) { + delete chart.focusElement; + } + if (comingInBackwards) { + // Focus the container instead + keyboardNavigation.tabbingInBackwards = true; + keyboardNavigation.tabindexContainer.focus(); + delete keyboardNavigation.tabbingInBackwards; + 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; + var curModule = keyboardNavigation.modules[keyboardNavigation.currentModuleIx]; + // Validate the module + if (curModule && + curModule.validate && !curModule.validate()) { + // Invalid. Try moving backwards to find next valid. + keyboardNavigation.move(-1); + } + 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; + } + }); + }; + /** + * Get the ix of the first module that either does not require validation or + * validates positively. + * @private + */ + KeyboardNavigation.prototype.getFirstValidModuleIx = function () { + var len = this.modules.length; + for (var i = 0; i < len; ++i) { + var mod = this.modules[i]; + if (!mod.validate || mod.validate()) { + return i; + } + } + return null; + }; + /** + * Remove all traces of keyboard navigation. + * @private + */ + KeyboardNavigation.prototype.destroy = function () { + this.removeExitAnchor(); + this.eventProvider.removeAddedEvents(); + this.chart.container.removeAttribute('tabindex'); + }; + return KeyboardNavigation; + }()); + /* * + * + * Class Namespace + * + * */ + (function (KeyboardNavigation) { + /* * + * + * Declarations + * + * */ + /* * + * + * Construction + * + * */ + var composedItems = []; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Composition function. + * @private + */ + function compose(ChartClass) { + MenuComponent.compose(ChartClass); + if (composedItems.indexOf(ChartClass) === -1) { + composedItems.push(ChartClass); + var chartProto = ChartClass.prototype; + chartProto.dismissPopupContent = chartDismissPopupContent; + } + if (composedItems.indexOf(doc) === -1) { + composedItems.push(doc); + addEvent(doc, 'keydown', documentOnKeydown); + } + return ChartClass; + } + KeyboardNavigation.compose = compose; + /** + * Dismiss popup content in chart, including export menu and tooltip. + * @private + */ + function chartDismissPopupContent() { + var chart = this; + fireEvent(this, 'dismissPopupContent', {}, function () { + if (chart.tooltip) { + chart.tooltip.hide(0); + } + chart.hideExportMenu(); + }); + } + /** + * Add event listener to document to detect ESC key press and dismiss + * hover/popup content. + * @private + */ + function documentOnKeydown(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(); + } + }); + } + } + })(KeyboardNavigation || (KeyboardNavigation = {})); + /* * + * + * Default Export + * + * */ + + return KeyboardNavigation; + }); + _registerModule(_modules, 'Accessibility/Components/LegendComponent.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Legend/Legend.js'], _modules['Core/Utilities.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (A, H, Legend, U, AccessibilityComponent, KeyboardNavigationHandler, CU, HU) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component for chart legend. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var animObject = A.animObject; + var doc = H.doc; + var addEvent = U.addEvent, + fireEvent = U.fireEvent, + isNumber = U.isNumber, + pick = U.pick, + syncTimeout = U.syncTimeout; + var getChartTitle = CU.getChartTitle; + var stripHTMLTags = HU.stripHTMLTagsFromString, + addClass = HU.addClass, + removeClass = HU.removeClass; + /* * + * + * Functions + * + * */ + /** + * @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); + } + /** + * @private + */ + function setLegendItemHoverState(hoverActive, legendItem) { + legendItem.setState(hoverActive ? 'hover' : '', true); + ['legendGroup', 'legendItem', 'legendSymbol'].forEach(function (i) { + var obj = legendItem[i]; + var el = obj && obj.element || obj; + if (el) { + fireEvent(el, hoverActive ? 'mouseover' : 'mouseout'); + } + }); + } + /* * + * + * Class + * + * */ + /** + * The LegendComponent class + * + * @private + * @class + * @name Highcharts.LegendComponent + */ + var LegendComponent = /** @class */ (function (_super) { + __extends(LegendComponent, _super); + function LegendComponent() { + /* * + * + * Properties + * + * */ + var _this = _super !== null && _super.apply(this, + arguments) || this; + _this.highlightedLegendItemIx = NaN; + _this.proxyGroup = null; + return _this; + } + /* * + * + * Functions + * + * */ + /** + * Init the component + * @private + */ + LegendComponent.prototype.init = function () { + var component = this; + this.recreateProxies(); + // Note: Chart could create legend dynamically, so events can not be + // tied to the component's chart's current legend. + // @todo 1. attach component to created legends + // @todo 2. move listeners to composition and access `this.component` + this.addEvent(Legend, 'afterScroll', function () { + if (this.chart === component.chart) { + component.proxyProvider.updateGroupProxyElementPositions('legend'); + component.updateLegendItemProxyVisibility(); + if (component.highlightedLegendItemIx > -1) { + this.chart.highlightLegendItem(component.highlightedLegendItemIx); + } + } + }); + this.addEvent(Legend, 'afterPositionItem', function (e) { + if (this.chart === component.chart && this.chart.renderer) { + component.updateProxyPositionForItem(e.item); + } + }); + this.addEvent(Legend, 'afterRender', function () { + if (this.chart === component.chart && + this.chart.renderer && + component.recreateProxies()) { + syncTimeout(function () { return component.proxyProvider + .updateGroupProxyElementPositions('legend'); }, animObject(pick(this.chart.renderer.globalAnimation, true)).duration); + } + }); + }; + /** + * Update visibility of legend items when using paged legend + * @private + */ + LegendComponent.prototype.updateLegendItemProxyVisibility = function () { + var chart = this.chart; + var legend = chart.legend; + var items = legend.allItems || []; + var curPage = legend.currentPage || 1; + var clipHeight = legend.clipHeight || 0; + items.forEach(function (item) { + if (item.a11yProxyElement) { + var hasPages = legend.pages && legend.pages.length; + var proxyEl = item.a11yProxyElement.element; + var hide = false; + if (hasPages) { + var itemPage = item.pageIx || 0; + var y = item._legendItemPos ? item._legendItemPos[1] : 0; + var h = item.legendItem ? + Math.round(item.legendItem.getBBox().height) : + 0; + hide = y + h - legend.pages[itemPage] > clipHeight || + itemPage !== curPage - 1; + } + if (hide) { + if (chart.styledMode) { + addClass(proxyEl, 'highcharts-a11y-invisible'); + } + else { + proxyEl.style.visibility = 'hidden'; + } + } + else { + removeClass(proxyEl, 'highcharts-a11y-invisible'); + proxyEl.style.visibility = ''; + } + } + }); + }; + /** + * @private + */ + LegendComponent.prototype.onChartRender = function () { + if (!shouldDoLegendA11y(this.chart)) { + this.removeProxies(); + } + }; + /** + * @private + */ + LegendComponent.prototype.highlightAdjacentLegendPage = function (direction) { + var chart = this.chart; + var legend = chart.legend; + var curPageIx = legend.currentPage || 1; + var newPageIx = curPageIx + direction; + var pages = legend.pages || []; + if (newPageIx > 0 && newPageIx <= pages.length) { + var len = legend.allItems.length; + for (var i = 0; i < len; ++i) { + if (legend.allItems[i].pageIx + 1 === newPageIx) { + var res = chart.highlightLegendItem(i); + if (res) { + this.highlightedLegendItemIx = i; + } + return; + } + } + } + }; + /** + * @private + */ + LegendComponent.prototype.updateProxyPositionForItem = function (item) { + if (item.a11yProxyElement) { + item.a11yProxyElement.refreshPosition(); + } + }; + /** + * Returns false if legend a11y is disabled and proxies were not created, + * true otherwise. + * @private + */ + LegendComponent.prototype.recreateProxies = function () { + var focusedElement = doc.activeElement; + var proxyGroup = this.proxyGroup; + var shouldRestoreFocus = focusedElement && proxyGroup && + proxyGroup.contains(focusedElement); + this.removeProxies(); + if (shouldDoLegendA11y(this.chart)) { + this.addLegendProxyGroup(); + this.proxyLegendItems(); + this.updateLegendItemProxyVisibility(); + this.updateLegendTitle(); + if (shouldRestoreFocus) { + this.chart.highlightLegendItem(this.highlightedLegendItemIx); + } + return true; + } + return false; + }; + /** + * @private + */ + LegendComponent.prototype.removeProxies = function () { + this.proxyProvider.removeGroup('legend'); + }; + /** + * @private + */ + LegendComponent.prototype.updateLegendTitle = function () { + var chart = this.chart; + var legendTitle = stripHTMLTags((chart.legend && + chart.legend.options.title && + chart.legend.options.title.text || + '').replace(/
    /g, ' ')); + var legendLabel = chart.langFormat('accessibility.legend.legendLabel' + (legendTitle ? '' : 'NoTitle'), { + chart: chart, + legendTitle: legendTitle, + chartTitle: getChartTitle(chart) + }); + this.proxyProvider.updateGroupAttrs('legend', { + 'aria-label': legendLabel + }); + }; + /** + * @private + */ + LegendComponent.prototype.addLegendProxyGroup = function () { + var a11yOptions = this.chart.options.accessibility; + var groupRole = a11yOptions.landmarkVerbosity === 'all' ? + 'region' : null; + this.proxyGroup = this.proxyProvider.addGroup('legend', 'ul', { + // Filled by updateLegendTitle, to keep up to date without + // recreating group + 'aria-label': '_placeholder_', + role: groupRole + }); + }; + /** + * @private + */ + LegendComponent.prototype.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.BubbleLegendItem|Point|Highcharts.Series} item + */ + LegendComponent.prototype.proxyLegendItem = function (item) { + if (!item.legendItem || !item.legendGroup) { + return; + } + var itemLabel = this.chart.langFormat('accessibility.legend.legendItem', { + chart: this.chart, + itemName: stripHTMLTags(item.name), + item: item + }); + var attribs = { + tabindex: -1, + 'aria-pressed': item.visible, + 'aria-label': itemLabel + }; + // Considers useHTML + var proxyPositioningElement = item.legendGroup.div ? + item.legendItem : + item.legendGroup; + item.a11yProxyElement = this.proxyProvider.addProxyElement('legend', { + click: item.legendItem, + visual: proxyPositioningElement.element + }, attribs); + }; + /** + * Get keyboard navigation handler for this component. + * @private + */ + LegendComponent.prototype.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 (keyCode) { + if (H.isFirefox && keyCode === keys.space) { // #15520 + return this.response.success; + } + return component.onKbdClick(this); + } + ], + [ + [keys.pageDown, keys.pageUp], + function (keyCode) { + var direction = keyCode === keys.pageDown ? 1 : -1; + component.highlightAdjacentLegendPage(direction); + return this.response.success; + } + ] + ], + validate: function () { + return component.shouldHaveLegendNavigation(); + }, + init: function () { + chart.highlightLegendItem(0); + component.highlightedLegendItemIx = 0; + }, + terminate: function () { + component.highlightedLegendItemIx = -1; + chart.legend.allItems.forEach(function (item) { return setLegendItemHoverState(false, item); }); + } + }); + }; + /** + * Arrow key navigation + * @private + */ + LegendComponent.prototype.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; + } + return response.success; + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler + * @return {number} Response code + */ + LegendComponent.prototype.onKbdClick = function (keyboardNavigationHandler) { + var legendItem = this.chart.legend.allItems[this.highlightedLegendItemIx]; + if (legendItem && legendItem.a11yProxyElement) { + legendItem.a11yProxyElement.click(); + } + return keyboardNavigationHandler.response.success; + }; + /** + * @private + */ + LegendComponent.prototype.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); + }; + return LegendComponent; + }(AccessibilityComponent)); + /* * + * + * Class Namespace + * + * */ + (function (LegendComponent) { + /* * + * + * Declarations + * + * */ + /* * + * + * Constants + * + * */ + var composedClasses = []; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Highlight legend item by index. + * @private + */ + function chartHighlightLegendItem(ix) { + var items = this.legend.allItems; + var oldIx = this.accessibility && + this.accessibility.components.legend.highlightedLegendItemIx; + var itemToHighlight = items[ix]; + if (itemToHighlight) { + if (isNumber(oldIx) && items[oldIx]) { + setLegendItemHoverState(false, items[oldIx]); + } + scrollLegendToItem(this.legend, ix); + var legendItemProp = itemToHighlight.legendItem; + var proxyBtn = itemToHighlight.a11yProxyElement && + itemToHighlight.a11yProxyElement.buttonElement; + if (legendItemProp && legendItemProp.element && proxyBtn) { + this.setFocusToElement(legendItemProp, proxyBtn); + } + setLegendItemHoverState(true, itemToHighlight); + return true; + } + return false; + } + /** + * @private + */ + function compose(ChartClass, LegendClass) { + if (composedClasses.indexOf(ChartClass) === -1) { + composedClasses.push(ChartClass); + var chartProto = ChartClass.prototype; + chartProto.highlightLegendItem = chartHighlightLegendItem; + } + if (composedClasses.indexOf(LegendClass) === -1) { + composedClasses.push(LegendClass); + addEvent(LegendClass, 'afterColorizeItem', legendOnAfterColorizeItem); + } + } + LegendComponent.compose = compose; + /** + * Keep track of pressed state for legend items. + * @private + */ + function legendOnAfterColorizeItem(e) { + var chart = this.chart, + a11yOptions = chart.options.accessibility, + legendItem = e.item; + if (a11yOptions.enabled && legendItem && legendItem.a11yProxyElement) { + legendItem.a11yProxyElement.buttonElement.setAttribute('aria-pressed', e.visible ? 'true' : 'false'); + } + } + })(LegendComponent || (LegendComponent = {})); + /* * + * + * Default Export + * + * */ + + return LegendComponent; + }); + _registerModule(_modules, 'Accessibility/Components/SeriesComponent/SeriesDescriber.js', [_modules['Accessibility/Components/AnnotationsA11y.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Core/FormatUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Core/Utilities.js']], function (AnnotationsA11y, ChartUtilities, F, HTMLUtilities, U) { + /* * + * + * (c) 2009-2021 Ø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 getPointAnnotationTexts = AnnotationsA11y.getPointAnnotationTexts; + var getAxisDescription = ChartUtilities.getAxisDescription, + getSeriesFirstPointElement = ChartUtilities.getSeriesFirstPointElement, + getSeriesA11yElement = ChartUtilities.getSeriesA11yElement, + unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT; + var format = F.format, + numberFormat = F.numberFormat; + var reverseChildNodes = HTMLUtilities.reverseChildNodes, + stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString; + var find = U.find, + isNumber = U.isNumber, + pick = U.pick, + defined = U.defined; + /* * + * + * Functions + * + * */ + /* 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; + } + /** + * Whether or not we should add a dummy point element in + * order to describe a point that has no graphic. + * @private + */ + function shouldAddDummyPoint(point) { + // Note: Sunburst series use isNull for hidden points on drilldown. + // Ignore these. + var series = point.series, + chart = series && series.chart, + isSunburst = series && series.is('sunburst'), + isNull = point.isNull, + shouldDescribeNull = chart && + chart + .options.accessibility.point.describeNull; + return isNull && !isSunburst && shouldDescribeNull; + } + /** + * @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 + */ + 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 + */ + function hasMorePointsThanDescriptionThreshold(series) { + var chartA11yOptions = series.chart.options.accessibility, + threshold = (chartA11yOptions.series.pointDescriptionEnabledThreshold); + return !!(threshold !== false && + series.points && + series.points.length >= threshold); + } + /** + * @private + */ + function shouldSetScreenReaderPropsOnPoints(series) { + var seriesA11yOptions = series.options.accessibility || {}; + return !hasMorePointsThanDescriptionThreshold(series) && + !seriesA11yOptions.exposeAsGroupOnly; + } + /** + * @private + */ + 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 + */ + 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 + */ + function pointNumberToString(point, value) { + var series = point.series, + chart = series.chart, + a11yPointOptions = chart.options.accessibility.point || {}, + seriesA11yPointOptions = series.options.accessibility && + series.options.accessibility.point || {}, + tooltipOptions = series.tooltipOptions || {}, + lang = chart.options.lang; + if (isNumber(value)) { + return numberFormat(value, seriesA11yPointOptions.valueDecimals || + a11yPointOptions.valueDecimals || + tooltipOptions.valueDecimals || + -1, lang.decimalPoint, lang.accessibility.thousandsSep || lang.thousandsSep); + } + return value; + } + /** + * @private + */ + function getSeriesDescriptionText(series) { + var seriesA11yOptions = series.options.accessibility || {}, + descOpt = seriesA11yOptions.description; + return descOpt && series.chart.langFormat('accessibility.series.description', { + description: descOpt, + series: series + }) || ''; + } + /** + * @private + */ + 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 getPointA11yTimeDescription(point) { + var series = point.series, + chart = series.chart, + seriesA11yOptions = series.options.accessibility && + series.options.accessibility.point || {}, + a11yOptions = chart.options.accessibility.point || {}, + dateXAxis = series.xAxis && series.xAxis.dateTime; + if (dateXAxis) { + var tooltipDateFormat = dateXAxis.getXDateFormat(point.x || 0, + chart.options.tooltip.dateTimeLabelFormats), + dateFormat = seriesA11yOptions.dateFormatter && + seriesA11yOptions.dateFormatter(point) || + a11yOptions.dateFormatter && a11yOptions.dateFormatter(point) || + seriesA11yOptions.dateFormat || + a11yOptions.dateFormat || + tooltipDateFormat; + return chart.time.dateFormat(dateFormat, point.x || 0, void 0); + } + } + /** + * @private + */ + 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 + */ + 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 + */ + function getPointValue(point) { + var series = point.series, + a11yPointOpts = series.chart.options.accessibility.point || {}, + seriesA11yPointOpts = series.chart.options.accessibility && + series.chart.options.accessibility.point || {}, + tooltipOptions = series.tooltipOptions || {}, + valuePrefix = seriesA11yPointOpts.valuePrefix || + a11yPointOpts.valuePrefix || + tooltipOptions.valuePrefix || + '', + valueSuffix = seriesA11yPointOpts.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 + */ + function getPointValueDescription(point) { + var series = point.series, chart = series.chart, seriesA11yOptions = series.options.accessibility, seriesValueDescFormat = seriesA11yOptions && seriesA11yOptions.point && + seriesA11yOptions.point.valueDescriptionFormat, pointValueDescriptionFormat = seriesValueDescFormat || + 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 + */ + function defaultPointDescriptionFormatter(point) { + var series = point.series, shouldExposeSeriesName = series.chart.series.length > 1 || + series.options.name, valText = getPointValueDescription(point), description = point.options && point.options.accessibility && + point.options.accessibility.description, userDescText = description ? ' ' + description : '', seriesNameText = shouldExposeSeriesName ? ' ' + 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 || {}, + seriesPointA11yOptions = series.options.accessibility && + series.options.accessibility.point || {}, + label = stripHTMLTags(seriesPointA11yOptions.descriptionFormatter && + seriesPointA11yOptions.descriptionFormatter(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), + shouldDescribeNullPoints = series.chart.options.accessibility + .point.describeNull; + if (setScreenReaderProps || setKeyboardProps) { + series.points.forEach(function (point) { + var pointEl = point.graphic && point.graphic.element || + shouldAddDummyPoint(point) && addDummyPointElement(point), + pointA11yDisabled = (point.options && + point.options.accessibility && + point.options.accessibility.enabled === false); + if (pointEl) { + if (point.isNull && !shouldDescribeNullPoints) { + pointEl.setAttribute('aria-hidden', true); + return; + } + // 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'); + if (!series.chart.styledMode) { + pointEl.style.outline = 'none'; + } + if (setScreenReaderProps && !pointA11yDisabled) { + setPointScreenReaderAttribs(point, pointEl); + } + else { + pointEl.setAttribute('aria-hidden', true); + } + } + }); + } + } + /** + * Return string with information about series. + * @private + */ + 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]; + }, seriesNumber = series.index + 1, xAxisInfo = getSeriesAxisDescriptionText(series, 'xAxis'), yAxisInfo = getSeriesAxisDescriptionText(series, 'yAxis'), summaryContext = { + seriesNumber: seriesNumber, + series: series, + chart: chart + }, combinationSuffix = chartTypes.length > 1 ? 'Combination' : '', summary = chart.langFormat('accessibility.series.summary.' + series.type + combinationSuffix, summaryContext) || chart.langFormat('accessibility.series.summary.default' + combinationSuffix, summaryContext), axisDescription = (shouldDescribeAxis('yAxis') ? ' ' + yAxisInfo + '.' : '') + (shouldDescribeAxis('xAxis') ? ' ' + xAxisInfo + '.' : ''), formatStr = chart.options.accessibility.series.descriptionFormat || ''; + return format(formatStr, { + seriesDescription: summary, + authorDescription: (description ? ' ' + description : ''), + axisDescription: axisDescription, + series: series, + chart: chart, + seriesNumber: seriesNumber + }, void 0); + } + /** + * 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 { + seriesElement.setAttribute('role', 'group'); + } + seriesElement.setAttribute('tabindex', '-1'); + if (!series.chart.styledMode) { + // Don't show browser outline on click, despite tabindex + seriesElement.style.outline = 'none'; + } + seriesElement.setAttribute('aria-label', 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.removeAttribute('aria-label'); + } + } + } + /* * + * + * Default Export + * + * */ + var SeriesDescriber = { + defaultPointDescriptionFormatter: defaultPointDescriptionFormatter, + defaultSeriesDescriptionFormatter: defaultSeriesDescriptionFormatter, + describeSeries: describeSeries + }; + + return SeriesDescriber; + }); + _registerModule(_modules, 'Accessibility/Components/SeriesComponent/NewDataAnnouncer.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/Utils/Announcer.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Accessibility/Components/SeriesComponent/SeriesDescriber.js']], function (H, U, Announcer, ChartUtilities, EventProvider, SeriesDescriber) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Handle announcing new data for a chart. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent, + defined = U.defined; + var getChartTitle = ChartUtilities.getChartTitle; + var defaultPointDescriptionFormatter = SeriesDescriber.defaultPointDescriptionFormatter, + defaultSeriesDescriptionFormatter = SeriesDescriber.defaultSeriesDescriptionFormatter; + /* * + * + * Functions + * + * */ + /* eslint-disable 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]; }); + } + /* * + * + * Class + * + * */ + /** + * @private + * @class + */ + var NewDataAnnouncer = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function NewDataAnnouncer(chart) { + /* * + * + * Public + * + * */ + this.announcer = void 0; + this.dirty = { + allSeries: {} + }; + this.eventProvider = void 0; + this.lastAnnouncementTime = 0; + this.chart = chart; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Initialize the new data announcer. + * @private + */ + NewDataAnnouncer.prototype.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 + */ + NewDataAnnouncer.prototype.destroy = function () { + this.eventProvider.removeAddedEvents(); + this.announcer.destroy(); + }; + /** + * Add event listeners for the announcer + * @private + */ + NewDataAnnouncer.prototype.addEventListeners = function () { + var announcer = this, + chart = this.chart, + e = this.eventProvider; + e.addEvent(chart, 'afterApplyDrilldown', function () { + announcer.lastAnnouncementTime = 0; + }); + e.addEvent(chart, 'afterAddSeries', function (e) { + announcer.onSeriesAdded(e.series); + }); + e.addEvent(chart, 'redraw', function () { + announcer.announceDirtyData(); + }); + }; + /** + * On new data series added, update dirty list. + * @private + * @param {Highcharts.Series} series + */ + NewDataAnnouncer.prototype.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; + } + }; + /** + * Gather what we know and announce the data to user. + * @private + */ + NewDataAnnouncer.prototype.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. + */ + NewDataAnnouncer.prototype.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. + */ + NewDataAnnouncer.prototype.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; + }()); + /* * + * + * Class Namespace + * + * */ + (function (NewDataAnnouncer) { + /* * + * + * Declarations + * + * */ + /* * + * + * Static Properties + * + * */ + NewDataAnnouncer.composedClasses = []; + /* * + * + * Static Functions + * + * */ + /** + * @private + */ + function compose(SeriesClass) { + if (NewDataAnnouncer.composedClasses.indexOf(SeriesClass) === -1) { + NewDataAnnouncer.composedClasses.push(SeriesClass); + addEvent(SeriesClass, 'addPoint', seriesOnAddPoint); + addEvent(SeriesClass, 'updatedData', seriesOnUpdatedData); + } + } + NewDataAnnouncer.compose = compose; + /** + * On new point added, update dirty list. + * @private + * @param {Highcharts.Point} point + */ + function seriesOnAddPoint(e) { + var chart = this.chart, + newDataAnnouncer = this.newDataAnnouncer; + if (newDataAnnouncer && + newDataAnnouncer.chart === chart && + chartHasAnnounceEnabled(chart)) { + // Add it to newPoint storage unless we already have one + newDataAnnouncer.dirty.newPoint = (defined(newDataAnnouncer.dirty.newPoint) ? + void 0 : + e.point); + } + } + /** + * On new data in the series, make sure we add it to the dirty list. + * @private + * @param {Highcharts.Series} series + */ + function seriesOnUpdatedData() { + var chart = this.chart, + newDataAnnouncer = this.newDataAnnouncer; + if (newDataAnnouncer && + newDataAnnouncer.chart === chart && + chartHasAnnounceEnabled(chart)) { + newDataAnnouncer.dirty.hasDirty = true; + newDataAnnouncer.dirty.allSeries[this.name + this.index] = this; + } + } + })(NewDataAnnouncer || (NewDataAnnouncer = {})); + /* * + * + * Default Export + * + * */ + + return NewDataAnnouncer; + }); + _registerModule(_modules, 'Accessibility/ProxyElement.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/HTMLUtilities.js']], function (H, U, EventProvider, ChartUtilities, HTMLUtilities) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Proxy elements are used to shadow SVG elements in HTML for assistive + * technology, such as screen readers or voice input software. + * + * The ProxyElement class represents such an element, and deals with + * overlay positioning and mirroring events for the target. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc; + var attr = U.attr, + css = U.css, + merge = U.merge; + var fireEventOnWrappedOrUnwrappedElement = ChartUtilities.fireEventOnWrappedOrUnwrappedElement; + var cloneMouseEvent = HTMLUtilities.cloneMouseEvent, + cloneTouchEvent = HTMLUtilities.cloneTouchEvent, + getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent, + removeElement = HTMLUtilities.removeElement; + /* * + * + * Class + * + * */ + /** + * Represents a proxy element that overlays a target and relays events + * to its target. + * + * @private + * @class + */ + var ProxyElement = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function ProxyElement(chart, target, groupType, attributes) { + this.chart = chart; + this.target = target; + this.groupType = groupType; + var isListItem = groupType === 'ul'; + this.eventProvider = new EventProvider(); + var wrapperEl = isListItem ? doc.createElement('li') : null; + var btnEl = this.buttonElement = doc.createElement('button'); + if (!chart.styledMode) { + this.hideButtonVisually(btnEl); + } + if (wrapperEl) { + if (isListItem && !chart.styledMode) { + wrapperEl.style.listStyle = 'none'; + } + wrapperEl.appendChild(btnEl); + this.element = wrapperEl; + } + else { + this.element = btnEl; + } + this.updateTarget(target, attributes); + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Fake a click event on the target. + */ + ProxyElement.prototype.click = function () { + var pos = this.getTargetPosition(); + pos.x += pos.width / 2; + pos.y += pos.height / 2; + var fakeEventObject = getFakeMouseEvent('click', + pos); + fireEventOnWrappedOrUnwrappedElement(this.target.click, fakeEventObject); + }; + /** + * Update the target to be proxied. The position and events are updated to + * match the new target. + * @param target The new target definition + * @param attributes New HTML attributes to apply to the button. Set an + * attribute to null to remove. + */ + ProxyElement.prototype.updateTarget = function (target, attributes) { + this.target = target; + this.updateCSSClassName(); + var attrs = attributes || {}; + Object.keys(attrs).forEach(function (a) { + if (attrs[a] === null) { + delete attrs[a]; + } + }); + attr(this.buttonElement, merge({ + 'aria-label': this.getTargetAttr(target.click, 'aria-label') + }, attrs)); + this.eventProvider.removeAddedEvents(); + this.addProxyEventsToButton(this.buttonElement, target.click); + this.refreshPosition(); + }; + /** + * Refresh the position of the proxy element to match the current target + */ + ProxyElement.prototype.refreshPosition = function () { + var bBox = this.getTargetPosition(); + css(this.buttonElement, { + width: (bBox.width || 1) + 'px', + height: (bBox.height || 1) + 'px', + left: (Math.round(bBox.x) || 0) + 'px', + top: (Math.round(bBox.y) || 0) + 'px' + }); + }; + /** + * Remove button from DOM, and clear events. + */ + ProxyElement.prototype.remove = function () { + this.eventProvider.removeAddedEvents(); + removeElement(this.element); + }; + // -------------------------- private ------------------------------------ + /** + * Update the CSS class name to match target + */ + ProxyElement.prototype.updateCSSClassName = function () { + var stringHasNoTooltip = function (s) { return (s.indexOf('highcharts-no-tooltip') > -1); }; + var legend = this.chart.legend; + var groupDiv = legend.group && legend.group.div; + var noTooltipOnGroup = stringHasNoTooltip(groupDiv && groupDiv.className || ''); + var targetClassName = this.getTargetAttr(this.target.click, 'class') || ''; + var noTooltipOnTarget = stringHasNoTooltip(targetClassName); + this.buttonElement.className = noTooltipOnGroup || noTooltipOnTarget ? + 'highcharts-a11y-proxy-button highcharts-no-tooltip' : + 'highcharts-a11y-proxy-button'; + }; + /** + * Mirror events for a proxy button to a target + */ + ProxyElement.prototype.addProxyEventsToButton = function (button, target) { + var _this = this; + [ + 'click', 'touchstart', 'touchend', 'touchcancel', 'touchmove', + 'mouseover', 'mouseenter', 'mouseleave', 'mouseout' + ].forEach(function (evtType) { + var isTouchEvent = evtType.indexOf('touch') === 0; + _this.eventProvider.addEvent(button, evtType, function (e) { + var clonedEvent = isTouchEvent ? + cloneTouchEvent(e) : + cloneMouseEvent(e); + if (target) { + fireEventOnWrappedOrUnwrappedElement(target, clonedEvent); + } + e.stopPropagation(); + // #9682, #15318: Touch scrolling didnt work when touching + // proxy + if (!isTouchEvent) { + e.preventDefault(); + } + }, { passive: false }); + }); + }; + /** + * Set visually hidden style on a proxy button + */ + ProxyElement.prototype.hideButtonVisually = function (button) { + css(button, { + borderWidth: 0, + backgroundColor: 'transparent', + cursor: 'pointer', + outline: 'none', + opacity: 0.001, + filter: 'alpha(opacity=1)', + zIndex: 999, + overflow: 'hidden', + padding: 0, + margin: 0, + display: 'block', + position: 'absolute', + '-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)' + }); + }; + /** + * Get the position relative to chart container for the target + */ + ProxyElement.prototype.getTargetPosition = function () { + var clickTarget = this.target.click; + // We accept both DOM elements and wrapped elements as click targets. + var clickTargetElement = clickTarget.element ? + clickTarget.element : + clickTarget; + var posElement = this.target.visual || clickTargetElement; + var chartDiv = this.chart.renderTo; + if (chartDiv && posElement && posElement.getBoundingClientRect) { + var rectEl = posElement.getBoundingClientRect(), + chartPos = this.chart.pointer.getChartPosition(); + return { + x: (rectEl.left - chartPos.left) / chartPos.scaleX, + y: (rectEl.top - chartPos.top) / chartPos.scaleY, + width: rectEl.right / chartPos.scaleX - + rectEl.left / chartPos.scaleX, + height: rectEl.bottom / chartPos.scaleY - + rectEl.top / chartPos.scaleY + }; + } + return { x: 0, y: 0, width: 1, height: 1 }; + }; + /** + * Get an attribute value of a target + */ + ProxyElement.prototype.getTargetAttr = function (target, key) { + if (target.element) { + return target.element.getAttribute(key); + } + return target.getAttribute(key); + }; + return ProxyElement; + }()); + /* * + * + * Default Export + * + * */ + + return ProxyElement; + }); + _registerModule(_modules, 'Accessibility/ProxyProvider.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/DOMElementProvider.js'], _modules['Accessibility/Utils/HTMLUtilities.js'], _modules['Accessibility/ProxyElement.js']], function (H, U, CU, DOMElementProvider, HU, ProxyElement) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Proxy elements are used to shadow SVG elements in HTML for assistive + * technology, such as screen readers or voice input software. + * + * The ProxyProvider keeps track of all proxy elements of the a11y module, + * and updating their order and positioning. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc; + var attr = U.attr, + css = U.css; + var unhideChartElementFromAT = CU.unhideChartElementFromAT; + var removeElement = HU.removeElement, + removeChildNodes = HU.removeChildNodes; + /* * + * + * Class + * + * */ + /** + * Keeps track of all proxy elements and proxy groups. + * + * @private + * @class + */ + var ProxyProvider = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function ProxyProvider(chart) { + this.chart = chart; + this.domElementProvider = new DOMElementProvider(); + this.groups = {}; + this.groupOrder = []; + this.beforeChartProxyPosContainer = this.createProxyPosContainer('before'); + this.afterChartProxyPosContainer = this.createProxyPosContainer('after'); + this.update(); + } + /* * + * + * Functions + * + * */ + /* eslint-disable */ + /** + * Add a new proxy element to a group, proxying a target control. + */ + ProxyProvider.prototype.addProxyElement = function (groupKey, target, attributes) { + var group = this.groups[groupKey]; + if (!group) { + throw new Error('ProxyProvider.addProxyElement: Invalid group key ' + groupKey); + } + var proxy = new ProxyElement(this.chart, + target, + group.type, + attributes); + group.proxyContainerElement.appendChild(proxy.element); + group.proxyElements.push(proxy); + return proxy; + }; + /** + * Create a group that will contain proxy elements. The group order is + * automatically updated according to the last group order keys. + * + * Returns the added group. + */ + ProxyProvider.prototype.addGroup = function (groupKey, groupType, attributes) { + var existingGroup = this.groups[groupKey]; + if (existingGroup) { + return existingGroup.groupElement; + } + var proxyContainer = this.domElementProvider.createElement(groupType); + // If we want to add a role to the group, and still use e.g. + // a list group, we need a wrapper div. + var groupElement; + if (attributes && attributes.role && groupType !== 'div') { + groupElement = this.domElementProvider.createElement('div'); + groupElement.appendChild(proxyContainer); + } + else { + groupElement = proxyContainer; + } + groupElement.className = 'highcharts-a11y-proxy-group highcharts-a11y-proxy-group-' + + groupKey.replace(/\W/g, '-'); + this.groups[groupKey] = { + proxyContainerElement: proxyContainer, + groupElement: groupElement, + type: groupType, + proxyElements: [] + }; + attr(groupElement, attributes || {}); + if (groupType === 'ul') { + proxyContainer.setAttribute('role', 'list'); // Needed for webkit + } + // Add the group to the end by default, and perhaps then we + // won't have to reorder the whole set of groups. + this.afterChartProxyPosContainer.appendChild(groupElement); + this.updateGroupOrder(this.groupOrder); + return groupElement; + }; + /** + * Update HTML attributes of a group. + */ + ProxyProvider.prototype.updateGroupAttrs = function (groupKey, attributes) { + var group = this.groups[groupKey]; + if (!group) { + throw new Error('ProxyProvider.updateGroupAttrs: Invalid group key ' + groupKey); + } + attr(group.groupElement, attributes); + }; + /** + * Reorder the proxy groups. + * + * The group key "series" refers to the chart's data points / element. + * This is so that the keyboardNavigation.order option can be used to + * determine the proxy group order. + */ + ProxyProvider.prototype.updateGroupOrder = function (groupKeys) { + var _this = this; + // Store so that we can update order when a new group is created + this.groupOrder = groupKeys.slice(); + // Don't unnecessarily reorder, because keyboard focus is lost + if (this.isDOMOrderGroupOrder()) { + return; + } + var seriesIx = groupKeys.indexOf('series'); + var beforeKeys = seriesIx > -1 ? groupKeys.slice(0, + seriesIx) : groupKeys; + var afterKeys = seriesIx > -1 ? groupKeys.slice(seriesIx + 1) : []; + // Store focused element since it will be lost when reordering + var activeElement = doc.activeElement; + // Add groups to correct container + ['before', 'after'].forEach(function (pos) { + var posContainer = _this[pos === 'before' ? + 'beforeChartProxyPosContainer' : + 'afterChartProxyPosContainer']; + var keys = pos === 'before' ? beforeKeys : afterKeys; + removeChildNodes(posContainer); + keys.forEach(function (groupKey) { + var group = _this.groups[groupKey]; + if (group) { + posContainer.appendChild(group.groupElement); + } + }); + }); + // Attempt to restore focus after reordering, but note that this may + // cause screen readers re-announcing the button. + if ((this.beforeChartProxyPosContainer.contains(activeElement) || + this.afterChartProxyPosContainer.contains(activeElement)) && + activeElement && activeElement.focus) { + activeElement.focus(); + } + }; + /** + * Remove all proxy elements in a group + */ + ProxyProvider.prototype.clearGroup = function (groupKey) { + var group = this.groups[groupKey]; + if (!group) { + throw new Error('ProxyProvider.clearGroup: Invalid group key ' + groupKey); + } + removeChildNodes(group.proxyContainerElement); + }; + /** + * Remove a group from the DOM and from the proxy provider's group list. + * All child elements are removed. + * If the group does not exist, nothing happens. + */ + ProxyProvider.prototype.removeGroup = function (groupKey) { + var group = this.groups[groupKey]; + if (group) { + removeElement(group.groupElement); + delete this.groups[groupKey]; + } + }; + /** + * Update the position and order of all proxy groups and elements + */ + ProxyProvider.prototype.update = function () { + this.updatePosContainerPositions(); + this.updateGroupOrder(this.groupOrder); + this.updateProxyElementPositions(); + }; + /** + * Update all proxy element positions + */ + ProxyProvider.prototype.updateProxyElementPositions = function () { + Object.keys(this.groups).forEach(this.updateGroupProxyElementPositions.bind(this)); + }; + /** + * Update a group's proxy elements' positions. + * If the group does not exist, nothing happens. + */ + ProxyProvider.prototype.updateGroupProxyElementPositions = function (groupKey) { + var group = this.groups[groupKey]; + if (group) { + group.proxyElements.forEach(function (el) { return el.refreshPosition(); }); + } + }; + /** + * Remove all added elements + */ + ProxyProvider.prototype.destroy = function () { + this.domElementProvider.destroyCreatedElements(); + }; + // -------------------------- private ------------------------------------ + /** + * Create and return a pos container element (the overall containers for + * the proxy groups). + */ + ProxyProvider.prototype.createProxyPosContainer = function (classNamePostfix) { + var el = this.domElementProvider.createElement('div'); + el.setAttribute('aria-hidden', 'false'); + el.className = 'highcharts-a11y-proxy-container' + (classNamePostfix ? '-' + classNamePostfix : ''); + css(el, { + top: '0', + left: '0' + }); + if (!this.chart.styledMode) { + el.style.whiteSpace = 'nowrap'; + el.style.position = 'absolute'; + } + return el; + }; + /** + * Get an array of group keys that corresponds to the current group order + * in the DOM. + */ + ProxyProvider.prototype.getCurrentGroupOrderInDOM = function () { + var _this = this; + var getGroupKeyFromElement = function (el) { + var allGroups = Object.keys(_this.groups); + var i = allGroups.length; + while (i--) { + var groupKey = allGroups[i]; + var group = _this.groups[groupKey]; + if (group && el === group.groupElement) { + return groupKey; + } + } + }; + var getChildrenGroupOrder = function (el) { + var childrenOrder = []; + var children = el.children; + for (var i = 0; i < children.length; ++i) { + var groupKey = getGroupKeyFromElement(children[i]); + if (groupKey) { + childrenOrder.push(groupKey); + } + } + return childrenOrder; + }; + var before = getChildrenGroupOrder(this.beforeChartProxyPosContainer); + var after = getChildrenGroupOrder(this.afterChartProxyPosContainer); + before.push('series'); + return before.concat(after); + }; + /** + * Check if the current DOM order matches the current group order, so that + * a reordering/update is unnecessary. + */ + ProxyProvider.prototype.isDOMOrderGroupOrder = function () { + var _this = this; + var domOrder = this.getCurrentGroupOrderInDOM(); + var groupOrderWithGroups = this.groupOrder.filter(function (x) { return x === 'series' || !!_this.groups[x]; }); + var i = domOrder.length; + if (i !== groupOrderWithGroups.length) { + return false; + } + while (i--) { + if (domOrder[i] !== groupOrderWithGroups[i]) { + return false; + } + } + return true; + }; + /** + * Update the DOM positions of the before/after proxy + * positioning containers for the groups. + */ + ProxyProvider.prototype.updatePosContainerPositions = function () { + var chart = this.chart; + // If exporting, don't add these containers to the DOM. + if (chart.renderer.forExport) { + return; + } + var rendererSVGEl = chart.renderer.box; + chart.container.insertBefore(this.afterChartProxyPosContainer, rendererSVGEl.nextSibling); + chart.container.insertBefore(this.beforeChartProxyPosContainer, rendererSVGEl); + unhideChartElementFromAT(this.chart, this.afterChartProxyPosContainer); + unhideChartElementFromAT(this.chart, this.beforeChartProxyPosContainer); + }; + return ProxyProvider; + }()); + /* * + * + * Export Default + * + * */ + + return ProxyProvider; + }); + _registerModule(_modules, 'Extensions/RangeSelector.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, D, SVGElement, U) { + /* * + * + * (c) 2010-2021 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var defaultOptions = D.defaultOptions; + var addEvent = U.addEvent, + createElement = U.createElement, + css = U.css, + defined = U.defined, + destroyObjectProperties = U.destroyObjectProperties, + discardElement = U.discardElement, + extend = U.extend, + find = U.find, + fireEvent = U.fireEvent, + isNumber = U.isNumber, + merge = U.merge, + objectEach = U.objectEach, + pad = U.pad, + pick = U.pick, + pInt = U.pInt, + splat = U.splat; + /** + * Define the time span for the button + * + * @typedef {"all"|"day"|"hour"|"millisecond"|"minute"|"month"|"second"|"week"|"year"|"ytd"} Highcharts.RangeSelectorButtonTypeValue + */ + /** + * Callback function to react on button clicks. + * + * @callback Highcharts.RangeSelectorClickCallbackFunction + * + * @param {global.Event} e + * Event arguments. + * + * @param {boolean|undefined} + * Return false to cancel the default button event. + */ + /** + * Callback function to parse values entered in the input boxes and return a + * valid JavaScript time as milliseconds since 1970. + * + * @callback Highcharts.RangeSelectorParseCallbackFunction + * + * @param {string} value + * Input value to parse. + * + * @return {number} + * Parsed JavaScript time value. + */ + /* ************************************************************************** * + * Start Range Selector code * + * ************************************************************************** */ + extend(defaultOptions, { + /** + * The range selector is a tool for selecting ranges to display within + * the chart. It provides buttons to select preconfigured ranges in + * the chart, like 1 day, 1 week, 1 month etc. It also provides input + * boxes where min and max dates can be manually input. + * + * @product highstock gantt + * @optionparent rangeSelector + */ + rangeSelector: { + /** + * Whether to enable all buttons from the start. By default buttons are + * only enabled if the corresponding time range exists on the X axis, + * but enabling all buttons allows for dynamically loading different + * time ranges. + * + * @sample {highstock} stock/rangeselector/allbuttonsenabled-true/ + * All buttons enabled + * + * @since 2.0.3 + */ + allButtonsEnabled: false, + /** + * An array of configuration objects for the buttons. + * + * Defaults to: + * ```js + * buttons: [{ + * type: 'month', + * count: 1, + * text: '1m', + * title: 'View 1 month' + * }, { + * type: 'month', + * count: 3, + * text: '3m', + * title: 'View 3 months' + * }, { + * type: 'month', + * count: 6, + * text: '6m', + * title: 'View 6 months' + * }, { + * type: 'ytd', + * text: 'YTD', + * title: 'View year to date' + * }, { + * type: 'year', + * count: 1, + * text: '1y', + * title: 'View 1 year' + * }, { + * type: 'all', + * text: 'All', + * title: 'View all' + * }] + * ``` + * + * @sample {highstock} stock/rangeselector/datagrouping/ + * Data grouping by buttons + * + * @type {Array<*>} + */ + buttons: void 0, + /** + * How many units of the defined type the button should span. If `type` + * is "month" and `count` is 3, the button spans three months. + * + * @type {number} + * @default 1 + * @apioption rangeSelector.buttons.count + */ + /** + * Fires when clicking on the rangeSelector button. One parameter, + * event, is passed to the function, containing common event + * information. + * + * ```js + * click: function(e) { + * console.log(this); + * } + * ``` + * + * Return false to stop default button's click action. + * + * @sample {highstock} stock/rangeselector/button-click/ + * Click event on the button + * + * @type {Highcharts.RangeSelectorClickCallbackFunction} + * @apioption rangeSelector.buttons.events.click + */ + /** + * Additional range (in milliseconds) added to the end of the calculated + * time span. + * + * @sample {highstock} stock/rangeselector/min-max-offsets/ + * Button offsets + * + * @type {number} + * @default 0 + * @since 6.0.0 + * @apioption rangeSelector.buttons.offsetMax + */ + /** + * Additional range (in milliseconds) added to the start of the + * calculated time span. + * + * @sample {highstock} stock/rangeselector/min-max-offsets/ + * Button offsets + * + * @type {number} + * @default 0 + * @since 6.0.0 + * @apioption rangeSelector.buttons.offsetMin + */ + /** + * When buttons apply dataGrouping on a series, by default zooming + * in/out will deselect buttons and unset dataGrouping. Enable this + * option to keep buttons selected when extremes change. + * + * @sample {highstock} stock/rangeselector/preserve-datagrouping/ + * Different preserveDataGrouping settings + * + * @type {boolean} + * @default false + * @since 6.1.2 + * @apioption rangeSelector.buttons.preserveDataGrouping + */ + /** + * A custom data grouping object for each button. + * + * @see [series.dataGrouping](#plotOptions.series.dataGrouping) + * + * @sample {highstock} stock/rangeselector/datagrouping/ + * Data grouping by range selector buttons + * + * @type {*} + * @extends plotOptions.series.dataGrouping + * @apioption rangeSelector.buttons.dataGrouping + */ + /** + * The text for the button itself. + * + * @type {string} + * @apioption rangeSelector.buttons.text + */ + /** + * Explanation for the button, shown as a tooltip on hover, and used by + * assistive technology. + * + * @type {string} + * @apioption rangeSelector.buttons.title + */ + /** + * Defined the time span for the button. Can be one of `millisecond`, + * `second`, `minute`, `hour`, `day`, `week`, `month`, `year`, `ytd`, + * and `all`. + * + * @type {Highcharts.RangeSelectorButtonTypeValue} + * @apioption rangeSelector.buttons.type + */ + /** + * The space in pixels between the buttons in the range selector. + */ + buttonSpacing: 5, + /** + * Whether to collapse the range selector buttons into a dropdown when + * there is not enough room to show everything in a single row, instead + * of dividing the range selector into multiple rows. + * Can be one of the following: + * - `always`: Always collapse + * - `responsive`: Only collapse when there is not enough room + * - `never`: Never collapse + * + * @sample {highstock} stock/rangeselector/dropdown/ + * Dropdown option + * + * @validvalue ["always", "responsive", "never"] + * @since 9.0.0 + */ + dropdown: 'responsive', + /** + * Enable or disable the range selector. Default to `true` for stock + * charts, using the `stockChart` factory. + * + * @sample {highstock} stock/rangeselector/enabled/ + * Disable the range selector + * + * @type {boolean|undefined} + * @default {highstock} true + */ + enabled: void 0, + /** + * The vertical alignment of the rangeselector box. Allowed properties + * are `top`, `middle`, `bottom`. + * + * @sample {highstock} stock/rangeselector/vertical-align-middle/ + * Middle + * @sample {highstock} stock/rangeselector/vertical-align-bottom/ + * Bottom + * + * @type {Highcharts.VerticalAlignValue} + * @since 6.0.0 + */ + verticalAlign: 'top', + /** + * A collection of attributes for the buttons. The object takes SVG + * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`, + * a collection of CSS properties for the text. + * + * The object can also be extended with states, so you can set + * presentational options for `hover`, `select` or `disabled` button + * states. + * + * CSS styles for the text label. + * + * In styled mode, the buttons are styled by the + * `.highcharts-range-selector-buttons .highcharts-button` rule with its + * different states. + * + * @sample {highstock} stock/rangeselector/styling/ + * Styling the buttons and inputs + * + * @type {Highcharts.SVGAttributes} + */ + buttonTheme: { + /** @ignore */ + width: 28, + /** @ignore */ + height: 18, + /** @ignore */ + padding: 2, + /** @ignore */ + zIndex: 7 // #484, #852 + }, + /** + * When the rangeselector is floating, the plot area does not reserve + * space for it. This opens for positioning anywhere on the chart. + * + * @sample {highstock} stock/rangeselector/floating/ + * Placing the range selector between the plot area and the + * navigator + * + * @since 6.0.0 + */ + floating: false, + /** + * The x offset of the range selector relative to its horizontal + * alignment within `chart.spacingLeft` and `chart.spacingRight`. + * + * @since 6.0.0 + */ + x: 0, + /** + * The y offset of the range selector relative to its horizontal + * alignment within `chart.spacingLeft` and `chart.spacingRight`. + * + * @since 6.0.0 + */ + y: 0, + /** + * Deprecated. The height of the range selector. Currently it is + * calculated dynamically. + * + * @deprecated + * @type {number|undefined} + * @since 2.1.9 + */ + height: void 0, + /** + * The border color of the date input boxes. + * + * @sample {highstock} stock/rangeselector/styling/ + * Styling the buttons and inputs + * + * @type {Highcharts.ColorString} + * @since 1.3.7 + */ + inputBoxBorderColor: 'none', + /** + * The pixel height of the date input boxes. + * + * @sample {highstock} stock/rangeselector/styling/ + * Styling the buttons and inputs + * + * @since 1.3.7 + */ + inputBoxHeight: 17, + /** + * The pixel width of the date input boxes. When `undefined`, the width + * is fitted to the rendered content. + * + * @sample {highstock} stock/rangeselector/styling/ + * Styling the buttons and inputs + * + * @type {number|undefined} + * @since 1.3.7 + */ + inputBoxWidth: void 0, + /** + * The date format in the input boxes when not selected for editing. + * Defaults to `%b %e, %Y`. + * + * This is used to determine which type of input to show, + * `datetime-local`, `date` or `time` and falling back to `text` when + * the browser does not support the input type or the format contains + * milliseconds. + * + * @sample {highstock} stock/rangeselector/input-type/ + * Input types + * @sample {highstock} stock/rangeselector/input-format/ + * Milliseconds in the range selector + * + */ + inputDateFormat: '%b %e, %Y', + /** + * A custom callback function to parse values entered in the input boxes + * and return a valid JavaScript time as milliseconds since 1970. + * The first argument passed is a value to parse, + * second is a boolean indicating use of the UTC time. + * + * This will only get called for inputs of type `text`. Since v8.2.3, + * the input type is dynamically determined based on the granularity + * of the `inputDateFormat` and the browser support. + * + * @sample {highstock} stock/rangeselector/input-format/ + * Milliseconds in the range selector + * + * @type {Highcharts.RangeSelectorParseCallbackFunction} + * @since 1.3.3 + */ + inputDateParser: void 0, + /** + * The date format in the input boxes when they are selected for + * editing. This must be a format that is recognized by JavaScript + * Date.parse. + * + * This will only be used for inputs of type `text`. Since v8.2.3, + * the input type is dynamically determined based on the granularity + * of the `inputDateFormat` and the browser support. + * + * @sample {highstock} stock/rangeselector/input-format/ + * Milliseconds in the range selector + * + */ + inputEditDateFormat: '%Y-%m-%d', + /** + * Enable or disable the date input boxes. + */ + inputEnabled: true, + /** + * Positioning for the input boxes. Allowed properties are `align`, + * `x` and `y`. + * + * @since 1.2.4 + */ + inputPosition: { + /** + * The alignment of the input box. Allowed properties are `left`, + * `center`, `right`. + * + * @sample {highstock} stock/rangeselector/input-button-position/ + * Alignment + * + * @type {Highcharts.AlignValue} + * @since 6.0.0 + */ + align: 'right', + /** + * X offset of the input row. + */ + x: 0, + /** + * Y offset of the input row. + */ + y: 0 + }, + /** + * The space in pixels between the labels and the date input boxes in + * the range selector. + * + * @since 9.0.0 + */ + inputSpacing: 5, + /** + * The index of the button to appear pre-selected. + * + * @type {number} + */ + selected: void 0, + /** + * Positioning for the button row. + * + * @since 1.2.4 + */ + buttonPosition: { + /** + * The alignment of the input box. Allowed properties are `left`, + * `center`, `right`. + * + * @sample {highstock} stock/rangeselector/input-button-position/ + * Alignment + * + * @type {Highcharts.AlignValue} + * @since 6.0.0 + */ + align: 'left', + /** + * X offset of the button row. + */ + x: 0, + /** + * Y offset of the button row. + */ + y: 0 + }, + /** + * CSS for the HTML inputs in the range selector. + * + * In styled mode, the inputs are styled by the + * `.highcharts-range-input text` rule in SVG mode, and + * `input.highcharts-range-selector` when active. + * + * @sample {highstock} stock/rangeselector/styling/ + * Styling the buttons and inputs + * + * @type {Highcharts.CSSObject} + * @apioption rangeSelector.inputStyle + */ + inputStyle: { + /** @ignore */ + color: "#335cad" /* Palette.highlightColor80 */, + /** @ignore */ + cursor: 'pointer' + }, + /** + * CSS styles for the labels - the Zoom, From and To texts. + * + * In styled mode, the labels are styled by the + * `.highcharts-range-label` class. + * + * @sample {highstock} stock/rangeselector/styling/ + * Styling the buttons and inputs + * + * @type {Highcharts.CSSObject} + */ + labelStyle: { + /** @ignore */ + color: "#666666" /* Palette.neutralColor60 */ + } + } + }); + extend(defaultOptions.lang, + /** + * Language object. The language object is global and it can't be set + * on each chart initialization. Instead, use `Highcharts.setOptions` to + * set it before any chart is initialized. + * + * ```js + * Highcharts.setOptions({ + * lang: { + * months: [ + * 'Janvier', 'Février', 'Mars', 'Avril', + * 'Mai', 'Juin', 'Juillet', 'Août', + * 'Septembre', 'Octobre', 'Novembre', 'Décembre' + * ], + * weekdays: [ + * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi', + * 'Jeudi', 'Vendredi', 'Samedi' + * ] + * } + * }); + * ``` + * + * @optionparent lang + */ + { + /** + * The text for the label for the range selector buttons. + * + * @product highstock gantt + */ + rangeSelectorZoom: 'Zoom', + /** + * The text for the label for the "from" input box in the range + * selector. Since v9.0, this string is empty as the label is not + * rendered by default. + * + * @product highstock gantt + */ + rangeSelectorFrom: '', + /** + * The text for the label for the "to" input box in the range selector. + * + * @product highstock gantt + */ + rangeSelectorTo: '→' + }); + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * The range selector. + * + * @private + * @class + * @name Highcharts.RangeSelector + * @param {Highcharts.Chart} chart + */ + var RangeSelector = /** @class */ (function () { + function RangeSelector(chart) { + /* * + * + * Properties + * + * */ + this.buttons = void 0; + this.buttonOptions = RangeSelector.prototype.defaultButtons; + this.initialButtonGroupWidth = 0; + this.options = void 0; + this.chart = chart; + // Run RangeSelector + this.init(chart); + } + /** + * The method to run when one of the buttons in the range selectors is + * clicked + * + * @private + * @function Highcharts.RangeSelector#clickButton + * @param {number} i + * The index of the button + * @param {boolean} [redraw] + */ + RangeSelector.prototype.clickButton = function (i, redraw) { + var rangeSelector = this, + chart = rangeSelector.chart, + rangeOptions = rangeSelector.buttonOptions[i], + baseAxis = chart.xAxis[0], + unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {}, + dataMin = unionExtremes.dataMin, + dataMax = unionExtremes.dataMax, + newMin, + newMax = baseAxis && Math.round(Math.min(baseAxis.max, + pick(dataMax, + baseAxis.max))), // #1568 + type = rangeOptions.type, + baseXAxisOptions, + range = rangeOptions._range, + rangeMin, + minSetting, + rangeSetting, + ctx, + ytdExtremes, + dataGrouping = rangeOptions.dataGrouping, + addOffsetMin = true; + // chart has no data, base series is removed + if (dataMin === null || dataMax === null) { + return; + } + // Set the fixed range before range is altered + chart.fixedRange = range; + rangeSelector.setSelected(i); + // Apply dataGrouping associated to button + if (dataGrouping) { + this.forcedDataGrouping = true; + Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false); + this.frozenStates = rangeOptions.preserveDataGrouping; + } + // Apply range + if (type === 'month' || type === 'year') { + if (!baseAxis) { + // This is set to the user options and picked up later when the + // axis is instantiated so that we know the min and max. + range = rangeOptions; + } + else { + ctx = { + range: rangeOptions, + max: newMax, + chart: chart, + dataMin: dataMin, + dataMax: dataMax + }; + newMin = baseAxis.minFromRange.call(ctx); + if (isNumber(ctx.newMax)) { + newMax = ctx.newMax; + } + // #15799: offsetMin is added in minFromRange so that it works + // with pre-selected buttons as well + addOffsetMin = false; + } + // Fixed times like minutes, hours, days + } + else if (range) { + newMin = Math.max(newMax - range, dataMin); + newMax = Math.min(newMin + range, dataMax); + addOffsetMin = false; + } + else if (type === 'ytd') { + // On user clicks on the buttons, or a delayed action running from + // the beforeRender event (below), the baseAxis is defined. + if (baseAxis) { + // When "ytd" is the pre-selected button for the initial view, + // its calculation is delayed and rerun in the beforeRender + // event (below). When the series are initialized, but before + // the chart is rendered, we have access to the xData array + // (#942). + if (typeof dataMax === 'undefined' || + typeof dataMin === 'undefined') { + dataMin = Number.MAX_VALUE; + dataMax = Number.MIN_VALUE; + chart.series.forEach(function (series) { + // reassign it to the last item + var xData = series.xData; + if (xData) { + dataMin = Math.min(xData[0], dataMin); + dataMax = Math.max(xData[xData.length - 1], dataMax); + } + }); + redraw = false; + } + ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC); + newMin = rangeMin = ytdExtremes.min; + newMax = ytdExtremes.max; + // "ytd" is pre-selected. We don't yet have access to processed + // point and extremes data (things like pointStart and pointInterval + // are missing), so we delay the process (#942) + } + else { + rangeSelector.deferredYTDClick = i; + return; + } + } + else if (type === 'all' && baseAxis) { + // If the navigator exist and the axis range is declared reset that + // range and from now on only use the range set by a user, #14742. + if (chart.navigator && chart.navigator.baseSeries[0]) { + chart.navigator.baseSeries[0].xAxis.options.range = void 0; + } + newMin = dataMin; + newMax = dataMax; + } + if (addOffsetMin && rangeOptions._offsetMin && defined(newMin)) { + newMin += rangeOptions._offsetMin; + } + if (rangeOptions._offsetMax && defined(newMax)) { + newMax += rangeOptions._offsetMax; + } + if (this.dropdown) { + this.dropdown.selectedIndex = i + 1; + } + // Update the chart + if (!baseAxis) { + // Axis not yet instanciated. Temporarily set min and range + // options and remove them on chart load (#4317). + baseXAxisOptions = splat(chart.options.xAxis)[0]; + rangeSetting = baseXAxisOptions.range; + baseXAxisOptions.range = range; + minSetting = baseXAxisOptions.min; + baseXAxisOptions.min = rangeMin; + addEvent(chart, 'load', function resetMinAndRange() { + baseXAxisOptions.range = rangeSetting; + baseXAxisOptions.min = minSetting; + }); + } + else { + // Existing axis object. Set extremes after render time. + baseAxis.setExtremes(newMin, newMax, pick(redraw, true), void 0, // auto animation + { + trigger: 'rangeSelectorButton', + rangeSelectorButton: rangeOptions + }); + } + fireEvent(this, 'afterBtnClick'); + }; + /** + * Set the selected option. This method only sets the internal flag, it + * doesn't update the buttons or the actual zoomed range. + * + * @private + * @function Highcharts.RangeSelector#setSelected + * @param {number} [selected] + */ + RangeSelector.prototype.setSelected = function (selected) { + this.selected = this.options.selected = selected; + }; + /** + * Initialize the range selector + * + * @private + * @function Highcharts.RangeSelector#init + * @param {Highcharts.Chart} chart + */ + RangeSelector.prototype.init = function (chart) { + var rangeSelector = this, + options = chart.options.rangeSelector, + buttonOptions = (options.buttons || rangeSelector.defaultButtons.slice()), + selectedOption = options.selected, + blurInputs = function () { + var minInput = rangeSelector.minInput, + maxInput = rangeSelector.maxInput; + // #3274 in some case blur is not defined + if (minInput && (minInput.blur)) { + fireEvent(minInput, 'blur'); + } + if (maxInput && (maxInput.blur)) { + fireEvent(maxInput, 'blur'); + } + }; + rangeSelector.chart = chart; + rangeSelector.options = options; + rangeSelector.buttons = []; + rangeSelector.buttonOptions = buttonOptions; + this.eventsToUnbind = []; + this.eventsToUnbind.push(addEvent(chart.container, 'mousedown', blurInputs)); + this.eventsToUnbind.push(addEvent(chart, 'resize', blurInputs)); + // Extend the buttonOptions with actual range + buttonOptions.forEach(rangeSelector.computeButtonRange); + // zoomed range based on a pre-selected button index + if (typeof selectedOption !== 'undefined' && + buttonOptions[selectedOption]) { + this.clickButton(selectedOption, false); + } + this.eventsToUnbind.push(addEvent(chart, 'load', function () { + // If a data grouping is applied to the current button, release it + // when extremes change + if (chart.xAxis && chart.xAxis[0]) { + addEvent(chart.xAxis[0], 'setExtremes', function (e) { + if (this.max - this.min !== + chart.fixedRange && + e.trigger !== 'rangeSelectorButton' && + e.trigger !== 'updatedData' && + rangeSelector.forcedDataGrouping && + !rangeSelector.frozenStates) { + this.setDataGrouping(false, false); + } + }); + } + })); + }; + /** + * Dynamically update the range selector buttons after a new range has been + * set + * + * @private + * @function Highcharts.RangeSelector#updateButtonStates + */ + RangeSelector.prototype.updateButtonStates = function () { + var rangeSelector = this, + chart = this.chart, + dropdown = this.dropdown, + baseAxis = chart.xAxis[0], + actualRange = Math.round(baseAxis.max - baseAxis.min), + hasNoData = !baseAxis.hasVisibleSeries, + day = 24 * 36e5, // A single day in milliseconds + unionExtremes = (chart.scroller && + chart.scroller.getUnionExtremes()) || baseAxis, + dataMin = unionExtremes.dataMin, + dataMax = unionExtremes.dataMax, + ytdExtremes = rangeSelector.getYTDExtremes(dataMax, + dataMin, + chart.time.useUTC), + ytdMin = ytdExtremes.min, + ytdMax = ytdExtremes.max, + selected = rangeSelector.selected, + selectedExists = isNumber(selected), + allButtonsEnabled = rangeSelector.options.allButtonsEnabled, + buttons = rangeSelector.buttons; + rangeSelector.buttonOptions.forEach(function (rangeOptions, i) { + var range = rangeOptions._range, + type = rangeOptions.type, + count = rangeOptions.count || 1, + button = buttons[i], + state = 0, + disable, + select, + offsetRange = rangeOptions._offsetMax - + rangeOptions._offsetMin, + isSelected = i === selected, + // Disable buttons where the range exceeds what is allowed in + // the current view + isTooGreatRange = range > + dataMax - dataMin, + // Disable buttons where the range is smaller than the minimum + // range + isTooSmallRange = range < baseAxis.minRange, + // Do not select the YTD button if not explicitly told so + isYTDButNotSelected = false, + // Disable the All button if we're already showing all + isAllButAlreadyShowingAll = false, + isSameRange = range === actualRange; + // Months and years have a variable range so we check the extremes + if ((type === 'month' || type === 'year') && + (actualRange + 36e5 >= + { month: 28, year: 365 }[type] * day * count - offsetRange) && + (actualRange - 36e5 <= + { month: 31, year: 366 }[type] * day * count + offsetRange)) { + isSameRange = true; + } + else if (type === 'ytd') { + isSameRange = (ytdMax - ytdMin + offsetRange) === actualRange; + isYTDButNotSelected = !isSelected; + } + else if (type === 'all') { + isSameRange = (baseAxis.max - baseAxis.min >= + dataMax - dataMin); + isAllButAlreadyShowingAll = (!isSelected && + selectedExists && + isSameRange); + } + // The new zoom area happens to match the range for a button - mark + // it selected. This happens when scrolling across an ordinal gap. + // It can be seen in the intraday demos when selecting 1h and scroll + // across the night gap. + disable = (!allButtonsEnabled && + (isTooGreatRange || + isTooSmallRange || + isAllButAlreadyShowingAll || + hasNoData)); + select = ((isSelected && isSameRange) || + (isSameRange && !selectedExists && !isYTDButNotSelected) || + (isSelected && rangeSelector.frozenStates)); + if (disable) { + state = 3; + } + else if (select) { + selectedExists = true; // Only one button can be selected + state = 2; + } + // If state has changed, update the button + if (button.state !== state) { + button.setState(state); + if (dropdown) { + dropdown.options[i + 1].disabled = disable; + if (state === 2) { + dropdown.selectedIndex = i + 1; + } + } + // Reset (#9209) + if (state === 0 && selected === i) { + rangeSelector.setSelected(); + } + } + }); + }; + /** + * Compute and cache the range for an individual button + * + * @private + * @function Highcharts.RangeSelector#computeButtonRange + * @param {Highcharts.RangeSelectorButtonsOptions} rangeOptions + */ + RangeSelector.prototype.computeButtonRange = function (rangeOptions) { + var type = rangeOptions.type, + count = rangeOptions.count || 1, + // these time intervals have a fixed number of milliseconds, as + // opposed to month, ytd and year + fixedTimes = { + millisecond: 1, + second: 1000, + minute: 60 * 1000, + hour: 3600 * 1000, + day: 24 * 3600 * 1000, + week: 7 * 24 * 3600 * 1000 + }; + // Store the range on the button object + if (fixedTimes[type]) { + rangeOptions._range = fixedTimes[type] * count; + } + else if (type === 'month' || type === 'year') { + rangeOptions._range = { + month: 30, + year: 365 + }[type] * 24 * 36e5 * count; + } + rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0); + rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0); + rangeOptions._range += + rangeOptions._offsetMax - rangeOptions._offsetMin; + }; + /** + * Get the unix timestamp of a HTML input for the dates + * + * @private + * @function Highcharts.RangeSelector#getInputValue + */ + RangeSelector.prototype.getInputValue = function (name) { + var input = name === 'min' ? this.minInput : this.maxInput; + var options = this.chart.options + .rangeSelector; + var time = this.chart.time; + if (input) { + return ((input.type === 'text' && options.inputDateParser) || + this.defaultInputDateParser)(input.value, time.useUTC, time); + } + return 0; + }; + /** + * Set the internal and displayed value of a HTML input for the dates + * + * @private + * @function Highcharts.RangeSelector#setInputValue + */ + RangeSelector.prototype.setInputValue = function (name, inputTime) { + var options = this.options, time = this.chart.time, input = name === 'min' ? this.minInput : this.maxInput, dateBox = name === 'min' ? this.minDateBox : this.maxDateBox; + if (input) { + var hcTimeAttr = input.getAttribute('data-hc-time'); + var updatedTime = defined(hcTimeAttr) ? Number(hcTimeAttr) : void 0; + if (defined(inputTime)) { + var previousTime = updatedTime; + if (defined(previousTime)) { + input.setAttribute('data-hc-time-previous', previousTime); + } + input.setAttribute('data-hc-time', inputTime); + updatedTime = inputTime; + } + input.value = time.dateFormat((this.inputTypeFormats[input.type] || + options.inputEditDateFormat), updatedTime); + if (dateBox) { + dateBox.attr({ + text: time.dateFormat(options.inputDateFormat, updatedTime) + }); + } + } + }; + /** + * Set the min and max value of a HTML input for the dates + * + * @private + * @function Highcharts.RangeSelector#setInputExtremes + */ + RangeSelector.prototype.setInputExtremes = function (name, min, max) { + var input = name === 'min' ? this.minInput : this.maxInput; + if (input) { + var format = this.inputTypeFormats[input.type]; + var time = this.chart.time; + if (format) { + var newMin = time.dateFormat(format, + min); + if (input.min !== newMin) { + input.min = newMin; + } + var newMax = time.dateFormat(format, + max); + if (input.max !== newMax) { + input.max = newMax; + } + } + } + }; + /** + * @private + * @function Highcharts.RangeSelector#showInput + * @param {string} name + */ + RangeSelector.prototype.showInput = function (name) { + var dateBox = name === 'min' ? this.minDateBox : this.maxDateBox; + var input = name === 'min' ? this.minInput : this.maxInput; + if (input && dateBox && this.inputGroup) { + var isTextInput = input.type === 'text'; + var _a = this.inputGroup, + translateX = _a.translateX, + translateY = _a.translateY; + var inputBoxWidth = this.options.inputBoxWidth; + css(input, { + width: isTextInput ? + ((dateBox.width + (inputBoxWidth ? -2 : 20)) + 'px') : + 'auto', + height: isTextInput ? ((dateBox.height - 2) + 'px') : 'auto', + border: '2px solid silver' + }); + if (isTextInput && inputBoxWidth) { + css(input, { + left: (translateX + dateBox.x) + 'px', + top: translateY + 'px' + }); + // Inputs of types date, time or datetime-local should be centered + // on top of the dateBox + } + else { + css(input, { + left: Math.min(Math.round(dateBox.x + + translateX - + (input.offsetWidth - dateBox.width) / 2), this.chart.chartWidth - input.offsetWidth) + 'px', + top: (translateY - (input.offsetHeight - dateBox.height) / 2) + 'px' + }); + } + } + }; + /** + * @private + * @function Highcharts.RangeSelector#hideInput + * @param {string} name + */ + RangeSelector.prototype.hideInput = function (name) { + var input = name === 'min' ? this.minInput : this.maxInput; + if (input) { + css(input, { + top: '-9999em', + border: 0, + width: '1px', + height: '1px' + }); + } + }; + /** + * @private + * @function Highcharts.RangeSelector#defaultInputDateParser + */ + RangeSelector.prototype.defaultInputDateParser = function (inputDate, useUTC, time) { + var hasTimezone = function (str) { + return str.length > 6 && + (str.lastIndexOf('-') === str.length - 6 || + str.lastIndexOf('+') === str.length - 6); + }; + var input = inputDate.split('/').join('-').split(' ').join('T'); + if (input.indexOf('T') === -1) { + input += 'T00:00'; + } + if (useUTC) { + input += 'Z'; + } + else if (H.isSafari && !hasTimezone(input)) { + var offset = new Date(input).getTimezoneOffset() / 60; + input += offset <= 0 ? "+".concat(pad(-offset), ":00") : "-".concat(pad(offset), ":00"); + } + var date = Date.parse(input); + // If the value isn't parsed directly to a value by the + // browser's Date.parse method, like YYYY-MM-DD in IE8, try + // parsing it a different way + if (!isNumber(date)) { + var parts = inputDate.split('-'); + date = Date.UTC(pInt(parts[0]), pInt(parts[1]) - 1, pInt(parts[2])); + } + if (time && useUTC && isNumber(date)) { + date += time.getTimezoneOffset(date); + } + return date; + }; + /** + * Draw either the 'from' or the 'to' HTML input box of the range selector + * + * @private + * @function Highcharts.RangeSelector#drawInput + */ + RangeSelector.prototype.drawInput = function (name) { + var _a = this, + chart = _a.chart, + div = _a.div, + inputGroup = _a.inputGroup; + var rangeSelector = this, + chartStyle = chart.renderer.style || {}, + renderer = chart.renderer, + options = chart.options.rangeSelector, + lang = defaultOptions.lang, + isMin = name === 'min'; + /** + * @private + */ + function updateExtremes() { + var value = rangeSelector.getInputValue(name), + chartAxis = chart.xAxis[0], + dataAxis = chart.scroller && chart.scroller.xAxis ? + chart.scroller.xAxis : + chartAxis, + dataMin = dataAxis.dataMin, + dataMax = dataAxis.dataMax; + var maxInput = rangeSelector.maxInput, + minInput = rangeSelector.minInput; + if (value !== Number(input.getAttribute('data-hc-time-previous')) && + isNumber(value)) { + input.setAttribute('data-hc-time-previous', value); + // Validate the extremes. If it goes beyound the data min or + // max, use the actual data extreme (#2438). + if (isMin && maxInput && isNumber(dataMin)) { + if (value > Number(maxInput.getAttribute('data-hc-time'))) { + value = void 0; + } + else if (value < dataMin) { + value = dataMin; + } + } + else if (minInput && isNumber(dataMax)) { + if (value < Number(minInput.getAttribute('data-hc-time'))) { + value = void 0; + } + else if (value > dataMax) { + value = dataMax; + } + } + // Set the extremes + if (typeof value !== 'undefined') { // @todo typof undefined + chartAxis.setExtremes(isMin ? value : chartAxis.min, isMin ? chartAxis.max : value, void 0, void 0, { trigger: 'rangeSelectorInput' }); + } + } + } + // Create the text label + var text = lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'] || ''; + var label = renderer + .label(text, 0) + .addClass('highcharts-range-label') + .attr({ + padding: text ? 2 : 0, + height: text ? options.inputBoxHeight : 0 + }) + .add(inputGroup); + // Create an SVG label that shows updated date ranges and and records + // click events that bring in the HTML input. + var dateBox = renderer + .label('', 0) + .addClass('highcharts-range-input') + .attr({ + padding: 2, + width: options.inputBoxWidth, + height: options.inputBoxHeight, + 'text-align': 'center' + }) + .on('click', + function () { + // If it is already focused, the onfocus event doesn't fire + // (#3713) + rangeSelector.showInput(name); + rangeSelector[name + 'Input'].focus(); + }); + if (!chart.styledMode) { + dateBox.attr({ + stroke: options.inputBoxBorderColor, + 'stroke-width': 1 + }); + } + dateBox.add(inputGroup); + // Create the HTML input element. This is rendered as 1x1 pixel then set + // to the right size when focused. + var input = createElement('input', { + name: name, + className: 'highcharts-range-selector' + }, + void 0, + div); + // #14788: Setting input.type to an unsupported type throws in IE, so + // we need to use setAttribute instead + input.setAttribute('type', preferredInputType(options.inputDateFormat || '%b %e, %Y')); + if (!chart.styledMode) { + // Styles + label.css(merge(chartStyle, options.labelStyle)); + dateBox.css(merge({ + color: "#333333" /* Palette.neutralColor80 */ + }, chartStyle, options.inputStyle)); + css(input, extend({ + position: 'absolute', + border: 0, + boxShadow: '0 0 15px rgba(0,0,0,0.3)', + width: '1px', + height: '1px', + padding: 0, + textAlign: 'center', + fontSize: chartStyle.fontSize, + fontFamily: chartStyle.fontFamily, + top: '-9999em' // #4798 + }, options.inputStyle)); + } + // Blow up the input box + input.onfocus = function () { + rangeSelector.showInput(name); + }; + // Hide away the input box + input.onblur = function () { + // update extermes only when inputs are active + if (input === H.doc.activeElement) { // Only when focused + // Update also when no `change` event is triggered, like when + // clicking inside the SVG (#4710) + updateExtremes(); + } + // #10404 - move hide and blur outside focus + rangeSelector.hideInput(name); + rangeSelector.setInputValue(name); + input.blur(); // #4606 + }; + var keyDown = false; + // handle changes in the input boxes + input.onchange = function () { + // Update extremes and blur input when clicking date input calendar + if (!keyDown) { + updateExtremes(); + rangeSelector.hideInput(name); + input.blur(); + } + }; + input.onkeypress = function (event) { + // IE does not fire onchange on enter + if (event.keyCode === 13) { + updateExtremes(); + } + }; + input.onkeydown = function (event) { + keyDown = true; + // Arrow keys + if (event.keyCode === 38 || event.keyCode === 40) { + updateExtremes(); + } + }; + input.onkeyup = function () { + keyDown = false; + }; + return { dateBox: dateBox, input: input, label: label }; + }; + /** + * Get the position of the range selector buttons and inputs. This can be + * overridden from outside for custom positioning. + * + * @private + * @function Highcharts.RangeSelector#getPosition + */ + RangeSelector.prototype.getPosition = function () { + var chart = this.chart, + options = chart.options.rangeSelector, + top = options.verticalAlign === 'top' ? + chart.plotTop - chart.axisOffset[0] : + 0; // set offset only for varticalAlign top + return { + buttonTop: top + options.buttonPosition.y, + inputTop: top + options.inputPosition.y - 10 + }; + }; + /** + * Get the extremes of YTD. Will choose dataMax if its value is lower than + * the current timestamp. Will choose dataMin if its value is higher than + * the timestamp for the start of current year. + * + * @private + * @function Highcharts.RangeSelector#getYTDExtremes + * @return {*} + * Returns min and max for the YTD + */ + RangeSelector.prototype.getYTDExtremes = function (dataMax, dataMin, useUTC) { + var time = this.chart.time, + min, + now = new time.Date(dataMax), + year = time.get('FullYear', + now), + startOfYear = useUTC ? + time.Date.UTC(year, 0, 1) : // eslint-disable-line new-cap + +new time.Date(year, 0, 1); + min = Math.max(dataMin, startOfYear); + var ts = now.getTime(); + return { + max: Math.min(dataMax || ts, ts), + min: min + }; + }; + /** + * Render the range selector including the buttons and the inputs. The first + * time render is called, the elements are created and positioned. On + * subsequent calls, they are moved and updated. + * + * @private + * @function Highcharts.RangeSelector#render + * @param {number} [min] + * X axis minimum + * @param {number} [max] + * X axis maximum + */ + RangeSelector.prototype.render = function (min, max) { + var chart = this.chart, + renderer = chart.renderer, + container = chart.container, + chartOptions = chart.options, + options = chartOptions.rangeSelector, + // Place inputs above the container + inputsZIndex = pick(chartOptions.chart.style && + chartOptions.chart.style.zIndex, 0) + 1, + inputEnabled = options.inputEnabled, + rendered = this.rendered; + if (options.enabled === false) { + return; + } + // create the elements + if (!rendered) { + this.group = renderer.g('range-selector-group') + .attr({ + zIndex: 7 + }) + .add(); + this.div = createElement('div', void 0, { + position: 'relative', + height: 0, + zIndex: inputsZIndex + }); + if (this.buttonOptions.length) { + this.renderButtons(); + } + // First create a wrapper outside the container in order to make + // the inputs work and make export correct + if (container.parentNode) { + container.parentNode.insertBefore(this.div, container); + } + if (inputEnabled) { + // Create the group to keep the inputs + this.inputGroup = renderer.g('input-group').add(this.group); + var minElems = this.drawInput('min'); + this.minDateBox = minElems.dateBox; + this.minLabel = minElems.label; + this.minInput = minElems.input; + var maxElems = this.drawInput('max'); + this.maxDateBox = maxElems.dateBox; + this.maxLabel = maxElems.label; + this.maxInput = maxElems.input; + } + } + if (inputEnabled) { + // Set or reset the input values + this.setInputValue('min', min); + this.setInputValue('max', max); + var unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || chart.xAxis[0] || {}; + if (defined(unionExtremes.dataMin) && + defined(unionExtremes.dataMax)) { + var minRange = chart.xAxis[0].minRange || 0; + this.setInputExtremes('min', unionExtremes.dataMin, Math.min(unionExtremes.dataMax, this.getInputValue('max')) - minRange); + this.setInputExtremes('max', Math.max(unionExtremes.dataMin, this.getInputValue('min')) + minRange, unionExtremes.dataMax); + } + // Reflow + if (this.inputGroup) { + var x_1 = 0; + [ + this.minLabel, + this.minDateBox, + this.maxLabel, + this.maxDateBox + ].forEach(function (label) { + if (label) { + var width = label.getBBox().width; + if (width) { + label.attr({ x: x_1 }); + x_1 += width + options.inputSpacing; + } + } + }); + } + } + this.alignElements(); + this.rendered = true; + }; + /** + * Render the range buttons. This only runs the first time, later the + * positioning is laid out in alignElements. + * + * @private + * @function Highcharts.RangeSelector#renderButtons + */ + RangeSelector.prototype.renderButtons = function () { + var _this = this; + var _a = this, + buttons = _a.buttons, + chart = _a.chart, + options = _a.options; + var lang = defaultOptions.lang; + var renderer = chart.renderer; + var buttonTheme = merge(options.buttonTheme); + var states = buttonTheme && buttonTheme.states; + // Prevent the button from resetting the width when the button state + // changes since we need more control over the width when collapsing + // the buttons + var width = buttonTheme.width || 28; + delete buttonTheme.width; + delete buttonTheme.states; + this.buttonGroup = renderer.g('range-selector-buttons').add(this.group); + var dropdown = this.dropdown = createElement('select', + void 0, { + position: 'absolute', + width: '1px', + height: '1px', + padding: 0, + border: 0, + top: '-9999em', + cursor: 'pointer', + opacity: 0.0001 + }, + this.div); + // Prevent page zoom on iPhone + addEvent(dropdown, 'touchstart', function () { + dropdown.style.fontSize = '16px'; + }); + // Forward events from select to button + [ + [H.isMS ? 'mouseover' : 'mouseenter'], + [H.isMS ? 'mouseout' : 'mouseleave'], + ['change', 'click'] + ].forEach(function (_a) { + var from = _a[0], + to = _a[1]; + addEvent(dropdown, from, function () { + var button = buttons[_this.currentButtonIndex()]; + if (button) { + fireEvent(button.element, to || from); + } + }); + }); + this.zoomText = renderer + .label((lang && lang.rangeSelectorZoom) || '', 0) + .attr({ + padding: options.buttonTheme.padding, + height: options.buttonTheme.height, + paddingLeft: 0, + paddingRight: 0 + }) + .add(this.buttonGroup); + if (!this.chart.styledMode) { + this.zoomText.css(options.labelStyle); + buttonTheme['stroke-width'] = pick(buttonTheme['stroke-width'], 0); + } + createElement('option', { + textContent: this.zoomText.textStr, + disabled: true + }, void 0, dropdown); + this.buttonOptions.forEach(function (rangeOptions, i) { + createElement('option', { + textContent: rangeOptions.title || rangeOptions.text + }, void 0, dropdown); + buttons[i] = renderer + .button(rangeOptions.text, 0, 0, function (e) { + // extract events from button object and call + var buttonEvents = (rangeOptions.events && + rangeOptions.events.click), + callDefaultEvent; + if (buttonEvents) { + callDefaultEvent = + buttonEvents.call(rangeOptions, e); + } + if (callDefaultEvent !== false) { + _this.clickButton(i); + } + _this.isActive = true; + }, buttonTheme, states && states.hover, states && states.select, states && states.disabled) + .attr({ + 'text-align': 'center', + width: width + }) + .add(_this.buttonGroup); + if (rangeOptions.title) { + buttons[i].attr('title', rangeOptions.title); + } + }); + }; + /** + * Align the elements horizontally and vertically. + * + * @private + * @function Highcharts.RangeSelector#alignElements + */ + RangeSelector.prototype.alignElements = function () { + var _this = this; + var _a = this, + buttonGroup = _a.buttonGroup, + buttons = _a.buttons, + chart = _a.chart, + group = _a.group, + inputGroup = _a.inputGroup, + options = _a.options, + zoomText = _a.zoomText; + var chartOptions = chart.options; + var navButtonOptions = (chartOptions.exporting && + chartOptions.exporting.enabled !== false && + chartOptions.navigation && + chartOptions.navigation.buttonOptions); + var buttonPosition = options.buttonPosition, + inputPosition = options.inputPosition, + verticalAlign = options.verticalAlign; + // Get the X offset required to avoid overlapping with the exporting + // button. This is is used both by the buttonGroup and the inputGroup. + var getXOffsetForExportButton = function (group, + position) { + if (navButtonOptions && + _this.titleCollision(chart) && + verticalAlign === 'top' && + position.align === 'right' && ((position.y - + group.getBBox().height - 12) < + ((navButtonOptions.y || 0) + + (navButtonOptions.height || 0) + + chart.spacing[0]))) { + return -40; + } + return 0; + }; + var plotLeft = chart.plotLeft; + if (group && buttonPosition && inputPosition) { + var translateX = buttonPosition.x - chart.spacing[3]; + if (buttonGroup) { + this.positionButtons(); + if (!this.initialButtonGroupWidth) { + var width_1 = 0; + if (zoomText) { + width_1 += zoomText.getBBox().width + 5; + } + buttons.forEach(function (button, i) { + width_1 += button.width; + if (i !== buttons.length - 1) { + width_1 += options.buttonSpacing; + } + }); + this.initialButtonGroupWidth = width_1; + } + plotLeft -= chart.spacing[3]; + this.updateButtonStates(); + // Detect collision between button group and exporting + var xOffsetForExportButton_1 = getXOffsetForExportButton(buttonGroup, + buttonPosition); + this.alignButtonGroup(xOffsetForExportButton_1); + // Skip animation + group.placed = buttonGroup.placed = chart.hasLoaded; + } + var xOffsetForExportButton = 0; + if (inputGroup) { + // Detect collision between the input group and exporting button + xOffsetForExportButton = getXOffsetForExportButton(inputGroup, inputPosition); + if (inputPosition.align === 'left') { + translateX = plotLeft; + } + else if (inputPosition.align === 'right') { + translateX = -Math.max(chart.axisOffset[1], -xOffsetForExportButton); + } + // Update the alignment to the updated spacing box + inputGroup.align({ + y: inputPosition.y, + width: inputGroup.getBBox().width, + align: inputPosition.align, + // fix wrong getBBox() value on right align + x: inputPosition.x + translateX - 2 + }, true, chart.spacingBox); + // Skip animation + inputGroup.placed = chart.hasLoaded; + } + this.handleCollision(xOffsetForExportButton); + // Vertical align + group.align({ + verticalAlign: verticalAlign + }, true, chart.spacingBox); + var alignTranslateY = group.alignAttr.translateY; + // Set position + var groupHeight = group.getBBox().height + 20; // # 20 padding + var translateY = 0; + // Calculate bottom position + if (verticalAlign === 'bottom') { + var legendOptions = chart.legend && chart.legend.options; + var legendHeight = (legendOptions && + legendOptions.verticalAlign === 'bottom' && + legendOptions.enabled && + !legendOptions.floating ? + (chart.legend.legendHeight + + pick(legendOptions.margin, 10)) : + 0); + groupHeight = groupHeight + legendHeight - 20; + translateY = (alignTranslateY - + groupHeight - + (options.floating ? 0 : options.y) - + (chart.titleOffset ? chart.titleOffset[2] : 0) - + 10 // 10 spacing + ); + } + if (verticalAlign === 'top') { + if (options.floating) { + translateY = 0; + } + if (chart.titleOffset && chart.titleOffset[0]) { + translateY = chart.titleOffset[0]; + } + translateY += ((chart.margin[0] - chart.spacing[0]) || 0); + } + else if (verticalAlign === 'middle') { + if (inputPosition.y === buttonPosition.y) { + translateY = alignTranslateY; + } + else if (inputPosition.y || buttonPosition.y) { + if (inputPosition.y < 0 || + buttonPosition.y < 0) { + translateY -= Math.min(inputPosition.y, buttonPosition.y); + } + else { + translateY = alignTranslateY - groupHeight; + } + } + } + group.translate(options.x, options.y + Math.floor(translateY)); + // Translate HTML inputs + var _b = this, + minInput = _b.minInput, + maxInput = _b.maxInput, + dropdown = _b.dropdown; + if (options.inputEnabled && minInput && maxInput) { + minInput.style.marginTop = group.translateY + 'px'; + maxInput.style.marginTop = group.translateY + 'px'; + } + if (dropdown) { + dropdown.style.marginTop = group.translateY + 'px'; + } + } + }; + /** + * Align the button group horizontally and vertically. + * + * @private + * @function Highcharts.RangeSelector#alignButtonGroup + * @param {number} xOffsetForExportButton + * @param {number} [width] + */ + RangeSelector.prototype.alignButtonGroup = function (xOffsetForExportButton, width) { + var _a = this, + chart = _a.chart, + options = _a.options, + buttonGroup = _a.buttonGroup, + buttons = _a.buttons; + var buttonPosition = options.buttonPosition; + var plotLeft = chart.plotLeft - chart.spacing[3]; + var translateX = buttonPosition.x - chart.spacing[3]; + if (buttonPosition.align === 'right') { + translateX += xOffsetForExportButton - plotLeft; // #13014 + } + else if (buttonPosition.align === 'center') { + translateX -= plotLeft / 2; + } + if (buttonGroup) { + // Align button group + buttonGroup.align({ + y: buttonPosition.y, + width: pick(width, this.initialButtonGroupWidth), + align: buttonPosition.align, + x: translateX + }, true, chart.spacingBox); + } + }; + /** + * @private + * @function Highcharts.RangeSelector#positionButtons + */ + RangeSelector.prototype.positionButtons = function () { + var _a = this, + buttons = _a.buttons, + chart = _a.chart, + options = _a.options, + zoomText = _a.zoomText; + var verb = chart.hasLoaded ? 'animate' : 'attr'; + var buttonPosition = options.buttonPosition; + var plotLeft = chart.plotLeft; + var buttonLeft = plotLeft; + if (zoomText && zoomText.visibility !== 'hidden') { + // #8769, allow dynamically updating margins + zoomText[verb]({ + x: pick(plotLeft + buttonPosition.x, plotLeft) + }); + // Button start position + buttonLeft += buttonPosition.x + + zoomText.getBBox().width + 5; + } + this.buttonOptions.forEach(function (rangeOptions, i) { + if (buttons[i].visibility !== 'hidden') { + buttons[i][verb]({ x: buttonLeft }); + // increase button position for the next button + buttonLeft += buttons[i].width + options.buttonSpacing; + } + else { + buttons[i][verb]({ x: plotLeft }); + } + }); + }; + /** + * Handle collision between the button group and the input group + * + * @private + * @function Highcharts.RangeSelector#handleCollision + * + * @param {number} xOffsetForExportButton + * The X offset of the group required to make room for the + * exporting button + */ + RangeSelector.prototype.handleCollision = function (xOffsetForExportButton) { + var _this = this; + var _a = this, + chart = _a.chart, + buttonGroup = _a.buttonGroup, + inputGroup = _a.inputGroup; + var _b = this.options, + buttonPosition = _b.buttonPosition, + dropdown = _b.dropdown, + inputPosition = _b.inputPosition; + var maxButtonWidth = function () { + var buttonWidth = 0; + _this.buttons.forEach(function (button) { + var bBox = button.getBBox(); + if (bBox.width > buttonWidth) { + buttonWidth = bBox.width; + } + }); + return buttonWidth; + }; + var groupsOverlap = function (buttonGroupWidth) { + if (inputGroup && buttonGroup) { + var inputGroupX = (inputGroup.alignAttr.translateX + + inputGroup.alignOptions.x - + xOffsetForExportButton + + // getBBox for detecing left margin + inputGroup.getBBox().x + + // 2px padding to not overlap input and label + 2); + var inputGroupWidth = inputGroup.alignOptions.width; + var buttonGroupX = buttonGroup.alignAttr.translateX + + buttonGroup.getBBox().x; + return (buttonGroupX + buttonGroupWidth > inputGroupX) && + (inputGroupX + inputGroupWidth > buttonGroupX) && + (buttonPosition.y < + (inputPosition.y + + inputGroup.getBBox().height)); + } + return false; + }; + var moveInputsDown = function () { + if (inputGroup && buttonGroup) { + inputGroup.attr({ + translateX: inputGroup.alignAttr.translateX + (chart.axisOffset[1] >= -xOffsetForExportButton ? + 0 : + -xOffsetForExportButton), + translateY: inputGroup.alignAttr.translateY + + buttonGroup.getBBox().height + 10 + }); + } + }; + if (buttonGroup) { + if (dropdown === 'always') { + this.collapseButtons(xOffsetForExportButton); + if (groupsOverlap(maxButtonWidth())) { + // Move the inputs down if there is still a collision + // after collapsing the buttons + moveInputsDown(); + } + return; + } + if (dropdown === 'never') { + this.expandButtons(); + } + } + // Detect collision + if (inputGroup && buttonGroup) { + if ((inputPosition.align === buttonPosition.align) || + // 20 is minimal spacing between elements + groupsOverlap(this.initialButtonGroupWidth + 20)) { + if (dropdown === 'responsive') { + this.collapseButtons(xOffsetForExportButton); + if (groupsOverlap(maxButtonWidth())) { + moveInputsDown(); + } + } + else { + moveInputsDown(); + } + } + else if (dropdown === 'responsive') { + this.expandButtons(); + } + } + else if (buttonGroup && dropdown === 'responsive') { + if (this.initialButtonGroupWidth > chart.plotWidth) { + this.collapseButtons(xOffsetForExportButton); + } + else { + this.expandButtons(); + } + } + }; + /** + * Collapse the buttons and put the select element on top. + * + * @private + * @function Highcharts.RangeSelector#collapseButtons + * @param {number} xOffsetForExportButton + */ + RangeSelector.prototype.collapseButtons = function (xOffsetForExportButton) { + var _a = this, + buttons = _a.buttons, + buttonOptions = _a.buttonOptions, + chart = _a.chart, + dropdown = _a.dropdown, + options = _a.options, + zoomText = _a.zoomText; + var userButtonTheme = (chart.userOptions.rangeSelector && + chart.userOptions.rangeSelector.buttonTheme) || {}; + var getAttribs = function (text) { return ({ + text: text ? "" + text + " \u25BE" : '▾', + width: 'auto', + paddingLeft: pick(options.buttonTheme.paddingLeft, + userButtonTheme.padding, 8), + paddingRight: pick(options.buttonTheme.paddingRight, + userButtonTheme.padding, 8) + }); }; + if (zoomText) { + zoomText.hide(); + } + var hasActiveButton = false; + buttonOptions.forEach(function (rangeOptions, i) { + var button = buttons[i]; + if (button.state !== 2) { + button.hide(); + } + else { + button.show(); + button.attr(getAttribs(rangeOptions.text)); + hasActiveButton = true; + } + }); + if (!hasActiveButton) { + if (dropdown) { + dropdown.selectedIndex = 0; + } + buttons[0].show(); + buttons[0].attr(getAttribs(this.zoomText && this.zoomText.textStr)); + } + var align = options.buttonPosition.align; + this.positionButtons(); + if (align === 'right' || align === 'center') { + this.alignButtonGroup(xOffsetForExportButton, buttons[this.currentButtonIndex()].getBBox().width); + } + this.showDropdown(); + }; + /** + * Show all the buttons and hide the select element. + * + * @private + * @function Highcharts.RangeSelector#expandButtons + */ + RangeSelector.prototype.expandButtons = function () { + var _a = this, + buttons = _a.buttons, + buttonOptions = _a.buttonOptions, + options = _a.options, + zoomText = _a.zoomText; + this.hideDropdown(); + if (zoomText) { + zoomText.show(); + } + buttonOptions.forEach(function (rangeOptions, i) { + var button = buttons[i]; + button.show(); + button.attr({ + text: rangeOptions.text, + width: options.buttonTheme.width || 28, + paddingLeft: pick(options.buttonTheme.paddingLeft, 'unset'), + paddingRight: pick(options.buttonTheme.paddingRight, 'unset') + }); + if (button.state < 2) { + button.setState(0); + } + }); + this.positionButtons(); + }; + /** + * Get the index of the visible button when the buttons are collapsed. + * + * @private + * @function Highcharts.RangeSelector#currentButtonIndex + */ + RangeSelector.prototype.currentButtonIndex = function () { + var dropdown = this.dropdown; + if (dropdown && dropdown.selectedIndex > 0) { + return dropdown.selectedIndex - 1; + } + return 0; + }; + /** + * Position the select element on top of the button. + * + * @private + * @function Highcharts.RangeSelector#showDropdown + */ + RangeSelector.prototype.showDropdown = function () { + var _a = this, + buttonGroup = _a.buttonGroup, + buttons = _a.buttons, + chart = _a.chart, + dropdown = _a.dropdown; + if (buttonGroup && dropdown) { + var translateX = buttonGroup.translateX, + translateY = buttonGroup.translateY; + var bBox = buttons[this.currentButtonIndex()].getBBox(); + css(dropdown, { + left: (chart.plotLeft + translateX) + 'px', + top: (translateY + 0.5) + 'px', + width: bBox.width + 'px', + height: bBox.height + 'px' + }); + this.hasVisibleDropdown = true; + } + }; + /** + * @private + * @function Highcharts.RangeSelector#hideDropdown + */ + RangeSelector.prototype.hideDropdown = function () { + var dropdown = this.dropdown; + if (dropdown) { + css(dropdown, { + top: '-9999em', + width: '1px', + height: '1px' + }); + this.hasVisibleDropdown = false; + } + }; + /** + * Extracts height of range selector + * + * @private + * @function Highcharts.RangeSelector#getHeight + * @return {number} + * Returns rangeSelector height + */ + RangeSelector.prototype.getHeight = function () { + var rangeSelector = this, + options = rangeSelector.options, + rangeSelectorGroup = rangeSelector.group, + inputPosition = options.inputPosition, + buttonPosition = options.buttonPosition, + yPosition = options.y, + buttonPositionY = buttonPosition.y, + inputPositionY = inputPosition.y, + rangeSelectorHeight = 0, + minPosition; + if (options.height) { + return options.height; + } + // Align the elements before we read the height in case we're switching + // between wrapped and non-wrapped layout + this.alignElements(); + rangeSelectorHeight = rangeSelectorGroup ? + // 13px to keep back compatibility + (rangeSelectorGroup.getBBox(true).height) + 13 + + yPosition : + 0; + minPosition = Math.min(inputPositionY, buttonPositionY); + if ((inputPositionY < 0 && buttonPositionY < 0) || + (inputPositionY > 0 && buttonPositionY > 0)) { + rangeSelectorHeight += Math.abs(minPosition); + } + return rangeSelectorHeight; + }; + /** + * Detect collision with title or subtitle + * + * @private + * @function Highcharts.RangeSelector#titleCollision + * @return {boolean} + * Returns collision status + */ + RangeSelector.prototype.titleCollision = function (chart) { + return !(chart.options.title.text || + chart.options.subtitle.text); + }; + /** + * Update the range selector with new options + * + * @private + * @function Highcharts.RangeSelector#update + * @param {Highcharts.RangeSelectorOptions} options + */ + RangeSelector.prototype.update = function (options) { + var chart = this.chart; + merge(true, chart.options.rangeSelector, options); + this.destroy(); + this.init(chart); + this.render(); + }; + /** + * Destroys allocated elements. + * + * @private + * @function Highcharts.RangeSelector#destroy + */ + RangeSelector.prototype.destroy = function () { + var rSelector = this, + minInput = rSelector.minInput, + maxInput = rSelector.maxInput; + if (rSelector.eventsToUnbind) { + rSelector.eventsToUnbind.forEach(function (unbind) { return unbind(); }); + rSelector.eventsToUnbind = void 0; + } + // Destroy elements in collections + destroyObjectProperties(rSelector.buttons); + // Clear input element events + if (minInput) { + minInput.onfocus = minInput.onblur = minInput.onchange = null; + } + if (maxInput) { + maxInput.onfocus = maxInput.onblur = maxInput.onchange = null; + } + // Destroy HTML and SVG elements + objectEach(rSelector, function (val, key) { + if (val && key !== 'chart') { + if (val instanceof SVGElement) { + // SVGElement + val.destroy(); + } + else if (val instanceof window.HTMLElement) { + // HTML element + discardElement(val); + } + } + if (val !== RangeSelector.prototype[key]) { + rSelector[key] = null; + } + }, this); + }; + return RangeSelector; + }()); + /** + * The default buttons for pre-selecting time frames + */ + RangeSelector.prototype.defaultButtons = [{ + type: 'month', + count: 1, + text: '1m', + title: 'View 1 month' + }, { + type: 'month', + count: 3, + text: '3m', + title: 'View 3 months' + }, { + type: 'month', + count: 6, + text: '6m', + title: 'View 6 months' + }, { + type: 'ytd', + text: 'YTD', + title: 'View year to date' + }, { + type: 'year', + count: 1, + text: '1y', + title: 'View 1 year' + }, { + type: 'all', + text: 'All', + title: 'View all' + }]; + /** + * The date formats to use when setting min, max and value on date inputs + */ + RangeSelector.prototype.inputTypeFormats = { + 'datetime-local': '%Y-%m-%dT%H:%M:%S', + 'date': '%Y-%m-%d', + 'time': '%H:%M:%S' + }; + /** + * Get the preferred input type based on a date format string. + * + * @private + * @function preferredInputType + */ + function preferredInputType(format) { + var ms = format.indexOf('%L') !== -1; + if (ms) { + return 'text'; + } + var date = ['a', 'A', 'd', 'e', 'w', 'b', 'B', 'm', 'o', 'y', 'Y'] + .some(function (char) { return format.indexOf('%' + char) !== -1; }); + var time = ['H', 'k', 'I', 'l', 'M', 'S'].some(function (char) { + return format.indexOf('%' + char) !== -1; + }); + if (date && time) { + return 'datetime-local'; + } + if (date) { + return 'date'; + } + if (time) { + return 'time'; + } + return 'text'; + } + /** + * Get the axis min value based on the range option and the current max. For + * stock charts this is extended via the {@link RangeSelector} so that if the + * selected range is a multiple of months or years, it is compensated for + * various month lengths. + * + * @private + * @function Highcharts.Axis#minFromRange + * @return {number|undefined} + * The new minimum value. + */ + Axis.prototype.minFromRange = function () { + var rangeOptions = this.range, + type = rangeOptions.type, + min, + max = this.max, + dataMin, + range, + time = this.chart.time, + // Get the true range from a start date + getTrueRange = function (base, + count) { + var timeName = type === 'year' ? + 'FullYear' : 'Month'; + var date = new time.Date(base); + var basePeriod = time.get(timeName, + date); + time.set(timeName, date, basePeriod + count); + if (basePeriod === time.get(timeName, date)) { + time.set('Date', date, 0); // #6537 + } + return date.getTime() - base; + }; + if (isNumber(rangeOptions)) { + min = max - rangeOptions; + range = rangeOptions; + } + else if (rangeOptions) { + min = max + getTrueRange(max, -(rangeOptions.count || 1)); + // Let the fixedRange reflect initial settings (#5930) + if (this.chart) { + this.chart.fixedRange = max - min; + } + } + dataMin = pick(this.dataMin, Number.MIN_VALUE); + if (!isNumber(min)) { + min = dataMin; + } + if (min <= dataMin) { + min = dataMin; + if (typeof range === 'undefined') { // #4501 + range = getTrueRange(min, rangeOptions.count); + } + this.newMax = Math.min(min + range, pick(this.dataMax, Number.MAX_VALUE)); + } + if (!isNumber(max)) { + min = void 0; + } + else if (!isNumber(rangeOptions) && + rangeOptions && + rangeOptions._offsetMin) { + min += rangeOptions._offsetMin; + } + return min; + }; + if (!H.RangeSelector) { + var chartDestroyEvents_1 = []; + var initRangeSelector_1 = function (chart) { + var extremes, + rangeSelector = chart.rangeSelector, + legend, + alignTo, + verticalAlign; + /** + * @private + */ + function render() { + if (rangeSelector) { + extremes = chart.xAxis[0].getExtremes(); + legend = chart.legend; + verticalAlign = (rangeSelector && + rangeSelector.options.verticalAlign); + if (isNumber(extremes.min)) { + rangeSelector.render(extremes.min, extremes.max); + } + // Re-align the legend so that it's below the rangeselector + if (legend.display && + verticalAlign === 'top' && + verticalAlign === legend.options.verticalAlign) { + // Create a new alignment box for the legend. + alignTo = merge(chart.spacingBox); + if (legend.options.layout === 'vertical') { + alignTo.y = chart.plotTop; + } + else { + alignTo.y += rangeSelector.getHeight(); + } + legend.group.placed = false; // Don't animate the alignment. + legend.align(alignTo); + } + } + } + if (rangeSelector) { + var events = find(chartDestroyEvents_1, + function (e) { return e[0] === chart; }); + if (!events) { + chartDestroyEvents_1.push([chart, [ + // redraw the scroller on setExtremes + addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) { + if (rangeSelector) { + rangeSelector.render(e.min, e.max); + } + }), + // redraw the scroller chart resize + addEvent(chart, 'redraw', render) + ]]); + } + // do it now + render(); + } + }; + // Initialize rangeselector for stock charts + addEvent(Chart, 'afterGetContainer', function () { + if (this.options.rangeSelector && + this.options.rangeSelector.enabled) { + this.rangeSelector = new RangeSelector(this); + } + }); + addEvent(Chart, 'beforeRender', function () { + var chart = this, + axes = chart.axes, + rangeSelector = chart.rangeSelector, + verticalAlign; + if (rangeSelector) { + if (isNumber(rangeSelector.deferredYTDClick)) { + rangeSelector.clickButton(rangeSelector.deferredYTDClick); + delete rangeSelector.deferredYTDClick; + } + axes.forEach(function (axis) { + axis.updateNames(); + axis.setScale(); + }); + chart.getAxisMargins(); + rangeSelector.render(); + verticalAlign = rangeSelector.options.verticalAlign; + if (!rangeSelector.options.floating) { + if (verticalAlign === 'bottom') { + this.extraBottomMargin = true; + } + else if (verticalAlign !== 'middle') { + this.extraTopMargin = true; + } + } + } + }); + addEvent(Chart, 'update', function (e) { + var chart = this, + options = e.options, + optionsRangeSelector = options.rangeSelector, + rangeSelector = chart.rangeSelector, + verticalAlign, + extraBottomMarginWas = this.extraBottomMargin, + extraTopMarginWas = this.extraTopMargin; + if (optionsRangeSelector && + optionsRangeSelector.enabled && + !defined(rangeSelector) && + this.options.rangeSelector) { + this.options.rangeSelector.enabled = true; + this.rangeSelector = rangeSelector = new RangeSelector(this); + } + this.extraBottomMargin = false; + this.extraTopMargin = false; + if (rangeSelector) { + initRangeSelector_1(this); + verticalAlign = (optionsRangeSelector && + optionsRangeSelector.verticalAlign) || (rangeSelector.options && rangeSelector.options.verticalAlign); + if (!rangeSelector.options.floating) { + if (verticalAlign === 'bottom') { + this.extraBottomMargin = true; + } + else if (verticalAlign !== 'middle') { + this.extraTopMargin = true; + } + } + if (this.extraBottomMargin !== extraBottomMarginWas || + this.extraTopMargin !== extraTopMarginWas) { + this.isDirtyBox = true; + } + } + }); + addEvent(Chart, 'render', function () { + var chart = this, + rangeSelector = chart.rangeSelector, + verticalAlign; + if (rangeSelector && !rangeSelector.options.floating) { + rangeSelector.render(); + verticalAlign = rangeSelector.options.verticalAlign; + if (verticalAlign === 'bottom') { + this.extraBottomMargin = true; + } + else if (verticalAlign !== 'middle') { + this.extraTopMargin = true; + } + } + }); + addEvent(Chart, 'getMargins', function () { + var rangeSelector = this.rangeSelector, + rangeSelectorHeight; + if (rangeSelector) { + rangeSelectorHeight = rangeSelector.getHeight(); + if (this.extraTopMargin) { + this.plotTop += rangeSelectorHeight; + } + if (this.extraBottomMargin) { + this.marginBottom += rangeSelectorHeight; + } + } + }); + Chart.prototype.callbacks.push(initRangeSelector_1); + // Remove resize/afterSetExtremes at chart destroy + addEvent(Chart, 'destroy', function destroyEvents() { + for (var i = 0; i < chartDestroyEvents_1.length; i++) { + var events = chartDestroyEvents_1[i]; + if (events[0] === this) { + events[1].forEach(function (unbind) { return unbind(); }); + chartDestroyEvents_1.splice(i, 1); + return; + } + } + }); + H.RangeSelector = RangeSelector; + } + + return RangeSelector; + }); + _registerModule(_modules, 'Accessibility/Components/RangeSelectorComponent.js', [_modules['Extensions/RangeSelector.js'], _modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Utils/Announcer.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Core/Utilities.js']], function (RangeSelector, AccessibilityComponent, ChartUtilities, Announcer, KeyboardNavigationHandler, U) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component for the range selector. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, + getAxisRangeDescription = ChartUtilities.getAxisRangeDescription; + var addEvent = U.addEvent, + attr = U.attr; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + function shouldRunInputNavigation(chart) { + return Boolean(chart.rangeSelector && + chart.rangeSelector.inputGroup && + chart.rangeSelector.inputGroup.element.style.visibility !== 'hidden' && + chart.options.rangeSelector.inputEnabled !== false && + chart.rangeSelector.minInput && + chart.rangeSelector.maxInput); + } + /* * + * + * Class + * + * */ + /** + * The RangeSelectorComponent class + * + * @private + * @class + * @name Highcharts.RangeSelectorComponent + */ + var RangeSelectorComponent = /** @class */ (function (_super) { + __extends(RangeSelectorComponent, _super); + function RangeSelectorComponent() { + /* * + * + * Properties + * + * */ + var _this = _super !== null && _super.apply(this, + arguments) || this; + _this.announcer = void 0; + return _this; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Init the component + * @private + */ + RangeSelectorComponent.prototype.init = function () { + var chart = this.chart; + this.announcer = new Announcer(chart, 'polite'); + }; + /** + * Called on first render/updates to the chart, including options changes. + */ + RangeSelectorComponent.prototype.onChartUpdate = function () { + var chart = this.chart, + component = this, + rangeSelector = chart.rangeSelector; + if (!rangeSelector) { + return; + } + this.updateSelectorVisibility(); + this.setDropdownAttrs(); + if (rangeSelector.buttons && + rangeSelector.buttons.length) { + rangeSelector.buttons.forEach(function (button) { + 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'); + } + }); + } + }; + /** + * Hide buttons from AT when showing dropdown, and vice versa. + * @private + */ + RangeSelectorComponent.prototype.updateSelectorVisibility = function () { + var chart = this.chart; + var rangeSelector = chart.rangeSelector; + var dropdown = (rangeSelector && + rangeSelector.dropdown); + var buttons = (rangeSelector && + rangeSelector.buttons || + []); + var hideFromAT = function (el) { return el.setAttribute('aria-hidden', + true); }; + if (rangeSelector && + rangeSelector.hasVisibleDropdown && + dropdown) { + unhideChartElementFromAT(chart, dropdown); + buttons.forEach(function (btn) { return hideFromAT(btn.element); }); + } + else { + if (dropdown) { + hideFromAT(dropdown); + } + buttons.forEach(function (btn) { return unhideChartElementFromAT(chart, btn.element); }); + } + }; + /** + * Set accessibility related attributes on dropdown element. + * @private + */ + RangeSelectorComponent.prototype.setDropdownAttrs = function () { + var chart = this.chart; + var dropdown = (chart.rangeSelector && + chart.rangeSelector.dropdown); + if (dropdown) { + var label = chart.langFormat('accessibility.rangeSelector.dropdownLabel', { rangeTitle: chart.options.lang.rangeSelectorZoom }); + dropdown.setAttribute('aria-label', label); + dropdown.setAttribute('tabindex', -1); + } + }; + /** + * @private + * @param {Highcharts.SVGElement} button + */ + RangeSelectorComponent.prototype.setRangeButtonAttrs = function (button) { + attr(button.element, { + tabindex: -1, + role: 'button' + }); + }; + /** + * @private + */ + RangeSelectorComponent.prototype.setRangeInputAttrs = function (input, langKey) { + var chart = this.chart; + attr(input, { + tabindex: -1, + 'aria-label': chart.langFormat(langKey, { chart: chart }) + }); + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler + * @param {number} keyCode + * @return {number} Response code + */ + RangeSelectorComponent.prototype.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 + */ + RangeSelectorComponent.prototype.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; + }; + /** + * Called whenever a range selector button has been clicked, either by + * mouse, touch, or kbd/voice/other. + * @private + */ + RangeSelectorComponent.prototype.onAfterBtnClick = function () { + var chart = this.chart; + var axisRangeDescription = getAxisRangeDescription(chart.xAxis[0]); + var announcement = chart.langFormat('accessibility.rangeSelector.clickButtonAnnouncement', { chart: chart, + axisRangeDescription: axisRangeDescription }); + if (announcement) { + this.announcer.announce(announcement); + } + }; + /** + * @private + */ + RangeSelectorComponent.prototype.onInputKbdMove = function (direction) { + var chart = this.chart; + var rangeSel = chart.rangeSelector; + var newIx = chart.highlightedInputRangeIx = (chart.highlightedInputRangeIx || 0) + direction; + var newIxOutOfRange = newIx > 1 || newIx < 0; + if (newIxOutOfRange) { + if (chart.accessibility) { + chart.accessibility.keyboardNavigation.tabindexContainer + .focus(); + chart.accessibility.keyboardNavigation.move(direction); + } + } + else if (rangeSel) { + var svgEl = rangeSel[newIx ? 'maxDateBox' : 'minDateBox']; + var inputEl = rangeSel[newIx ? 'maxInput' : 'minInput']; + if (svgEl && inputEl) { + chart.setFocusToElement(svgEl, inputEl); + } + } + }; + /** + * @private + * @param {number} direction + */ + RangeSelectorComponent.prototype.onInputNavInit = function (direction) { + var _this = this; + var component = this; + var chart = this.chart; + var buttonIxToHighlight = direction > 0 ? 0 : 1; + var rangeSel = chart.rangeSelector; + var svgEl = (rangeSel && + rangeSel[buttonIxToHighlight ? 'maxDateBox' : 'minDateBox']); + var minInput = (rangeSel && rangeSel.minInput); + var maxInput = (rangeSel && rangeSel.maxInput); + var inputEl = buttonIxToHighlight ? maxInput : minInput; + chart.highlightedInputRangeIx = buttonIxToHighlight; + if (svgEl && minInput && maxInput) { + chart.setFocusToElement(svgEl, inputEl); + // Tab-press with the input focused does not propagate to chart + // automatically, so we manually catch and handle it when relevant. + if (this.removeInputKeydownHandler) { + this.removeInputKeydownHandler(); + } + var keydownHandler = function (e) { + var isTab = (e.which || e.keyCode) === _this.keyCodes.tab; + if (isTab) { + e.preventDefault(); + e.stopPropagation(); + component.onInputKbdMove(e.shiftKey ? -1 : 1); + } + }; + var minRemover_1 = addEvent(minInput, 'keydown', + keydownHandler); + var maxRemover_1 = addEvent(maxInput, 'keydown', + keydownHandler); + this.removeInputKeydownHandler = function () { + minRemover_1(); + maxRemover_1(); + }; + } + }; + /** + * @private + */ + RangeSelectorComponent.prototype.onInputNavTerminate = function () { + var rangeSel = (this.chart.rangeSelector || {}); + if (rangeSel.maxInput) { + rangeSel.hideInput('max'); + } + if (rangeSel.minInput) { + rangeSel.hideInput('min'); + } + if (this.removeInputKeydownHandler) { + this.removeInputKeydownHandler(); + delete this.removeInputKeydownHandler; + } + }; + /** + * @private + */ + RangeSelectorComponent.prototype.initDropdownNav = function () { + var _this = this; + var chart = this.chart; + var rangeSelector = chart.rangeSelector; + var dropdown = (rangeSelector && rangeSelector.dropdown); + if (rangeSelector && dropdown) { + chart.setFocusToElement(rangeSelector.buttonGroup, dropdown); + if (this.removeDropdownKeydownHandler) { + this.removeDropdownKeydownHandler(); + } + // Tab-press with dropdown focused does not propagate to chart + // automatically, so we manually catch and handle it when relevant. + this.removeDropdownKeydownHandler = addEvent(dropdown, 'keydown', function (e) { + var isTab = (e.which || e.keyCode) === _this.keyCodes.tab, + a11y = chart.accessibility; + if (isTab) { + e.preventDefault(); + e.stopPropagation(); + if (a11y) { + a11y.keyboardNavigation.tabindexContainer.focus(); + a11y.keyboardNavigation.move(e.shiftKey ? -1 : 1); + } + } + }); + } + }; + /** + * Get navigation for the range selector buttons. + * @private + * @return {Highcharts.KeyboardNavigationHandler} The module object. + */ + RangeSelectorComponent.prototype.getRangeSelectorButtonNavigation = function () { + var chart = this.chart; + var keys = this.keyCodes; + var 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 () { + return !!(chart.rangeSelector && + chart.rangeSelector.buttons && + chart.rangeSelector.buttons.length); + }, + init: function (direction) { + var rangeSelector = chart.rangeSelector; + if (rangeSelector && rangeSelector.hasVisibleDropdown) { + component.initDropdownNav(); + } + else if (rangeSelector) { + var lastButtonIx = rangeSelector.buttons.length - 1; + chart.highlightRangeSelectorButton(direction > 0 ? 0 : lastButtonIx); + } + }, + terminate: function () { + if (component.removeDropdownKeydownHandler) { + component.removeDropdownKeydownHandler(); + delete component.removeDropdownKeydownHandler; + } + } + }); + }; + /** + * Get navigation for the range selector input boxes. + * @private + * @return {Highcharts.KeyboardNavigationHandler} + * The module object. + */ + RangeSelectorComponent.prototype.getRangeSelectorInputNavigation = function () { + var chart = this.chart; + var component = this; + return new KeyboardNavigationHandler(chart, { + keyCodeMap: [], + validate: function () { + return shouldRunInputNavigation(chart); + }, + init: function (direction) { + component.onInputNavInit(direction); + }, + terminate: function () { + component.onInputNavTerminate(); + } + }); + }; + /** + * Get keyboard navigation handlers for this component. + * @return {Array} + * List of module objects. + */ + RangeSelectorComponent.prototype.getKeyboardNavigation = function () { + return [ + this.getRangeSelectorButtonNavigation(), + this.getRangeSelectorInputNavigation() + ]; + }; + /** + * Remove component traces + */ + RangeSelectorComponent.prototype.destroy = function () { + if (this.removeDropdownKeydownHandler) { + this.removeDropdownKeydownHandler(); + } + if (this.removeInputKeydownHandler) { + this.removeInputKeydownHandler(); + } + if (this.announcer) { + this.announcer.destroy(); + } + }; + return RangeSelectorComponent; + }(AccessibilityComponent)); + /* * + * + * Class Namespace + * + * */ + (function (RangeSelectorComponent) { + /* * + * + * Declarations + * + * */ + /* * + * + * Constants + * + * */ + var composedClasses = []; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Highlight range selector button by index. + * + * @private + * @function Highcharts.Chart#highlightRangeSelectorButton + */ + function chartHighlightRangeSelectorButton(ix) { + var buttons = (this.rangeSelector && + this.rangeSelector.buttons || + []); + var curHighlightedIx = this.highlightedRangeSelectorItemIx; + var curSelectedIx = (this.rangeSelector && + this.rangeSelector.selected); + // Deselect old + if (typeof curHighlightedIx !== 'undefined' && + buttons[curHighlightedIx] && + curHighlightedIx !== curSelectedIx) { + buttons[curHighlightedIx].setState(this.oldRangeSelectorItemState || 0); + } + // Select new + this.highlightedRangeSelectorItemIx = ix; + if (buttons[ix]) { + this.setFocusToElement(buttons[ix].box, buttons[ix].element); + if (ix !== curSelectedIx) { + this.oldRangeSelectorItemState = buttons[ix].state; + buttons[ix].setState(1); + } + return true; + } + return false; + } + /** + * @private + */ + function compose(ChartClass, RangeSelectorClass) { + if (composedClasses.indexOf(ChartClass) === -1) { + composedClasses.push(ChartClass); + var chartProto = ChartClass.prototype; + chartProto.highlightRangeSelectorButton = (chartHighlightRangeSelectorButton); + } + if (composedClasses.indexOf(RangeSelectorClass) === -1) { + composedClasses.push(RangeSelectorClass); + addEvent(RangeSelector, 'afterBtnClick', rangeSelectorAfterBtnClick); + } + } + RangeSelectorComponent.compose = compose; + /** + * Range selector does not have destroy-setup for class instance events - so + * we set it on the class and call the component from here. + * @private + */ + function rangeSelectorAfterBtnClick() { + var a11y = this.chart.accessibility; + if (a11y && a11y.components.rangeSelector) { + return a11y.components.rangeSelector.onAfterBtnClick(); + } + } + })(RangeSelectorComponent || (RangeSelectorComponent = {})); + /* * + * + * Export Default + * + * */ + + return RangeSelectorComponent; + }); + _registerModule(_modules, 'Accessibility/Components/SeriesComponent/ForcedMarkers.js', [_modules['Core/Utilities.js']], function (U) { + /* * + * + * (c) 2009-2021 Ø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; + /* * + * + * Composition + * + * */ + var ForcedMarkersComposition; + (function (ForcedMarkersComposition) { + /* * + * + * Declarations + * + * */ + /* * + * + * Compositions + * + * */ + var composedClasses = []; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + function compose(SeriesClass) { + if (composedClasses.indexOf(SeriesClass) === -1) { + composedClasses.push(SeriesClass); + addEvent(SeriesClass, 'afterSetOptions', seriesOnAfterSetOptions); + addEvent(SeriesClass, 'render', seriesOnRender); + addEvent(SeriesClass, 'afterRender', seriesOnAfterRender); + } + } + ForcedMarkersComposition.compose = compose; + /** + * @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; + } + /** + * @private + */ + function handleForcePointMarkers(series) { + var i = series.points.length; + while (i--) { + var point = series.points[i]; + var pointOptions = point.options; + var hadForcedMarker = point.hasForcedA11yMarker; + delete point.hasForcedA11yMarker; + if (pointOptions.marker) { + var isStillForcedMarker = hadForcedMarker && + getPointMarkerOpacity(pointOptions) === 0; + if (pointOptions.marker.enabled && !isStillForcedMarker) { + unforcePointMarkerOptions(pointOptions); + point.hasForcedA11yMarker = false; + } + else if (pointOptions.marker.enabled === false) { + forceZeroOpacityMarkerOptions(pointOptions); + point.hasForcedA11yMarker = true; + } + } + } + } + /** + * @private + */ + function hasIndividualPointMarkerOptions(series) { + return !!(series._hasPointMarkers && + series.points && + series.points.length); + } + /** + * @private + */ + function isWithinDescriptionThreshold(series) { + var a11yOptions = series.chart.options.accessibility; + return series.points.length < + a11yOptions.series.pointDescriptionEnabledThreshold || + a11yOptions.series + .pointDescriptionEnabledThreshold === false; + } + /** + * Process marker graphics after render + * @private + */ + function seriesOnAfterRender() { + var series = this; + // For styled mode the rendered graphic does not reflect the style + // options, and we need to add/remove classes to achieve the same. + if (series.chart.styledMode) { + if (series.markerGroup) { + series.markerGroup[series.a11yMarkersForced ? 'addClass' : 'removeClass']('highcharts-a11y-markers-hidden'); + } + // Do we need to handle individual points? + if (hasIndividualPointMarkerOptions(series)) { + series.points.forEach(function (point) { + if (point.graphic) { + point.graphic[point.hasForcedA11yMarker ? + 'addClass' : 'removeClass']('highcharts-a11y-marker-hidden'); + point.graphic[point.hasForcedA11yMarker === false ? + 'addClass' : + 'removeClass']('highcharts-a11y-marker-visible'); + } + }); + } + } + } + /** + * Keep track of options to reset markers to if no longer forced. + * @private + */ + function seriesOnAfterSetOptions(e) { + this.resetA11yMarkerOptions = merge(e.options.marker || {}, this.userOptions.marker || {}); + } + /** + * Keep track of forcing markers. + * @private + */ + function seriesOnRender() { + var series = this, + options = series.options; + if (shouldForceMarkers(series)) { + if (options.marker && options.marker.enabled === false) { + series.a11yMarkersForced = true; + forceZeroOpacityMarkerOptions(series.options); + } + if (hasIndividualPointMarkerOptions(series)) { + handleForcePointMarkers(series); + } + } + else if (series.a11yMarkersForced) { + delete series.a11yMarkersForced; + unforceSeriesMarkerOptions(series); + delete series.resetA11yMarkerOptions; + } + } + /** + * @private + */ + function shouldForceMarkers(series) { + var chart = series.chart, + chartA11yEnabled = chart.options.accessibility.enabled, + seriesA11yEnabled = (series.options.accessibility && + series.options.accessibility.enabled) !== false; + return (chartA11yEnabled && + seriesA11yEnabled && + isWithinDescriptionThreshold(series)); + } + /** + * @private + */ + function unforcePointMarkerOptions(pointOptions) { + merge(true, pointOptions.marker, { + states: { + normal: { + opacity: getPointMarkerOpacity(pointOptions) || 1 + } + } + }); + } + /** + * Reset markers to normal + * @private + */ + function unforceSeriesMarkerOptions(series) { + var resetMarkerOptions = series.resetA11yMarkerOptions; + if (resetMarkerOptions) { + var originalOpactiy = resetMarkerOptions.states && + resetMarkerOptions.states.normal && + resetMarkerOptions.states.normal.opacity; + series.update({ + marker: { + enabled: resetMarkerOptions.enabled, + states: { + normal: { opacity: originalOpactiy } + } + } + }); + } + } + })(ForcedMarkersComposition || (ForcedMarkersComposition = {})); + /* * + * + * Default Export + * + * */ + + return ForcedMarkersComposition; + }); + _registerModule(_modules, 'Accessibility/Components/SeriesComponent/SeriesKeyboardNavigation.js', [_modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Accessibility/Utils/EventProvider.js'], _modules['Accessibility/Utils/ChartUtilities.js']], function (Point, Series, SeriesRegistry, H, U, KeyboardNavigationHandler, EventProvider, ChartUtilities) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Handle keyboard navigation for series. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var seriesTypes = SeriesRegistry.seriesTypes; + var doc = H.doc; + var defined = U.defined, + fireEvent = U.fireEvent; + var getPointFromXY = ChartUtilities.getPointFromXY, + getSeriesFromName = ChartUtilities.getSeriesFromName, + scrollToPoint = ChartUtilities.scrollToPoint; + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * 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; + var 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(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(point) { + var a11yOptions = point.series.chart.options.accessibility; + var pointA11yDisabled = (point.options.accessibility && + point.options.accessibility.enabled === false); + return point.isNull && + a11yOptions.keyboardNavigation.seriesNavigation.skipNullPoints || + point.visible === false || + point.isInside === false || + pointA11yDisabled || + isSkipSeries(point.series); + } + /** + * Get the first point that is not a skip point in this series. + * @private + */ + function getFirstValidPointInSeries(series) { + var points = series.points || [], + len = points.length; + for (var i = 0; i < len; ++i) { + if (!isSkipPoint(points[i])) { + return points[i]; + } + } + return null; + } + /** + * Get the first point that is not a skip point in this chart. + * @private + */ + function getFirstValidPointInChart(chart) { + var series = chart.series || [], + len = series.length; + for (var i = 0; i < len; ++i) { + if (!isSkipSeries(series[i])) { + var point = getFirstValidPointInSeries(series[i]); + if (point) { + return point; + } + } + } + return null; + } + /** + * @private + */ + function highlightLastValidPointInChart(chart) { + var numSeries = chart.series.length; + var 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].highlightNextValidPoint(); + if (res) { + break; + } + } + return res; + } + /** + * After drilling down/up, we need to set focus to the first point for + * screen readers and keyboard nav. + * @private + */ + function updateChartFocusAfterDrilling(chart) { + var point = getFirstValidPointInChart(chart); + if (point) { + point.highlight(false); // Do not visually highlight + } + } + /** + * Highlight the first point in chart that is not a skip point + * @private + */ + function highlightFirstValidPointInChart(chart) { + delete chart.highlightedPoint; + var point = getFirstValidPointInChart(chart); + return point ? point.highlight() : false; + } + /* * + * + * Class + * + * */ + /** + * @private + * @class + * @name Highcharts.SeriesKeyboardNavigation + */ + var SeriesKeyboardNavigation = /** @class */ (function () { + /* * + * + * Constructor + * + * */ + function SeriesKeyboardNavigation(chart, keyCodes) { + this.keyCodes = keyCodes; + this.chart = chart; + } + /* * + * + * Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * Init the keyboard navigation + */ + SeriesKeyboardNavigation.prototype.init = function () { + var keyboardNavigation = this, + chart = this.chart, + e = this.eventProvider = new EventProvider(); + e.addEvent(Series, 'destroy', function () { + return keyboardNavigation.onSeriesDestroy(this); + }); + e.addEvent(chart, 'afterApplyDrilldown', 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); + }); + // Heatmaps et al. alter z-index in setState, causing elements + // to lose focus + e.addEvent(Point, 'afterSetState', function () { + var point = this; + var pointEl = point.graphic && point.graphic.element; + var focusedElement = doc.activeElement; + // VO brings focus with it to container, causing series nav to run. + // If then navigating with virtual cursor, it is possible to leave + // keyboard nav module state on the data points and still activate + // proxy buttons. + var focusedElClassName = (focusedElement && focusedElement.getAttribute('class')); + var isProxyFocused = focusedElClassName && + focusedElClassName.indexOf('highcharts-a11y-proxy-button') > -1; + if (chart.highlightedPoint === point && + focusedElement !== pointEl && + !isProxyFocused && + pointEl && + pointEl.focus) { + pointEl.focus(); + } + }); + }; + /** + * After drillup we want to find the point that was drilled down to and + * highlight it. + * @private + */ + SeriesKeyboardNavigation.prototype.onDrillupAll = function () { + var last = this.lastDrilledDownPoint, + chart = this.chart, + series = last && getSeriesFromName(chart, + last.seriesName); + var point; + if (last && series && defined(last.x) && defined(last.y)) { + point = getPointFromXY(series, last.x, last.y); + } + point = point || getFirstValidPointInChart(chart); + // Container focus can be lost on drillup due to deleted elements. + if (chart.container) { + chart.container.focus(); + } + if (point && point.highlight) { + point.highlight(false); // Do not visually highlight + } + }; + /** + * @private + */ + SeriesKeyboardNavigation.prototype.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 (keyCode, event) { + var point = chart.highlightedPoint; + if (point) { + event.point = point; + fireEvent(point.series, 'click', event); + point.firePointEvent('click'); + } + return this.response.success; + }], + [[keys.home], + function () { + highlightFirstValidPointInChart(chart); + return this.response.success; + }], + [[keys.end], + function () { + highlightLastValidPointInChart(chart); + return this.response.success; + }], + [[keys.pageDown, keys.pageUp], + function (keyCode) { + chart.highlightAdjacentSeries(keyCode === keys.pageDown); + return this.response.success; + }] + ], + init: function () { + return keyboardNavigation.onHandlerInit(this); + }, + validate: function () { + return !!getFirstValidPointInChart(chart); + }, + terminate: function () { + return keyboardNavigation.onHandlerTerminate(); + } + }); + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} handler + * @param {number} keyCode + * @return {number} + * response + */ + SeriesKeyboardNavigation.prototype.onKbdSideways = function (handler, keyCode) { + var keys = this.keyCodes, + isNext = keyCode === keys.right || keyCode === keys.down; + return this.attemptHighlightAdjacentPoint(handler, isNext); + }; + /** + * When keyboard navigation inits. + * @private + * @param {Highcharts.KeyboardNavigationHandler} handler The handler object + * @return {number} + * response + */ + SeriesKeyboardNavigation.prototype.onHandlerInit = function (handler) { + var chart = this.chart, + kbdNavOptions = chart.options.accessibility.keyboardNavigation; + if (kbdNavOptions.seriesNavigation.rememberPointFocus && + chart.highlightedPoint) { + chart.highlightedPoint.highlight(); + } + else { + highlightFirstValidPointInChart(chart); + } + return handler.response.success; + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} handler + * @param {number} keyCode + * @return {number} + * response + */ + SeriesKeyboardNavigation.prototype.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 + */ + SeriesKeyboardNavigation.prototype.onHandlerTerminate = function () { + var chart = this.chart, + kbdNavOptions = chart.options.accessibility.keyboardNavigation; + if (chart.tooltip) { + chart.tooltip.hide(0); + } + var hoverSeries = (chart.highlightedPoint && chart.highlightedPoint.series); + if (hoverSeries && hoverSeries.onMouseOut) { + hoverSeries.onMouseOut(); + } + if (chart.highlightedPoint && chart.highlightedPoint.onMouseOut) { + chart.highlightedPoint.onMouseOut(); + } + if (!kbdNavOptions.seriesNavigation.rememberPointFocus) { + delete chart.highlightedPoint; + } + }; + /** + * Function that attempts to highlight next/prev point. Handles wrap around. + * @private + */ + SeriesKeyboardNavigation.prototype.attemptHighlightAdjacentPoint = function (handler, directionIsNext) { + var chart = this.chart, + wrapAround = chart.options.accessibility.keyboardNavigation + .wrapAround, + highlightSuccessful = chart.highlightAdjacentPoint(directionIsNext); + if (!highlightSuccessful) { + if (wrapAround && (directionIsNext ? + highlightFirstValidPointInChart(chart) : + highlightLastValidPointInChart(chart))) { + return handler.response.success; + } + return handler.response[directionIsNext ? 'next' : 'prev']; + } + return handler.response.success; + }; + /** + * @private + */ + SeriesKeyboardNavigation.prototype.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 + */ + SeriesKeyboardNavigation.prototype.destroy = function () { + this.eventProvider.removeAddedEvents(); + }; + return SeriesKeyboardNavigation; + }()); + /* * + * + * Class Namespace + * + * */ + (function (SeriesKeyboardNavigation) { + /* * + * + * Declarations + * + * */ + /* * + * + * Constants + * + * */ + var composedClasses = []; + /* * + * + * Functions + * + * */ + /** + * 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). + */ + function chartHighlightAdjacentPoint(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]; + var 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 the closest point vertically. + * @private + */ + function chartHighlightAdjacentPointVertical(down) { + var curPoint = this.highlightedPoint; + var 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; + var 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 || + distance < 5 || // Points in same spot => infinite loop + isSkipPoint(point)) { + return; + } + if (distance < minDistance) { + minDistance = distance; + bestPoint = point; + } + }); + }); + return bestPoint ? bestPoint.highlight() : false; + } + /** + * Highlight next/previous series in chart. Returns false if no adjacent + * series in the direction, otherwise returns new highlighted point. + * @private + */ + function chartHighlightAdjacentSeries(down) { + var chart = this, + curPoint = chart.highlightedPoint, + lastSeries = chart.series && chart.series[chart.series.length - 1], + lastPoint = lastSeries && lastSeries.points && + lastSeries.points[lastSeries.points.length - 1]; + var newSeries, + newPoint, + adjacentNewPoint; + // 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(); + // Try recurse + adjacentNewPoint = chart.highlightAdjacentSeries(down); + 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.highlightNextValidPoint(); + } + /** + * @private + */ + function compose(ChartClass, PointClass, SeriesClass) { + if (composedClasses.indexOf(ChartClass) === -1) { + composedClasses.push(ChartClass); + var chartProto = ChartClass.prototype; + chartProto.highlightAdjacentPoint = chartHighlightAdjacentPoint; + chartProto.highlightAdjacentPointVertical = (chartHighlightAdjacentPointVertical); + chartProto.highlightAdjacentSeries = chartHighlightAdjacentSeries; + } + if (composedClasses.indexOf(PointClass) === -1) { + composedClasses.push(PointClass); + var pointProto = PointClass.prototype; + pointProto.highlight = pointHighlight; + } + if (composedClasses.indexOf(SeriesClass) === -1) { + composedClasses.push(SeriesClass); + var seriesProto = SeriesClass.prototype; + /** + * 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. + * @private + */ + seriesProto.keyboardMoveVertical = true; + [ + 'column', + 'gantt', + 'pie' + ].forEach(function (type) { + if (seriesTypes[type]) { + seriesTypes[type].prototype.keyboardMoveVertical = false; + } + }); + seriesProto.highlightNextValidPoint = (seriesHighlightNextValidPoint); + } + } + SeriesKeyboardNavigation.compose = compose; + /** + * 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(point, series, xWeight, yWeight) { + var minDistance = Infinity, + dPoint, + minIx, + distance, + i = series.points.length; + var 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, display hover state, focus element). + * + * @private + * @function Highcharts.Point#highlight + * + * @return {Highcharts.Point} + * This highlighted point. + */ + function pointHighlight(highlightVisually) { + if (highlightVisually === void 0) { highlightVisually = true; } + var chart = this.series.chart; + if (!this.isNull && highlightVisually) { + this.onMouseOver(); // Show the hover marker and tooltip + } + else { + if (chart.tooltip) { + chart.tooltip.hide(0); + } + // Do not call blur on the element, as it messes up the focus of the + // div element of the chart + } + 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); + if (!highlightVisually && chart.focusElement) { + chart.focusElement.removeFocusBorder(); + } + } + chart.highlightedPoint = this; + return this; + } + /** + * 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#highlightNextValidPoint + */ + function seriesHighlightNextValidPoint() { + 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; + } + })(SeriesKeyboardNavigation || (SeriesKeyboardNavigation = {})); + /* * + * + * Default Export + * + * */ + + return SeriesKeyboardNavigation; + }); + _registerModule(_modules, 'Accessibility/Components/SeriesComponent/SeriesComponent.js', [_modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/Components/SeriesComponent/ForcedMarkers.js'], _modules['Accessibility/Components/SeriesComponent/NewDataAnnouncer.js'], _modules['Accessibility/Components/SeriesComponent/SeriesDescriber.js'], _modules['Accessibility/Components/SeriesComponent/SeriesKeyboardNavigation.js'], _modules['Core/Tooltip.js']], function (AccessibilityComponent, ChartUtilities, ForcedMarkers, NewDataAnnouncer, SeriesDescriber, SeriesKeyboardNavigation, Tooltip) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component for series and points. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var hideSeriesFromAT = ChartUtilities.hideSeriesFromAT; + var describeSeries = SeriesDescriber.describeSeries; + /* * + * + * Class + * + * */ + /** + * The SeriesComponent class + * + * @private + * @class + * @name Highcharts.SeriesComponent + */ + var SeriesComponent = /** @class */ (function (_super) { + __extends(SeriesComponent, _super); + function SeriesComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + /* * + * + * Static Functions + * + * */ + /* eslint-disable valid-jsdoc */ + /** + * @private + */ + SeriesComponent.compose = function (ChartClass, PointClass, SeriesClass) { + NewDataAnnouncer.compose(SeriesClass); + ForcedMarkers.compose(SeriesClass); + SeriesKeyboardNavigation.compose(ChartClass, PointClass, SeriesClass); + }; + /* * + * + * Functions + * + * */ + /** + * Init the component. + */ + SeriesComponent.prototype.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 + */ + SeriesComponent.prototype.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 + */ + SeriesComponent.prototype.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. + */ + SeriesComponent.prototype.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. + * @private + */ + SeriesComponent.prototype.getKeyboardNavigation = function () { + return this.keyboardNavigation.getKeyboardNavigationHandler(); + }; + /** + * Remove traces + * @private + */ + SeriesComponent.prototype.destroy = function () { + this.newDataAnnouncer.destroy(); + this.keyboardNavigation.destroy(); + }; + return SeriesComponent; + }(AccessibilityComponent)); + /* * + * + * Default Export + * + * */ + + return SeriesComponent; + }); + _registerModule(_modules, 'Accessibility/Components/ZoomComponent.js', [_modules['Accessibility/AccessibilityComponent.js'], _modules['Accessibility/Utils/ChartUtilities.js'], _modules['Accessibility/KeyboardNavigationHandler.js'], _modules['Core/Utilities.js']], function (AccessibilityComponent, CU, KeyboardNavigationHandler, U) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Accessibility component for chart zoom. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, + b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, + b) { d.__proto__ = b; }) || + function (d, + b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var unhideChartElementFromAT = CU.unhideChartElementFromAT; + var attr = U.attr, + pick = U.pick; + /* * + * + * Functions + * + * */ + /** + * 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 axisPanStep(axis, direction, granularity) { + var gran = granularity || 3; + var extremes = axis.getExtremes(); + var step = (extremes.max - extremes.min) / gran * direction; + var newMax = extremes.max + step; + var newMin = extremes.min + step; + var 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; + } + axis.setExtremes(newMin, newMax); + } + /** + * @private + */ + function chartHasMapZoom(chart) { + return !!((chart.mapZoom) && + chart.mapNavigation && + chart.mapNavigation.navButtons.length); + } + /* * + * + * Class + * + * */ + /** + * The ZoomComponent class + * + * @private + * @class + * @name Highcharts.ZoomComponent + */ + var ZoomComponent = /** @class */ (function (_super) { + __extends(ZoomComponent, _super); + function ZoomComponent() { + /* * + * + * Properties + * + * */ + var _this = _super !== null && _super.apply(this, + arguments) || this; + _this.focusedMapNavButtonIx = -1; + return _this; + } + /* * + * + * Functions + * + * */ + /** + * Initialize the component + */ + ZoomComponent.prototype.init = function () { + var component = this, + chart = this.chart; + this.proxyProvider.addGroup('zoom', 'div'); + [ + 'afterShowResetZoom', 'afterApplyDrilldown', 'drillupall' + ].forEach(function (eventType) { + component.addEvent(chart, eventType, function () { + component.updateProxyOverlays(); + }); + }); + }; + /** + * Called when chart is updated + */ + ZoomComponent.prototype.onChartUpdate = function () { + var chart = this.chart, + component = this; + // Make map zoom buttons accessible + if (chart.mapNavigation) { + chart.mapNavigation.navButtons.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 + */ + ZoomComponent.prototype.setMapNavButtonAttrs = function (button, labelFormatKey) { + var chart = this.chart, + label = chart.langFormat(labelFormatKey, { chart: chart }); + attr(button, { + tabindex: -1, + role: 'button', + 'aria-label': label + }); + }; + /** + * Update the proxy overlays on every new render to ensure positions are + * correct. + */ + ZoomComponent.prototype.onChartRender = function () { + this.updateProxyOverlays(); + }; + /** + * Update proxy overlays, recreating the buttons. + */ + ZoomComponent.prototype.updateProxyOverlays = function () { + var chart = this.chart; + // Always start with a clean slate + this.proxyProvider.clearGroup('zoom'); + if (chart.resetZoomButton) { + this.createZoomProxyButton(chart.resetZoomButton, 'resetZoomProxyButton', chart.langFormat('accessibility.zoom.resetZoomButton', { chart: chart })); + } + if (chart.drillUpButton && + chart.breadcrumbs && + chart.breadcrumbs.list) { + var lastBreadcrumb = chart.breadcrumbs.list[chart.breadcrumbs.list.length - 1]; + this.createZoomProxyButton(chart.drillUpButton, 'drillUpProxyButton', chart.langFormat('accessibility.drillUpButton', { + chart: chart, + buttonText: chart.breadcrumbs.getButtonText(lastBreadcrumb) + })); + } + }; + /** + * @private + * @param {Highcharts.SVGElement} buttonEl + * @param {string} buttonProp + * @param {string} label + */ + ZoomComponent.prototype.createZoomProxyButton = function (buttonEl, buttonProp, label) { + this[buttonProp] = this.proxyProvider.addProxyElement('zoom', { + click: buttonEl + }, { + 'aria-label': label, + tabindex: -1 + }); + }; + /** + * Get keyboard navigation handler for map zoom. + * @private + * @return {Highcharts.KeyboardNavigationHandler} The module object + */ + ZoomComponent.prototype.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 + */ + ZoomComponent.prototype.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; + axisPanStep(this.chart[panAxis][0], stepDirection); + return keyboardNavigationHandler.response.success; + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler + * @param {global.KeyboardEvent} event + * @return {number} Response code + */ + ZoomComponent.prototype.onMapKbdTab = function (keyboardNavigationHandler, event) { + var chart = this.chart; + var response = keyboardNavigationHandler.response; + var isBackwards = event.shiftKey; + var isMoveOutOfRange = isBackwards && !this.focusedMapNavButtonIx || + !isBackwards && this.focusedMapNavButtonIx; + // Deselect old + chart.mapNavigation.navButtons[this.focusedMapNavButtonIx].setState(0); + if (isMoveOutOfRange) { + chart.mapZoom(); // Reset zoom + return response[isBackwards ? 'prev' : 'next']; + } + // Select other button + this.focusedMapNavButtonIx += isBackwards ? -1 : 1; + var button = chart.mapNavigation.navButtons[this.focusedMapNavButtonIx]; + chart.setFocusToElement(button.box, button.element); + button.setState(2); + return response.success; + }; + /** + * @private + * @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler + * @return {number} Response code + */ + ZoomComponent.prototype.onMapKbdClick = function (keyboardNavigationHandler) { + var el = this.chart.mapNavButtons[this.focusedMapNavButtonIx].element; + this.fakeClickEvent(el); + return keyboardNavigationHandler.response.success; + }; + /** + * @private + * @param {number} direction + */ + ZoomComponent.prototype.onMapNavInit = function (direction) { + var chart = this.chart, + zoomIn = chart.mapNavigation.navButtons[0], + zoomOut = chart.mapNavigation.navButtons[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 + */ + ZoomComponent.prototype.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].buttonElement); + return hasButton; + }, + init: function () { + chart.setFocusToElement(chart[buttonProp].box, component[proxyProp].buttonElement); + } + }); + }; + /** + * Get keyboard navigation handlers for this component. + * @return {Array} + * List of module objects + */ + ZoomComponent.prototype.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; + }(AccessibilityComponent)); + /* * + * + * Default Export + * + * */ + + return ZoomComponent; + }); + _registerModule(_modules, 'Accessibility/HighContrastMode.js', [_modules['Core/Globals.js']], function (H) { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Handling for Windows High Contrast Mode. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc, + isMS = H.isMS, + win = H.win; + /* * + * + * Functions + * + * */ + /** + * Detect WHCM in the browser. + * + * @function Highcharts#isHighContrastModeActive + * @private + * @return {boolean} Returns true if the browser is in High Contrast mode. + */ + function isHighContrastModeActive() { + // 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'); + var imageSrc = 'data:image/gif;base64,' + + 'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; + testDiv.style.backgroundImage = "url(".concat(imageSrc, ")"); // #13071 + doc.body.appendChild(testDiv); + var bi = (testDiv.currentStyle || + win.getComputedStyle(testDiv)).backgroundImage; + doc.body.removeChild(testDiv); + return bi === 'none'; + } + // Other browsers use the forced-colors standard + return win.matchMedia && win.matchMedia('(forced-colors: active)').matches; + } + /** + * 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} + */ + function setHighContrastTheme(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(); + } + /* * + * + * Default Export + * + * */ + var whcm = { + isHighContrastModeActive: isHighContrastModeActive, + setHighContrastTheme: setHighContrastTheme + }; + + return whcm; + }); + _registerModule(_modules, 'Accessibility/HighContrastTheme.js', [], function () { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Default theme for Windows High Contrast Mode. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /* * + * + * Theme + * + * */ + 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' + } + }; + /* * + * + * Default Export + * + * */ + + return theme; + }); + _registerModule(_modules, 'Accessibility/Options/A11yDefaults.js', [], function () { + /* * + * + * (c) 2009-2021 Øystein Moseng + * + * Default options for accessibility. + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /* * + * + * API Options + * + * */ + /** + * 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 + */ /** + * Enable or disable exposing the point to assistive technology + * @name Highcharts.PointAccessibilityOptionsObject#enabled + * @type {boolean|undefined} + * @requires modules/accessibility + * @since 9.0.1 + */ + /* * + * @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/accessibility/accessibility-module). + * + * @since 5.0.0 + * @requires modules/accessibility + * @optionparent accessibility + */ + accessibility: { + /** + * Enable accessibility functionality for the chart. For more + * information on how to include these features, and why this is + * recommended, see [Highcharts Accessibility](https://www.highcharts.com/docs/accessibility/accessibility-module). + * + * Highcharts will by default emit a warning to the console if + * the [accessibility module](https://code.highcharts.com/modules/accessibility.js) + * is not loaded. Setting this option to `false` will override + * and silence the warning. + * + * Once the module is loaded, setting this option to `false` + * will disable the module for this 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 ``, `

    `, `

    `, ``, `