| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- /* *
- *
- * (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. !!!!!!!
- *
- * */
- import H from '../../Core/Globals.js';
- var doc = H.doc;
- import AST from '../../Core/Renderer/HTML/AST.js';
- import U from '../../Core/Utilities.js';
- var extend = U.extend, format = U.format, pick = U.pick;
- import AccessibilityComponent from '../AccessibilityComponent.js';
- import Announcer from '../Utils/Announcer.js';
- import AnnotationsA11y from './AnnotationsA11y.js';
- var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML;
- import ChartUtilities from '../Utils/ChartUtilities.js';
- var getAxisDescription = ChartUtilities.getAxisDescription, getAxisRangeDescription = ChartUtilities.getAxisRangeDescription, getChartTitle = ChartUtilities.getChartTitle, unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
- import HTMLUtilities from '../Utils/HTMLUtilities.js';
- var addClass = HTMLUtilities.addClass, escapeStringForHTML = HTMLUtilities.escapeStringForHTML, getElement = HTMLUtilities.getElement, getHeadingTagNameForElement = HTMLUtilities.getHeadingTagNameForElement, setElAttrs = HTMLUtilities.setElAttrs, stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString, visuallyHideElement = HTMLUtilities.visuallyHideElement;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * @private
- */
- function stripEmptyHTMLTags(str) {
- return str.replace(/<(\w+)[^>]*?>\s*<\/\1>/g, '');
- }
- /**
- * @private
- */
- function getTypeDescForMapChart(chart, formatContext) {
- return formatContext.mapTitle ?
- chart.langFormat('accessibility.chartTypes.mapTypeDescription', formatContext) :
- chart.langFormat('accessibility.chartTypes.unknownMap', formatContext);
- }
- /**
- * @private
- */
- function getTypeDescForCombinationChart(chart, formatContext) {
- return chart.langFormat('accessibility.chartTypes.combinationChart', formatContext);
- }
- /**
- * @private
- */
- function getTypeDescForEmptyChart(chart, formatContext) {
- return chart.langFormat('accessibility.chartTypes.emptyChart', formatContext);
- }
- /**
- * @private
- */
- function buildTypeDescriptionFromSeries(chart, types, context) {
- var firstType = types[0], typeExplaination = chart.langFormat('accessibility.seriesTypeDescriptions.' + firstType, context), multi = chart.series && chart.series.length < 2 ? 'Single' : 'Multiple';
- return (chart.langFormat('accessibility.chartTypes.' + firstType + multi, context) ||
- chart.langFormat('accessibility.chartTypes.default' + multi, context)) + (typeExplaination ? ' ' + typeExplaination : '');
- }
- /**
- * @private
- */
- function getTableSummary(chart) {
- return chart.langFormat('accessibility.table.tableSummary', { chart: chart });
- }
- /**
- * 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.
- */
- H.Chart.prototype.getTypeDescription = function (types) {
- var firstType = types[0], firstSeries = this.series && this.series[0] || {}, formatContext = {
- numSeries: this.series.length,
- numPoints: firstSeries.points && firstSeries.points.length,
- chart: this,
- mapTitle: firstSeries.mapTitle
- };
- if (!firstType) {
- return getTypeDescForEmptyChart(this, formatContext);
- }
- if (firstType === 'map') {
- return getTypeDescForMapChart(this, formatContext);
- }
- if (this.types.length > 1) {
- return getTypeDescForCombinationChart(this, formatContext);
- }
- return buildTypeDescriptionFromSeries(this, types, formatContext);
- };
- /**
- * The InfoRegionsComponent class
- *
- * @private
- * @class
- * @name Highcharts.InfoRegionsComponent
- */
- var InfoRegionsComponent = function () { };
- InfoRegionsComponent.prototype = new AccessibilityComponent();
- extend(InfoRegionsComponent.prototype, /** @lends Highcharts.InfoRegionsComponent */ {
- /**
- * Init the component
- * @private
- */
- init: function () {
- var chart = this.chart;
- var component = this;
- this.initRegionsDefinitions();
- this.addEvent(chart, '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
- */
- initRegionsDefinitions: function () {
- var component = this;
- this.screenReaderSections = {
- before: {
- element: null,
- buildContent: function (chart) {
- var formatter = chart.options.accessibility
- .screenReaderSection.beforeChartFormatter;
- return formatter ? formatter(chart) :
- component.defaultBeforeChartFormatter(chart);
- },
- insertIntoDOM: function (el, chart) {
- chart.renderTo.insertBefore(el, chart.renderTo.firstChild);
- },
- afterInserted: function () {
- if (typeof component.sonifyButtonId !== 'undefined') {
- component.initSonifyButton(component.sonifyButtonId);
- }
- if (typeof component.dataTableButtonId !== 'undefined') {
- component.initDataTableButton(component.dataTableButtonId);
- }
- }
- },
- after: {
- element: null,
- buildContent: function (chart) {
- var formatter = chart.options.accessibility.screenReaderSection
- .afterChartFormatter;
- return formatter ? formatter(chart) :
- component.defaultAfterChartFormatter();
- },
- insertIntoDOM: function (el, chart) {
- chart.renderTo.insertBefore(el, chart.container.nextSibling);
- }
- }
- };
- },
- /**
- * Called on chart render. Have to update the sections on render, in order
- * to get a11y info from series.
- */
- onChartRender: function () {
- var component = this;
- this.linkedDescriptionElement = this.getLinkedDescriptionElement();
- this.setLinkedDescriptionAttrs();
- Object.keys(this.screenReaderSections).forEach(function (regionKey) {
- component.updateScreenReaderSection(regionKey);
- });
- },
- /**
- * @private
- */
- getLinkedDescriptionElement: function () {
- var chartOptions = this.chart.options, linkedDescOption = chartOptions.accessibility.linkedDescription;
- if (!linkedDescOption) {
- return;
- }
- if (typeof linkedDescOption !== 'string') {
- return linkedDescOption;
- }
- var query = format(linkedDescOption, this.chart), queryMatch = doc.querySelectorAll(query);
- if (queryMatch.length === 1) {
- return queryMatch[0];
- }
- },
- /**
- * @private
- */
- setLinkedDescriptionAttrs: function () {
- var el = this.linkedDescriptionElement;
- if (el) {
- el.setAttribute('aria-hidden', 'true');
- addClass(el, 'highcharts-linked-description');
- }
- },
- /**
- * @private
- * @param {string} regionKey The name/key of the region to update
- */
- updateScreenReaderSection: function (regionKey) {
- var chart = this.chart, region = this.screenReaderSections[regionKey], content = region.buildContent(chart), sectionDiv = region.element = (region.element || this.createElement('div')), hiddenDiv = (sectionDiv.firstChild || this.createElement('div'));
- this.setScreenReaderSectionAttribs(sectionDiv, regionKey);
- AST.setElementHTML(hiddenDiv, content);
- sectionDiv.appendChild(hiddenDiv);
- region.insertIntoDOM(sectionDiv, chart);
- visuallyHideElement(hiddenDiv);
- unhideChartElementFromAT(chart, hiddenDiv);
- if (region.afterInserted) {
- region.afterInserted();
- }
- },
- /**
- * @private
- * @param {Highcharts.HTMLDOMElement} sectionDiv The section element
- * @param {string} regionKey Name/key of the region we are setting attrs for
- */
- setScreenReaderSectionAttribs: function (sectionDiv, regionKey) {
- var labelLangKey = ('accessibility.screenReaderSection.' + regionKey + 'RegionLabel'), chart = this.chart, labelText = chart.langFormat(labelLangKey, { chart: chart }), sectionId = 'highcharts-screen-reader-region-' + regionKey + '-' +
- chart.index;
- setElAttrs(sectionDiv, {
- id: sectionId,
- 'aria-label': labelText
- });
- // Sections are wrapped to be positioned relatively to chart in case
- // elements inside are tabbed to.
- sectionDiv.style.position = 'relative';
- if (chart.options.accessibility.landmarkVerbosity === 'all' &&
- labelText) {
- sectionDiv.setAttribute('role', 'region');
- }
- },
- /**
- * @private
- * @return {string}
- */
- defaultBeforeChartFormatter: function () {
- var _a;
- var chart = this.chart, format = chart.options.accessibility
- .screenReaderSection.beforeChartFormat, axesDesc = this.getAxesDescription(), shouldHaveSonifyBtn = chart.sonify && ((_a = chart.options.sonification) === null || _a === void 0 ? void 0 : _a.enabled), sonifyButtonId = 'highcharts-a11y-sonify-data-btn-' +
- chart.index, dataTableButtonId = 'hc-linkto-highcharts-data-table-' +
- chart.index, annotationsList = getAnnotationsInfoHTML(chart), annotationsTitleStr = chart.langFormat('accessibility.screenReaderSection.annotations.heading', { chart: chart }), context = {
- 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 = H.i18nFormat(format, context, chart);
- this.dataTableButtonId = dataTableButtonId;
- this.sonifyButtonId = sonifyButtonId;
- return stripEmptyHTMLTags(formattedString);
- },
- /**
- * @private
- * @return {string}
- */
- defaultAfterChartFormatter: function () {
- var chart = this.chart, format = chart.options.accessibility
- .screenReaderSection.afterChartFormat, context = {
- endOfChartMarker: this.getEndOfChartMarkerText()
- }, formattedString = H.i18nFormat(format, context, chart);
- return stripEmptyHTMLTags(formattedString);
- },
- /**
- * @private
- * @return {string}
- */
- getLinkedDescription: function () {
- var el = this.linkedDescriptionElement, content = el && el.innerHTML || '';
- return stripHTMLTagsFromString(content);
- },
- /**
- * @private
- * @return {string}
- */
- getLongdescText: function () {
- var chartOptions = this.chart.options, captionOptions = chartOptions.caption, captionText = captionOptions && captionOptions.text, linkedDescription = this.getLinkedDescription();
- return (chartOptions.accessibility.description ||
- linkedDescription ||
- captionText ||
- '');
- },
- /**
- * @private
- * @return {string}
- */
- getTypeDescriptionText: function () {
- var chart = this.chart;
- return chart.types ?
- chart.options.accessibility.typeDescription ||
- chart.getTypeDescription(chart.types) : '';
- },
- /**
- * @private
- * @param {string} buttonId
- * @return {string}
- */
- getDataTableButtonText: function (buttonId) {
- var chart = this.chart, buttonText = chart.langFormat('accessibility.table.viewAsDataTableButtonText', { chart: chart, chartTitle: getChartTitle(chart) });
- return '<button id="' + buttonId + '">' + buttonText + '</button>';
- },
- /**
- * @private
- * @param {string} buttonId
- * @return {string}
- */
- getSonifyButtonText: function (buttonId) {
- var _a;
- var chart = this.chart;
- if (((_a = chart.options.sonification) === null || _a === void 0 ? void 0 : _a.enabled) === false) {
- return '';
- }
- var buttonText = chart.langFormat('accessibility.sonification.playAsSoundButtonText', { chart: chart, chartTitle: getChartTitle(chart) });
- return '<button id="' + buttonId + '">' + buttonText + '</button>';
- },
- /**
- * @private
- * @return {string}
- */
- getSubtitleText: function () {
- var subtitle = (this.chart.options.subtitle);
- return stripHTMLTagsFromString(subtitle && subtitle.text || '');
- },
- /**
- * @private
- * @return {string}
- */
- getEndOfChartMarkerText: function () {
- var chart = this.chart, markerText = chart.langFormat('accessibility.screenReaderSection.endOfChartMarker', { chart: chart }), id = 'highcharts-end-of-chart-marker-' + chart.index;
- return '<div id="' + id + '">' + markerText + '</div>';
- },
- /**
- * @private
- * @param {Highcharts.Dictionary<string>} e
- */
- 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
- */
- focusDataTable: function () {
- var tableDiv = this.dataTableDiv, table = tableDiv && tableDiv.getElementsByTagName('table')[0];
- if (table && table.focus) {
- table.focus();
- }
- },
- /**
- * @private
- * @param {string} sonifyButtonId
- */
- initSonifyButton: function (sonifyButtonId) {
- var _this = this;
- var el = this.sonifyButton = getElement(sonifyButtonId);
- var chart = this.chart;
- var defaultHandler = function (e) {
- el === null || el === void 0 ? void 0 : el.setAttribute('aria-hidden', 'true');
- el === null || el === void 0 ? void 0 : el.setAttribute('aria-label', '');
- e.preventDefault();
- e.stopPropagation();
- var announceMsg = chart.langFormat('accessibility.sonification.playAsSoundClickAnnouncement', { chart: chart });
- _this.announcer.announce(announceMsg);
- setTimeout(function () {
- el === null || el === void 0 ? void 0 : el.removeAttribute('aria-hidden');
- el === null || el === void 0 ? void 0 : el.removeAttribute('aria-label');
- if (chart.sonify) {
- chart.sonify();
- }
- }, 1000); // Delay to let screen reader speak the button press
- };
- if (el && chart) {
- setElAttrs(el, {
- tabindex: '-1'
- });
- el.onclick = function (e) {
- var _a;
- var onPlayAsSoundClick = (_a = chart.options.accessibility) === null || _a === void 0 ? void 0 : _a.screenReaderSection.onPlayAsSoundClick;
- (onPlayAsSoundClick || defaultHandler).call(this, e, chart);
- };
- }
- },
- /**
- * Set attribs and handlers for default viewAsDataTable button if exists.
- * @private
- * @param {string} tableButtonId
- */
- initDataTableButton: function (tableButtonId) {
- var el = this.viewDataTableButton = getElement(tableButtonId), chart = this.chart, tableId = tableButtonId.replace('hc-linkto-', '');
- if (el) {
- setElAttrs(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
- * @return {Highcharts.Dictionary<string>}
- */
- getAxesDescription: function () {
- var chart = this.chart, shouldDescribeColl = function (collectionKey, defaultCondition) {
- var axes = chart[collectionKey];
- return axes.length > 1 || axes[0] &&
- pick(axes[0].options.accessibility &&
- axes[0].options.accessibility.enabled, defaultCondition);
- }, hasNoMap = !!chart.types && chart.types.indexOf('map') < 0, hasCartesian = !!chart.hasCartesianSeries, showXAxes = shouldDescribeColl('xAxis', !chart.angular && hasCartesian && hasNoMap), showYAxes = shouldDescribeColl('yAxis', hasCartesian && hasNoMap), desc = {};
- if (showXAxes) {
- desc.xAxis = this.getAxisDescriptionText('xAxis');
- }
- if (showYAxes) {
- desc.yAxis = this.getAxisDescriptionText('yAxis');
- }
- return desc;
- },
- /**
- * @private
- * @param {string} collectionKey
- * @return {string}
- */
- getAxisDescriptionText: function (collectionKey) {
- var 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
- */
- destroy: function () {
- var _a;
- (_a = this.announcer) === null || _a === void 0 ? void 0 : _a.destroy();
- }
- });
- export default InfoRegionsComponent;
|