/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { Host, } from "@stencil/core";
import { h } from "../../utils/renderer";
import Modal from "../../utils/modal";
import { lockBodyScrolling, unlockBodyScrolling } from "../../utils/scroll";
import { HasSlotController } from "../../utils/slot";
import { waitForEvent } from "../../utils/events";
import { ComponentStore, } from "../../utils/store/component-store";
import { getActiveElement } from "../../utils/active-element";
import { drag } from "../../utils/drag";
import { getTabbableElements } from "../../utils/tabbable";
/**
 * Drawers slide in from a container to expose additional options and information.
 *
 * @version 5.0.0
 * @status stable
 *
 * @slot - The drawer's main content
 * @slot label - The drawer's label. Alternatively, you can use the `label` attribute
 * @slot header-actions - Optional actions to add to the header. Works best with `<nano-icon-button>`
 * @slot handle - The drawer's resize handle, used to resize the drawer when `resizable` is set
 * @slot footer - The drawer's footer, usually one or more buttons representing various options
 *
 * @part base - The component's base wrapper
 * @part overlay - The overlay that covers the screen behind the drawer
 * @part panel - The drawer's panel (where the drawer and its content are rendered)
 * @part header - The drawer's header. This element wraps the title and header actions
 * @part header-actions - Optional actions to add to the header. Works best with `<nano-icon-button>`
 * @part handle - The resize handle, used to resize the drawer when `resizable` is set
 * @part title - The drawer's title
 * @part close-button - The close button, an `<nano-icon-button>`
 * @part close-button__base - The close button's exported `base` part
 * @part body - The drawer's body
 * @part footer - The drawer's footer
 */
