Icard/angular-clarity-master(work.../node_modules/highcharts/es-modules/Accessibility/ProxyProvider.js

316 lines
11 KiB
JavaScript
Raw Permalink Normal View History

2024-07-16 14:55:36 +00:00
/* *
*
* (c) 2009-2024 Øystein Moseng
*
* Proxy elements are used to shadow SVG elements in HTML for assistive
* technology, such as screen readers or voice input software.
*
* The ProxyProvider keeps track of all proxy elements of the a11y module,
* and updating their order and positioning.
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
const { doc } = H;
import U from '../Core/Utilities.js';
const { attr, css } = U;
import CU from './Utils/ChartUtilities.js';
const { unhideChartElementFromAT } = CU;
import DOMElementProvider from './Utils/DOMElementProvider.js';
import HU from './Utils/HTMLUtilities.js';
const { removeChildNodes } = HU;
import ProxyElement from './ProxyElement.js';
/* *
*
* Class
*
* */
/**
* Keeps track of all proxy elements and proxy groups.
*
* @private
* @class
*/
class ProxyProvider {
/* *
*
* Constructor
*
* */
constructor(chart) {
this.chart = chart;
this.domElementProvider = new DOMElementProvider();
this.groups = {};
this.groupOrder = [];
this.beforeChartProxyPosContainer = this.createProxyPosContainer('before');
this.afterChartProxyPosContainer = this.createProxyPosContainer('after');
this.update();
}
/* *
*
* Functions
*
* */
/* eslint-disable */
/**
* Add a new proxy element to a group, proxying a target control.
*/
addProxyElement(groupKey, target, proxyElementType = 'button', attributes) {
const group = this.groups[groupKey];
if (!group) {
throw new Error('ProxyProvider.addProxyElement: Invalid group key ' + groupKey);
}
const wrapperElementType = group.type === 'ul' || group.type === 'ol' ?
'li' : void 0, proxy = new ProxyElement(this.chart, target, proxyElementType, wrapperElementType, attributes);
group.proxyContainerElement.appendChild(proxy.element);
group.proxyElements.push(proxy);
return proxy;
}
/**
* Create a group that will contain proxy elements. The group order is
* automatically updated according to the last group order keys.
*
* Returns the added group.
*/
addGroup(groupKey, groupElementType = 'div', attributes) {
const existingGroup = this.groups[groupKey];
if (existingGroup) {
return existingGroup.groupElement;
}
const proxyContainer = this.domElementProvider
.createElement(groupElementType);
// If we want to add a role to the group, and still use e.g.
// a list group, we need a wrapper div around the proxyContainer.
// Used for setting region role on legend.
let groupElement;
if (attributes && attributes.role && groupElementType !== 'div') {
groupElement = this.domElementProvider.createElement('div');
groupElement.appendChild(proxyContainer);
}
else {
groupElement = proxyContainer;
}
groupElement.className = 'highcharts-a11y-proxy-group highcharts-a11y-proxy-group-' +
groupKey.replace(/\W/g, '-');
this.groups[groupKey] = {
proxyContainerElement: proxyContainer,
groupElement,
type: groupElementType,
proxyElements: []
};
attr(groupElement, attributes || {});
if (groupElementType === 'ul') {
proxyContainer.setAttribute('role', 'list'); // Needed for webkit
}
// Add the group to the end by default, and perhaps then we
// won't have to reorder the whole set of groups.
this.afterChartProxyPosContainer.appendChild(groupElement);
this.updateGroupOrder(this.groupOrder);
return groupElement;
}
/**
* Update HTML attributes of a group.
*/
updateGroupAttrs(groupKey, attributes) {
const group = this.groups[groupKey];
if (!group) {
throw new Error('ProxyProvider.updateGroupAttrs: Invalid group key ' + groupKey);
}
attr(group.groupElement, attributes);
}
/**
* Reorder the proxy groups.
*
* The group key "series" refers to the chart's data points / <svg> element.
* This is so that the keyboardNavigation.order option can be used to
* determine the proxy group order.
*/
updateGroupOrder(groupKeys) {
// Store so that we can update order when a new group is created
this.groupOrder = groupKeys.slice();
// Don't unnecessarily reorder, because keyboard focus is lost
if (this.isDOMOrderGroupOrder()) {
return;
}
const seriesIx = groupKeys.indexOf('series');
const beforeKeys = seriesIx > -1 ? groupKeys.slice(0, seriesIx) : groupKeys;
const afterKeys = seriesIx > -1 ? groupKeys.slice(seriesIx + 1) : [];
// Store focused element since it will be lost when reordering
const activeElement = doc.activeElement;
// Add groups to correct container
['before', 'after'].forEach((pos) => {
const posContainer = this[pos === 'before' ?
'beforeChartProxyPosContainer' :
'afterChartProxyPosContainer'];
const keys = pos === 'before' ? beforeKeys : afterKeys;
removeChildNodes(posContainer);
keys.forEach((groupKey) => {
const group = this.groups[groupKey];
if (group) {
posContainer.appendChild(group.groupElement);
}
});
});
// Attempt to restore focus after reordering, but note that this may
// cause screen readers re-announcing the button.
if ((this.beforeChartProxyPosContainer.contains(activeElement) ||
this.afterChartProxyPosContainer.contains(activeElement)) &&
activeElement && activeElement.focus) {
activeElement.focus();
}
}
/**
* Remove all proxy elements in a group
*/
clearGroup(groupKey) {
const group = this.groups[groupKey];
if (!group) {
throw new Error('ProxyProvider.clearGroup: Invalid group key ' + groupKey);
}
removeChildNodes(group.proxyContainerElement);
}
/**
* Remove a group from the DOM and from the proxy provider's group list.
* All child elements are removed.
* If the group does not exist, nothing happens.
*/
removeGroup(groupKey) {
const group = this.groups[groupKey];
if (group) {
// Remove detached HTML elements to prevent memory leak (#20329).
this.domElementProvider.removeElement(group.groupElement);
// Sometimes groupElement is a wrapper around the proxyContainer, so
// the real one proxyContainer needs to be removed also.
if (group.groupElement !== group.proxyContainerElement) {
this.domElementProvider.removeElement(group.proxyContainerElement);
}
delete this.groups[groupKey];
}
}
/**
* Update the position and order of all proxy groups and elements
*/
update() {
this.updatePosContainerPositions();
this.updateGroupOrder(this.groupOrder);
this.updateProxyElementPositions();
}
/**
* Update all proxy element positions
*/
updateProxyElementPositions() {
Object.keys(this.groups).forEach(this.updateGroupProxyElementPositions.bind(this));
}
/**
* Update a group's proxy elements' positions.
* If the group does not exist, nothing happens.
*/
updateGroupProxyElementPositions(groupKey) {
const group = this.groups[groupKey];
if (group) {
group.proxyElements.forEach((el) => el.refreshPosition());
}
}
/**
* Remove all added elements
*/
destroy() {
this.domElementProvider.destroyCreatedElements();
}
// -------------------------- private ------------------------------------
/**
* Create and return a pos container element (the overall containers for
* the proxy groups).
*/
createProxyPosContainer(classNamePostfix) {
const el = this.domElementProvider.createElement('div');
el.setAttribute('aria-hidden', 'false');
el.className = 'highcharts-a11y-proxy-container' + (classNamePostfix ? '-' + classNamePostfix : '');
css(el, {
top: '0',
left: '0'
});
if (!this.chart.styledMode) {
el.style.whiteSpace = 'nowrap';
el.style.position = 'absolute';
}
return el;
}
/**
* Get an array of group keys that corresponds to the current group order
* in the DOM.
*/
getCurrentGroupOrderInDOM() {
const getGroupKeyFromElement = (el) => {
const allGroups = Object.keys(this.groups);
let i = allGroups.length;
while (i--) {
const groupKey = allGroups[i];
const group = this.groups[groupKey];
if (group && el === group.groupElement) {
return groupKey;
}
}
};
const getChildrenGroupOrder = (el) => {
const childrenOrder = [];
const children = el.children;
for (let i = 0; i < children.length; ++i) {
const groupKey = getGroupKeyFromElement(children[i]);
if (groupKey) {
childrenOrder.push(groupKey);
}
}
return childrenOrder;
};
const before = getChildrenGroupOrder(this.beforeChartProxyPosContainer);
const after = getChildrenGroupOrder(this.afterChartProxyPosContainer);
before.push('series');
return before.concat(after);
}
/**
* Check if the current DOM order matches the current group order, so that
* a reordering/update is unnecessary.
*/
isDOMOrderGroupOrder() {
const domOrder = this.getCurrentGroupOrderInDOM();
const groupOrderWithGroups = this.groupOrder.filter((x) => x === 'series' || !!this.groups[x]);
let i = domOrder.length;
if (i !== groupOrderWithGroups.length) {
return false;
}
while (i--) {
if (domOrder[i] !== groupOrderWithGroups[i]) {
return false;
}
}
return true;
}
/**
* Update the DOM positions of the before/after proxy
* positioning containers for the groups.
*/
updatePosContainerPositions() {
const chart = this.chart;
// If exporting, don't add these containers to the DOM.
if (chart.renderer.forExport) {
return;
}
const rendererSVGEl = chart.renderer.box;
chart.container.insertBefore(this.afterChartProxyPosContainer, rendererSVGEl.nextSibling);
chart.container.insertBefore(this.beforeChartProxyPosContainer, rendererSVGEl);
unhideChartElementFromAT(this.chart, this.afterChartProxyPosContainer);
unhideChartElementFromAT(this.chart, this.beforeChartProxyPosContainer);
}
}
/* *
*
* Export Default
*
* */
export default ProxyProvider;