Source: service/zoom/LightboxZoom.js

(function() {
    /**
     * @typedef {Object} Help4.service.zoom.LightboxZoom.Params
     * @property {Help4.controller.Controller} controller
     */

    /**
     * @typedef {Object} Help4.service.zoom.LightboxZoom.ZoomData
     * @property {boolean} zoomed
     * @property {Help4.control.Lightbox|Help4.control2.Lightbox|null} lightbox
     */

    /**
     * @augments Help4.jscore.ControlBase
     * @property {Object} _images
     */
    Help4.service.zoom.LightboxZoom = class extends Help4.jscore.ControlBase {
        /**
         * @override
         * @param {Help4.service.zoom.LightboxZoom.Params} params
         */
        constructor(params) {
            const {TYPES: T} = Help4.jscore.ControlBase;

            super(params, {
                params: {
                    controller: {type: T.instance, init: null, mandatory: true, readonly: true}
                },
                statics: {
                    _images: {init: {}, destroy: false}  /** see {@link Help4.service.zoom.LightboxZoom.ZoomData} */
                }
            });
        }

        /**
         * transform img tag for zoom
         * @param {string} input
         * @returns {?string}
         */
        static transform(input) {
            if (input.indexOf('data-zoomable="true') < 0) return null;  // zoom needs to be enabled
            if (input.indexOf('zoom="inplace"') >= 0) return null;  // do not interfere with in-place zoom

            // add id, if missing
            const id = /id="(.*?)"/.exec(input)?.[1];
            if (!id) {
                const newId = Help4.createId();
                input = `<img id="${newId}"` + input.slice(4);
            }

            // add zoom-lightbox class
            const css = /class="(.*?)"/.exec(input)?.[1];
            let newCss = Help4.Element.createClassName('zoom-lightbox');
            if (css) {
                newCss = `${css} ${newCss}`;
                input = input.replace(`class="${css}"`, `class="${newCss}"`);
            } else {
                input = `<img class="${newCss}"` + input.slice(4);
            }

            return input;
        }

        /**
         * watch additional images
         * @param {HTMLImageElement[]} images
         * @returns {Help4.service.zoom.LightboxZoom}
         */
        activate(images) {
            const listener = event => _onEvent.call(this, event);
            const {_images} = this;

            for (const img of images) {
                const {id} = img;

                if (id &&
                    Help4.clampBoolean(img.getAttribute('data-zoomable')) &&
                    img.getAttribute('data-zoom') !== 'inplace')  // do not interfere with in-place zoom
                {
                    _images[id] = /** @type {Help4.service.zoom.LightboxZoom.ZoomData} */ {zoomed: false, lightbox: null};
                    img.addEventListener('click', listener, {passive: true});
                }
            }

            return this;
        }

        /**
         * update image information
         * @returns {Help4.service.zoom.LightboxZoom}
         */
        update() {
            const {dom, dom2} = _getDom.call(this);
            const {_images} = this;

            for (const id of Object.keys(_images)) {
                const search = `#${id}`;
                if (dom.querySelector(search) || dom2?.querySelector(search)) continue;
                delete _images[id];
            }

            return this;
        }

        /**
         * @param {HTMLImageElement[]} removeList
         * @returns {Help4.service.zoom.LightboxZoom}
         */
        deactivate(removeList) {
            const {_images} = this;

            for (const [id, data] of Object.entries(_images)) {
                for (const img of removeList) {
                    if (img.id === id) {
                        data.zoomed && _resetZoom.call(this, data);
                        delete _images[id];
                        break;
                    }
                }
            }

            return this;
        }

        /** @returns {number} */
        getCount() {
            return Object.keys(this._images).length;
        }
    }

    /**
     * @memberof Help4.service.zoom.LightboxZoom#
     * @private
     * @returns {{dom: HTMLElement, dom2: ?HTMLElement}}
     */
    function _getDom() {
        const {/** @type {Help4.controller.Controller} */ controller} = this;
        const dom = /** @type {HTMLElement} */ controller.getDom();
        const dom2 = /** @type {?HTMLElement} */ controller.getDom2('dom');
        return {dom, dom2};
    }

    /**
     * @memberof Help4.service.zoom.LightboxZoom#
     * @private
     * @param {PointerEvent} event
     */
    function _onEvent(event) {
        const image = /** @type {HTMLImageElement} */ event.target;
        const {id} = image;

        const {_images} = this;
        const data = id && _images[id];
        if (data) {
            data.zoomed
                ? _resetZoom.call(this, data)
                : _zoom.call(this, data, image);
        }
    }

    /**
     * @memberof Help4.service.zoom.LightboxZoom#
     * @private
     * @param {Help4.service.zoom.LightboxZoom.ZoomData} data
     * @param {HTMLImageElement} image
     */
    function _zoom(data, image) {
        const {controller} = this;
        const lightboxService = controller.getService('lightbox');
        const {core: {isActiveCMP4, isRemoteMode}} = /** @type {Help4.typedef.SystemConfiguration} */ controller.getConfiguration();

        const size = {
            w: image.naturalWidth,
            h: image.naturalHeight
        };
        const content = image.outerHTML.replace(/style="[^"]*"/gi, '');
        const onClose = () => _resetZoom.call(this, data);

        if (isActiveCMP4) {
            const widget = Help4.widget.getActiveInstance();
            const name = widget?.getName();

            /** @type {Help4.widget.help.view.ContentView|Help4.widget.tour.View} */ let view;
            if (name === 'help' || name === 'whatsnew') {
                view = /** @type {Help4.widget.help.view2.View} */ widget.getContext().widget.help.view?.getContentView();
            } else if (name === 'tour') {
                view = /** @type {Help4.widget.tour.View} */ widget.getContext().widget.tour.view;
            }

            const dom = view?.getDom();
            if (!dom) return;

            const {control2} = Help4;

            data.zoomed = true;
            data.lightbox = /** @type {Help4.control2.Lightbox} */ control2.createControl(control2.Lightbox, view, {
                dom,
                sizing: 'explicit',
                clientArea: {x: 0, y: 0, w: window.innerWidth, h: window.innerHeight},
                size,
                content,
                fullCover: true
            })
            .addListener('close', onClose);
        } else {
            const clientArea = isRemoteMode
                ? controller.getEngine('remoteControl').getContentArea()
                : controller.getHandler().getContentArea();

            data.zoomed = true;
            data.lightbox = lightboxService.add({
                sizing: 'explicit',
                clientArea,
                size,
                content,
                showDetach: false,
                fullCover: true,
                autoDestroy: false,
                onclose: onClose,
            }, 'control');
        }
    }

    /**
     * @memberof Help4.service.zoom.LightboxZoom#
     * @private
     * @param {Help4.service.zoom.LightboxZoom.ZoomData} data
     */
    function _resetZoom(data) {
        data.lightbox?.destroy();
        data.lightbox = null;
        data.zoomed = false;
    }
})();