/*!
 * 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 { ComponentStore, } from "../../utils/store/component-store";
import { getTabbableElements } from "../../utils/tabbable";
let id = 0;
/**
 * Dialogs, sometimes called "modals", appear above the page and require the user's immediate attention.
 *
 * @version 1.0.0
 * @status stable
 *
 * @part base - The component’s base wrapper
 * @part overlay - The overlay that covers the screen behind the dialog
 * @part panel - The dialog’s panel (where the dialog and its content are rendered)
 * @part header - The dialog’s header. This element wraps the title and header actions
 * @part title - The dialog’s title
 * @part close-button - The `<nano-icon-button>` close button
 * @part close-button__base - The close button's exported `base` part
 * @part body - dilog body / content
 * @part footer - dialog footer bar
 *
 * @slot label - The dialog's label. Alternatively, you can use the label prop.
 * @slot footer - The dialog's footer, usually one or more buttons representing various options.
 * @slot - The dialog's content.
 */
export class Dialog {
    componentId = `dialog-${++id}`;
    modal;
    willShow = false;
    willHide = false;
    originalTrigger;
    overlay;
    dialog;
    slotCtrl = new HasSlotController(this, 'footer');
    get panel() {
        return this._panel;
    }
    set panel(p) {
        if (this._panel) {
            this.panel.removeEventListener('transitionend', this.handleTransitionEnd);
        }
        p.addEventListener('transitionend', this.handleTransitionEnd);
        this._panel = p;
    }
    _panel;
    host;
    noDismiss = false;
    hidden = true;
    /** Show a colour ribbon at the top of the modal */
    showRibbon = true;
    /** Indicates whether or not the dialog is open. You can use this in lieu of the show/hide methods. */
    open = false;
    handleOpenChange() {
        if (this.open)
            this.show();
        else
            this.hide();
    }
    /** The dialog'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. */
    label;
    /** Set to true to disable the header. This will also remove the default close button,
     * so please ensure you  provide an easy, accessible way for users to dismiss the dialog. */
    noHeader = false;
    /** 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;
    /** 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';
    /** Relocate the dialog to the root of the DOM. Useful for elements bound via css `transform: ...` */
    hoist = 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 dialog opens. Calling `event.preventDefault()` will prevent it from being opened. */
    nanoShow;
    /** Emitted after the dialog opens and all transitions are complete. */
    nanoAfterShow;
    /** Emitted when the dialog closes. Calling `event.preventDefault()` will prevent it from being closed. */
    nanoHide;
    /** Emitted after the dialog closes and all transitions are complete. */
    nanoAfterHide;
    /** Emitted when the dialog opens and the panel gains focus. Calling `event.preventDefault()` will prevent
     * focus and allow you to set it on a different element in the dialog, such as an input or button. */
    nanoInitialFocus;
    /** Emitted when the user clicks the close button, clicks the overlay, or presses the `Escape` key. Calling `event.preventDefault()` will prevent the dialog from closing. */
    nanoRequestClose;
    /** Shows the dialog */
    async show() {
        if (this.willShow) {
            return;
        }
        const nanoShow = this.nanoShow.emit();
        if (nanoShow.defaultPrevented) {
            this.open = false;
            return;
        }
        if (this.dialog)
            this.dialog.hidden = false;
        this.originalTrigger = document.activeElement;
        this.willShow = true;
        this.hidden = false;
        this.open = true;
        this.modal.activate();
        if (typeof this.dialog?.showPopover === 'function') {
            this.dialog.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');
        }
        // Wait for the next frame before setting initial focus so the dialog is technically visible
        this.host.addEventListener('nanoAfterShow', () => {
            const nanoInitialFocus = this.nanoInitialFocus.emit();
            if (!nanoInitialFocus.defaultPrevented) {
                const firstTabbable = autoFocusTarget ||
                    getTabbableElements(this.host, true, true, true)[0] ||
                    this.panel;
                firstTabbable.focus({ preventScroll: true });
            }
        }, { once: true });
    }
    /** Hides the dialog */
    async hide() {
        if (this.willHide) {
            return;
        }
        const nanoHide = this.nanoHide.emit();
        if (nanoHide.defaultPrevented) {
            this.open = true;
            return;
        }
        this.willHide = true;
        this.open = false;
        this.modal.deactivate();
        this.stopVideos();
        // Restore focus to the original trigger
        const trigger = this.originalTrigger;
        if (trigger) {
            if (trigger['setFocus'] && typeof trigger['setFocus'] === 'function') {
                // @ts-expect-error setFocus is a custom method on the element
                trigger.setFocus();
            }
            else
                trigger.focus();
        }
        setTimeout(() => {
            if (!this.open && typeof this.dialog?.hidePopover === 'function') {
                this.dialog.hidePopover();
            }
            unlockBodyScrolling(this.host);
        }, 300);
    }
    handleKeyDown = (event) => {
        if (event.key === 'Escape') {
            this.requestClose();
        }
    };
    requestClose = () => {
        const nanoOverlayDismiss = this.nanoRequestClose.emit();
        if (!nanoOverlayDismiss.defaultPrevented && !this.noUserDismiss) {
            this.hide();
        }
        else {
            this.noDismiss = true;
            setTimeout(() => (this.noDismiss = false), 250);
        }
    };
    handleTransitionEnd = (event) => {
        if (event.propertyName === 'opacity' &&
            event
                .composedPath()
                .find((node) => node === this.panel || node === this.overlay)) {
            // Ensure we only emit one event when the target element is no longer visible
            this.willShow = false;
            this.willHide = false;
            if (this.open)
                this.nanoAfterShow.emit();
            else
                this.nanoAfterHide.emit();
            if (!this.open) {
                this.dialog.hidden = true;
                this.hidden = true;
            }
        }
    };
    stopVideos() {
        const videos = Array.from(this.host.querySelectorAll('iframe,video'));
        videos.forEach((video) => {
            if (video.tagName.toLowerCase() === 'video')
                video.pause();
            else {
                const src = video.src;
                video.src = src;
            }
        });
    }
    connectedCallback() {
        this.handleHoistChange();
        this.modal = new Modal(this.host);
    }
    componentWillLoad() {
        if (this.storeId) {
            ComponentStore.init(this, ['open'], this.storeMethod, this.storeId);
        }
    }
    componentDidLoad() {
        this.dialog.hidden = !this.open;
        // Show on init if open
        if (this.open) {
            this.show();
            this.nanoAfterShow.emit();
        }
    }
    disconnectedCallback() {
        unlockBodyScrolling(this.host);
        if (!this.panel)
            return;
        this.panel.removeEventListener('transitionend', this.handleTransitionEnd);
    }
    render() {
        return (h(Host, { key: 'b6ba3eea63e7aee1da10dca2a653a6c9f7d66266', class: "nano-dialog" }, h("div", { key: '8ac30cda032a4fbc89b432f16ce3e9adb88ca68e', part: "base", class: {
                dialog: true,
                'dialog--open': this.open,
                'dialog--has-footer': this.slotCtrl.has('footer'),
                'dialog--has-header': !this.noHeader,
                'dialog--nodismiss': this.noDismiss,
                'dialog--with-ribbon': this.showRibbon,
            }, onKeyDown: this.handleKeyDown, ref: (ele) => (this.dialog = ele), popover: "manual" }, h("div", { key: '4d2cde77fa9c8796daa2591eb425d1694f211d90', part: "overlay", class: "dialog__overlay", ref: (el) => (this.overlay = el), onClick: this.requestClose }), h("div", { key: 'a99ea663fbb2560aaa8f49f23325a661f96dece6', ref: (el) => (this.panel = el), part: "panel", class: "dialog__panel", role: "dialog", "aria-modal": "true", "aria-hidden": !this.hidden ? 'false' : 'true', "aria-label": this.noHeader ? this.label : null, "aria-labelledby": !this.noHeader ? `${this.componentId}-title` : null, tabIndex: -1 }, h("div", { key: '0e0bab9c4307d64cd9d7deb14603d1a5e1f9d27c', class: "dialog__body-wrap" }, !this.noHeader && (h("nano-sticker", { key: '6f1ea1f827d3a18622515f57b5c55d9cb4daa8a0' }, h("header", { key: 'd1cb39f7c1878810f85c7641fdae2217180327b3', part: "header", class: "dialog__header" }, h("span", { key: '7f0ac92358b64688eca01979fd68a665802636d6', part: "title", class: "dialog__title", id: `${this.componentId}-title` }, h("slot", { key: '9b53b34fa5f815a46e9bbd961acecbe592b14cb5', name: "label" }, this.label || String.fromCharCode(65279))), !this.noUserDismiss && (h("nano-icon-button", { key: '4b110b4b490ede1f757046d252de8e2015f50d6e', part: "close-button", exportparts: "base:close-button__base", class: "dialog__close-icon", label: "close dialog", onClick: this.requestClose, iconName: "light/xmark" }))))), h("div", { key: 'f2225d9d4753b91afd697466b897e20441704f82', part: "body", class: "dialog__body" }, h("slot", { key: '1bc02a8964d47650a45dd047bac390615823b176' })), this.slotCtrl.has('footer') && (h("nano-sticker", { key: '9cca925aa007316b93475d54194278ee69fcfdd6', position: "bottom" }, h("footer", { key: '8b1b57752c43c3e010361c90da2cd3e647243828', part: "footer", class: "dialog__footer" }, h("slot", { key: '3fdf3ec86fb5efd8a97a8ddee887dac53d3f58be', name: "footer" })))))))));
    }
    static get is() { return "nano-dialog"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["dialog.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["dialog.css"]
        };
    }
    static get properties() {
        return {
            "showRibbon": {
                "type": "boolean",
                "attribute": "show-ribbon",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Show a colour ribbon at the top of the modal"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "true"
            },
            "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 dialog is open. You can use this in lieu of the show/hide methods."
                },
                "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 dialog's label as displayed in the header. You should always include a relevant\nlabel even when using `no-header`, as it is required for proper accessibility."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "noHeader": {
                "type": "boolean",
                "attribute": "no-header",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to true to disable the header. This will also remove the default close button,\nso please ensure you  provide an easy, accessible way for users to dismiss the dialog."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "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"
            },
            "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'"
            },
            "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"
            }
        };
    }
    static get states() {
        return {
            "noDismiss": {},
            "hidden": {}
        };
    }
    static get events() {
        return [{
                "method": "nanoShow",
                "name": "nanoShow",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the dialog opens. Calling `event.preventDefault()` will prevent it from being opened."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoAfterShow",
                "name": "nanoAfterShow",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted after the dialog opens and all transitions are complete."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoHide",
                "name": "nanoHide",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the dialog closes. Calling `event.preventDefault()` will prevent it from being closed."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoAfterHide",
                "name": "nanoAfterHide",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted after the dialog closes and all transitions are complete."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoInitialFocus",
                "name": "nanoInitialFocus",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the dialog opens and the panel gains focus. Calling `event.preventDefault()` will prevent\nfocus and allow you to set it on a different element in the dialog, such as an input or button."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }, {
                "method": "nanoRequestClose",
                "name": "nanoRequestClose",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the user clicks the close button, clicks the overlay, or presses the `Escape` key. Calling `event.preventDefault()` will prevent the dialog from closing."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "references": {}
                }
            }];
    }
    static get methods() {
        return {
            "show": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        },
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Shows the dialog",
                    "tags": []
                }
            },
            "hide": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Hides the dialog",
                    "tags": []
                }
            }
        };
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "open",
                "methodName": "handleOpenChange"
            }, {
                "propName": "hoist",
                "methodName": "handleHoistChange"
            }];
    }
}
