tupali/librerias/gantt/code/es-modules/modules/accessibility/components/InfoRegionsComponent/InfoRegionsComponent.js
2020-05-23 15:45:54 -05:00

500 lines
19 KiB
JavaScript

/* *
*
* (c) 2009-2019 Ø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';
import H from '../../../../parts/Globals.js';
var doc = H.win.document, format = H.format;
import U from '../../../../parts/Utilities.js';
var extend = U.extend, pick = U.pick;
import AccessibilityComponent from '../../AccessibilityComponent.js';
import AnnotationsA11y from '../AnnotationsA11y.js';
var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML;
import ChartUtilities from '../../utils/chartUtilities.js';
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, getChartTitle = ChartUtilities.getChartTitle, getAxisDescription = ChartUtilities.getAxisDescription;
import HTMLUtilities from '../../utils/htmlUtilities.js';
var addClass = HTMLUtilities.addClass, setElAttrs = HTMLUtilities.setElAttrs, escapeStringForHTML = HTMLUtilities.escapeStringForHTML, stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString, getElement = HTMLUtilities.getElement, visuallyHideElement = HTMLUtilities.visuallyHideElement;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* @private
*/
function getTypeDescForMapChart(chart, formatContext) {
return formatContext.mapTitle ?
chart.langFormat('accessibility.chartTypes.mapTypeDescription', formatContext) :
chart.langFormat('accessibility.chartTypes.unknownMap', formatContext);
}
/**
* @private
*/
function getTypeDescForCombinationChart(chart, formatContext) {
return chart.langFormat('accessibility.chartTypes.combinationChart', formatContext);
}
/**
* @private
*/
function getTypeDescForEmptyChart(chart, formatContext) {
return chart.langFormat('accessibility.chartTypes.emptyChart', formatContext);
}
/**
* @private
*/
function buildTypeDescriptionFromSeries(chart, types, context) {
var firstType = types[0], typeExplaination = chart.langFormat('accessibility.seriesTypeDescriptions.' + firstType, context), multi = chart.series && chart.series.length < 2 ? 'Single' : 'Multiple';
return (chart.langFormat('accessibility.chartTypes.' + firstType + multi, context) ||
chart.langFormat('accessibility.chartTypes.default' + multi, context)) + (typeExplaination ? ' ' + typeExplaination : '');
}
/**
* @private
*/
function getTableSummary(chart) {
return chart.langFormat('accessibility.table.tableSummary', { chart: chart });
}
/**
* @private
*/
function stripEmptyHTMLTags(str) {
return str.replace(/<(\w+)[^>]*?>\s*<\/\1>/g, '');
}
/**
* @private
*/
function enableSimpleHTML(str) {
return str
.replace(/&lt;(h[1-7]|p|div|ul|ol|li)&gt;/g, '<$1>')
.replace(/&lt;&#x2F;(h[1-7]|p|div|ul|ol|li|a|button)&gt;/g, '</$1>')
.replace(/&lt;(div|a|button) id=&quot;([a-zA-Z\-0-9#]*?)&quot;&gt;/g, '<$1 id="$2">');
}
/**
* @private
*/
function stringToSimpleHTML(str) {
return stripEmptyHTMLTags(enableSimpleHTML(escapeStringForHTML(str)));
}
/**
* Return simplified explaination of chart type. Some types will not be familiar
* to most users, but in those cases we try to add an explaination of the type.
*
* @private
* @function Highcharts.Chart#getTypeDescription
* @param {Array<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, component = this;
this.initRegionsDefinitions();
this.addEvent(chart, 'afterGetTable', function (e) {
component.onDataTableCreated(e);
});
this.addEvent(chart, 'afterViewData', function (tableDiv) {
component.dataTableDiv = tableDiv;
// Use small delay to give browsers & AT time to register new table
setTimeout(function () {
component.focusDataTable();
}, 300);
});
},
/**
* @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.dataTableButtonId !== 'undefined') {
component.initDataTableButton(component.dataTableButtonId);
}
}
},
after: {
element: null,
buildContent: function (chart) {
var formatter = chart.options.accessibility.screenReaderSection
.afterChartFormatter;
return formatter ? formatter(chart) :
component.defaultAfterChartFormatter();
},
insertIntoDOM: function (el, chart) {
chart.renderTo.insertBefore(el, chart.container.nextSibling);
}
}
};
},
/**
* Called on chart render. Have to update the sections on render, in order
* to get a11y info from series.
*/
onChartRender: function () {
var component = this;
this.linkedDescriptionElement = this.getLinkedDescriptionElement();
this.setLinkedDescriptionAttrs();
Object.keys(this.screenReaderSections).forEach(function (regionKey) {
component.updateScreenReaderSection(regionKey);
});
},
/**
* @private
*/
getLinkedDescriptionElement: function () {
var chartOptions = this.chart.options, linkedDescOption = chartOptions.accessibility.linkedDescription;
if (!linkedDescOption) {
return;
}
if (typeof linkedDescOption !== 'string') {
return linkedDescOption;
}
var query = format(linkedDescOption, this.chart), queryMatch = doc.querySelectorAll(query);
if (queryMatch.length === 1) {
return queryMatch[0];
}
},
/**
* @private
*/
setLinkedDescriptionAttrs: function () {
var el = this.linkedDescriptionElement;
if (el) {
el.setAttribute('aria-hidden', 'true');
addClass(el, 'highcharts-linked-description');
}
},
/**
* @private
* @param {string} regionKey The name/key of the region to update
*/
updateScreenReaderSection: function (regionKey) {
var chart = this.chart, region = this.screenReaderSections[regionKey], content = region.buildContent(chart), sectionDiv = region.element = (region.element || this.createElement('div')), hiddenDiv = (sectionDiv.firstChild || this.createElement('div'));
this.setScreenReaderSectionAttribs(sectionDiv, regionKey);
hiddenDiv.innerHTML = content;
sectionDiv.appendChild(hiddenDiv);
region.insertIntoDOM(sectionDiv, chart);
visuallyHideElement(hiddenDiv);
unhideChartElementFromAT(chart, hiddenDiv);
if (region.afterInserted) {
region.afterInserted();
}
},
/**
* @private
* @param {Highcharts.HTMLDOMElement} sectionDiv The section element
* @param {string} regionKey Name/key of the region we are setting attrs for
*/
setScreenReaderSectionAttribs: function (sectionDiv, regionKey) {
var labelLangKey = ('accessibility.screenReaderSection.' + regionKey + 'RegionLabel'), chart = this.chart, labelText = chart.langFormat(labelLangKey, { chart: chart }), sectionId = 'highcharts-screen-reader-region-' + regionKey + '-' +
chart.index;
setElAttrs(sectionDiv, {
id: sectionId,
'aria-label': labelText
});
// Sections are wrapped to be positioned relatively to chart in case
// elements inside are tabbed to.
sectionDiv.style.position = 'relative';
if (chart.options.accessibility.landmarkVerbosity === 'all' &&
labelText) {
sectionDiv.setAttribute('role', 'region');
}
},
/**
* @private
* @return {string}
*/
defaultBeforeChartFormatter: function () {
var chart = this.chart, format = chart.options.accessibility
.screenReaderSection.beforeChartFormat, axesDesc = this.getAxesDescription(), dataTableButtonId = 'hc-linkto-highcharts-data-table-' +
chart.index, annotationsTitleStr = chart.langFormat('accessibility.screenReaderSection.annotations.heading', { chart: chart }), context = {
chartTitle: getChartTitle(chart),
typeDescription: this.getTypeDescriptionText(),
chartSubtitle: this.getSubtitleText(),
chartLongdesc: this.getLongdescText(),
xAxisDescription: axesDesc.xAxis,
yAxisDescription: axesDesc.yAxis,
viewTableButton: chart.getCSV ?
this.getDataTableButtonText(dataTableButtonId) : '',
annotationsTitle: annotationsTitleStr,
annotationsList: getAnnotationsInfoHTML(chart)
}, formattedString = H.i18nFormat(format, context, chart);
this.dataTableButtonId = dataTableButtonId;
return stringToSimpleHTML(formattedString);
},
/**
* @private
* @return {string}
*/
defaultAfterChartFormatter: function () {
var chart = this.chart, format = chart.options.accessibility
.screenReaderSection.afterChartFormat, context = {
endOfChartMarker: this.getEndOfChartMarkerText()
}, formattedString = H.i18nFormat(format, context, chart);
return stringToSimpleHTML(formattedString);
},
/**
* @private
* @return {string}
*/
getLinkedDescription: function () {
var el = this.linkedDescriptionElement, content = el && el.innerHTML || '';
return stripHTMLTagsFromString(content);
},
/**
* @private
* @return {string}
*/
getLongdescText: function () {
var chartOptions = this.chart.options, captionOptions = chartOptions.caption, captionText = captionOptions && captionOptions.text, linkedDescription = this.getLinkedDescription();
return (chartOptions.accessibility.description ||
linkedDescription ||
captionText ||
'');
},
/**
* @private
* @return {string}
*/
getTypeDescriptionText: function () {
var chart = this.chart;
return chart.types ?
chart.options.accessibility.typeDescription ||
chart.getTypeDescription(chart.types) : '';
},
/**
* @private
* @param {string} buttonId
* @return {string}
*/
getDataTableButtonText: function (buttonId) {
var chart = this.chart, buttonText = chart.langFormat('accessibility.table.viewAsDataTableButtonText', { chart: chart, chartTitle: getChartTitle(chart) });
return '<a id="' + buttonId + '">' + buttonText + '</a>';
},
/**
* @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');
}
e.html = e.html.replace('<table ', '<table tabindex="0" summary="' + getTableSummary(chart) + '"');
}
},
/**
* @private
*/
focusDataTable: function () {
var tableDiv = this.dataTableDiv, table = tableDiv && tableDiv.getElementsByTagName('table')[0];
if (table && table.focus) {
table.focus();
}
},
/**
* 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, {
role: 'button',
tabindex: '-1',
'aria-expanded': !!getElement(tableId),
href: '#' + 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 component = this, chart = this.chart, axes = chart[collectionKey];
return chart.langFormat('accessibility.axis.' + collectionKey + 'Description' + (axes.length > 1 ? 'Plural' : 'Singular'), {
chart: chart,
names: axes.map(function (axis) {
return getAxisDescription(axis);
}),
ranges: axes.map(function (axis) {
return component.getAxisRangeDescription(axis);
}),
numAxes: axes.length
});
},
/**
* Return string with text description of the axis range.
* @private
* @param {Highcharts.Axis} axis The axis to get range desc of.
* @return {string} A string with the range description for the axis.
*/
getAxisRangeDescription: function (axis) {
var axisOptions = axis.options || {};
// Handle overridden range description
if (axisOptions.accessibility &&
typeof axisOptions.accessibility.rangeDescription !== 'undefined') {
return axisOptions.accessibility.rangeDescription;
}
// Handle category axes
if (axis.categories) {
return this.getCategoryAxisRangeDesc(axis);
}
// Use time range, not from-to?
if (axis.isDatetimeAxis && (axis.min === 0 || axis.dataMin === 0)) {
return this.getAxisTimeLengthDesc(axis);
}
// Just use from and to.
// We have the range and the unit to use, find the desc format
return this.getAxisFromToDescription(axis);
},
/**
* @private
* @param {Highcharts.Axis} axis
* @return {string}
*/
getCategoryAxisRangeDesc: function (axis) {
var chart = this.chart;
if (axis.dataMax && axis.dataMin) {
return chart.langFormat('accessibility.axis.rangeCategories', {
chart: chart,
axis: axis,
numCategories: axis.dataMax - axis.dataMin + 1
});
}
return '';
},
/**
* @private
* @param {Highcharts.Axis} axis
* @return {string}
*/
getAxisTimeLengthDesc: function (axis) {
var chart = this.chart, range = {}, rangeUnit = 'Seconds';
range.Seconds = ((axis.max || 0) - (axis.min || 0)) / 1000;
range.Minutes = range.Seconds / 60;
range.Hours = range.Minutes / 60;
range.Days = range.Hours / 24;
['Minutes', 'Hours', 'Days'].forEach(function (unit) {
if (range[unit] > 2) {
rangeUnit = unit;
}
});
var rangeValue = range[rangeUnit].toFixed(rangeUnit !== 'Seconds' &&
rangeUnit !== 'Minutes' ? 1 : 0 // Use decimals for days/hours
);
// We have the range and the unit to use, find the desc format
return chart.langFormat('accessibility.axis.timeRange' + rangeUnit, {
chart: chart,
axis: axis,
range: rangeValue.replace('.0', '')
});
},
/**
* @private
* @param {Highcharts.Axis} axis
* @return {string}
*/
getAxisFromToDescription: function (axis) {
var chart = this.chart, dateRangeFormat = chart.options.accessibility
.screenReaderSection.axisRangeDateFormat, format = function (axisKey) {
return axis.isDatetimeAxis ? chart.time.dateFormat(dateRangeFormat, axis[axisKey]) : axis[axisKey];
};
return chart.langFormat('accessibility.axis.rangeFromTo', {
chart: chart,
axis: axis,
rangeFrom: format('min'),
rangeTo: format('max')
});
}
});
export default InfoRegionsComponent;