export class Drawer {
    // Private State
    host;
    originalTrigger;
    drawer;
    overlay;
    modal;
    slotCtrl = new HasSlotController(this, 'footer');
    get panel() {
        return this._panel;
    }
    set panel(ele) {
        if (this._panel) {
            this.panel.removeEventListener('animationend', this.handleTransitionEnd);
        }
        if (!ele)
            return;
        ele.addEventListener('transitionend', this.handleTransitionEnd);
        this._panel = ele;
    }
    _panel;
    noDismiss = false;
    isOpen = false;
    // Public API
    /**
     * Indicates whether or not the drawer is open. You can toggle this attribute to show and hide the drawer, or you can
     * use the `show()` and `hide()` methods and this attribute will reflect the drawer's open state.
     */
    open = false;
    /**
     * The drawer's label as displayed in the header. You should always include a relevant label even when using
     * `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead.
     */
    label;
    /** The direction from which the drawer will open. */
    placement = 'end';
    /**
     * By default, the drawer slides out of its containing block (usually the viewport). To make the drawer slide out of
     * its parent element, set this attribute and add `position: relative` to the parent.
     */
    contained = false;
    /**
     * Removes the header. This will also remove the default close button, so please ensure you provide an easy,
     * accessible way for users to dismiss the drawer.
     */
    noHeader = false;
    /** Store search queries (against this ID) to the component store. Use in conjunction with storeMethod */
    storeId;
    /** The method of storage. Either session storage, url hash (after the '#') or url query (after the '?'). */
    storeMethod = 'url-hash';
    /** An alternative to `preventDefault()` on the `nanoRequestClose` event. This will hide the close button and disable clicks on the overlay or presses the `Escape` key  */
    noUserDismiss = false;
    /** Relocate the dialog to the root of the DOM. Useful for elements bound via css `transform: ...` */
    hoist = false;
    /** Makes the drawer resizable */
    resizable = false;
    handleHoistChange() {
        // if native popover is supported then bail as this is not required
        if ('popover' in globalThis.HTMLElement.prototype ||
            !this.hoist ||
            Array.from(document.body.children).includes(this.host))
            return;
        document.body.prepend(this.host);
    }
    /** Emitted when the drawer opens. */
    nanoShow;
    /** Emitted when the drawer closes. */
    nanoHide;
    /** Emitted after the drawer closes and all animations are complete. */
    nanoAfterHide;
    /** Emitted after the drawer opens and all animations are complete. */
    nanoAfterShow;
    /**
     * Emitted when the drawer opens and is ready to receive focus. Calling
     * `event.preventDefault()` will prevent focusing and allow you to set it on a different element, such as an input.
     */
    nanoInitialFocus;
    /**
     * Emitted when the user attempts to
     * close the drawer by clicking the close button, clicking the overlay, or pressing escape. Calling
     * `event.preventDefault()` will keep the drawer open. Avoid using this unless closing the drawer will result in
     * destructive behavior such as data loss.
     */
    nanoRequestClose;
    /**
     * Shows the drawer.
     * @returns a promise that resolves when the drawer has finished opening
     */
    async show() {
        if (this.open)
            return undefined;
        this.open = true;
        return waitForEvent(this.host, 'nanoAfterShow');
    }
    /**
     * Hides the drawer
     * @returns a promise that resolves when the drawer has finished closing
     */
    async hide() {
        if (!this.open)
            return undefined;
        this.open = false;
        return waitForEvent(this.host, 'nanoAfterHide');
    }
    requestClose(source) {
        const nanoRequestClose = this.nanoRequestClose.emit({ source });
        if (nanoRequestClose.defaultPrevented || this.noUserDismiss) {
            this.noDismiss = true;
            setTimeout(() => (this.noDismiss = false), 250);
            return;
        }
        this.hide();
    }
    // Prop Change Handlers
    handleNoModalChange() {
        if (this.open && !this.contained) {
            this.modal.activate();
            if (typeof this.drawer?.showPopover === 'function') {
                this.drawer.showPopover();
            }
            lockBodyScrolling(this.host);
        }
        if (this.open && this.contained) {
            this.modal.deactivate();
            unlockBodyScrolling(this.host);
        }
    }
    async handleOpenChange() {
        if (this.open) {
            // Show
            this.nanoShow.emit();
            this.addOpenListeners();
            this.originalTrigger = getActiveElement();
            // Lock body scrolling only if the drawer isn't contained
            if (!this.contained) {
                this.modal.activate();
                if (typeof this.drawer?.showPopover === 'function') {
                    this.drawer.showPopover();
                }
                lockBodyScrolling(this.host);
            }
            // When the drawer is shown, Safari will attempt to set focus on whatever element has autofocus. This causes the
            // drawer's animation to jitter.
            const autoFocusTarget = this.host.querySelector('[autofocus]');
            if (autoFocusTarget) {
                autoFocusTarget.removeAttribute('autofocus');
            }
            this.drawer.hidden = false;
            this.isOpen = true;
            // Set initial focus
            requestAnimationFrame(() => {
                const nanoInitialFocus = this.nanoInitialFocus.emit();
                if (!nanoInitialFocus.defaultPrevented) {
                    const firstTabbable = autoFocusTarget ||
                        getTabbableElements(this.host, true, true, true)[0] ||
                        this.panel;
                    firstTabbable.focus({ preventScroll: true });
                }
                // Restore the autofocus attribute
                if (autoFocusTarget) {
                    autoFocusTarget.setAttribute('autofocus', '');
                }
            });
            return;
        }
        // Hide
        this.nanoHide.emit();
        this.removeOpenListeners();
        if (!this.contained) {
            this.modal.deactivate();
            unlockBodyScrolling(this.host);
        }
        // Restore focus to the original trigger
        const returnFocus = this.originalTrigger && typeof this.originalTrigger.focus === 'function'
            ? this.originalTrigger
            : document.body;
        returnFocus.focus();
        requestAnimationFrame(() => {
            this.isOpen = false;
        });
    }
    // Event Handling
    addOpenListeners() {
        document.addEventListener('keydown', this.handleDocumentKeyDown);
    }
    removeOpenListeners() {
        document.removeEventListener('keydown', this.handleDocumentKeyDown);
    }
    handleDocumentKeyDown = (event) => {
        // Contained drawers aren't modal and don't respond to the escape key
        if (this.contained) {
            return;
        }
        if (event.key === 'Escape' && this.modal.isActive() && this.open) {
            event.stopImmediatePropagation();
            this.requestClose('keyboard');
        }
    };
    handleTransitionEnd = (event) => {
        if (event.propertyName === 'opacity' &&
            event
                .composedPath()
                .find((node) => node === this.panel || node === this.overlay)) {
            if (!this.open) {
                this.drawer.hidden = true;
                // Now that the dialog is hidden, restore the overlay and panel for next time
                this.overlay.hidden = false;
                this.panel.hidden = false;
                if (!this.contained && typeof this.drawer?.hidePopover === 'function') {
                    this.drawer.hidePopover();
                }
                this.nanoAfterHide.emit();
                return;
            }
            this.nanoAfterShow.emit();
        }
    };
    get panelSize() {
        const vertical = ['top', 'bottom'].includes(this.placement);
        return vertical ? this.panel.offsetHeight : this.panel.offsetWidth;
    }
    set panelSize(size) {
        const prop = ['top', 'bottom'].includes(this.placement)
            ? 'blockSize'
            : 'inlineSize';
        this.panel.style[prop] = `clamp(var(--min-size), ${size + 'px'}, var(--max-size))`;
    }
    handleKeyDown = (event) => {
        if ([
            'ArrowLeft',
            'ArrowRight',
            'ArrowUp',
            'ArrowDown',
            'Home',
            'End',
        ].includes(event.key)) {
            let newPosition = this.panelSize;
            const flip = this.placement === 'end' || this.placement === 'bottom';
            const incr = (event.shiftKey ? 30 : 5) * (flip ? -1 : 1);
            const vertical = ['top', 'bottom'].includes(this.placement);
            event.preventDefault();
            if ((event.key === 'ArrowLeft' && !vertical) ||
                (event.key === 'ArrowUp' && vertical)) {
                newPosition -= incr;
            }
            if ((event.key === 'ArrowRight' && !vertical) ||
                (event.key === 'ArrowDown' && vertical)) {
                newPosition += incr;
            }
            if (event.key === 'Home') {
                newPosition = 0;
            }
            if (event.key === 'End') {
                newPosition = 10000;
            }
            this.panelSize = newPosition;
        }
    };
    handleDrag = (e) => {
        // Prevent text selection when dragging
        e.preventDefault();
        const vertical = ['top', 'bottom'].includes(this.placement);
        const flip = this.placement === 'end' || this.placement === 'bottom';
        const initDimension = vertical
            ? this.panel.offsetHeight
            : this.panel.offsetWidth;
        drag(this.host, {
            relative: true,
            initialEvent: e,
            onMove: (x, y) => {
                let movedPixels = vertical ? y : x;
                if (flip)
                    movedPixels *= -1;
                this.panelSize = initDimension + movedPixels;
            },
        });
    };
    // Component Lifecycle
    connectedCallback() {
        this.handleHoistChange();
        this.modal = new Modal(this.host);
    }
    disconnectedCallback() {
        unlockBodyScrolling(this.host);
    }
    componentWillLoad() {
        // Show on init if open
        if (this.storeId)
            ComponentStore.init(this, ['open'], this.storeMethod, this.storeId);
    }
    componentDidLoad() {
        this.drawer.hidden = !this.open;
        if (this.open) {
            this.handleNoModalChange();
            this.show();
        }
    }
    render() {
        return (h(Host, { key: '487ea65e478b81f58f3302110777a756e5dcf7d4', class: "nano-drawer" }, h("div", { key: '12feebdc459f4643d41d5d2dc19643c13c0d9809', ref: (ele) => (this.drawer = ele), part: "base", class: {
                drawer: true,
                'drawer--open': this.open,
                [`drawer--${this.placement}`]: true,
                'drawer--contained': this.contained,
                'drawer--fixed': !this.contained,
                'drawer--has-footer': this.slotCtrl.has('footer'),
                'drawer--has-header': !this.noHeader,
                'drawer--nodismiss': this.noDismiss,
            }, popover: !this.contained ? 'manual' : undefined }, h("div", { key: 'e38b14383dadea34b11db92d6871518b82192859', ref: (ele) => (this.overlay = ele), part: "overlay", class: "drawer__overlay", onClick: () => this.requestClose('overlay'), tabindex: "-1" }), h("div", { key: '4a934cc84b9d4ff06bff84feaf75ac8d463d2d5f', ref: (ele) => (this.panel = ele), part: "panel", class: "drawer__panel", role: "dialog", "aria-modal": "true", "aria-hidden": this.isOpen ? 'false' : 'true', "aria-label": this.noHeader ? this.label : undefined, "aria-labelledby": !this.noHeader ? 'title' : undefined, tabindex: "0" }, this.resizable && (h("div", { key: '7c8d1c12356974522a698d69ff2e9015dd60ae92', part: "resize-handle", class: "drawer__resize-handle", tabindex: "0", role: "separator", "aria-label": "Resize", onKeyDown: this.handleKeyDown, onMouseDown: this.handleDrag, onTouchStart: this.handleDrag }, h("slot", { key: '48e2497abc58310beb96648b4cc44d80ae28431f', name: "handle" }, ['start', 'end'].includes(this.placement) ? (h("nano-icon", { slot: "handle", name: "solid/grip-dots-vertical" })) : (h("nano-icon", { slot: "handle", name: "solid/grip-dots" }))))), h("div", { key: '425d5eb60035fb44b7c3ea4ddf4f338d429893ab', class: "drawer__main" }, !this.noHeader && (h("nano-sticker", { key: '1f8eac374619dd3b607862254c2573e19963374c' }, h("div", { key: '070d62dd270a2040f32e509ec0a2a5eb8c739367', part: "header", class: "drawer__header" }, h("div", { key: 'f3d0155227515ad548ee70e65a2213711dc13a3a', part: "title", class: "drawer__title", id: "title" }, h("slot", { key: '61410c8918849984dd31854a042ec3209c1ca175', name: "label" }, this.label.length > 0
            ? this.label
            : String.fromCharCode(65279))), h("div", { key: '262e4616c018e346596b089968f6af8b4a39436a', part: "header-actions", class: "drawer__header-actions" }, h("slot", { key: '81245d6449637ca85f631d2065b173dfa3b6c663', name: "header-actions" }), !this.noUserDismiss && (h("nano-icon-button", { key: '0b88624e93df33a23f3f132d05d5f8fbc418f520', part: "close-button", exportparts: "base:close-button__base", class: "drawer__close", iconName: "light/xmark", label: "Close", onClick: () => this.requestClose('close-button') })))))), h("div", { key: '50a441f9c9417ba4fa29f30b63cc54eaf5618a98', part: "body", class: "drawer__body" }, h("slot", { key: '478310362285c4ea2fcde71838da527640bf09da' })), h("nano-sticker", { key: 'bb6c4a2c6ce89a03270daaf194b0a78914437c2b', position: "bottom" }, h("footer", { key: 'efbb6ac1f46e3bce948a236f01e26adc63b628cc', part: "footer", class: "drawer__footer" }, h("slot", { key: '5a3d95c01ce45c51715d89e4cc3064f337d59b23', name: "footer" }))))))));
    }
    static get is() { return "nano-drawer"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["drawer.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["drawer.css"]
        };
    }
    static get properties() {
        return {
            "open": {
                "type": "boolean",
                "attribute": "open",
                "mutable": true,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Indicates whether or not the drawer is open. You can toggle this attribute to show and hide the drawer, or you can\nuse the `show()` and `hide()` methods and this attribute will reflect the drawer's open state."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "false"
            },
            "label": {
                "type": "string",
                "attribute": "label",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The drawer's label as displayed in the header. You should always include a relevant label even when using\n`no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead."
                },
                "getter": false,
                "setter": false,
                "reflect": true
            },
            "placement": {
                "type": "string",
                "attribute": "placement",
                "mutable": false,
                "complexType": {
                    "original": "'top' | 'end' | 'bottom' | 'start'",
                    "resolved": "\"bottom\" | \"end\" | \"start\" | \"top\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The direction from which the drawer will open."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "'end'"
            },
            "contained": {
                "type": "boolean",
                "attribute": "contained",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "By default, the drawer slides out of its containing block (usually the viewport). To make the drawer slide out of\nits parent element, set this attribute and add `position: relative` to the parent."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "false"
            },
            "noHeader": {
                "type": "boolean",
                "attribute": "no-header",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Removes the header. This will also remove the default close button, so please ensure you provide an easy,\naccessible way for users to dismiss the drawer."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "false"
            },
            "storeId": {
                "type": "string",
                "attribute": "store-id",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Store search queries (against this ID) to the component store. Use in conjunction with storeMethod"
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "storeMethod": {
                "type": "string",
                "attribute": "store-method",
                "mutable": false,
                "complexType": {
                    "original": "StorageMethods",
                    "resolved": "\"local\" | \"session\" | \"url-hash\" | \"url-hash-push\" | \"url-query\" | \"url-query-push\"",
                    "references": {
                        "StorageMethods": {
                            "location": "import",
                            "path": "../../utils/store/component-store",
                            "id": "src/utils/store/component-store.ts::StorageMethods"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The method of storage. Either session storage, url hash (after the '#') or url query (after the '?')."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "'url-hash'"
            },
            "noUserDismiss": {
                "type": "boolean",
                "attribute": "no-user-dismiss",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "An alternative to `preventDefault()` on the `nanoRequestClose` event. This will hide the close button and disable clicks on the overlay or presses the `Escape` key"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "hoist": {
                "type": "boolean",
                "attribute": "hoist",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Relocate the dialog to the root of the DOM. Useful for elements bound via css `transform: ...`"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "resizable": {
                "type": "boolean",
                "attribute": "resizable",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Makes the drawer resizable"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            }
        };
    }
    static get states() {
        return {
            "noDismiss": {},
            "isOpen": {}
        };
    }
    static get events() {
        return [{
                "method": "nanoShow",
                "name": "nanoShow",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the drawer opens."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoHide",
                "name": "nanoHide",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the drawer closes."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoAfterHide",
                "name": "nanoAfterHide",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted after the drawer closes and all animations are complete."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoAfterShow",
                "name": "nanoAfterShow",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted after the drawer opens and all animations are complete."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoInitialFocus",
                "name": "nanoInitialFocus",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the drawer opens and is ready to receive focus. Calling\n`event.preventDefault()` will prevent focusing and allow you to set it on a different element, such as an input."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoRequestClose",
                "name": "nanoRequestClose",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the user attempts to\nclose the drawer by clicking the close button, clicking the overlay, or pressing escape. Calling\n`event.preventDefault()` will keep the drawer open. Avoid using this unless closing the drawer will result in\ndestructive behavior such as data loss."
                },
                "complexType": {
                    "original": "{\n    source: 'close-button' | 'keyboard' | 'overlay';\n  }",
                    "resolved": "{ source: \"overlay\" | \"close-button\" | \"keyboard\"; }",
                    "references": {}
                }
            }];
    }
    static get methods() {
        return {
            "show": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Shows the drawer.",
                    "tags": [{
                            "name": "returns",
                            "text": "a promise that resolves when the drawer has finished opening"
                        }]
                }
            },
            "hide": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Hides the drawer",
                    "tags": [{
                            "name": "returns",
                            "text": "a promise that resolves when the drawer has finished closing"
                        }]
                }
            }
        };
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "hoist",
                "methodName": "handleHoistChange"
            }, {
                "propName": "contained",
                "methodName": "handleNoModalChange"
            }, {
                "propName": "open",
                "methodName": "handleOpenChange"
            }];
    }
}
