/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { proxyCustomElement, HTMLElement, createEvent, Host } from '@stencil/core/internal/client';
import { t as transformTag, h } from './renderer.js';
import { g as getActiveElement } from './active-element.js';
import { g as getDirectChildren } from './dom.js';
import { g as getTextContent } from './slot.js';
import { g as getTabbableElements } from './tabbable.js';

const menuCss = ":host,*,*::before,*::after{box-sizing:border-box}[hidden]{display:none !important}@media (prefers-reduced-motion: reduce){:host,*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important}}:host,*,*::before,*::after{box-sizing:border-box}[hidden]{display:none !important}@media (prefers-reduced-motion: reduce){:host,*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important}}:host{--padding:var(--nano-spacing-sm) var(--nano-spacing-sm);--font-size:1rem;--background:var(--nano-color-neutral-75);--nav-item-padding:var(--padding);display:block;background:var(--background);color:var(--nano-color-neutral-1400);overflow-x:clip}.menu{font-size:var(--font-size);overscroll-behavior:none;min-inline-size:var(--width);position:relative}.menu:focus{outline:none}::slotted(*:not(.nano-nav-item,.nano-option,hr,slot)){padding:var(--padding);display:block}";

const Menu = /*@__PURE__*/ proxyCustomElement(class Menu extends HTMLElement {
    constructor() {
        super();
        this.__registerHost();
        this.__attachShadow();
        this.nanoFocus = createEvent(this, "nanoFocus", 7);
        this.nanoBlur = createEvent(this, "nanoBlur", 7);
        this.nanoSelect = createEvent(this, "nanoSelect", 7);
    }
    ignoreMouseEvents = false;
    ignoreMouseTimeout;
    menu;
    typeToSelect = '';
    typeToSelectTimeout;
    get el() { return this; }
    // Public API
    /** get the focus state of the menu @readonly */
    get hasFocus() {
        return this._hasFocus;
    }
    _hasFocus = false;
    /** changes the role of the underlying control (required for accessibility semantics).
     * By default this will be set via the frequency of element type (e.g. `<button>` vs `<a>`) */
    type;
    // we'll use this internally if it's not set externally
    _type;
    /** an accessible label */
    label;
    // Events
    /** Emitted when the menu gains focus. */
    nanoFocus;
    /** Emitted when the menu loses focus. */
    nanoBlur;
    /** Emitted when a menu item is selected. */
    nanoSelect;
    // Public Methods
    /** Sets focus on the menu. */
    async setFocus() {
        if (this.menu?.focus) {
            this.menu.focus({ preventScroll: true });
        }
    }
    /** Removes focus from the menu. */
    async removeFocus() {
        if (this.menu?.blur)
            this.menu.blur();
    }
    /** Sets the current active item */
    async showActiveElement() {
        if (this.selectedItem)
            this.selectedItem.scrollIntoView({ block: 'nearest' });
    }
    /** Removes any active item's state */
    async resetActiveItem() {
        this.getItems
            .filter((i) => i.tagName.toLowerCase() === transformTag('nano-option'))
            .map((i) => i.setAttribute('tabindex', '-1'));
    }
    // Private methods
    get getItems() {
        let items;
        if (this._type === 'listbox') {
            items = getDirectChildren(this.el, `${transformTag('nano-nav-item')}, ${transformTag('nano-option')}`, 'slot');
        }
        else {
            const opened = this.el.querySelectorAll('[open]');
            items = getTabbableElements(opened[opened.length - 1] || this.el);
        }
        return items.filter((el) => !el.disabled && !el.hidden);
    }
    get activeItem() {
        const activeElement = getActiveElement();
        return this.getItems.find((i) => i.getAttribute('tabindex') === '0' ||
            i === activeElement ||
            i === document.activeElement ||
            i.classList.contains('has-focus'));
    }
    get selectedItem() {
        return this.getItems.find((i) => i.selected);
    }
    async setActiveFocusItem(item, focus = true, preventScroll = false) {
        const items = this.getItems;
        const activeItem = !item
            ? items.find((i) => i.selected) || items[0]
            : item;
        items
            .filter((i) => i.tagName.toLowerCase() === transformTag('nano-option'))
            .map((i) => i.setAttribute('tabindex', i === activeItem ? '0' : '-1'));
        if (!focus)
            return;
        if (activeItem) {
            if (preventScroll) {
                activeItem.focus({ preventScroll: true });
            }
            else if (activeItem.setFocus) {
                activeItem.setFocus();
            }
            else {
                activeItem.focus();
            }
        }
        else if (!this.hasFocus) {
            this.menu.focus({ preventScroll });
        }
    }
    // Event Handlers
    handleBlur(e) {
        if (!this.hasFocus)
            return;
        const kev = e;
        let found;
        if (kev.key) {
            if (kev.key !== 'Tab') {
                return;
            }
            found =
                getActiveElement() &&
                    getActiveElement().closest(this.el.tagName.toLowerCase()) === this.el;
        }
        else {
            found = !!e.composedPath().find((el) => el === this.el);
        }
        if (!found) {
            this.resetActiveItem();
            this._hasFocus = false;
            this.nanoBlur.emit();
        }
    }
    handleFocus = () => {
        this._hasFocus = true;
        this.setActiveFocusItem(this.selectedItem || this.getItems[0]);
        this.nanoFocus.emit();
    };
    handleClick = (event) => {
        const target = event.target;
        const item = target.closest(transformTag('nano-nav-item'));
        if (item && !item.disabled) {
            this.nanoSelect.emit(item);
        }
    };
    handleKeyDown = (event) => {
        // When keying through the menu, if the mouse happens to be hovering over a menu item and the menu scrolls, the
        // mouseout/mouseover event will fire causing the selection to be different than what the user expects. This gives
        // us a way to temporarily ignore mouse events while the user is interacting with a keyboard.
        clearTimeout(this.ignoreMouseTimeout);
        this.ignoreMouseTimeout = setTimeout(() => (this.ignoreMouseEvents = false), 500);
        this.ignoreMouseEvents = true;
        // Make a selection when pressing enter
        switch (event.key) {
            case ' ':
                if (this.activeItem) {
                    event.stopPropagation();
                    if (this.activeItem.nodeName !== 'BUTTON') {
                        this.activeItem.click();
                    }
                }
                break;
            case 'ArrowDown':
            case 'ArrowUp':
            case 'PageDown':
            case 'PageUp':
            case 'Home':
            case 'End': {
                const items = this.getItems;
                const selectedItem = this.activeItem;
                let index = items.indexOf(selectedItem);
                if (items.length) {
                    event.preventDefault();
                    if (event.key === 'ArrowDown') {
                        index++;
                    }
                    else if (event.key === 'ArrowUp') {
                        index--;
                    }
                    else if (event.key === 'Home' || event.key === 'PageUp') {
                        index = 0;
                    }
                    else if (event.key === 'End' || event.key === 'PageDown') {
                        index = items.length - 1;
                    }
                    if (index < 0)
                        index = items.length - 1;
                    if (index > items.length - 1)
                        index = 0;
                    this.setActiveFocusItem(items[index]);
                    if (items[index])
                        items[index].scrollIntoView({ block: 'nearest' });
                    return;
                }
                break;
            }
        }
        // Handle type-to-search behavior when non-control characters are entered
        if (event.key === ' ' || /^[a-z0-9]+$/i.test(event.key)) {
            clearTimeout(this.typeToSelectTimeout);
            this.typeToSelectTimeout = setTimeout(() => (this.typeToSelect = ''), 750);
            this.typeToSelect += event.key;
            const items = this.getItems;
            for (const item of items) {
                const label = getTextContent(item).toLowerCase().trim();
                if (label.substring(0, this.typeToSelect.length) === this.typeToSelect) {
                    this.setActiveFocusItem(item);
                    break;
                }
            }
        }
    };
    handleMouseOver = (event) => {
        const target = event.target;
        const item = target.closest(transformTag('nano-nav-item')) ||
            target.closest(transformTag('nano-option')) ||
            target.closest('a');
        if (item && !this.ignoreMouseEvents) {
            this.setActiveFocusItem(item, true, true);
        }
    };
    handleSlotContent = () => {
        this._type = this.type || null;
        if (!this._type) {
            let linkCount, btnCount = 0;
            getTabbableElements(this.el).forEach((item) => {
                if (item.tagName === 'A' || item.role === 'link') {
                    linkCount++;
                }
                else {
                    btnCount++;
                }
            });
            this._type = linkCount > btnCount ? 'nav' : 'menu';
        }
        // a nav role can have any type of children
        if (this._type === 'nav')
            return;
        // any direct children of a role="menu|listbox" need an explicit role, other a role of 'none'
        getDirectChildren(this.el, `*:not(${transformTag('nano-nav-item')}, a, ${transformTag('nano-option')}, ${transformTag('nano-dropdown')})`, true).forEach((el) => {
            if (this.getItems.indexOf(el) < 0) {
                el.setAttribute('role', 'none');
            }
        });
        this.el
            .querySelectorAll(`a, ${transformTag('nano-dropdown')}`)
            .forEach((el) => {
            if (el.tagName === 'A') {
                // links need to be set to role="menuitem" to be valid
                el.setAttribute('role', 'menuitem');
            }
            else {
                // nested dropdowns need to be set to role="group" to be valid
                el.setAttribute('role', 'group');
            }
        });
    };
    // Component Lifecycle
    componentWillLoad() {
        this.handleSlotContent();
    }
    render() {
        return (h(Host, { key: '40403e7a4f942f8fa8594edc9ceb24f33aba6979', role: this._type === 'listbox' ? 'group' : this._type, class: "nano-menu" }, h("div", { key: '561716ed34aa038cebacb31dc3b8562829f062e4', onClick: this.handleClick, onKeyDown: this.handleKeyDown, onMouseOver: this.handleMouseOver, onFocus: this.handleFocus, "aria-label": this.label ? this.label : undefined, ref: (el) => (this.menu = el), part: "base", class: {
                menu: true,
                ['menu--' + this._type]: true,
                'menu--has-focus': this.hasFocus,
            }, tabIndex: -1, role: "group" }, h("slot", { key: 'e36d4071433a6e7ddd700aee284868856cbd82ff', onSlotchange: this.handleSlotContent }))));
    }
    static get style() { return menuCss; }
}, [257, "nano-menu", {
        "hasFocus": [2052, "has-focus"],
        "type": [1],
        "label": [1],
        "_type": [32],
        "setFocus": [64],
        "removeFocus": [64],
        "showActiveElement": [64],
        "resetActiveItem": [64]
    }, [[17, "mousedown", "handleBlur"], [0, "keydown", "handleBlur"]]]);
function defineCustomElement() {
    if (typeof customElements === "undefined") {
        return;
    }
    const components = ["nano-menu"];
    components.forEach(tagName => { switch (tagName) {
        case "nano-menu":
            if (!customElements.get(tagName)) {
                customElements.define(tagName, Menu);
            }
            break;
    } });
}

export { Menu as M, defineCustomElement as d };
