Source: control2/recording/SelectionRect.js

(function() {
    /**
     * @typedef {Help4.control2.Control.Params} Help4.control2.recording.SelectionRect.Params
     */

    /**
     * Draws the selection rect around elements during hotspot assignment.
     * @augments Help4.control2.Control
     */
    Help4.control2.recording.SelectionRect = class extends Help4.control2.Control {
        /**
         * @override
         * @param {Help4.control2.recording.SelectionRect.Params} [params]
         */
        constructor(params) {
            super(params, {
                statics: {
                    _mbox:             {init: null},
                    _quality:          {},
                    _message:          {},
                    _dnd:              {init: null, destroy: true},
                    _rect:             {init: null},
                    _borderWidth:      {init: 0},
                    _borderHeight:     {init: 0},
                    _onContainerEvent: {}
                },
                config: {
                    css: 'control-capturerect'
                }
            });
        }

        /**
         * @param {Object} info
         * @param {Object} selector
         * @returns {Help4.control2.recording.SelectionRect}
         */
        draw(info, selector) {
            const {x, y, w, h} = this._rect = info.element.rect;
            Help4.extendObject(this.getDom().style, {
                left: x + 'px',
                top: y + 'px',
                width: w + 'px',
                height: h + 'px'
            });

            // use the worst quality
            let {quality} = info.selector;
            while (info.iframe || info.shadow) {
                info = info.iframe || info.shadow;
                if (info.selector.quality > quality) quality = info.selector.quality;
            }

            _setQuality.call(this, quality);
            _setMessage.call(this, quality, selector);
            return this;
        }

        /**
         * @override
         * @param {Help4.control2.recording.SelectionRect.Params} params - same params as provided to the constructor
         */
        _onAfterInit(params) {
            const {container} = this;
            if (container) {
                /** @param {{name: string}} event */
                this._onContainerEvent = ({name}) => {
                    name === 'visible' && _alignMessageBox.call(this);
                }

                container.addListener('dataChange', this._onContainerEvent);
            }
        }

        /** @override */
        _onBeforeDestroy() {
            const {container, _onContainerEvent} = this;
            _onContainerEvent && container?.removeListener?.('dataChange', _onContainerEvent);
        }

        /**
         * @override
         * @param {HTMLElement} dom - control DOM
         */
        _onDomCreated(dom) {
            const _mbox = this._mbox = this._createElement('div', {css: 'mbox'});
            if (Help4.Feature.RecordingDebug) {
                this._dnd = new Help4.jscore.DragDrop({object: _mbox});
            }

            if (this.rtl) this.addCss('rtl');

            const {borderLeftWidth: blw, borderRightWidth: brw, borderBottomWidth: bbw} = getComputedStyle(dom);
            this._borderWidth = parseInt(blw) + parseInt(brw);
            this._borderHeight = parseInt(bbw);
        }
    }

    /**
     * @memberof Help4.control2.recording.SelectionRect#
     * @param {Help4.selector.SELECTOR_QUALITY} quality
     * @private
     */
    function _setQuality(quality) {
        if (this._quality !== quality) {
            const q = Help4.selector.SELECTOR_QUALITY;
            let c  = '';

            switch (this._quality = quality) {
                case q.best: c = 'safe'; break;
                case q.good: c = 'good'; break;
                case q.lang: c = 'lang'; break;
                default: c = 'other'; break;
            }

            this
            .removeCss('safe good lang other')
            .addCss(c);
        }
    }

    /**
     * @memberof Help4.control2.recording.SelectionRect#
     * @param {Help4.selector.SELECTOR_QUALITY} quality
     * @param {Object} selector
     * @private
     */
    function _setMessage(quality, selector) {
        let message;

        if (Help4.Feature.RecordingDebug) {
            message = selector.rule + ': ' + selector.value;
            let s = selector;
            while (s && ({iframe, shadow} = s)) {
                if (s = (iframe || shadow)) message += `\n<${shadow ? 'shadow':'iframe'}> ${s.rule}: ${s.value}`;
            }
        } else {
            const q = Help4.selector.SELECTOR_QUALITY;
            let c = '';

            switch (quality) {
                case q.best: c = 'safe'; break;
                case q.good: c = 'good'; break;
                case q.lang: c = 'lang'; break;
                default: c = 'other'; break;
            }

            message = Help4.Localization.getText('capture.quality.' + c);
        }

        const {_mbox} = this;

        if (this._message !== message) {
            // set message to box
            Help4.Element.setText(_mbox, this._message = message);

            if (Help4.Feature.RecordingDebug) {
                const newCss = [];
                const css = _mbox.className.split(' ');
                for (const c of css) {
                    if (c.indexOf('-selector-') < 0) newCss.push(c);
                }
                _mbox.className = newCss.join(' ');

                Help4.Element.addClass(_mbox, 'selector-' + selector.rule);
            }
        }

        _alignMessageBox.call(this);
    }

    /**
     * @memberof Help4.control2.recording.SelectionRect#
     * @private
     */
    function _alignMessageBox() {
        if (this.isDestroyed()) return;  // XRAY-4753

        // set x, y
        const {_mbox} = this;

        if (_mbox.offsetWidth) {
            const {_rect: {w, h}} = this;
            _mbox.style.top = h + this._borderHeight + 'px';
            _mbox.style.left = ((w + this._borderWidth - _mbox.offsetWidth) >> 1) + 'px';
        } else {
            // XRAY-4720, sometimes the box needs time to render
            setTimeout(() => _alignMessageBox.call(this), Help4.Queue.getTime());
        }
    }
})();