Source: widget/tour/Observer.js

(function() {
    /**
     * @typedef {Object} Help4.widget.tour.Observer.Status
     * @property {?string} [id]
     * @property {?boolean} [visible]
     */

    /**
     * @typedef {Object} Help4.widget.tour.Observer.ElementEvent
     * @property {MouseEvent|KeyboardEvent|FocusEvent|Object} event
     * @property {string} [id]
     * @property {Object} [params]
     * @property {Object} params.value
     * @property {*} params.value.old
     * @property {*} params.value.cur
     */

    /**
     * Element event observation for tour playback
     * @augments Help4.jscore.Base
     * @property {Help4.widget.tour.View} _view
     * @property {Object} _observed
     * @property {Function} _onElementEvent
     * @property {boolean} _blocked
     */
    Help4.widget.tour.Observer = class extends Help4.jscore.Base {
        /**
         * @override
         * @param {Help4.widget.tour.View} view
         */
        constructor(view) {
            super({
                statics: {
                    _view:           {init: view, destroy: false},
                    _observed:       {init: {}, destroy: false},  /** {@link Help4.widget.tour.Observer.Status} */
                    _onElementEvent: {init: event => this.onElementEvent(event), destroy: false},
                    _blocked:        {init: false, destroy: false}
                }
            });

            _startStop.call(this, 'start');
        }

        /** destroy instance */
        destroy() {
            _startStop.call(this, 'stop');
            this.disconnect();
            super.destroy();
        }

        /**
         * see {@link Help4.engine.hotspotManager.HotspotManagerEngine.prototype.observe}
         * @param {Help4.data.TileData} tileData
         * @returns {Help4.widget.tour.Observer}
         */
        observe(tileData) {
            const {/** @type {Help4.widget.tour.View} */ _view} = this;
            const {/** @type {Help4.service.recording.PlaybackService} */ playbackService} = _view.getContext().service;
            const {/** @type {Help4.widget.tour.Observer.Status} */ _observed} = this;

            const {status} = tileData.hotspot;
            const {/** @type {boolean} */ visible} = status;
            const {
                /** @type {string} */ id,
                /** @type {Help4.data.HotspotData} */ hotspot
            } = tileData;

            if (_observed.id !== id) {  // not yet observed
                // disconnect previous observation
                this.disconnect();

                // save status
                _observed.id = id;
                _observed.visible = visible;

                // only start observation in case new element is visible
                visible && playbackService.observe(id, hotspot);
            } else if (_observed.visible !== visible) {  // update same observation
                // already observed; but visibility has changed
                (_observed.visible = visible)
                    ? playbackService.observe(id, hotspot)
                    : playbackService.disconnect(id);
            }

            return this;
        }

        /**
         * stop observation
         * @returns {Help4.widget.tour.Observer}
         */
        disconnect() {
            const {
                /** @type {Help4.widget.tour.View} */ _view,
                /** @type {Help4.widget.tour.Observer.Status} */ _observed
            } = this;
            const {/** @type {Help4.service.recording.PlaybackService} */ playbackService} = _view.getContext().service;

            _observed.id && playbackService.disconnect(_observed.id);
            delete _observed.id;
            delete _observed.visible;
            return this;
        }

        /** @param {Help4.widget.tour.Observer.ElementEvent} event */
        onElementEvent(event) {
            const {/** @type {Help4.widget.tour.View} */ _view} = this;

            let {type} = event.event;
            const {params: {value = null} = {}} = event;

            if (type === 'keyup' && value && value.old === value.cur) {
                // only accept keys in case they change the input field value
                type = null;
            }

            const {/** @type {string[]} */ autoProgress} = /** @type {?Help4.widget.help.ProjectTile} */ _view.getCurrentTile();

            // XRAY-5411: some UI5 controls block click events from reaching CMP.
            // added mouseup as an alternative for click event
            if (Help4.includes(autoProgress, 'click')) autoProgress.push('mouseup');

            if (!Help4.includes(autoProgress, type)) {
                // XRAY-1903
                if (!Help4.Platform.iPad ||
                    type !== 'click' ||
                    !Help4.includes(autoProgress, 'mouseover'))
                {
                    return;
                }
            }

            if (!this._blocked) {
                // XRAY-5422: make sure not to handle this very event again
                this._blocked = true;

                // XRAY-5373: allow screen to update itself before navigating to next step
                setTimeout(async () => {
                    this._blocked = false;
                    // XRAY-6392: rely on navigation to progress to next step, only call if next step is on the same screen
                    if (_view.isNextStepSameScreen()) {
                        await _view.nextStep(true);
                    }
                }, Help4.widget.tour.Widget.NAVIGATE_WAIT_TIME);
            }
        }
    }

    /**
     * @memberof Help4.widget.tour.Observer#
     * @private
     * @param {'start'|'stop'} mode
     */
    function _startStop(mode) {
        const {_onElementEvent, _view} = this;

        const {
            engine: {
                /** @type {Help4.engine.crossorigin.CoreEngine} */ crossOriginEngine: {onElementEvent: coe}
            },
            service: {
                /** @type {Help4.service.recording.PlaybackService} */ playbackService: {onElementEvent: ps},
                /** @type {Help4.service.CrossOriginMessageService} */ crossOriginService: {onElementEvent: cos}
            }
        } = _view.getContext();

        const fun = mode === 'start' ? 'addListener' : 'removeListener';
        ps[fun](_onElementEvent);
        coe[fun](_onElementEvent);
        cos[fun](_onElementEvent);
    }
})();