Carga
This commit is contained in:
2025-04-17 00:35:33 -06:00
parent 4977462629
commit 67fc72aed5
1333 changed files with 1077639 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Annotations accessibility code.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import HTMLUtilities from '../Utils/HTMLUtilities.js';
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<object>} 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<string>} 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 ? "<li>".concat(desc, "</li>") : '';
});
}
/**
* 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 "<ul style=\"list-style-type: none\">".concat(annotationItems.join(' '), "</ul>");
}
/**
* 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<string>} 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
};
export default AnnotationsA11y;

View File

@@ -0,0 +1,160 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Accessibility component for chart container.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import AccessibilityComponent from '../AccessibilityComponent.js';
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
import CU from '../Utils/ChartUtilities.js';
var unhideChartElementFromAT = CU.unhideChartElementFromAT, getChartTitle = CU.getChartTitle;
import H from '../../Core/Globals.js';
var doc = H.doc;
import HU from '../Utils/HTMLUtilities.js';
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
*
* */
export default ContainerComponent;

View File

@@ -0,0 +1,550 @@
/* *
*
* (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. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import A11yI18n from '../A11yI18n.js';
import AccessibilityComponent from '../AccessibilityComponent.js';
import Announcer from '../Utils/Announcer.js';
import AnnotationsA11y from './AnnotationsA11y.js';
var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML;
import AST from '../../Core/Renderer/HTML/AST.js';
import CU from '../Utils/ChartUtilities.js';
var getAxisDescription = CU.getAxisDescription, getAxisRangeDescription = CU.getAxisRangeDescription, getChartTitle = CU.getChartTitle, unhideChartElementFromAT = CU.unhideChartElementFromAT;
import F from '../../Core/FormatUtilities.js';
var format = F.format;
import H from '../../Core/Globals.js';
var doc = H.doc;
import HU from '../Utils/HTMLUtilities.js';
var addClass = HU.addClass, getElement = HU.getElement, getHeadingTagNameForElement = HU.getHeadingTagNameForElement, stripHTMLTagsFromString = HU.stripHTMLTagsFromString, visuallyHideElement = HU.visuallyHideElement;
import U from '../../Core/Utilities.js';
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<string>} 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 '<button id="' + buttonId + '">' + buttonText + '</button>';
};
/**
* @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 '<button id="' + buttonId + '">' + buttonText + '</button>';
};
/**
* @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 '<div id="' + id + '">' + markerText + '</div>';
};
/**
* @private
* @param {Highcharts.Dictionary<string>} 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
*
* */
export default InfoRegionsComponent;

View File

