227 lines
8.0 KiB
JavaScript
227 lines
8.0 KiB
JavaScript
/* *
|
|
*
|
|
* (c) 2009-2021 Øystein Moseng
|
|
*
|
|
* Proxy elements are used to shadow SVG elements in HTML for assistive
|
|
* technology, such as screen readers or voice input software.
|
|
*
|
|
* The ProxyElement class represents such an element, and deals with
|
|
* overlay positioning and mirroring events for the target.
|
|
*
|
|
* License: www.highcharts.com/license
|
|
*
|
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
|
*
|
|
* */
|
|
'use strict';
|
|
import H from '../Core/Globals.js';
|
|
var doc = H.doc;
|
|
import U from '../Core/Utilities.js';
|
|
var attr = U.attr, css = U.css, merge = U.merge;
|
|
import EventProvider from './Utils/EventProvider.js';
|
|
import ChartUtilities from './Utils/ChartUtilities.js';
|
|
var fireEventOnWrappedOrUnwrappedElement = ChartUtilities.fireEventOnWrappedOrUnwrappedElement;
|
|
import HTMLUtilities from './Utils/HTMLUtilities.js';
|
|
var cloneMouseEvent = HTMLUtilities.cloneMouseEvent, cloneTouchEvent = HTMLUtilities.cloneTouchEvent, getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent, removeElement = HTMLUtilities.removeElement;
|
|
/* *
|
|
*
|
|
* Class
|
|
*
|
|
* */
|
|
/**
|
|
* Represents a proxy element that overlays a target and relays events
|
|
* to its target.
|
|
*
|
|
* @private
|
|
* @class
|
|
*/
|
|
var ProxyElement = /** @class */ (function () {
|
|
/* *
|
|
*
|
|
* Constructor
|
|
*
|
|
* */
|
|
function ProxyElement(chart, target, groupType, attributes) {
|
|
this.chart = chart;
|
|
this.target = target;
|
|
this.groupType = groupType;
|
|
var isListItem = groupType === 'ul';
|
|
this.eventProvider = new EventProvider();
|
|
var wrapperEl = isListItem ? doc.createElement('li') : null;
|
|
var btnEl = this.buttonElement = doc.createElement('button');
|
|
if (!chart.styledMode) {
|
|
this.hideButtonVisually(btnEl);
|
|
}
|
|
if (wrapperEl) {
|
|
if (isListItem && !chart.styledMode) {
|
|
wrapperEl.style.listStyle = 'none';
|
|
}
|
|
wrapperEl.appendChild(btnEl);
|
|
this.element = wrapperEl;
|
|
}
|
|
else {
|
|
this.element = btnEl;
|
|
}
|
|
this.updateTarget(target, attributes);
|
|
}
|
|
/* *
|
|
*
|
|
* Functions
|
|
*
|
|
* */
|
|
/* eslint-disable valid-jsdoc */
|
|
/**
|
|
* Fake a click event on the target.
|
|
*/
|
|
ProxyElement.prototype.click = function () {
|
|
var pos = this.getTargetPosition();
|
|
pos.x += pos.width / 2;
|
|
pos.y += pos.height / 2;
|
|
var fakeEventObject = getFakeMouseEvent('click', pos);
|
|
fireEventOnWrappedOrUnwrappedElement(this.target.click, fakeEventObject);
|
|
};
|
|
/**
|
|
* Update the target to be proxied. The position and events are updated to
|
|
* match the new target.
|
|
* @param target The new target definition
|
|
* @param attributes New HTML attributes to apply to the button. Set an
|
|
* attribute to null to remove.
|
|
*/
|
|
ProxyElement.prototype.updateTarget = function (target, attributes) {
|
|
this.target = target;
|
|
this.updateCSSClassName();
|
|
var attrs = attributes || {};
|
|
Object.keys(attrs).forEach(function (a) {
|
|
if (attrs[a] === null) {
|
|
delete attrs[a];
|
|
}
|
|
});
|
|
attr(this.buttonElement, merge({
|
|
'aria-label': this.getTargetAttr(target.click, 'aria-label')
|
|
}, attrs));
|
|
this.eventProvider.removeAddedEvents();
|
|
this.addProxyEventsToButton(this.buttonElement, target.click);
|
|
this.refreshPosition();
|
|
};
|
|
/**
|
|
* Refresh the position of the proxy element to match the current target
|
|
*/
|
|
ProxyElement.prototype.refreshPosition = function () {
|
|
var bBox = this.getTargetPosition();
|
|
css(this.buttonElement, {
|
|
width: (bBox.width || 1) + 'px',
|
|
height: (bBox.height || 1) + 'px',
|
|
left: (Math.round(bBox.x) || 0) + 'px',
|
|
top: (Math.round(bBox.y) || 0) + 'px'
|
|
});
|
|
};
|
|
/**
|
|
* Remove button from DOM, and clear events.
|
|
*/
|
|
ProxyElement.prototype.remove = function () {
|
|
this.eventProvider.removeAddedEvents();
|
|
removeElement(this.element);
|
|
};
|
|
// -------------------------- private ------------------------------------
|
|
/**
|
|
* Update the CSS class name to match target
|
|
*/
|
|
ProxyElement.prototype.updateCSSClassName = function () {
|
|
var stringHasNoTooltip = function (s) { return (s.indexOf('highcharts-no-tooltip') > -1); };
|
|
var legend = this.chart.legend;
|
|
var groupDiv = legend.group && legend.group.div;
|
|
var noTooltipOnGroup = stringHasNoTooltip(groupDiv && groupDiv.className || '');
|
|
var targetClassName = this.getTargetAttr(this.target.click, 'class') || '';
|
|
var noTooltipOnTarget = stringHasNoTooltip(targetClassName);
|
|
this.buttonElement.className = noTooltipOnGroup || noTooltipOnTarget ?
|
|
'highcharts-a11y-proxy-button highcharts-no-tooltip' :
|
|
'highcharts-a11y-proxy-button';
|
|
};
|
|
/**
|
|
* Mirror events for a proxy button to a target
|
|
*/
|
|
ProxyElement.prototype.addProxyEventsToButton = function (button, target) {
|
|
var _this = this;
|
|
[
|
|
'click', 'touchstart', 'touchend', 'touchcancel', 'touchmove',
|
|
'mouseover', 'mouseenter', 'mouseleave', 'mouseout'
|
|
].forEach(function (evtType) {
|
|
var isTouchEvent = evtType.indexOf('touch') === 0;
|
|
_this.eventProvider.addEvent(button, evtType, function (e) {
|
|
var clonedEvent = isTouchEvent ?
|
|
cloneTouchEvent(e) :
|
|
cloneMouseEvent(e);
|
|
if (target) {
|
|
fireEventOnWrappedOrUnwrappedElement(target, clonedEvent);
|
|
}
|
|
e.stopPropagation();
|
|
// #9682, #15318: Touch scrolling didnt work when touching
|
|
// proxy
|
|
if (!isTouchEvent) {
|
|
e.preventDefault();
|
|
}
|
|
}, { passive: false });
|
|
});
|
|
};
|
|
/**
|
|
* Set visually hidden style on a proxy button
|
|
*/
|
|
ProxyElement.prototype.hideButtonVisually = function (button) {
|
|
css(button, {
|
|
borderWidth: 0,
|
|
backgroundColor: 'transparent',
|
|
cursor: 'pointer',
|
|
outline: 'none',
|
|
opacity: 0.001,
|
|
filter: 'alpha(opacity=1)',
|
|
zIndex: 999,
|
|
overflow: 'hidden',
|
|
padding: 0,
|
|
margin: 0,
|
|
display: 'block',
|
|
position: 'absolute',
|
|
'-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)'
|
|
});
|
|
};
|
|
/**
|
|
* Get the position relative to chart container for the target
|
|
*/
|
|
ProxyElement.prototype.getTargetPosition = function () {
|
|
var clickTarget = this.target.click;
|
|
// We accept both DOM elements and wrapped elements as click targets.
|
|
var clickTargetElement = clickTarget.element ?
|
|
clickTarget.element :
|
|
clickTarget;
|
|
var posElement = this.target.visual || clickTargetElement;
|
|
var chartDiv = this.chart.renderTo;
|
|
if (chartDiv && posElement && posElement.getBoundingClientRect) {
|
|
var rectEl = posElement.getBoundingClientRect(), chartPos = this.chart.pointer.getChartPosition();
|
|
return {
|
|
x: (rectEl.left - chartPos.left) / chartPos.scaleX,
|
|
y: (rectEl.top - chartPos.top) / chartPos.scaleY,
|
|
width: rectEl.right / chartPos.scaleX -
|
|
rectEl.left / chartPos.scaleX,
|
|
height: rectEl.bottom / chartPos.scaleY -
|
|
rectEl.top / chartPos.scaleY
|
|
};
|
|
}
|
|
return { x: 0, y: 0, width: 1, height: 1 };
|
|
};
|
|
/**
|
|
* Get an attribute value of a target
|
|
*/
|
|
ProxyElement.prototype.getTargetAttr = function (target, key) {
|
|
if (target.element) {
|
|
return target.element.getAttribute(key);
|
|
}
|
|
return target.getAttribute(key);
|
|
};
|
|
return ProxyElement;
|
|
}());
|
|
/* *
|
|
*
|
|
* Default Export
|
|
*
|
|
* */
|
|
export default ProxyElement;
|