Source: control2/Lightbox.js

(function() {
    /** @typedef {'full'|'client'|'explicit'|'explicitFull'} Help4.control2.Lightbox.Sizing */

    /**
     * @typedef {Help4.control2.Control.Params} Help4.control2.Lightbox.Params
     * @property {?Help4.control2.Lightbox.Sizing} [sizing = 'full']
     * @property {?Help4.control2.SizeWH} [size = null]
     * @property {?string} [url = null]
     * @property {?string} [content = null]
     * @property {Help4.control2.AreaXYWH} [clientArea = {x: 0, y: 0, w: 0, h: 0}]
     * @property {boolean} [fullCover = false]
     * @property {boolean} [showDetach = false]
     * @property {string} [checkboxText = '']
     */

    /**
     * Lightbox control
     * @augments Help4.control2.Control
     * @property {Help4.control2.Lightbox.Sizing} sizing
     * @property {?Help4.control2.SizeWH} size
     * @property {?string} url
     * @property {?string} content
     * @property {Help4.control2.AreaXYWH} clientArea
     * @property {boolean} fullCover
     * @property {boolean} showDetach
     * @property {string} checkboxText
     * @property {?Help4.control2.button.Option} _notAgain
     * @property {?Help4.control2.button.Button} _detach
     * @property {?Help4.control2.button.Button} _close
     * @property {?HTMLElement} _wrapper
     * @property {?HTMLElement} _header
     * @property {?HTMLElement} _appleWrapper
     * @property {?HTMLElement} _frame
     */
    Help4.control2.Lightbox = class extends Help4.control2.Control {
        /**
         * @override
         * @param {Help4.control2.Lightbox.Params} [params]
         */
        constructor(params) {
            params ||= {};

            let {size} = params;
            if (size) {
                if (typeof size.w !== 'number' || typeof size.h !== 'number') {  // XRAY-966
                    size = null;
                } else {  // XRAY-4580
                    if (size.w % 2 === 1) size.w++;
                    if (size.h % 2 === 1) size.h++;
                }
                params.size = size;
            }

            const T = Help4.jscore.ControlBase.TYPES;
            super(params, {
                params: {
                    url:          {type: T.string_null, readonly: true},
                    content:      {type: T.string_null, readonly: true},

                    sizing:       {type: T.string, init: 'full', readonly: true},  // full, client, explicit, explicitFull
                    size:         {type: T.wh_null, readonly: true},
                    fullCover:    {type: T.boolean, readonly: true},
                    clientArea:   {type: T.xywh},

                    showDetach:   {type: T.boolean, readonly: true},
                    checkboxText: {type: T.string, readonly: true},

                    role:         {init: 'dialog'}
                },
                statics: {
                    _notAgain: {},
                    _detach:   {},
                    _close:    {},

                    _wrapper:      {destroy: false},
                    _header:       {destroy: false},
                    _appleWrapper: {destroy: false},
                    _frame:        {destroy: false}
                },
                config: {
                    css: 'control-lightbox'
                }
            });
        }

        /**
         * @override
         * @param {HTMLElement} dom
         */
        _onDomAvailable(dom) {
            const {
                control2: {
                    button: {APPEARANCES, Button, Option},
                    ICONS
                },
                Localization,
                Platform: {iPad, iPhone}
            } = Help4;

            const {checkboxText, showDetach, url, content} = this;

            const wrapper = this._createElement('div', {css: 'wrapper'});
            const header = this._createElement('div', {css: 'header', dom: wrapper});
            const buttons = checkboxText || showDetach
                ? this._createElement('div', {css: 'wrapper-buttons', dom: header})
                : null;
            this._wrapper = wrapper;
            this._header = header;

            if (checkboxText) {
                this.addCss('notagain');
                this._notAgain = /** @type {Help4.control2.button.Option} */ this._createControl(Option, {
                    dom: buttons,
                    text: checkboxText,
                    css: 'compact notagain'
                })
                .addListener('select', ({data: {active}}) => {
                    this._fireEvent({type: 'lightboxCheckbox', active});
                });
            }

            if (showDetach) {
                this._detach = /** @type {Help4.control2.button.Button} */ this._createControl(Button, {
                    dom: buttons,
                    css: 'external compact',
                    appearance: APPEARANCES.icon,
                    icon: ICONS.external,
                    title: Localization.getText('tooltip.detachwindow')
                })
                .addListener('click', () => {
                    Help4.windowOpen(url);
                    this._fireEvent({type: 'close'});
                });
            } else {
                this.addCss('noexternal');
            }

            this._close = /** @type {Help4.control2.button.Button} */ this._createControl(Button, {
                dom: header,
                css: 'close',
                appearance: APPEARANCES.icon,
                icon: ICONS.close,
                title: Localization.getText('tooltip.closebox')
            })
            .addListener('click', () => {
                this._fireEvent({type: 'close'});
            });

            // XRAY-980: iframe needs a wrapper on apple devices
            let contentDom = wrapper;
            let css = 'content';
            if (url && (iPad || iPhone)) {
                this._appleWrapper = contentDom = this._createElement('div', {css: `${css} has-frame`, dom: wrapper});
                css = 'frame'
            }
            this._frame = this._createElement(url ? 'iframe' : 'div', {css, src: url, html: content, dom: contentDom});
        }

        /**
         * @override
         * @param {Help4.jscore.ControlBase.PropertyChangeEvent} event
         */
        _applyPropertyToDom(event) {
            event.name === 'clientArea'
                ? _align.call(this)
                : super._applyPropertyToDom(event);
        }

        /**
         * XXX: works?
         * @returns {boolean}
         */
        // _handleESC() {
        //     this._fireEvent({type: 'close'});
        //     return true;
        // }
    }

    /**
     * @memberof Help4.control2.Lightbox#
     * @private
     */
    function _align() {
        const {LIGHTBOX_SIZES: {full, explicitFull, client, explicit}} = Help4;

        const {size, clientArea, fullCover, _wrapper, _appleWrapper, _frame, _header} = this;
        let {sizing} = this;

        const fullArea = {x: 0, y: 0, w: window.innerWidth, h: window.innerHeight};
        let coverPosition;  // position of cover
        let wrapperPosition;    // position of wrapper (lightbox within cover)
        switch (sizing) {
            case full:
            case explicitFull:
                coverPosition = wrapperPosition = fullArea;
                break;
            case client:
            case explicit:
                coverPosition = fullCover ? fullArea : clientArea;
                wrapperPosition = fullCover
                    ? {...clientArea, y: 0}
                    : {...clientArea, x: 0, y: 0};
                break;
        }

        // is the lightbox big enough for requested size?
        const doesFit = size && (sizing === explicitFull || sizing === explicit)
            ? wrapperPosition.w > size.w && wrapperPosition.h > size.h
            : true;

        // fallback in case size too small
        if (!doesFit || wrapperPosition.w < 200 || wrapperPosition.h < 200) {
            coverPosition = wrapperPosition = fullArea;
            sizing = full;
        }

        // align cover and wrapper (box)
        this.removeCss(client, explicit, full);
        this.addCss(sizing === explicitFull ? explicit : sizing);
        _setAreaStyle.call(this, this.getDom(), coverPosition);
        _setAreaStyle.call(this, _wrapper, wrapperPosition);

        const frame = _appleWrapper || _frame;
        if (size && (sizing === explicit || sizing === explicitFull)) {
            // manual mode: set styles with JS instead CSS
            Help4.extendObject(frame.style, {width: `${size.w}px`, height: `${size.h}px`});
            Help4.extendObject(_header.style, {width: `${size.w}px`, marginTop: (-size.h >> 1) + 'px'});
        } else {
            Help4.extendObject(frame.style, {width: null, height: null});
            Help4.extendObject(_header.style, {width: null, marginTop: null});
        }
    }

    /**
     * @memberof Help4.control2.Lightbox#
     * @private
     * @param {HTMLElement} dom
     * @param {Help4.control2.AreaXYWH} area
     */
    function _setAreaStyle(dom, area) {
        const map = {x: 'left', y: 'top', w: 'width', h: 'height'};
        const style = {};
        Object.entries(map).forEach(([areaName, styleName]) => {
            style[styleName] = typeof area[areaName] === 'string' ? area[areaName] : `${area[areaName]}px`;
        });
        Help4.extendObject(dom.style, style);
    }
})();