@@ -0,0 +1,486 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Accessibility component for chart legend.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import A from '../../Core/Animation/AnimationUtilities.js';
var animObject = A.animObject;
import H from '../../Core/Globals.js';
var doc = H.doc;
import Legend from '../../Core/Legend/Legend.js';
import U from '../../Core/Utilities.js';
var addEvent = U.addEvent, fireEvent = U.fireEvent, isNumber = U.isNumber, pick = U.pick, syncTimeout = U.syncTimeout;
import AccessibilityComponent from '../AccessibilityComponent.js';
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
import CU from '../Utils/ChartUtilities.js';
var getChartTitle = CU.getChartTitle;
import HU from '../Utils/HTMLUtilities.js';
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 || {}), unsupportedColorAxis = chart.colorAxis && chart.colorAxis.some(function (c) { return !c.dataClasses || !c.dataClasses.length; });
return !!(items && items.length &&
!unsupportedColorAxis &&
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 cannot 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(/<br ?\/?>/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 () {
if (!shouldDoLegendA11y(this.chart)) {
return false;
}
var chart = this.chart, legendOptions = chart.options.legend || {}, legendA11yOptions = (legendOptions.accessibility || {});
return !!(chart.legend.display &&
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
*
* */
export default LegendComponent;

View File

@@ -0,0 +1,452 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Accessibility component for exporting menu.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Chart from '../../Core/Chart/Chart.js';
import U from '../../Core/Utilities.js';
var attr = U.attr;
import AccessibilityComponent from '../AccessibilityComponent.js';
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
import ChartUtilities from '../Utils/ChartUtilities.js';
var getChartTitle = ChartUtilities.getChartTitle, unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
import HTMLUtilities from '../Utils/HTMLUtilities.js';
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
*
* */
export default MenuComponent;

View File

@@ -0,0 +1,508 @@
/* *
*
* (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. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import RangeSelector from '../../Extensions/RangeSelector.js';
import AccessibilityComponent from '../AccessibilityComponent.js';
import ChartUtilities from '../Utils/ChartUtilities.js';
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, getAxisRangeDescription = ChartUtilities.getAxisRangeDescription;
import Announcer from '../Utils/Announcer.js';
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
import U from '../../Core/Utilities.js';
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<Highcharts.KeyboardNavigationHandler>}
* 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
*
* */
export default RangeSelectorComponent;

View File

@@ -0,0 +1,218 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Handle forcing series markers.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import U from '../../../Core/Utilities.js';
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
*
* */
export default ForcedMarkersComposition;

View File

@@ -0,0 +1,324 @@
/* *
*
* (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. !!!!!!!
*
* */
'use strict';
import H from '../../../Core/Globals.js';
import U from '../../../Core/Utilities.js';
var addEvent = U.addEvent, defined = U.defined;
import Announcer from '../../Utils/Announcer.js';
import ChartUtilities from '../../Utils/ChartUtilities.js';
var getChartTitle = ChartUtilities.getChartTitle;
import EventProvider from '../../Utils/EventProvider.js';
import SeriesDescriber from './SeriesDescriber.js';
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<Highcharts.Series>} 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<Highcharts.Series>} 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
*
* */
export default NewDataAnnouncer;

View File

@@ -0,0 +1,149 @@
/* *
*
* (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. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import AccessibilityComponent from '../../AccessibilityComponent.js';
import ChartUtilities from '../../Utils/ChartUtilities.js';
var hideSeriesFromAT = ChartUtilities.hideSeriesFromAT;
import ForcedMarkers from './ForcedMarkers.js';
import NewDataAnnouncer from './NewDataAnnouncer.js';
import SeriesDescriber from './SeriesDescriber.js';
var describeSeries = SeriesDescriber.describeSeries;
import SeriesKeyboardNavigation from './SeriesKeyboardNavigation.js';
import Tooltip from '../../../Core/Tooltip.js';
/* *
*
* 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
*
* */
export default SeriesComponent;

View File

@@ -0,0 +1,407 @@
/* *
*
* (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. !!!!!!!
*
* */
'use strict';
import AnnotationsA11y from '../AnnotationsA11y.js';
var getPointAnnotationTexts = AnnotationsA11y.getPointAnnotationTexts;
import ChartUtilities from '../../Utils/ChartUtilities.js';
var getAxisDescription = ChartUtilities.getAxisDescription, getSeriesFirstPointElement = ChartUtilities.getSeriesFirstPointElement, getSeriesA11yElement = ChartUtilities.getSeriesA11yElement, unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
import F from '../../../Core/FormatUtilities.js';
var format = F.format, numberFormat = F.numberFormat;
import HTMLUtilities from '../../Utils/HTMLUtilities.js';
var reverseChildNodes = HTMLUtilities.reverseChildNodes, stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
import U from '../../../Core/Utilities.js';
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('<br/>', ' '), canUseId = defined(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 cannot 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
};
export default SeriesDescriber;

View File

@@ -0,0 +1,707 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Handle keyboard navigation for series.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Point from '../../../Core/Series/Point.js';
import Series from '../../../Core/Series/Series.js';
import SeriesRegistry from '../../../Core/Series/SeriesRegistry.js';
var seriesTypes = SeriesRegistry.seriesTypes;
import H from '../../../Core/Globals.js';
var doc = H.doc;
import U from '../../../Core/Utilities.js';
var defined = U.defined, fireEvent = U.fireEvent;
import KeyboardNavigationHandler from '../../KeyboardNavigationHandler.js';
import EventProvider from '../../Utils/EventProvider.js';
import ChartUtilities from '../../Utils/ChartUtilities.js';
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
*
* */
export default SeriesKeyboardNavigation;

View File

@@ -0,0 +1,338 @@
/* *
*
* (c) 2009-2021 Øystein Moseng
*
* Accessibility component for chart zoom.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import AccessibilityComponent from '../AccessibilityComponent.js';
import CU from '../Utils/ChartUtilities.js';
var unhideChartElementFromAT = CU.unhideChartElementFromAT;
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
import U from '../../Core/Utilities.js';
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<Highcharts.KeyboardNavigationHandler>}
* 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
*
* */
export default ZoomComponent;