"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const keyboardTrap = __importStar(require("makeup-keyboard-trap"));
const screenReaderTrap = __importStar(require("makeup-screenreader-trap"));
const bodyScroll = __importStar(require("../../../common/body-scroll"));
const eventUtils = __importStar(require("../../../common/event-utils"));
const transition_1 = __importDefault(require("../../../common/transition"));
class DialogBase {
    get useHiddenProperty() {
        return this.input.useHiddenProperty || false;
    }
    trackLastClick(e) {
        if (e.defaultPrevented ||
            e.metaKey ||
            e.ctrlKey ||
            e.shiftKey ||
            e.button !== 0) {
            return;
        }
        let el = e.target;
        // Find an <button> element that may have been clicked.
        while (el !== null && el.nodeName !== "BUTTON") {
            el = el.parentNode;
        }
        // Store the button that was clicked.
        this.clickTarget = el;
    }
    getActiveElement(input) {
        let closeFocusEl = null;
        if (input && input.closeFocus) {
            closeFocusEl = document.getElementById(input.closeFocus);
        }
        const el = document.activeElement === document.body
            ? this.clickTarget
            : document.activeElement;
        return closeFocusEl || el;
    }
    handleStartClick({ target }) {
        this.startEl = target;
    }
    handleScroll() {
        this.emit("scroll");
    }
    handleKeydown(event) {
        eventUtils.handleEscapeKeydown(event, () => {
            this.state.open = false;
            this.emit("escape");
        });
    }
    handleDialogClick({ target, clientY, }) {
        const { closeEl, windowEl, startEl } = this;
        this.startEl = null;
        if (this.input.buttonPosition === "hidden") {
            return;
        }
        if (windowEl === null || windowEl === void 0 ? void 0 : windowEl.contains(startEl !== null && startEl !== void 0 ? startEl : null)) {
            // Started on dialog window and user dragged out, don't close
            return;
        }
        // Checks if we clicked inside the white panel of the dialog.
        if (!(closeEl === null || closeEl === void 0 ? void 0 : closeEl.contains(target)) && (windowEl === null || windowEl === void 0 ? void 0 : windowEl.contains(target))) {
            const { bottom } = windowEl.getBoundingClientRect();
            const { paddingBottom } = getComputedStyle(windowEl);
            const windowBottom = bottom - parseInt(paddingBottom, 10);
            if (clientY < windowBottom) {
                return;
            }
        }
        this.state.open = false;
    }
    handleCloseButtonClick() {
        this.state.open = false;
    }
    onInput(input) {
        input.isModal = input.isModal !== false;
        if (!this.isAnimating) {
            this.state = { open: input.open || false };
        }
    }
    onRender() {
        if (typeof window !== "undefined") {
            this._release();
        }
    }
    onMount() {
        var _a, _b, _c, _d;
        this.rootEl = (_a = this.getEl()) !== null && _a !== void 0 ? _a : null;
        this.windowEl = (_b = this.getEl("window")) !== null && _b !== void 0 ? _b : null;
        this.closeEl = (_c = this.getEl("close")) !== null && _c !== void 0 ? _c : null;
        this.bodyEl = (_d = this.getEl("body")) !== null && _d !== void 0 ? _d : null;
        this.transitionList = this.input.transitionList || [
            "background-color",
            "transform",
        ];
        // Add an event listener to the dialog to fix an issue with Safari not recognizing it as a touch target.
        this.subscribeTo(this.rootEl).on("click", () => { });
        this._trap({
            firstRender: true,
        });
    }
    onUpdate() {
        this._trap({
            firstRender: false,
        });
    }
    _triggerFocus(focusEl) {
        if (this.input.isModal && focusEl) {
            focusEl.focus();
        }
    }
    _triggerBodyScroll(prevent) {
        if (this.input.isModal) {
            if (prevent) {
                bodyScroll.prevent();
            }
            else {
                bodyScroll.restore();
            }
        }
    }
    onDestroy() {
        this._cancelAsync();
        this._release();
        if (this.isTrapped) {
            this._triggerBodyScroll(false);
        }
    }
    _getTrapCallback(restoreTrap, isTrapped, wasTrapped) {
        const willTrap = this.input.isModal && (restoreTrap || (isTrapped && !wasTrapped));
        const useHiddenProperty = this.useHiddenProperty;
        return () => {
            if (willTrap) {
                screenReaderTrap.trap(this.el, { useHiddenProperty });
                if (!useHiddenProperty) {
                    // Adding request animation frame because focusables will return that all elements are not visible since dialog is still animating.
                    requestAnimationFrame(() => keyboardTrap.trap(this.windowEl));
                }
            }
        };
    }
    /**
     * Ensures that if a component is supposed to be trapped that this is
     * trapped after rendering.
     */
    _trap(opts) {
        var _a, _b;
        const { isTrapped: wasTrapped, restoreTrap } = this;
        const isTrapped = (this.isTrapped = this.state.open);
        const isFirstRender = opts && opts.firstRender;
        const wasToggled = isTrapped !== wasTrapped;
        const focusEl = (this.input.focus && document.getElementById(this.input.focus)) ||
            this.closeEl;
        const runTraps = this._getTrapCallback(restoreTrap !== null && restoreTrap !== void 0 ? restoreTrap : false, isTrapped, wasTrapped !== null && wasTrapped !== void 0 ? wasTrapped : false);
        // Ensure focus is set and body scroll prevented on initial render.
        if (isFirstRender && this.input.isModal && isTrapped) {
            this._prevFocusEl = this.getActiveElement(this.input);
            focusEl && this._triggerFocus(focusEl);
            this._triggerBodyScroll(true);
        }
        if (wasToggled) {
            this._cancelAsync();
            const onFinishTransition = () => {
                var _a, _b, _c, _d;
                this.cancelTransition = undefined;
                runTraps();
                if (isTrapped) {
                    (_a = this.rootEl) === null || _a === void 0 ? void 0 : _a.removeAttribute("hidden");
                    this._triggerFocus(focusEl);
                    this.emit("open");
                    this.isAnimating = false;
                }
                else {
                    this._triggerBodyScroll(false);
                    const activeElement = this.getActiveElement(this.input);
                    (_b = this.rootEl) === null || _b === void 0 ? void 0 : _b.setAttribute("hidden", "");
                    this.emit("close");
                    this.isAnimating = false;
                    if (
                    // Skip restoring focus if the focused element was changed via the dialog-close event
                    activeElement === this.getActiveElement(this.input) &&
                        // Skip restoring focus if the previously focused element was removed from the DOM.
                        document.documentElement.contains((_c = this._prevFocusEl) !== null && _c !== void 0 ? _c : null)) {
                        (_d = this._prevFocusEl) === null || _d === void 0 ? void 0 : _d.focus();
                    }
                    // Reset dialog scroll position lazily to avoid jank.
                    // Note since the dialog is not in the dom at this point none of the scroll methods will work.
                    this.cancelScrollReset = setTimeout(() => {
                        var _a, _b;
                        (_b = (_a = this.rootEl) === null || _a === void 0 ? void 0 : _a.parentNode) === null || _b === void 0 ? void 0 : _b.replaceChild(this.rootEl, this.rootEl);
                        this.cancelScrollReset = undefined;
                    }, 20);
                }
            };
            if (isTrapped) {
                if (!isFirstRender) {
                    this._prevFocusEl = this.getActiveElement(this.input);
                    this._triggerBodyScroll(true);
                    this.isAnimating = true;
                    this.cancelTransition = (0, transition_1.default)({
                        el: this.rootEl,
                        className: `${this.input.classPrefix}--show`,
                        transitionList: this.transitionList,
                    }, onFinishTransition);
                }
                else {
                    this.isAnimating = false;
                    (_a = this.rootEl) === null || _a === void 0 ? void 0 : _a.removeAttribute("hidden");
                    runTraps();
                }
            }
            else {
                if (!isFirstRender) {
                    this.isAnimating = true;
                    this.cancelTransition = (0, transition_1.default)({
                        el: this.rootEl,
                        className: `${this.input.classPrefix}--hide`,
                        transitionList: this.transitionList,
                    }, onFinishTransition);
                }
                else {
                    this.isAnimating = false;
                    (_b = this.rootEl) === null || _b === void 0 ? void 0 : _b.setAttribute("hidden", "");
                }
            }
        }
        else if (restoreTrap) {
            // In the case where the page rerendered, attach all traps again
            runTraps();
        }
    }
    /**
     * Releases the trap before each render and on destroy so
     * that Marko can update normally without the inserted dom nodes.
     * Only releases on isModal dialogs
     */
    _release() {
        if (this.isTrapped && this.input.isModal) {
            this.restoreTrap = this.state.open;
            screenReaderTrap.untrap(this.el);
            if (!this.useHiddenProperty) {
                keyboardTrap.untrap(this.windowEl);
            }
        }
        else {
            this.restoreTrap = false;
        }
    }
    _cancelAsync() {
        if (this.cancelScrollReset) {
            clearTimeout(this.cancelScrollReset);
            this.cancelScrollReset = undefined;
        }
        if (this.cancelTransition) {
            this.cancelTransition();
            this.cancelTransition = undefined;
        }
    }
}
module.exports = DialogBase;
