/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { Host, Build, } from "@stencil/core";
import { focusInContainer } from "../../utils/active-element";
import Popover from "../../utils/popover";
import { getNearestTabbableElement } from "../../utils/tabbable";
import { transformTag, h } from "../../utils/renderer";
let dropDownIds = 0;
/**
 * Dropdowns expose additional content that “drops down” in a panel.
 *
 * @slot trigger - The dropdown's trigger.
 * @slot - The dropdown's content.
 *
 * @part base - The base element of the dropdown.
 * @part trigger - The trigger wrapper of the dropdown.
 * @part panel - The wrapper around the dropdown content.
 *
 * @version 1.0.0
 * @status stable
 */
export class Dropdown {
    dropdownId = `dropdown-${dropDownIds++}`;
    labelId = this.dropdownId + '-title';
    ignoreOpenWatcher = false;
    panel;
    popover;
    trigger;
    positioner;
    accessibleTrigger;
    didLoad = false;
    focusFirstEle = true;
    get menu() {
        return this.host.querySelector(transformTag('nano-menu'));
    }
    host;
    /** Determines if the dropdown should open automatically when the trigger is clicked */
    autoOpen = true;
    /** Indicates whether or not the dropdown is open. You can use this in lieu of the show/hide methods. */
    open = false;
    handleOpenChange() {
        if (this.ignoreOpenWatcher)
            return;
        if (this.open)
            this.show();
        else
            this.hide();
        if (this.accessibleTrigger) {
            this.accessibleTrigger.setAttribute('aria-expanded', this.open.toString());
            return;
        }
        this.updateAccessibleTrigger();
    }
    /**
     * Determines whether the dropdown should hide when a menu item is selected.
     */
    closeOnSelect = true;
    /**
     * The dropdown will close when the user interacts outside of this element (e.g. clicking).
     */
    containingElement;
    /**
     * If you don't want to use the trigger slot, you can use this to 'tether' the dropdown to another element.
     * And you will still need to control when the component is opened (using show() / hide() or the `open` prop)
     */
    tetherTo = null;
    handleTetherToChange() {
        this.createPopover();
        this.updateAccessibleTrigger();
    }
    /**
     * The preferred placement of the dropdown panel. Note that the actual placement may vary as needed to keep the panel
     * inside of the viewport.
     */
    placement = 'bottom-start';
    /**
     * The distance in pixels from which to offset the panel away from its trigger.
     */
    distance = 5;
    /**
     * The distance in pixels from which to offset the panel along its trigger.
     */
    skidding = 0;
    /**
     * Enable this option to prevent the panel from being clipped when the component is placed inside a container with
     * `overflow: auto|scroll`.
     */
    hoist = false;
    handlePopoverOptionsChange() {
        if (!this.popover)
            return;
        this.popover.setOptions({
            strategy: this.hoist ? 'fixed' : 'absolute',
            placement: this.placement,
            skidding: this.skidding,
            distance: this.distance,
        });
    }
    /**
     * Title used to describe the dropdown content for accessibility
     */
    dialogTitle;
    // Events
    /**
     * Emitted when the dropdown opens. Calling `event.preventDefault()` will prevent it from being opened.
     */
    nanoShow;
    /**
     * Emitted after the dropdown opens and all transitions are complete.
     */
    nanoAfterShow;
    /**
     * Emitted when the dropdown closes. Calling `event.preventDefault()` will prevent it from being closed.
     */
    nanoHide;
    /**
     * Emitted after the dropdown closes and all transitions are complete.
     */
    nanoAfterHide;
    // this is a massive hack to make stencil expose the `EventEmitter` type in the final build
    // because the `Dropdown` class is used as a type in `nano-datalist`
    /** @internal */
    eventType;
    // Listeners
    secondaryOpen(ev) {
        if (!ev.detail.secondaryMenu)
            return;
        ev.stopPropagation();
        this.panel.classList.add('loading');
        this.panel.addEventListener('transitionend', () => {
            this.panel.classList.remove('loading');
        }, { once: true });
        this.panel.style.minHeight =
            ev.detail.secondaryMenu.scrollHeight + 'px';
    }
    secondaryClose(ev) {
        if (!ev.detail.secondaryMenu)
            return;
        ev.stopPropagation();
        if (!ev.target.parentElement)
            return;
        this.panel.style.minHeight =
            ev.target.parentElement.scrollHeight + 'px';
    }
    handlePanelSelect(event) {
        const target = event.target;
        // Hide the dropdown when a menu item is selected
        if (this.closeOnSelect &&
            target.tagName.toLowerCase() === transformTag('nano-menu'))
            this.hide();
    }
    // Methods
    /** Shows the dropdown panel */
    async show() {
        this.ignoreOpenWatcher = true;
        this.open = true;
        const nanoShow = this.nanoShow.emit();
        if (nanoShow.defaultPrevented) {
            this.open = false;
            this.ignoreOpenWatcher = false;
            return;
        }
        if (typeof this.positioner?.showPopover === 'function') {
            this.hoist = true;
            this.popover.show();
            this.positioner.showPopover();
        }
        else {
            this.popover.show();
        }
        this.ignoreOpenWatcher = false;
        document.addEventListener('mousedown', this.handleDocumentMouseDown);
        document.addEventListener('keydown', this.handleDocumentKeyDown);
    }
    /** Hides the dropdown panel */
    async hide() {
        this.ignoreOpenWatcher = true;
        this.open = false;
        const nanoHide = this.nanoHide.emit();
        if (nanoHide.defaultPrevented) {
            this.open = true;
            this.ignoreOpenWatcher = false;
            return;
        }
        if (this.popover)
            this.popover.hide();
        this.ignoreOpenWatcher = false;
        document.removeEventListener('mousedown', this.handleDocumentMouseDown);
        document.removeEventListener('keydown', this.handleDocumentKeyDown);
    }
    // Private methods
    afterHide() {
        if (typeof this.positioner?.hidePopover === 'function') {
            this.positioner.hidePopover();
        }
        this.panel.scrollTop = 0;
        if (this.accessibleTrigger &&
            focusInContainer(this.containingElement, this.trigger)) {
            this.accessibleTrigger.focus();
        }
    }
    togglePanel = () => {
        if (!this.autoOpen)
            return;
        if (this.open)
            this.hide();
        else
            this.show();
    };
    // Slotted triggers can be arbitrary content, but we need to link them to the dropdown panel with `aria-haspopup` and
    // `aria-expanded`. These must be applied to the "accessible trigger" (the tabbable portion of the trigger element
    // that gets slotted in) so screen readers will understand them. The accessible trigger could be the slotted element,
    updateAccessibleTrigger() {
        if (!this.didLoad)
            return;
        let accessibleTrigger;
        this.accessibleTrigger = null;
        if (!this.tetherTo) {
            const assignedElements = Array.from(this.host.querySelectorAll('[slot="trigger"]'));
            accessibleTrigger = assignedElements.map(getNearestTabbableElement)[0];
        }
        else {
            accessibleTrigger = getNearestTabbableElement(this.tetherTo);
        }
        if (accessibleTrigger && !accessibleTrigger.getAttribute('aria-haspopup')) {
            accessibleTrigger.setAttribute('aria-haspopup', 'true');
            accessibleTrigger.setAttribute('aria-expanded', this.open ? 'true' : 'false');
            this.accessibleTrigger = accessibleTrigger;
        }
    }
    // Handlers
    handleDocumentKeyDown(event) {
        // Close when escape is pressed
        if (event.key === 'Escape') {
            this.hide();
            return;
        }
        // Close when tabbing results in the focus leaving the dropdown element
        if (event.key === 'Tab') {
            setTimeout(() => {
                if (!focusInContainer(this.containingElement, this.trigger, event)) {
                    this.hide();
                    return;
                }
            });
        }
        else {
            // If a menu is present, focus on it when certain keys are pressed
            if (this.menu &&
                ((this.placement.match(/bottom|top/) &&
                    ['ArrowDown', 'ArrowUp'].includes(event.key)) ||
                    (this.placement.match(/right|left/) &&
                        ['ArrowRight', 'ArrowLeft'].includes(event.key)))) {
                // must have menu item, must have pressed down, must be open and must not have focus within dd
                if (!this.open ||
                    focusInContainer(this.containingElement, this.trigger, event)) {
                    if (this.containingElement.tagName === 'NANO-DROPDOWN' &&
                        this.containingElement.placement.match(/right|left/) &&
                        this.trigger.getRootNode()?.host?.tagName ===
                            'NANO-DROPDOWN') {
                        // if the dropdown is inside another dropdown, we need to close the inner one
                        this.hide();
                        return;
                    }
                    return;
                }
                event.preventDefault();
                this.menu.setFocus();
                this.focusFirstEle = true;
                return;
            }
            // All other keys focus the menu and pass the event through to menu (necessary for type-to-search to work)
            const ePath = event.composedPath();
            if (this.menu && ePath.length && !ePath.find((el) => el !== this.menu)) {
                this.menu.dispatchEvent(new KeyboardEvent(event.type, event));
                return;
            }
        }
    }
    handleDocumentMouseDown(event) {
        // Close when clicking outside of the close element
        if (!focusInContainer(this.containingElement, this.trigger, event)) {
            this.hide();
            return;
        }
    }
    handleTriggerKeyDown = (event) => {
        // Open the panel when pressing down or up while focused on the trigger
        if ((this.placement.match(/bottom|top/) &&
            ['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(event.key)) ||
            (this.placement.match(/right|left/) &&
                ['ArrowRight', 'ArrowLeft', ' ', 'Enter'].includes(event.key))) {
            this.show();
            event.preventDefault();
            event.stopPropagation();
            if (this.menu) {
                this.menu.setFocus();
                this.focusFirstEle = true;
            }
        }
    };
    handleTriggerSlotChange = () => {
        this.updateAccessibleTrigger();
    };
    createPopover() {
        if ((!this.tetherTo && !this.trigger) || !this.positioner)
            return;
        if (this.popover) {
            if (this.open)
                this.hide();
            this.popover.destroy();
            this.popover = null;
        }
        this.popover = new Popover(this.tetherTo || this.trigger, this.positioner, {
            strategy: this.hoist ? 'fixed' : 'absolute',
            placement: this.placement,
            distance: this.distance,
            skidding: this.skidding,
            transitionElement: this.panel,
            onAfterHide: () => {
                this.nanoAfterHide.emit();
            },
            onAfterShow: () => {
                this.nanoAfterShow.emit();
            },
            onTransitionEnd: () => {
                if (!this.open) {
                    this.afterHide();
                }
                else if (this.menu) {
                    this.menu.showActiveElement();
                    if (this.focusFirstEle) {
                        this.menu.setFocus();
                        this.focusFirstEle = false;
                    }
                }
            },
        });
        // Show on init if open
        if (this.open) {
            this.show();
        }
    }
    isOverflowing() {
        if (Build.isServer)
            return false;
        else
            return ['auto', 'scroll'].includes(window.getComputedStyle(this.host).getPropertyValue('--overflow'));
    }
    // Stencil hooks
    connectedCallback() {
        this.handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
        this.handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
        if (!this.containingElement)
            this.containingElement = this.host;
    }
    componentDidLoad() {
        this.didLoad = true;
        this.createPopover();
        setTimeout(() => this.updateAccessibleTrigger(), 100);
    }
    disconnectedCallback() {
        if (this.host.isConnected)
            this.hide();
        if (this.popover)
            this.popover.destroy();
    }
    render() {
        return (h(Host, { key: 'c0ed4b8b03d27f7437c148c8df305886c6cf29d9', class: "nano-dropdown" }, h("div", { key: '27165586e2caa99ba746e7db3aa1574f0e9d1576', part: "base", id: this.dropdownId, class: {
                dropdown: true,
                'dropdown--open': this.open,
            } }, h("span", { key: 'b401a4d341e786ae4fe8c54adf06f8da1be4f531', part: "trigger", class: "dropdown__trigger", ref: (el) => (this.trigger = el), onKeyDown: this.handleTriggerKeyDown, onClick: this.togglePanel }, h("slot", { key: 'bd61e0f2d5e164b21d3ecba81c63ec72b0b7cc6f', name: "trigger", onSlotchange: this.handleTriggerSlotChange })), h("div", { key: 'e0cb2a8823490b70f6aa01073d3873fa785fd763', ref: (el) => (this.positioner = el), class: "dropdown__positioner", popover: "manual", id: this.dropdownId + '-positioner' }, h("div", { key: '3f1b7e3911cd7616ff85d042fc0d1e3e358f36c2', ref: (el) => (this.panel = el), part: "panel", class: `dropdown__panel ${this.placement.split('-').join(' ')}`, "aria-hidden": !this.open, role: this.host.getAttribute('role') === 'group' ? 'group' : 'dialog', "aria-modal": this.host.getAttribute('role') === 'group' ? undefined : 'true', "aria-labelledby": this.dialogTitle ? this.labelId : undefined, tabIndex: this.isOverflowing() ? 0 : undefined }, this.dialogTitle && (h("span", { key: '19cc4dd331a4c2cbdc73a05687681cb2830fff0c', id: this.labelId, class: "dropdown__accessible-title", "aria-live": this.host.getAttribute('role') === 'group'
                ? undefined
                : 'polite' }, this.dialogTitle)), h("slot", { key: '5da1881086c31481463b3b7244552e17ee66d997' }))))));
    }
    static get is() { return "nano-dropdown"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["dropdown.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["dropdown.css"]
        };
    }
    static get properties() {
        return {
            "autoOpen": {
                "type": "boolean",
                "attribute": "auto-open",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Determines if the dropdown should open automatically when the trigger is clicked"
                },
                "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 dropdown is open. You can use this in lieu of the show/hide methods."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "defaultValue": "false"
            },
            "closeOnSelect": {
                "type": "boolean",
                "attribute": "close-on-select",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Determines whether the dropdown should hide when a menu item is selected."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "true"
            },
            "containingElement": {
                "type": "unknown",
                "attribute": "containing-element",
                "mutable": true,
                "complexType": {
                    "original": "HTMLElement",
                    "resolved": "HTMLElement",
                    "references": {
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The dropdown will close when the user interacts outside of this element (e.g. clicking)."
                },
                "getter": false,
                "setter": false
            },
            "tetherTo": {
                "type": "unknown",
                "attribute": "tether-to",
                "mutable": false,
                "complexType": {
                    "original": "HTMLElement",
                    "resolved": "HTMLElement",
                    "references": {
                        "HTMLElement": {
                            "location": "global",
                            "id": "global::HTMLElement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "If you don't want to use the trigger slot, you can use this to 'tether' the dropdown to another element.\nAnd you will still need to control when the component is opened (using show() / hide() or the `open` prop)"
                },
                "getter": false,
                "setter": false,
                "defaultValue": "null"
            },
            "placement": {
                "type": "string",
                "attribute": "placement",
                "mutable": false,
                "complexType": {
                    "original": "PopoverPlacement",
                    "resolved": "\"auto\" | \"auto-end\" | \"auto-start\" | \"bottom\" | \"bottom-end\" | \"bottom-start\" | \"center\" | \"left\" | \"left-end\" | \"left-start\" | \"right\" | \"right-end\" | \"right-start\" | \"top\" | \"top-end\" | \"top-start\"",
                    "references": {
                        "PopoverPlacement": {
                            "location": "import",
                            "path": "../../utils/popover",
                            "id": "src/utils/popover.ts::PopoverPlacement"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The preferred placement of the dropdown panel. Note that the actual placement may vary as needed to keep the panel\ninside of the viewport."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "'bottom-start'"
            },
            "distance": {
                "type": "number",
                "attribute": "distance",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The distance in pixels from which to offset the panel away from its trigger."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "5"
            },
            "skidding": {
                "type": "number",
                "attribute": "skidding",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The distance in pixels from which to offset the panel along its trigger."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "0"
            },
            "hoist": {
                "type": "boolean",
                "attribute": "hoist",
                "mutable": true,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Enable this option to prevent the panel from being clipped when the component is placed inside a container with\n`overflow: auto|scroll`."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "false"
            },
            "dialogTitle": {
                "type": "string",
                "attribute": "dialog-title",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": true,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Title used to describe the dropdown content for accessibility"
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "eventType": {
                "type": "unknown",
                "attribute": "event-type",
                "mutable": false,
                "complexType": {
                    "original": "EventEmitter",
                    "resolved": "EventEmitter<any>",
                    "references": {
                        "EventEmitter": {
                            "location": "import",
                            "path": "@stencil/core",
                            "id": "../../node_modules/.pnpm/@stencil+core@4.36.1/node_modules/@stencil/core/internal/stencil-core/index.d.ts::EventEmitter"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "internal",
                            "text": undefined
                        }],
                    "text": ""
                },
                "getter": false,
                "setter": false
            }
        };
    }
    static get events() {
        return [{
                "method": "nanoShow",
                "name": "nanoShow",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the dropdown 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 dropdown 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 dropdown 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 dropdown closes and all transitions are complete."
                },
                "complexType": {
                    "original": "any",
                    "resolved": "any",
                    "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 dropdown panel",
                    "tags": []
                }
            },
            "hide": {
                "complexType": {
                    "signature": "() => Promise<void>",
                    "parameters": [],
                    "references": {
                        "Promise": {
                            "location": "global",
                            "id": "global::Promise"
                        }
                    },
                    "return": "Promise<void>"
                },
                "docs": {
                    "text": "Hides the dropdown panel",
                    "tags": []
                }
            }
        };
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "open",
                "methodName": "handleOpenChange"
            }, {
                "propName": "tetherTo",
                "methodName": "handleTetherToChange"
            }, {
                "propName": "placement",
                "methodName": "handlePopoverOptionsChange"
            }, {
                "propName": "distance",
                "methodName": "handlePopoverOptionsChange"
            }, {
                "propName": "skidding",
                "methodName": "handlePopoverOptionsChange"
            }, {
                "propName": "hoist",
                "methodName": "handlePopoverOptionsChange"
            }];
    }
    static get listeners() {
        return [{
                "name": "nanoOpen",
                "method": "secondaryOpen",
                "target": undefined,
                "capture": false,
                "passive": false
            }, {
                "name": "nanoClose",
                "method": "secondaryClose",
                "target": undefined,
                "capture": false,
                "passive": false
            }, {
                "name": "nanoSelect",
                "method": "handlePanelSelect",
                "target": undefined,
                "capture": false,
                "passive": false
            }];
    }
}
