tupali/librerias/gantt/code/es-modules/modules/accessibility/AccessibilityComponent.js

324 lines
11 KiB
JavaScript

/* *
*
* (c) 2009-2020 Øystein Moseng
*
* Accessibility component class definition
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../../parts/Globals.js';
var win = H.win, doc = win.document;
import U from '../../parts/Utilities.js';
var extend = U.extend, fireEvent = U.fireEvent, merge = U.merge;
import HTMLUtilities from './utils/htmlUtilities.js';
var removeElement = HTMLUtilities.removeElement, getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent;
import ChartUtilities from './utils/chartUtilities.js';
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
import EventProvider from './utils/EventProvider.js';
import DOMElementProvider from './utils/DOMElementProvider.js';
/* eslint-disable valid-jsdoc */
/** @lends Highcharts.AccessibilityComponent */
var functionsToOverrideByDerivedClasses = {
/**
* Called on component initialization.
*/
init: function () { },
/**
* Get keyboard navigation handler for this component.
* @return {Highcharts.KeyboardNavigationHandler}
*/
getKeyboardNavigation: function () { },
/**
* Called on updates to the chart, including options changes.
* Note that this is also called on first render of chart.
*/
onChartUpdate: function () { },
/**
* Called on every chart render.
*/
onChartRender: function () { },
/**
* Called when accessibility is disabled or chart is destroyed.
*/
destroy: function () { }
};
/**
* The AccessibilityComponent base class, representing a part of the chart that
* has accessibility logic connected to it. This class can be inherited from to
* create a custom accessibility component for a chart.
*
* Components should take care to destroy added elements and unregister event
* handlers on destroy. This is handled automatically if using this.addEvent and
* this.createElement.
*
* @sample highcharts/accessibility/custom-component
* Custom accessibility component
*
* @requires module:modules/accessibility
* @class
* @name Highcharts.AccessibilityComponent
*/
function AccessibilityComponent() { }
/**
* @lends Highcharts.AccessibilityComponent
*/
AccessibilityComponent.prototype = {
/**
* Initialize the class
* @private
* @param {Highcharts.Chart} chart
* Chart object
*/
initBase: function (chart) {
this.chart = chart;
this.eventProvider = new EventProvider();
this.domElementProvider = new DOMElementProvider();
// Key code enum for common keys
this.keyCodes = {
left: 37,
right: 39,
up: 38,
down: 40,
enter: 13,
space: 32,
esc: 27,
tab: 9
};
},
/**
* Add an event to an element and keep track of it for later removal.
* See EventProvider for details.
* @private
*/
addEvent: function () {
return this.eventProvider.addEvent
.apply(this.eventProvider, arguments);
},
/**
* Create an element and keep track of it for later removal.
* See DOMElementProvider for details.
* @private
*/
createElement: function () {
return this.domElementProvider.createElement.apply(this.domElementProvider, arguments);
},
/**
* Fire an event on an element that is either wrapped by Highcharts,
* or a DOM element
* @private
* @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement|
* Highcharts.SVGDOMElement|Highcharts.SVGElement} el
* @param {Event} eventObject
*/
fireEventOnWrappedOrUnwrappedElement: function (el, eventObject) {
var type = eventObject.type;
if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
if (el.dispatchEvent) {
el.dispatchEvent(eventObject);
}
else {
el.fireEvent(type, eventObject);
}
}
else {
fireEvent(el, type, eventObject);
}
},
/**
* Utility function to attempt to fake a click event on an element.
* @private
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element
*/
fakeClickEvent: function (element) {
if (element) {
var fakeEventObject = getFakeMouseEvent('click');
this.fireEventOnWrappedOrUnwrappedElement(element, fakeEventObject);
}
},
/**
* Add a new proxy group to the proxy container. Creates the proxy container
* if it does not exist.
* @private
* @param {Highcharts.HTMLAttributes} [attrs]
* The attributes to set on the new group div.
* @return {Highcharts.HTMLDOMElement}
* The new proxy group element.
*/
addProxyGroup: function (attrs) {
this.createOrUpdateProxyContainer();
var groupDiv = this.createElement('div');
Object.keys(attrs || {}).forEach(function (prop) {
if (attrs[prop] !== null) {
groupDiv.setAttribute(prop, attrs[prop]);
}
});
this.chart.a11yProxyContainer.appendChild(groupDiv);
return groupDiv;
},
/**
* Creates and updates DOM position of proxy container
* @private
*/
createOrUpdateProxyContainer: function () {
var chart = this.chart, rendererSVGEl = chart.renderer.box;
chart.a11yProxyContainer = chart.a11yProxyContainer ||
this.createProxyContainerElement();
if (rendererSVGEl.nextSibling !== chart.a11yProxyContainer) {
chart.container.insertBefore(chart.a11yProxyContainer, rendererSVGEl.nextSibling);
}
},
/**
* @private
* @return {Highcharts.HTMLDOMElement} element
*/
createProxyContainerElement: function () {
var pc = doc.createElement('div');
pc.className = 'highcharts-a11y-proxy-container';
return pc;
},
/**
* Create an invisible proxy HTML button in the same position as an SVG
* element
* @private
* @param {Highcharts.SVGElement} svgElement
* The wrapped svg el to proxy.
* @param {Highcharts.HTMLDOMElement} parentGroup
* The proxy group element in the proxy container to add this button to.
* @param {Highcharts.SVGAttributes} [attributes]
* Additional attributes to set.
* @param {Highcharts.SVGElement} [posElement]
* Element to use for positioning instead of svgElement.
* @param {Function} [preClickEvent]
* Function to call before click event fires.
*
* @return {Highcharts.HTMLDOMElement} The proxy button.
*/
createProxyButton: function (svgElement, parentGroup, attributes, posElement, preClickEvent) {
var svgEl = svgElement.element, proxy = this.createElement('button'), attrs = merge({
'aria-label': svgEl.getAttribute('aria-label')
}, attributes), bBox = this.getElementPosition(posElement || svgElement);
Object.keys(attrs).forEach(function (prop) {
if (attrs[prop] !== null) {
proxy.setAttribute(prop, attrs[prop]);
}
});
proxy.className = 'highcharts-a11y-proxy-button';
if (preClickEvent) {
this.addEvent(proxy, 'click', preClickEvent);
}
this.setProxyButtonStyle(proxy, bBox);
this.proxyMouseEventsForButton(svgEl, proxy);
// Add to chart div and unhide from screen readers
parentGroup.appendChild(proxy);
if (!attrs['aria-hidden']) {
unhideChartElementFromAT(this.chart, proxy);
}
return proxy;
},
/**
* Get the position relative to chart container for a wrapped SVG element.
* @private
* @param {Highcharts.SVGElement} element
* The element to calculate position for.
* @return {Highcharts.BBoxObject}
* Object with x and y props for the position.
*/
getElementPosition: function (element) {
var el = element.element, div = this.chart.renderTo;
if (div && el && el.getBoundingClientRect) {
var rectEl = el.getBoundingClientRect(), rectDiv = div.getBoundingClientRect();
return {
x: rectEl.left - rectDiv.left,
y: rectEl.top - rectDiv.top,
width: rectEl.right - rectEl.left,
height: rectEl.bottom - rectEl.top
};
}
return { x: 0, y: 0, width: 1, height: 1 };
},
/**
* @private
* @param {Highcharts.HTMLElement} button
* @param {Highcharts.BBoxObject} bBox
*/
setProxyButtonStyle: function (button, bBox) {
merge(true, button.style, {
'border-width': 0,
'background-color': 'transparent',
cursor: 'pointer',
outline: 'none',
opacity: 0.001,
filter: 'alpha(opacity=1)',
'-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)',
zIndex: 999,
overflow: 'hidden',
padding: 0,
margin: 0,
display: 'block',
position: 'absolute',
width: (bBox.width || 1) + 'px',
height: (bBox.height || 1) + 'px',
left: (bBox.x || 0) + 'px',
top: (bBox.y || 0) + 'px'
});
},
/**
* @private
* @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement|
* Highcharts.SVGDOMElement|Highcharts.SVGElement} source
* @param {Highcharts.HTMLElement} button
*/
proxyMouseEventsForButton: function (source, button) {
var component = this;
[
'click', 'touchstart', 'touchend', 'touchcancel', 'touchmove',
'mouseover', 'mouseenter', 'mouseleave', 'mouseout'
].forEach(function (evtType) {
component.addEvent(button, evtType, function (e) {
var clonedEvent = component.cloneMouseEvent(e);
if (source) {
component.fireEventOnWrappedOrUnwrappedElement(source, clonedEvent);
}
e.stopPropagation();
e.preventDefault();
});
});
},
/**
* Utility function to clone a mouse event for re-dispatching.
* @private
* @param {global.MouseEvent} e The event to clone.
* @return {global.MouseEvent} The cloned event
*/
cloneMouseEvent: function (e) {
if (typeof win.MouseEvent === 'function') {
return new win.MouseEvent(e.type, e);
}
// No MouseEvent support, try using initMouseEvent
if (doc.createEvent) {
var evt = doc.createEvent('MouseEvent');
if (evt.initMouseEvent) {
evt.initMouseEvent(e.type, e.bubbles, // #10561, #12161
e.cancelable, e.view || win, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
return evt;
}
}
return getFakeMouseEvent(e.type);
},
/**
* Remove traces of the component.
* @private
*/
destroyBase: function () {
removeElement(this.chart.a11yProxyContainer);
this.domElementProvider.destroyCreatedElements();
this.eventProvider.removeAddedEvents();
}
};
extend(AccessibilityComponent.prototype, functionsToOverrideByDerivedClasses);
export default AccessibilityComponent;