Source: control2/hotspot/Icon.js

(function() {
    /**
     * @typedef {Help4.control2.hotspot.Hotspot.Params} Help4.control2.hotspot.Icon.Params
     * @property {Help4.control2.AreaXYWH} [rect = {x: 0, y: 0, w: 0, h: 0}] - area of the assigned element
     * @property {Help4.control2.PositionXY} [point = {x: 0, y: 0}] - center point of the assigned element
     * @property {Help4.control2.PositionLeftTop} [delta = {left: 0, top: 0}] - in case hotspot is moved
     * @property {string} icon - icon type
     * @property {string} [size = ''] - icon size
     * @property {string} pos - icon position relative to element
     * @property {Help4.control2.hotspot.ANIMATION_TYPES} [animationType = 'none'] - type of animation
     */

    /**
     * Creates an icon hotspot.
     * @augments Help4.control2.hotspot.Hotspot
     * @property {Help4.control2.AreaXYWH} rect - area of the assigned element
     * @property {Help4.control2.PositionXY} point - center point of the assigned element
     * @property {Help4.control2.PositionLeftTop} delta - in case hotspot is moved
     * @property {string} icon - icon type
     * @property {string} size - icon size
     * @property {string} pos - icon position relative to element
     * @property {Help4.control2.hotspot.ANIMATION_TYPES} animationType - type of animation
     */
    Help4.control2.hotspot.Icon = class extends Help4.control2.hotspot.Hotspot {
        /**
         * @override
         * @param {Help4.control2.hotspot.Icon.Params} [params]
         */
        constructor(params) {
            const T = Help4.jscore.ControlBase.TYPES;
            super(params, {
                params: {
                    rect:          {type: T.xywh},
                    point:         {type: T.xy},
                    delta:         {type: T.leftTop},
                    icon:          {type: T.string, mandatory: true},
                    size:          {type: T.string},
                    pos:           {type: T.string, mandatory: true},
                    animationType: {type: T.string, init: Help4.control2.hotspot.ANIMATION_TYPES.none}
                },
                statics: {
                    _icon: {destroy: false}
                },
                config: {
                    css: 'icon'
                }
            });
        }

        /**
         * @override
         * @param {Help4.control2.PositionXY} point
         */
        calcMeetingPoint({x, y}) {
            const ra = _getSizeConfig(this.size).icon / 2;
            const pt = {x: this._x + ra, y: this._y + ra};

            const dx = pt.x - x;
            const dy = pt.y - y;
            const dist = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));

            // size of hotspot is: radius + border; line should start right at the border (so border-w + 1)
            // see H4H.Connected._create for borderSize information
            const norm = 1 - (ra + this.borderSize + 1) / dist;

            return norm > 0
                ? {x: x + norm * dx, y: y + norm * dy}
                : {x, y};
        }

        /**
         * @override
         * @param {Help4.control2.hotspot.Icon.Params} params - same params as provided to the constructor
         */
        _onAfterInit(params) {
            super._onAfterInit(params);
            this._x = 0;
            this._y = 0;
        }

        /** @override */
        _onDomCreated(dom) {
            super._onDomCreated(dom);

            const c = this._content = this._createElement('div', {css: 'inner'});
            this._icon = this._createElement('span', {dom: c, id: '-ico'});
        }

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

                case 'icon':
                    this.removeCss(oldValue);
                    this.addCss(value);
                    this._icon.innerHTML = Help4.control2.ICONS['icon_' + value];
                    break;

                case 'size':
                    let config = _getSizeConfig(oldValue);
                    this.removeCss('size-' + config.localization);

                    config = _getSizeConfig(value);
                    this.addCss('size-' + config.localization);
                    _apply.call(this);
                    break;

                case 'animationType':
                    this.removeCss(`ani-${oldValue}`);
                    this.addCss(`ani-${value}`);
                    break;

                default:
                    super._applyPropertyToDom({name, value, oldValue});
                    break;
            }
        }

        /**
         * @override
         */
        getDragStartPosition() {
            const s = _getSizeConfig(this.size).icon;
            const {x: rx, y: ry, w: rw, h: rh} = this.rect;

            let x, y;
            switch (_getPos(this.pos, this.rtl)) {
                case 'A':  // Top Left, Outside
                    x = rx - s;
                    y = ry - s;
                    break;
                case 'B':  // Top Left, Above
                    x = rx;
                    y = ry - s;
                    break;
                case 'C':  // Top Center
                    x = rx + (rw - s) / 2;
                    y = ry - s;
                    break;
                case 'D':  // Top Right, Above
                    x = rx + rw - s;
                    y = ry - s;
                    break;
                case 'E':  // Top Right, Outside
                    x = rx + rw;
                    y = ry - s;
                    break;
                case 'F':  // Top Left
                    x = rx - s;
                    y = ry;
                    break;
                case 'G':  // Top Right
                    x = rx + rw;
                    y = ry;
                    break;
                case 'H':  // Middle Left
                    x = rx - s;
                    y = ry + (rh - s) / 2;
                    break;
                case 'I':  // Middle Right
                    x = rx + rw;
                    y = ry + (rh - s) / 2;
                    break;
                case 'J':  // Bottom Left
                    x = rx - s;
                    y = ry + rh - s;
                    break;
                case 'K':  // Bottom Right
                    x = rx + rw;
                    y = ry + rh - s;
                    break;
                case 'L':  // Bottom Left, Outside
                    x = rx - s;
                    y = ry + rh;
                    break;
                case 'M':  // Bottom Left, Below
                    x = rx;
                    y = ry + rh;
                    break;
                case 'N':  // Bottom Center
                    x = rx + (rw - s) / 2;
                    y = ry + rh;
                    break;
                case 'O':  // Bottom Right, Below
                    x = rx + rw - s;
                    y = ry + rh;
                    break;
                case 'P':  // Bottom Right, Outside
                    x = rx + rw;
                    y = ry + rh;
                    break;
                case 'Q':  // Centered
                    x = rx + (rw - s) / 2;
                    y = ry + (rh - s) / 2;
                    break;
                case 'R':  // Manual
                    const {x: px, y: py} = this.point;
                    x = px - s / 2;
                    y = py - s / 2;
                    break;
            }

            return {x, y};
        }
    }

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

        const {x, y} = this.getDragStartPosition();
        const {left: dx, top: dy} = this.delta;
        dom.style.left = (this._x = x + dx) + 'px';
        dom.style.top = (this._y = y + dy) + 'px';
    }

    /**
     * @memberof Help4.control2.hotspot.Icon#
     * @param {string} pos
     * @param {boolean} rtl
     * @returns {string}
     * @private
     */
    function _getPos(pos, rtl) {
        const iconPosRtl = rtl ? Help4.getController().getConfiguration().iconPositionRtl : false;
        if (!iconPosRtl) return pos;

        // XRAY-4671: "Left" means "Start" and "Right" means "End" in case "iconPositionRtl" is set to true
        // this effectively means that they move to the opposite side

        return {
            A: 'E',  // Top Left, Outside
            B: 'D',  // Top Left, Above
            // C: Top Center
            D: 'B',  // Top Right, Above
            E: 'A',  // Top Right, Outside
            F: 'G',  // Top Left
            G: 'F',  // Top Right
            H: 'I',  // Middle Left
            I: 'H',  // Middle Right
            J: 'K',  // Bottom Left
            K: 'J',  // Bottom Right
            L: 'P',  // Bottom Left, Outside
            M: 'O',  // Bottom Left, Below
            // N: Bottom Center
            O: 'M',  // Bottom Right, Below
            P: 'L'   // Bottom Right, Outside
            // Q: Centered
            // R: Manual
        }[pos] || pos;
    }

    /**
     * @memberof Help4.control2.hotspot.Icon#
     * @param {string} size
     * @returns {Help4.typedef.HotspotSize}
     * @private
     */
    function _getSizeConfig(size) {
        const {HOTSPOT_SIZES} = Help4;
        return HOTSPOT_SIZES.find(hs => hs.size === size) || HOTSPOT_SIZES[0];
    }
})();