import { __decorate } from "tslib";
import { html, LitElement } from "lit";
import { property, queryAssignedElements, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { styleMap } from "lit/directives/style-map.js";
import { randomString } from "../../lib/Random.js";
import { ElementPositioning } from "../lib/ElementPositioning.js";
import { ScrollHelper } from "../lib/ScrollHelper.js";
const itemRole = ['menuitem', 'menuitemcheckbox', 'menuitemradio'];
/**
 * An overlay list rendered over a button.
 *
 * Examples:
 *
 * <ui-dropdown-list>
 *  <ui-button>Click me</ui-button>
 *  <ui-list slot="dropdown" role="menu">
 *    <ui-list-item role="menuitem">Item 1</ui-list-item>
 *    <ui-list-item role="menuitem">Item 2</ui-list-item>
 *  </ui-list>
 * </ui-dropdown-list>
 *
 * @slot - The default slot for the dropdown trigger (button)
 * @slot dropdown - The slot for the list.
 * @fires select - Custom event with the selected item on the `detail.item` when the user selected an item. When the event is cancelled then there's no side effects (closing the dropdown)
 * @fires dropdownopen - An event informing other dropdowns that this one was opened and other should close.
 * @fires open - An event dispatched when the open state change through a user interaction
 */
export default class UiDropdownList extends LitElement {
    /**
     * The first element located in the default slot.
     */
    get trigger() {
        const { triggers } = this;
        if (!triggers || !triggers.length) {
            return null;
        }
        const [button] = triggers;
        return button;
    }
    /**
     * The first element located in the "dropdown" slot.
     */
    get dropdown() {
        const { dropdowns } = this;
        if (!dropdowns || !dropdowns.length) {
            return null;
        }
        const [content] = dropdowns;
        return content;
    }
    constructor() {
        super();
        this.triggerId = `ui-trigger-${randomString(4)}`;
        this.menuId = `ui-menu-${randomString(4)}`;
        /**
         * Whether the menu is opened.
         * @attribute
         */
        this.open = false;
        this.dropdownOpenHandler = this.dropdownOpenHandler.bind(this);
        this.scrollHandler = this.scrollHandler.bind(this);
        this.clickHandler = this.clickHandler.bind(this);
    }
    connectedCallback() {
        super.connectedCallback();
        window.addEventListener('dropdownopen', this.dropdownOpenHandler);
        window.addEventListener('click', this.clickHandler, { capture: true });
        ScrollHelper.addListeners(this, this.scrollHandler);
        this.setAttribute('aria-haspopup', 'menu');
        this.setAttribute('aria-expanded', 'false');
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        window.removeEventListener('dropdownopen', this.dropdownOpenHandler);
        window.removeEventListener('click', this.clickHandler, { capture: true });
        ScrollHelper.removeListeners(this);
    }
    dropdownOpenHandler(e) {
        if (e.composedPath()[0] === this) {
            return;
        }
        this.close();
    }
    updateExpanded() {
        this.setAttribute('aria-expanded', String(this.open));
    }
    triggerChanged() {
        const { trigger } = this;
        if (!trigger) {
            return;
        }
        this.decorateTrigger(trigger);
    }
    dropdownChanged() {
        const { dropdown } = this;
        if (!dropdown) {
            return;
        }
        this.decorateDropdown(dropdown);
    }
    /**
     * Decorates the trigger with aria attributes.
     */
    decorateTrigger(button) {
        button.setAttribute('aria-controls', this.menuId);
        button.setAttribute('tabindex', '0');
        if (button.id) {
            this.triggerId = button.id;
        }
        else {
            // eslint-disable-next-line no-param-reassign
            button.id = this.triggerId;
        }
    }
    decorateDropdown(list) {
        if (!list.hasAttribute('role')) {
            list.setAttribute('role', 'menu');
        }
        if (!list.hasAttribute('aria-labelledby')) {
            list.setAttribute('aria-labelledby', this.triggerId);
        }
        list.setAttribute('tabindex', '-1');
        // eslint-disable-next-line no-param-reassign
        list.id = this.menuId;
    }
    triggerClickHandler() {
        this.open = !this.open;
        this.updateExpanded();
        this.notifyOpen();
    }
    triggerKeyDownHandler(e) {
        if (['Enter', 'Space', 'ArrowDown', 'ArrowUp'].includes(e.code)) {
            e.preventDefault();
            this.open = true;
            this.updateExpanded();
            this.notifyOpen();
        }
    }
    contentKeyDownHandler(e) {
        if (e.defaultPrevented) {
            return;
        }
        if (e.code === 'Escape') {
            this.close();
        }
        else if (e.code === 'Tab') {
            if (this.closeOnTab) {
                this.close();
            }
        }
        else if (['Enter', 'Space'].includes(e.code)) {
            this.activate(e);
        }
    }
    contentClickHandler(e) {
        this.activate(e);
    }
    close() {
        this.open = false;
        this.updateExpanded();
        this.notifyOpen();
    }
    contentCloseHandler(e) {
        e.stopPropagation();
        this.close();
    }
    willUpdate(cp) {
        super.willUpdate(cp);
        if ((cp.has('noOverlap') || cp.has('verticalAlign') || cp.has('horizontalAlign') || cp.has('open')) && this.open) {
            this.computePositioning();
        }
    }
    updated(cp) {
        super.updated(cp);
        if (cp.has('open')) {
            this.toggleOpened();
        }
    }
    /**
     * Since the container for the dropdown content is positioned as fixed
     * we need to position the dropdown according to the configured rules.
     */
    computePositioning() {
        const { trigger, dropdown } = this;
        if (!trigger || !dropdown) {
            return;
        }
        this.overlayPositioning = ElementPositioning.positionOverlay(dropdown, trigger, {
            horizontal: this.horizontalAlign,
            vertical: this.verticalAlign,
            noOverlap: this.noOverlap,
            constrain: true,
            constrainPaddingY: 20,
            matchAnchorWidth: this.matchTriggerWidth,
        });
    }
    scrollHandler() {
        if (this.open) {
            this.computePositioning();
        }
    }
    clickHandler(e) {
        if (!this.open || !this.closeOnOutsideClick) {
            return;
        }
        const inside = e.composedPath().some(i => i === this);
        if (inside) {
            return;
        }
        this.close();
    }
    toggleOpened() {
        const { open } = this;
        if (open) {
            this.handleOpened();
        }
        else {
            this.handleClosed();
        }
    }
    handleOpened() {
        const { trigger, dropdown } = this;
        if (trigger) {
            trigger.removeAttribute('tabindex');
        }
        if (dropdown) {
            dropdown.setAttribute('tabindex', '0');
            dropdown.focus();
        }
    }
    handleClosed() {
        const { trigger, dropdown } = this;
        if (trigger) {
            trigger.setAttribute('tabindex', '0');
            if (this.matches(':focus-within')) {
                trigger.focus();
            }
        }
        if (dropdown) {
            dropdown.removeAttribute('tabindex');
        }
    }
    activate(e) {
        if (e.defaultPrevented) {
            return;
        }
        const path = e.composedPath();
        let item;
        while (!item) {
            const next = path.shift();
            if (next === this) {
                break;
            }
            if (next.nodeType !== Node.ELEMENT_NODE) {
                continue;
            }
            if (itemRole.includes(next.getAttribute('role') || '')) {
                item = next;
            }
        }
        if (!item) {
            return;
        }
        const event = new CustomEvent('select', {
            cancelable: true,
            // composed: true,
            detail: {
                item,
            },
        });
        this.dispatchEvent(event);
        if (event.defaultPrevented) {
            return;
        }
        this.close();
    }
    notifyOpen() {
        this.dispatchEvent(new Event('open'));
        if (this.open) {
            this.dispatchEvent(new Event('dropdownopen', {
                bubbles: true,
                composed: true,
                cancelable: true,
            }));
        }
    }
    render() {
        const classes = {
            container: true,
            open: this.open,
        };
        return html `
    <div class="${classMap(classes)}">
      ${this.renderTrigger()}
      ${this.renderContent()}
    </div>
    `;
    }
    renderTrigger() {
        return html `
    <div 
      class="trigger" 
      @click="${this.triggerClickHandler}" 
      @keydown="${this.triggerKeyDownHandler}"
    ><slot @slotchange="${this.triggerChanged}"></slot></div>
    `;
    }
    renderContent() {
        const contentStyles = this.overlayPositioning || {};
        return html `
    <div 
      class="content" 
      style="${styleMap(contentStyles)}"
    >
      <slot 
        name="dropdown" 
        @slotchange="${this.dropdownChanged}" 
        @keydown="${this.contentKeyDownHandler}" 
        @click="${this.contentClickHandler}"
        @close="${this.contentCloseHandler}"
      ></slot>
    </div>
    `;
    }
}
__decorate([
    queryAssignedElements()
], UiDropdownList.prototype, "triggers", void 0);
__decorate([
    queryAssignedElements({ slot: 'dropdown' })
], UiDropdownList.prototype, "dropdowns", void 0);
__decorate([
    state()
], UiDropdownList.prototype, "triggerId", void 0);
__decorate([
    state()
], UiDropdownList.prototype, "menuId", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], UiDropdownList.prototype, "open", void 0);
__decorate([
    property({ type: String, reflect: true })
], UiDropdownList.prototype, "verticalAlign", void 0);
__decorate([
    property({ type: String, reflect: true })
], UiDropdownList.prototype, "horizontalAlign", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], UiDropdownList.prototype, "noOverlap", void 0);
__decorate([
    property({ type: Boolean })
], UiDropdownList.prototype, "closeOnOutsideClick", void 0);
__decorate([
    property({ type: Boolean })
], UiDropdownList.prototype, "matchTriggerWidth", void 0);
__decorate([
    property({ type: Boolean })
], UiDropdownList.prototype, "closeOnTab", void 0);
__decorate([
    state()
], UiDropdownList.prototype, "overlayPositioning", void 0);
//# sourceMappingURL=UiDropdownList.js.map