Source: control2/hotspot/Underline.js

(function() {
    /**
     * @typedef {Help4.control2.hotspot.Hotspot.Params} Help4.control2.hotspot.Underline.Params
     * @property {Help4.control2.AreaXYWH} [rect = {x: 0, y: 0, w: 0, h: 0}] - the rect of the assigned element
     * @property {Help4.control2.SizeWidthHeight} [delta = {width: 0, height: 0}] - in case the hotspot is moved
     */

    /**
     * Creates an underline hotspot
     * @augments Help4.control2.hotspot.Hotspot
     * @property {Help4.control2.AreaXYWH} rect - the rect of the assigned element
     * @property {Help4.control2.SizeWidthHeight} delta - in case the hotspot is moved
     */
    Help4.control2.hotspot.Underline = class extends Help4.control2.hotspot.Hotspot {
        /**
         * @override
         * @param {Help4.control2.hotspot.Underline.Params} [params]
         */
        constructor(params) {
            const T = Help4.jscore.ControlBase.TYPES;
            super(params, {
                params: {
                    rect:  {type: T.xywh},
                    delta: {type: T.widthHeight}
                },
                statics: {
                    _rectHelper: {destroy: false}
                },
                config: {
                    css: 'underline'
                }
            });
        }

        /**
         * @override
         * @param {Help4.control2.PositionXY} point
         */
        calcMeetingPoint({x, y}) {
            const {x: rx, y: ry, w: rw, h: rh} = this.rect;
            const {width: dw, height: dh} = this.delta;

            const rect = {
                x: rx - dw / 2,
                y: ry + rh + dh / 2,
                w: Math.max(0, rw + dw),
                h: parseInt(getComputedStyle(this.getDom()).height) || 0
            };

            const line = {  // line from tile to the center of hotspot
                x1: rect.x + rect.w / 2,
                y1: rect.y + rect.h / 2,
                x2: x,
                y2: y
            };

            return Help4.getRectIntersect(rect, line, this.borderSize) || {x, y};
        }

        /** @override */
        getConnectionPoints(params) {
            const {y: ry, h: rh} = this.rect;
            const {height: dh} = this.delta;
            const {left, right, width, height: lineH} = this.getDom().getBoundingClientRect();

            const top = ry - dh / 2;
            const height = rh + dh + lineH;
            const bottom = top + height;
            const mx = left + (width >> 1);  // mid x
            const my = top + (height >> 1);  // mid y

            return {
                l: {x: left, y: my},
                r: {x: right, y: my},
                t: {x: mx, y: top},
                b: {x: mx, y: bottom},
                m: {x: mx, y: my}
            };
        }

        /**
         * @override
         * @param {HTMLElement} dom - control DOM
         */
        _onDomCreated(dom) {
            super._onDomCreated(dom);

            const cnt = this._content = this._createElement('div', {css: 'inner'});
            this._createElement('div', {id: '-arrow', css: 'arrow', dom: cnt});

            _apply.call(this);
        }

        /**
         * @override
         * @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
         */
        _applyPropertyToDom({name, value, oldValue}) {
            switch(name) {
                case 'rect':
                case 'delta':
                    _apply.call(this);
                    break;

                case 'spotlight':
                    _handleSpotlight.call(this, value);
                    // fall-through is intended
                default:
                    super._applyPropertyToDom({name, value, oldValue});
                    break;
            }
        }
    }

    /**
     * @memberof Help4.control2.hotspot.Underline#
     * @param {boolean} value - whether spotlight is enabled
     * @private
     */
    function _handleSpotlight(value) {
        const {_rectHelper} = this;

        if (!value) {
            _rectHelper?.parentNode.removeChild(_rectHelper);
            delete this._rectHelper;
        } else if (!_rectHelper) {
            this._rectHelper = this._createElement('div', {id: '-rh', css: 'spotlight-helper'});
            _apply.call(this);
        }
    }

    /**
     * @memberof Help4.control2.hotspot.Underline#
     * @private
     */
    function _apply() {
        const dom = this.getDom();
        if (!dom) return;

        const {x: rx, y: ry, w: rw, h: rh} = this.rect;
        const {width: dw, height: dh} = this.delta;
        const w = Math.max(0, rw + dw);
        Help4.extendObject(dom.style, {
            left: rx - dw / 2 + 'px',
            top: ry + rh + dh / 2 + 'px',
            width: w + 'px'
        });

        // XRAY-1465 rect helper
        const {_rectHelper} = this;
        if (_rectHelper) {
            const h = Math.max(0, rh + dh);
            Help4.extendObject(_rectHelper.style, {
                top: -h + 'px',
                width: w + 'px',
                height: h + 'px'
            });
        }
    }
})();