| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Extend SVG and Chart classes with focus border capabilities.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import H from '../Core/Globals.js';
- import SVGElement from '../Core/Renderer/SVG/SVGElement.js';
- import SVGLabel from '../Core/Renderer/SVG/SVGLabel.js';
- import U from '../Core/Utilities.js';
- var addEvent = U.addEvent, extend = U.extend, pick = U.pick;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- // Attributes that trigger a focus border update
- var svgElementBorderUpdateTriggers = [
- 'x', 'y', 'transform', 'width', 'height', 'r', 'd', 'stroke-width'
- ];
- /**
- * Add hook to destroy focus border if SVG element is destroyed, unless
- * hook already exists.
- * @private
- * @param el Element to add destroy hook to
- */
- function addDestroyFocusBorderHook(el) {
- if (el.focusBorderDestroyHook) {
- return;
- }
- var origDestroy = el.destroy;
- el.destroy = function () {
- var _a, _b;
- (_b = (_a = el.focusBorder) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
- return origDestroy.apply(el, arguments);
- };
- el.focusBorderDestroyHook = origDestroy;
- }
- /**
- * Remove hook from SVG element added by addDestroyFocusBorderHook, if
- * existing.
- * @private
- * @param el Element to remove destroy hook from
- */
- function removeDestroyFocusBorderHook(el) {
- if (!el.focusBorderDestroyHook) {
- return;
- }
- el.destroy = el.focusBorderDestroyHook;
- delete el.focusBorderDestroyHook;
- }
- /**
- * Add hooks to update the focus border of an element when the element
- * size/position is updated, unless already added.
- * @private
- * @param el Element to add update hooks to
- * @param updateParams Parameters to pass through to addFocusBorder when updating.
- */
- function addUpdateFocusBorderHooks(el) {
- var updateParams = [];
- for (var _i = 1; _i < arguments.length; _i++) {
- updateParams[_i - 1] = arguments[_i];
- }
- if (el.focusBorderUpdateHooks) {
- return;
- }
- el.focusBorderUpdateHooks = {};
- svgElementBorderUpdateTriggers.forEach(function (trigger) {
- var setterKey = trigger + 'Setter';
- var origSetter = el[setterKey] || el._defaultSetter;
- el.focusBorderUpdateHooks[setterKey] = origSetter;
- el[setterKey] = function () {
- var ret = origSetter.apply(el, arguments);
- el.addFocusBorder.apply(el, updateParams);
- return ret;
- };
- });
- }
- /**
- * Remove hooks from SVG element added by addUpdateFocusBorderHooks, if
- * existing.
- * @private
- * @param el Element to remove update hooks from
- */
- function removeUpdateFocusBorderHooks(el) {
- if (!el.focusBorderUpdateHooks) {
- return;
- }
- Object.keys(el.focusBorderUpdateHooks).forEach(function (setterKey) {
- var origSetter = el.focusBorderUpdateHooks[setterKey];
- if (origSetter === el._defaultSetter) {
- delete el[setterKey];
- }
- else {
- el[setterKey] = origSetter;
- }
- });
- delete el.focusBorderUpdateHooks;
- }
- /*
- * Add focus border functionality to SVGElements. Draws a new rect on top of
- * element around its bounding box. This is used by multiple components.
- */
- extend(SVGElement.prototype, {
- /**
- * @private
- * @function Highcharts.SVGElement#addFocusBorder
- *
- * @param {number} margin
- *
- * @param {Highcharts.CSSObject} style
- */
- addFocusBorder: function (margin, style) {
- // Allow updating by just adding new border
- if (this.focusBorder) {
- this.removeFocusBorder();
- }
- // Add the border rect
- var bb = this.getBBox(), pad = pick(margin, 3);
- bb.x += this.translateX ? this.translateX : 0;
- bb.y += this.translateY ? this.translateY : 0;
- var borderPosX = bb.x - pad, borderPosY = bb.y - pad, borderWidth = bb.width + 2 * pad, borderHeight = bb.height + 2 * pad;
- // For text elements, apply x and y offset, #11397.
- /**
- * @private
- * @function
- *
- * @param {Highcharts.SVGElement} text
- *
- * @return {TextAnchorCorrectionObject}
- */
- function getTextAnchorCorrection(text) {
- var posXCorrection = 0, posYCorrection = 0;
- if (text.attr('text-anchor') === 'middle') {
- posXCorrection = H.isFirefox && text.rotation ? 0.25 : 0.5;
- posYCorrection = H.isFirefox && !text.rotation ? 0.75 : 0.5;
- }
- else if (!text.rotation) {
- posYCorrection = 0.75;
- }
- else {
- posXCorrection = 0.25;
- }
- return {
- x: posXCorrection,
- y: posYCorrection
- };
- }
- var isLabel = this instanceof SVGLabel;
- if (this.element.nodeName === 'text' || isLabel) {
- var isRotated = !!this.rotation;
- var correction = !isLabel ? getTextAnchorCorrection(this) :
- {
- x: isRotated ? 1 : 0,
- y: 0
- };
- var attrX = +this.attr('x');
- var attrY = +this.attr('y');
- if (!isNaN(attrX)) {
- borderPosX = attrX - (bb.width * correction.x) - pad;
- }
- if (!isNaN(attrY)) {
- borderPosY = attrY - (bb.height * correction.y) - pad;
- }
- if (isLabel && isRotated) {
- var temp = borderWidth;
- borderWidth = borderHeight;
- borderHeight = temp;
- if (!isNaN(attrX)) {
- borderPosX = attrX - (bb.height * correction.x) - pad;
- }
- if (!isNaN(attrY)) {
- borderPosY = attrY - (bb.width * correction.y) - pad;
- }
- }
- }
- this.focusBorder = this.renderer.rect(borderPosX, borderPosY, borderWidth, borderHeight, parseInt((style && style.borderRadius || 0).toString(), 10))
- .addClass('highcharts-focus-border')
- .attr({
- zIndex: 99
- })
- .add(this.parentGroup);
- if (!this.renderer.styledMode) {
- this.focusBorder.attr({
- stroke: style && style.stroke,
- 'stroke-width': style && style.strokeWidth
- });
- }
- addUpdateFocusBorderHooks(this, margin, style);
- addDestroyFocusBorderHook(this);
- },
- /**
- * @private
- * @function Highcharts.SVGElement#removeFocusBorder
- */
- removeFocusBorder: function () {
- removeUpdateFocusBorderHooks(this);
- removeDestroyFocusBorderHook(this);
- if (this.focusBorder) {
- this.focusBorder.destroy();
- delete this.focusBorder;
- }
- }
- });
- /**
- * Redraws the focus border on the currently focused element.
- *
- * @private
- * @function Highcharts.Chart#renderFocusBorder
- */
- H.Chart.prototype.renderFocusBorder = function () {
- var focusElement = this.focusElement, focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder;
- if (focusElement) {
- focusElement.removeFocusBorder();
- if (focusBorderOptions.enabled) {
- focusElement.addFocusBorder(focusBorderOptions.margin, {
- stroke: focusBorderOptions.style.color,
- strokeWidth: focusBorderOptions.style.lineWidth,
- borderRadius: focusBorderOptions.style.borderRadius
- });
- }
- }
- };
- /**
- * Set chart's focus to an SVGElement. Calls focus() on it, and draws the focus
- * border. This is used by multiple components.
- *
- * @private
- * @function Highcharts.Chart#setFocusToElement
- *
- * @param {Highcharts.SVGElement} svgElement
- * Element to draw the border around.
- *
- * @param {SVGDOMElement|HTMLDOMElement} [focusElement]
- * If supplied, it draws the border around svgElement and sets the focus
- * to focusElement.
- */
- H.Chart.prototype.setFocusToElement = function (svgElement, focusElement) {
- var focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder, browserFocusElement = focusElement || svgElement.element;
- // Set browser focus if possible
- if (browserFocusElement &&
- browserFocusElement.focus) {
- // If there is no focusin-listener, add one to work around Edge issue
- // where Narrator is not reading out points despite calling focus().
- if (!(browserFocusElement.hcEvents &&
- browserFocusElement.hcEvents.focusin)) {
- addEvent(browserFocusElement, 'focusin', function () { });
- }
- browserFocusElement.focus();
- // Hide default focus ring
- if (focusBorderOptions.hideBrowserFocusOutline) {
- browserFocusElement.style.outline = 'none';
- }
- }
- if (this.focusElement) {
- this.focusElement.removeFocusBorder();
- }
- this.focusElement = svgElement;
- this.renderFocusBorder();
- };
|