// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var DropdownElement_1;
import { HTMLElementEventEmitter, on } from '@breadstone/mosaik-elements';
import { CssLength } from '@breadstone/mosaik-themes';
import { autoUpdate, computePosition, flip, offset as floatingOffset, platform, shift } from '@floating-ui/dom';
import { AnimationPlayer } from '../../../../Animations/AnimationPlayer';
import { fadeIn, fadeOut } from '../../../../Animations/Presets/Fade';
import { Emit } from '../../../../events/Decorators/EmitDecorator';
import { Animatable } from '../../../Behaviors/Animatable';
import { Disableable } from '../../../Behaviors/Disableable';
import { IDropDownableProps } from '../../../Behaviors/DropDownable';
import { Themeable } from '../../../Behaviors/Themeable';
import { ClickOutsideController } from '../../../Controllers/ClickOutsideController';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { DropdownChangeReason } from '../../../Types/DropdownChangeReason';
import { DropdownTrigger } from '../../../Types/DropdownTrigger';
import { Placement } from '../../../Types/Placement';
import { Strategy } from '../../../Types/Strategy';
import { CustomElement } from '../../Abstracts/CustomElement';
import { dropdownElementTemplate } from './DropdownElementTemplate';
import { IDropdownElementProps } from './IDropdownElementProps';
import { dropdownElementCosmopolitanStyle } from './Themes/DropdownElement.Cosmopolitan';
import { dropdownElementJoyStyle } from './Themes/DropdownElement.Joy';
import { dropdownElementMemphisStyle } from './Themes/DropdownElement.Memphis';
// #endregion
/**
 * Dropdown - A generic anchored floating-content primitive for building dropdowns, popovers, menus, and floating panels.
 *
 * @description
 * MosaicDropdown is a lower-level generic primitive that can be used to build every kind of anchored dropdown,
 * popover-like panel, menu surface, filter panel, user menu, contextual action panel or custom floating-content surface.
 * It owns only the dropdown lifecycle, trigger behavior, open state, native positioning, outside interaction handling,
 * focus restoration, keyboard close behavior, event dispatching and animation orchestration.
 * The semantic meaning of the projected content belongs to the projected content itself.
 *
 * The component supports two equivalent authoring styles:
 * 1. Direct slot projection: Place any element with `slot="anchor"` and any element with `slot="panel"`.
 * 2. Alias elements: Use `<mosaik-dropdown-anchor>` and `<mosaik-dropdown-panel>` wrappers.
 *
 * @name Dropdown
 * @element mosaik-dropdown
 * @category Primitives
 *
 * @slot anchor - The anchor content that triggers the dropdown.
 * @slot panel - The floating panel content.
 *
 * @csspart anchor - The anchor wrapper part.
 * @csspart panel - The panel wrapper part.
 *
 * @cssprop {String} --dropdown-background-color - The background color CSS custom property.
 * @cssprop {String} --dropdown-border-color - The border color CSS custom property.
 * @cssprop {String} --dropdown-border-radius - The border radius CSS custom property.
 * @cssprop {String} --dropdown-border-style - The border style CSS custom property.
 * @cssprop {String} --dropdown-border-width - The border width CSS custom property.
 * @cssprop {String} --dropdown-font-family - The font family CSS custom property.
 * @cssprop {String} --dropdown-font-letter-spacing - The font letter spacing CSS custom property.
 * @cssprop {String} --dropdown-font-line-height - The font line height CSS custom property.
 * @cssprop {String} --dropdown-font-size - The font size CSS custom property.
 * @cssprop {String} --dropdown-font-text-decoration - The font text decoration CSS custom property.
 * @cssprop {String} --dropdown-font-text-transform - The font text transform CSS custom property.
 * @cssprop {String} --dropdown-font-weight - The font weight CSS custom property.
 * @cssprop {String} --dropdown-foreground-color - The foreground color CSS custom property.
 * @cssprop {String} --dropdown-gap - The gap CSS custom property.
 * @cssprop {String} --dropdown-padding-bottom - The padding bottom CSS custom property.
 * @cssprop {String} --dropdown-padding-left - The padding left CSS custom property.
 * @cssprop {String} --dropdown-padding-right - The padding right CSS custom property.
 * @cssprop {String} --dropdown-padding-top - The padding top CSS custom property.
 * @cssprop {String} --dropdown-panel-background-color - The background color of the dropdown panel.
 * @cssprop {String} --dropdown-panel-border-color - The border color of the dropdown panel.
 * @cssprop {String} --dropdown-panel-border-radius - The border radius of the dropdown panel.
 * @cssprop {String} --dropdown-panel-border-style - The border style of the dropdown panel.
 * @cssprop {String} --dropdown-panel-border-width - The border width of the dropdown panel.
 * @cssprop {String} --dropdown-panel-box-shadow - The box shadow of the dropdown panel.
 * @cssprop {String} --dropdown-panel-max-height - The max height of the dropdown panel.
 * @cssprop {String} --dropdown-panel-offset - The offset between anchor and panel.
 * @cssprop {String} --dropdown-panel-padding - The padding of the dropdown panel.
 * @cssprop {String} --dropdown-panel-z-index - The z-index of the dropdown panel.
 * @cssprop {String} --dropdown-shadow - The shadow CSS custom property.
 * @cssprop {String} --dropdown-shadow-blur - The shadow blur CSS custom property.
 * @cssprop {String} --dropdown-shadow-color - The shadow color CSS custom property.
 * @cssprop {String} --dropdown-shadow-offset-x - The shadow offset x CSS custom property.
 * @cssprop {String} --dropdown-shadow-offset-y - The shadow offset y CSS custom property.
 * @cssprop {String} --dropdown-shadow-spread - The shadow spread CSS custom property.
 * @cssprop {String} --dropdown-transition-duration - The transition duration CSS custom property.
 * @cssprop {String} --dropdown-transition-mode - The transition mode CSS custom property.
 * @cssprop {String} --dropdown-transition-property - The transition property CSS custom property.
 * @cssprop {String} --dropdown-translate - The translate CSS custom property.
 *
 * @fires openChanged {CustomEvent<{open: boolean, reason: DropdownChangeReason}>} - Fired when the open state changes.
 *
 * @example
 * Basic dropdown with menu:
 * ```html
 * <mosaik-dropdown placement="bottom-start">
 *   <mosaik-button slot="anchor">Actions</mosaik-button>
 *   <mosaik-menu slot="panel">
 *     <mosaik-menu-item label="Edit"></mosaik-menu-item>
 *     <mosaik-menu-item label="Duplicate"></mosaik-menu-item>
 *     <mosaik-menu-item label="Delete"></mosaik-menu-item>
 *   </mosaik-menu>
 * </mosaik-dropdown>
 * ```
 *
 * @example
 * Dropdown with alias elements:
 * ```html
 * <mosaik-dropdown placement="bottom-end">
 *   <mosaik-dropdown-anchor>
 *     <mosaik-avatar user-id="current"></mosaik-avatar>
 *   </mosaik-dropdown-anchor>
 *   <mosaik-dropdown-panel>
 *     <mosaik-card>Account details</mosaik-card>
 *   </mosaik-dropdown-panel>
 * </mosaik-dropdown>
 * ```
 *
 * @example
 * Controlled dropdown:
 * ```html
 * <mosaik-dropdown .open="${this.isOpen}" trigger="manual">
 *   <mosaik-button slot="anchor">Toggle</mosaik-button>
 *   <div slot="panel">Panel content</div>
 * </mosaik-dropdown>
 * ```
 *
 * @public
 */
let DropdownElement = class DropdownElement extends Themeable(Animatable(Disableable(CustomElement))) {
    static { DropdownElement_1 = this; }
    // #region Fields
    static _anchorCounter = 0;
    _clickOutsideController;
    _animationPlayer;
    _openChanged;
    _instanceId;
    _trigger;
    _closeOnEscape;
    _closeOnSelect;
    _restoreFocus;
    _hasPopup;
    _matchAnchorWidth;
    _popupRole;
    _isVisible;
    _dropDownPlacement;
    _dropDownStrategy;
    _dropDownDistance;
    _dropDownSkidding;
    _dropDownStaysOpen;
    _dropDownMaxHeight;
    _dropDownHeight;
    _dropDownWidth;
    _dropDownMaxWidth;
    _isDropDownOpen;
    _anchorElement;
    _panelElement;
    _anchorSlot;
    _panelSlot;
    _hoverTimeout;
    _focusTimeout;
    _clickSubscription;
    _anchorMouseEnterSubscription;
    _anchorMouseLeaveSubscription;
    _anchorFocusInSubscription;
    _anchorFocusOutSubscription;
    _panelClickSubscription;
    _panelKeydownSubscription;
    _panelMouseEnterSubscription;
    _panelMouseLeaveSubscription;
    _positioningCleanup;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._trigger = IDropdownElementProps.DEFAULTS.trigger;
        this._closeOnEscape = IDropdownElementProps.DEFAULTS.closeOnEscape;
        this._closeOnSelect = IDropdownElementProps.DEFAULTS.closeOnSelect;
        this._restoreFocus = IDropdownElementProps.DEFAULTS.restoreFocus;
        this._matchAnchorWidth = IDropdownElementProps.DEFAULTS.matchAnchorWidth;
        this._popupRole = '';
        this._isVisible = false;
        this._dropDownPlacement = IDropDownableProps.DEFAULTS.dropDownPlacement;
        this._dropDownStrategy = IDropDownableProps.DEFAULTS.dropDownStrategy;
        this._dropDownDistance = IDropDownableProps.DEFAULTS.dropDownDistance;
        this._dropDownSkidding = IDropDownableProps.DEFAULTS.dropDownSkidding;
        this._dropDownStaysOpen = IDropDownableProps.DEFAULTS.dropDownStaysOpen;
        this._dropDownMaxHeight = IDropDownableProps.DEFAULTS.dropDownMaxHeight;
        this._dropDownHeight = IDropDownableProps.DEFAULTS.dropDownHeight;
        this._dropDownWidth = IDropDownableProps.DEFAULTS.dropDownWidth;
        this._dropDownMaxWidth = IDropDownableProps.DEFAULTS.dropDownMaxWidth;
        this._isDropDownOpen = IDropDownableProps.DEFAULTS.isDropDownOpen;
        this._anchorElement = null;
        this._panelElement = null;
        this._anchorSlot = null;
        this._panelSlot = null;
        this._hoverTimeout = null;
        this._focusTimeout = null;
        this._clickSubscription = null;
        this._anchorMouseEnterSubscription = null;
        this._anchorMouseLeaveSubscription = null;
        this._anchorFocusInSubscription = null;
        this._anchorFocusOutSubscription = null;
        this._panelClickSubscription = null;
        this._panelKeydownSubscription = null;
        this._panelMouseEnterSubscription = null;
        this._panelMouseLeaveSubscription = null;
        this._positioningCleanup = null;
        this._instanceId = this.constructor._anchorCounter++;
        this._animationPlayer = new AnimationPlayer();
        this._openChanged = new HTMLElementEventEmitter(this, 'openChanged');
        this._clickOutsideController = new ClickOutsideController(this, {
            skipInitial: true,
            callback: () => this.onClickedOutside()
        });
        // REMARK: This is a workaround for the issue with the docs generation.
        Object.assign(this, { maxHeight: '200px' });
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     * The `is` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-dropdown';
    }
    /**
     * Gets the unique anchor ID for this instance.
     *
     * @public
     * @readonly
     */
    get _anchorId() {
        return `mosaik-dropdown-anchor-${this._instanceId}`;
    }
    /**
     * Gets the unique panel ID for this instance.
     *
     * @public
     * @readonly
     */
    get _panelId() {
        return `mosaik-dropdown-panel-${this._instanceId}`;
    }
    /**
     * Gets the anchor name CSS custom property for CSS Anchor Positioning.
     *
     * @public
     * @readonly
     */
    get _anchorName() {
        return `--dropdown-anchor-${this._instanceId}`;
    }
    /**
     * Gets the current popup role based on the hasPopup property.
     *
     * @public
     * @readonly
     */
    get popupRole() {
        return this._popupRole;
    }
    /**
     * Gets whether the panel is currently visible.
     * This is separate from `open` to allow exit animations to complete before hiding.
     *
     * @public
     * @readonly
     */
    get isVisible() {
        return this._isVisible;
    }
    /**
     * Gets the animation target element.
     * Animations are applied to the panel element.
     *
     * @public
     * @override
     * @readonly
     */
    get animationTarget() {
        return this._panelElement ?? this;
    }
    /**
     * Gets or sets the `dropDownPlacement` property.
     *
     * @public
     * @attr drop-down-placement
     */
    get dropDownPlacement() {
        return this._dropDownPlacement;
    }
    set dropDownPlacement(value) {
        if (this._dropDownPlacement !== value) {
            this._dropDownPlacement = value;
            this.requestUpdate('dropDownPlacement');
            this.requestUpdate('placement');
        }
    }
    /**
     * Gets or sets the `dropDownStrategy` property.
     *
     * @public
     * @attr drop-down-strategy
     */
    get dropDownStrategy() {
        return this._dropDownStrategy;
    }
    set dropDownStrategy(value) {
        if (this._dropDownStrategy !== value) {
            this._dropDownStrategy = value;
            this.requestUpdate('dropDownStrategy');
            this.requestUpdate('strategy');
        }
    }
    /**
     * Gets or sets the `dropDownDistance` property.
     *
     * @public
     * @attr drop-down-distance
     */
    get dropDownDistance() {
        return this._dropDownDistance;
    }
    set dropDownDistance(value) {
        if (this._dropDownDistance !== value) {
            this._dropDownDistance = value;
            this.requestUpdate('dropDownDistance');
            this.requestUpdate('offset');
        }
    }
    /**
     * Gets or sets the `dropDownSkidding` property.
     *
     * @public
     * @attr drop-down-skidding
     */
    get dropDownSkidding() {
        return this._dropDownSkidding;
    }
    set dropDownSkidding(value) {
        if (this._dropDownSkidding !== value) {
            this._dropDownSkidding = value;
            this.requestUpdate('dropDownSkidding');
            this.requestUpdate('skidding');
        }
    }
    /**
     * Gets or sets the `dropDownStaysOpen` property.
     *
     * @public
     * @attr drop-down-stays-open
     */
    get dropDownStaysOpen() {
        return this._dropDownStaysOpen;
    }
    set dropDownStaysOpen(value) {
        if (this._dropDownStaysOpen !== value) {
            this._dropDownStaysOpen = value;
            this.requestUpdate('dropDownStaysOpen');
            this.requestUpdate('closeOnOutsideClick');
        }
    }
    /**
     * Gets or sets the `dropDownMaxHeight` property.
     *
     * @public
     * @attr drop-down-max-height
     */
    get dropDownMaxHeight() {
        return this._dropDownMaxHeight;
    }
    set dropDownMaxHeight(value) {
        if (this._dropDownMaxHeight !== value) {
            this._dropDownMaxHeight = value;
            this.requestUpdate('dropDownMaxHeight');
            this.requestUpdate('maxHeight');
        }
    }
    /**
     * Gets or sets the `dropDownHeight` property.
     *
     * @public
     * @attr drop-down-height
     */
    get dropDownHeight() {
        return this._dropDownHeight;
    }
    set dropDownHeight(value) {
        if (this._dropDownHeight !== value) {
            this._dropDownHeight = value;
            this.requestUpdate('dropDownHeight');
            this.requestUpdate('height');
        }
    }
    /**
     * Gets or sets the `dropDownWidth` property.
     *
     * @public
     * @attr drop-down-width
     */
    get dropDownWidth() {
        return this._dropDownWidth;
    }
    set dropDownWidth(value) {
        if (this._dropDownWidth !== value) {
            this._dropDownWidth = value;
            this.requestUpdate('dropDownWidth');
            this.requestUpdate('width');
        }
    }
    /**
     * Gets or sets the `dropDownMaxWidth` property.
     *
     * @public
     * @attr drop-down-max-width
     */
    get dropDownMaxWidth() {
        return this._dropDownMaxWidth;
    }
    set dropDownMaxWidth(value) {
        if (this._dropDownMaxWidth !== value) {
            this._dropDownMaxWidth = value;
            this.requestUpdate('dropDownMaxWidth');
            this.requestUpdate('maxWidth');
        }
    }
    /**
     * Gets or sets the `isDropDownOpen` property.
     *
     * @public
     * @attr is-drop-down-open
     */
    get isDropDownOpen() {
        return this._isDropDownOpen;
    }
    set isDropDownOpen(value) {
        if (this._isDropDownOpen !== value) {
            this._isDropDownOpen = value;
            this.requestUpdate('isDropDownOpen');
            this.requestUpdate('open');
        }
    }
    /**
     * Gets or sets the `placement` property.
     *
     * @public
     * @attr placement
     */
    get placement() {
        return this.dropDownPlacement;
    }
    set placement(value) {
        if (this.dropDownPlacement !== value) {
            this.dropDownPlacement = value;
            this.requestUpdate('placement');
        }
    }
    /**
     * Gets or sets the `strategy` property.
     *
     * @public
     * @attr strategy
     */
    get strategy() {
        return this.dropDownStrategy;
    }
    set strategy(value) {
        if (this.dropDownStrategy !== value) {
            this.dropDownStrategy = value;
            this.requestUpdate('strategy');
        }
    }
    /**
     * Gets or sets the `offset` property.
     *
     * @public
     * @attr offset
     */
    get offset() {
        return this.dropDownDistance;
    }
    set offset(value) {
        if (this.dropDownDistance !== value) {
            this.dropDownDistance = value;
            this.requestUpdate('offset');
        }
    }
    /**
     * Gets or sets the `skidding` property.
     *
     * @public
     * @attr skidding
     */
    get skidding() {
        return this.dropDownSkidding;
    }
    set skidding(value) {
        if (this.dropDownSkidding !== value) {
            this.dropDownSkidding = value;
            this.requestUpdate('skidding');
        }
    }
    /**
     * Gets or sets the `open` property.
     * Supports both controlled and uncontrolled usage.
     *
     * @public
     * @attr open
     */
    get open() {
        return this.isDropDownOpen;
    }
    set open(value) {
        if (this.isDropDownOpen !== value) {
            this.isDropDownOpen = value;
            this.requestUpdate('open');
        }
    }
    /**
     * Gets or sets the `trigger` property.
     *
     * @public
     * @attr trigger
     */
    get trigger() {
        return this._trigger;
    }
    set trigger(value) {
        if (this._trigger !== value) {
            this._trigger = value;
            this.requestUpdate('trigger');
        }
    }
    /**
     * Gets or sets the `closeOnOutsideClick` property.
     *
     * @public
     * @attr close-on-outside-click
     */
    get closeOnOutsideClick() {
        return !this.dropDownStaysOpen;
    }
    set closeOnOutsideClick(value) {
        if (this.closeOnOutsideClick !== value) {
            this.dropDownStaysOpen = !value;
            this.requestUpdate('closeOnOutsideClick');
        }
    }
    /**
     * Gets or sets the `closeOnEscape` property.
     *
     * @public
     * @attr close-on-escape
     */
    get closeOnEscape() {
        return this._closeOnEscape;
    }
    set closeOnEscape(value) {
        if (this._closeOnEscape !== value) {
            this._closeOnEscape = value;
            this.requestUpdate('closeOnEscape');
        }
    }
    /**
     * Gets or sets the `closeOnSelect` property.
     *
     * @public
     * @attr close-on-select
     */
    get closeOnSelect() {
        return this._closeOnSelect;
    }
    set closeOnSelect(value) {
        if (this._closeOnSelect !== value) {
            this._closeOnSelect = value;
            this.requestUpdate('closeOnSelect');
        }
    }
    /**
     * Gets or sets the `restoreFocus` property.
     *
     * @public
     * @attr restore-focus
     */
    get restoreFocus() {
        return this._restoreFocus;
    }
    set restoreFocus(value) {
        if (this._restoreFocus !== value) {
            this._restoreFocus = value;
            this.requestUpdate('restoreFocus');
        }
    }
    /**
     * Gets or sets the `hasPopup` property.
     *
     * @public
     * @attr has-popup
     */
    get hasPopup() {
        return this._hasPopup;
    }
    set hasPopup(value) {
        if (this._hasPopup !== value) {
            this._hasPopup = value;
            this._popupRole = value ?? '';
            this.requestUpdate('hasPopup');
        }
    }
    /**
     * Gets or sets the `matchAnchorWidth` property.
     *
     * @public
     * @attr match-anchor-width
     */
    get matchAnchorWidth() {
        return this._matchAnchorWidth;
    }
    set matchAnchorWidth(value) {
        if (this._matchAnchorWidth !== value) {
            this._matchAnchorWidth = value;
            this.requestUpdate('matchAnchorWidth');
        }
    }
    /**
     * Gets or sets the `maxHeight` property.
     *
     * @public
     * @attr max-height
     */
    get maxHeight() {
        return this.dropDownMaxHeight;
    }
    set maxHeight(value) {
        if (this.dropDownMaxHeight !== value) {
            this.dropDownMaxHeight = value;
            this.requestUpdate('maxHeight');
        }
    }
    /**
     * Gets or sets the `height` property.
     *
     * @public
     * @attr height
     */
    get height() {
        return this.dropDownHeight;
    }
    set height(value) {
        if (this.dropDownHeight !== value) {
            this.dropDownHeight = value;
            this.requestUpdate('height');
        }
    }
    /**
     * Gets or sets the `width` property.
     *
     * @public
     * @attr width
     */
    get width() {
        return this.dropDownWidth;
    }
    set width(value) {
        if (this.dropDownWidth !== value) {
            this.dropDownWidth = value;
            this.requestUpdate('width');
        }
    }
    /**
     * Gets or sets the `maxWidth` property.
     *
     * @public
     * @attr max-width
     */
    get maxWidth() {
        return this.dropDownMaxWidth;
    }
    set maxWidth(value) {
        if (this.dropDownMaxWidth !== value) {
            this.dropDownMaxWidth = value;
            this.requestUpdate('maxWidth');
        }
    }
    /**
     * Called when the open state changes.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get openChanged() {
        return this._openChanged;
    }
    // #endregion
    // #region Methods
    /**
     * Opens the dropdown.
     *
     * @param reason - The reason for opening.
     * @public
     */
    openDropdown(reason = DropdownChangeReason.Programmatic) {
        if (this.disabled) {
            return;
        }
        if (this.open) {
            return;
        }
        this.open = true;
        this._openChanged.emit({ open: true, reason });
    }
    /**
     * Closes the dropdown.
     *
     * @param reason - The reason for closing.
     * @public
     */
    closeDropdown(reason = DropdownChangeReason.Programmatic) {
        if (!this.open) {
            return;
        }
        this.open = false;
        this._openChanged.emit({ open: false, reason });
    }
    /**
     * Toggles the dropdown.
     *
     * @param reason - The reason for toggling.
     * @public
     */
    toggleDropdown(reason = DropdownChangeReason.Programmatic) {
        if (this.disabled) {
            this.closeDropdown(DropdownChangeReason.Disabled);
            return;
        }
        if (this.open) {
            this.closeDropdown(reason);
        }
        else {
            this.openDropdown(reason);
        }
    }
    /**
     * Updates the panel position. With native CSS Anchor Positioning,
     * this is usually not needed but serves as an imperative escape hatch.
     *
     * @public
     */
    updatePosition() {
        if (!this._panelElement || !this._anchorElement) {
            return;
        }
        this.applyPositioning();
    }
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        // Set up anchor click handling
        this._clickSubscription = on(this, 'click', (event) => {
            const path = event.composedPath();
            if (this.isAnchorEventPath(path)) {
                this.onAnchorClick();
            }
        });
        this._panelElement = this.shadowRoot?.querySelector('[part="panel"]') ?? null;
        if (this.open) {
            this._isVisible = true;
            this.requestUpdate('isVisible');
        }
        // Set up trigger and panel event listeners after the template is available.
        void this.updateComplete.then(() => {
            this.onAnchorSlotChange(new Event('slotchange'));
            this.setupAnchorListeners();
            this.setupPanelListeners();
            if (this.open) {
                this._clickOutsideController.observe();
                this.showPanel();
            }
        });
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        this._clickSubscription?.dispose();
        this._clickSubscription = null;
        this._anchorMouseEnterSubscription?.dispose();
        this._anchorMouseEnterSubscription = null;
        this._anchorMouseLeaveSubscription?.dispose();
        this._anchorMouseLeaveSubscription = null;
        this._anchorFocusInSubscription?.dispose();
        this._anchorFocusInSubscription = null;
        this._anchorFocusOutSubscription?.dispose();
        this._anchorFocusOutSubscription = null;
        this._panelClickSubscription?.dispose();
        this._panelClickSubscription = null;
        this._panelKeydownSubscription?.dispose();
        this._panelKeydownSubscription = null;
        this._panelMouseEnterSubscription?.dispose();
        this._panelMouseEnterSubscription = null;
        this._panelMouseLeaveSubscription?.dispose();
        this._panelMouseLeaveSubscription = null;
        this.clearHoverTimeout();
        this.clearFocusTimeout();
        this._clickOutsideController.unobserve();
        this.stopPositioning();
    }
    /**
     * Handles anchor slot changes.
     *
     * @public
     */
    onAnchorSlotChange(_event) {
        if (!this._anchorSlot) {
            this._anchorSlot = this.shadowRoot?.querySelector('slot[name="anchor"]') ?? null;
        }
        const assigned = this._anchorSlot?.assignedElements({ flatten: true }) ?? [];
        this._anchorElement = assigned[0] ?? null;
        if (this._anchorElement) {
            // Apply anchor-name for CSS Anchor Positioning
            this._anchorElement.style.setProperty('anchor-name', this._anchorName);
            // Apply hasPopup and aria-expanded if the anchor is a button
            this.applyAriaAttributes();
        }
        if (this.open) {
            this.startPositioning();
        }
    }
    /**
     * Handles panel slot changes.
     *
     * @public
     */
    onPanelSlotChange(_event) {
        if (!this._panelSlot) {
            this._panelSlot = this.shadowRoot?.querySelector('slot[name="panel"]') ?? null;
        }
        this._panelElement = this.shadowRoot?.querySelector('[part="panel"]') ?? null;
        if (this._panelElement) {
            this.setupPanelListeners();
        }
    }
    // #endregion
    // #region Lifecycle
    /**
     * @protected
     */
    onOpenPropertyChanged(_prev, next) {
        if (next) {
            if (this.disabled) {
                this.isDropDownOpen = false;
                this._isVisible = false;
                this.requestUpdate('open');
                this.requestUpdate('isVisible');
                return;
            }
            this._isVisible = true;
            this.requestUpdate('isVisible');
            void this.updateComplete.then(() => {
                this._panelElement = this.shadowRoot?.querySelector('[part="panel"]') ?? null;
                this.onPanelSlotChange(new Event('slotchange'));
                this.setupPanelListeners();
                this.showPanel();
                this._clickOutsideController.observe();
                this.applyAriaAttributes();
            });
        }
        else {
            this._clickOutsideController.unobserve();
            this.applyAriaAttributes();
            void this.hidePanel();
            // Restore focus to anchor
            if (this._restoreFocus && this._anchorElement) {
                this._anchorElement.focus?.();
            }
        }
    }
    /**
     * @protected
     */
    onDisabledPropertyChanged(_prev, next) {
        if (next) {
            this.closeDropdown(DropdownChangeReason.Disabled);
        }
    }
    /**
     * @protected
     */
    onTriggerPropertyChanged(_prev, next) {
        this.clearHoverTimeout();
        this.clearFocusTimeout();
    }
    /**
     * @protected
     */
    onPositioningPropertyChanged() {
        if (this.open) {
            this.startPositioning();
        }
    }
    // #endregion
    // #region Private Methods
    onClickedOutside() {
        if (this.closeOnOutsideClick) {
            this.closeDropdown(DropdownChangeReason.OutsideClick);
        }
    }
    onAnchorClick() {
        if (this._trigger === DropdownTrigger.Click) {
            this.toggleDropdown(DropdownChangeReason.AnchorClick);
        }
    }
    isAnchorEventPath(path) {
        if (this._anchorElement && path.includes(this._anchorElement)) {
            return true;
        }
        if (this._anchorSlot && path.includes(this._anchorSlot)) {
            return true;
        }
        const anchorPart = this.shadowRoot?.querySelector('[part="anchor"]');
        return Boolean(anchorPart && path.includes(anchorPart));
    }
    onAnchorMouseEnter() {
        if (this._trigger === DropdownTrigger.Hover) {
            this.clearHoverTimeout();
            this._hoverTimeout = setTimeout(() => {
                this.openDropdown(DropdownChangeReason.AnchorHover);
            }, 100);
        }
    }
    onAnchorMouseLeave() {
        if (this._trigger === DropdownTrigger.Hover) {
            this.clearHoverTimeout();
            this._hoverTimeout = setTimeout(() => {
                this.closeDropdown(DropdownChangeReason.AnchorHover);
            }, 100);
        }
    }
    onAnchorFocusIn() {
        if (this._trigger === DropdownTrigger.Focus) {
            this.clearFocusTimeout();
            this.openDropdown(DropdownChangeReason.AnchorFocus);
        }
    }
    onAnchorFocusOut() {
        if (this._trigger === DropdownTrigger.Focus) {
            this._focusTimeout = setTimeout(() => {
                // Only close if focus moved outside the dropdown
                const activeElement = document.activeElement;
                if (activeElement && !this.contains(activeElement)) {
                    this.closeDropdown(DropdownChangeReason.AnchorFocus);
                }
            }, 100);
        }
    }
    onPanelMouseEnter() {
        if (this._trigger === DropdownTrigger.Hover) {
            this.clearHoverTimeout();
        }
    }
    onPanelMouseLeave() {
        if (this._trigger === DropdownTrigger.Hover) {
            this._hoverTimeout = setTimeout(() => {
                this.closeDropdown(DropdownChangeReason.AnchorHover);
            }, 100);
        }
    }
    onPanelClick(event) {
        if (this._closeOnSelect) {
            const path = event.composedPath();
            const selectable = path.find((node) => {
                if (!(node instanceof HTMLElement)) {
                    return false;
                }
                // Check for Mosaic selection events or selectable markers
                return node.hasAttribute('selectable') ||
                    node.hasAttribute('force-close') ||
                    node.tagName?.toLowerCase().includes('menu-item') ||
                    node.getAttribute('role') === 'menuitem' ||
                    node.getAttribute('role') === 'option';
            });
            if (selectable) {
                this.closeDropdown(DropdownChangeReason.Selection);
            }
        }
    }
    onPanelKeydown(event) {
        if (event.key === 'Escape' && this._closeOnEscape) {
            event.preventDefault();
            event.stopPropagation();
            this.closeDropdown(DropdownChangeReason.Escape);
        }
    }
    showPanel() {
        if (!this._panelElement) {
            return;
        }
        const previousVisibility = this._panelElement.style.getPropertyValue('visibility');
        this._panelElement.style.setProperty('visibility', 'hidden');
        this.showNativePopover();
        this.startPositioning();
        if (previousVisibility) {
            this._panelElement.style.setProperty('visibility', previousVisibility);
        }
        else {
            this._panelElement.style.removeProperty('visibility');
        }
        // Play enter animation
        const enterAnimation = this.enter ?? fadeIn;
        if (enterAnimation) {
            void this._animationPlayer.stopAll(this._panelElement).then(() => {
                void this._animationPlayer.play(this._panelElement, enterAnimation);
            });
        }
    }
    showNativePopover() {
        if (!this._panelElement) {
            return;
        }
        try {
            if (typeof this._panelElement.showPopover === 'function' && !this._panelElement.matches(':popover-open')) {
                this._panelElement.showPopover();
            }
        }
        catch (_e) {
            // Fallback: just make visible via style
            this._panelElement.style.removeProperty('display');
        }
    }
    async hidePanel() {
        if (!this._panelElement) {
            this.stopPositioning();
            this._isVisible = false;
            this.requestUpdate('isVisible');
            return;
        }
        // Play exit animation, then hide
        const exitAnimation = this.exit ?? fadeOut;
        if (exitAnimation) {
            await this._animationPlayer.stopAll(this._panelElement);
            await this._animationPlayer.play(this._panelElement, exitAnimation);
            this.hideNativePopover();
        }
        else {
            this.hideNativePopover();
        }
        this.stopPositioning();
        this._isVisible = false;
        this.requestUpdate('isVisible');
    }
    hideNativePopover() {
        if (!this._panelElement) {
            return;
        }
        try {
            if (typeof this._panelElement.hidePopover === 'function') {
                this._panelElement.hidePopover();
            }
        }
        catch (_e) {
            // Fallback: hide via style
            this._panelElement.style.display = 'none';
        }
    }
    startPositioning() {
        if (!this._panelElement || !this._anchorElement) {
            return;
        }
        this.stopPositioning();
        this.applyPositioning();
        this._positioningCleanup = autoUpdate(this._anchorElement, this._panelElement, () => {
            this.applyPositioning();
        });
    }
    stopPositioning() {
        if (this._positioningCleanup) {
            this._positioningCleanup();
            this._positioningCleanup = null;
        }
    }
    applyPositioning() {
        if (!this._panelElement || !this._anchorElement) {
            return;
        }
        const panel = this._panelElement;
        const anchor = this._anchorElement;
        const getOffsetParent = this.strategy === Strategy.Absolute
            ? (element) => platform.getOffsetParent(element)
            : platform.getOffsetParent;
        panel.style.setProperty('position', this.strategy);
        panel.style.setProperty('right', 'auto');
        panel.style.setProperty('bottom', 'auto');
        panel.style.removeProperty('position-anchor');
        panel.style.removeProperty('position-area');
        this.applyDimensions();
        void computePosition(anchor, panel, {
            placement: this.placement,
            strategy: this.strategy,
            middleware: [
                floatingOffset({
                    mainAxis: this.offset,
                    crossAxis: this.skidding
                }),
                flip(),
                shift()
            ],
            platform: {
                ...platform,
                getOffsetParent
            }
        }).then(({ x, y }) => {
            panel.style.setProperty('left', `${x}px`);
            panel.style.setProperty('top', `${y}px`);
        });
    }
    applyDimensions() {
        if (!this._panelElement) {
            return;
        }
        const panel = this._panelElement;
        const toCssString = (value) => {
            return typeof value === 'number' ? `${value}px` : String(value);
        };
        // Apply max-height
        if (this.maxHeight) {
            panel.style.setProperty('max-height', toCssString(this.maxHeight));
            panel.style.setProperty('overflow-y', 'auto');
        }
        // Apply fixed height
        if (this.height && this.height !== 'auto') {
            panel.style.setProperty('height', toCssString(this.height));
        }
        else {
            panel.style.removeProperty('height');
        }
        // Apply fixed width
        if (this.width && this.width !== 'auto') {
            panel.style.setProperty('width', toCssString(this.width));
        }
        else if (!this._matchAnchorWidth) {
            panel.style.removeProperty('width');
        }
        // Apply max-width
        if (this.maxWidth && this.maxWidth !== 'auto') {
            panel.style.setProperty('max-width', toCssString(this.maxWidth));
        }
        else {
            panel.style.removeProperty('max-width');
        }
        // Apply match-anchor-width
        if (this._matchAnchorWidth && this._anchorElement) {
            const anchorRect = this._anchorElement.getBoundingClientRect();
            panel.style.setProperty('width', `${anchorRect.width}px`);
        }
    }
    applyAriaAttributes() {
        if (!this._anchorElement) {
            return;
        }
        const anchor = this._anchorElement;
        // Apply hasPopup if set
        if (this._hasPopup) {
            anchor.setAttribute('aria-haspopup', this._hasPopup);
        }
        // Apply aria-expanded
        anchor.setAttribute('aria-expanded', this.open ? 'true' : 'false');
        // Apply aria-controls if panel has an ID
        if (this._panelElement) {
            anchor.setAttribute('aria-controls', this._panelId);
        }
    }
    setupPanelListeners() {
        if (!this._panelElement) {
            return;
        }
        // Panel click for close-on-select
        this._panelClickSubscription?.dispose();
        this._panelClickSubscription = on(this._panelElement, 'click', (event) => {
            this.onPanelClick(event);
        });
        // Panel keydown for escape
        this._panelKeydownSubscription?.dispose();
        this._panelKeydownSubscription = on(this._panelElement, 'keydown', (event) => {
            this.onPanelKeydown(event);
        });
        // Panel hover for hover trigger
        this._panelMouseEnterSubscription?.dispose();
        this._panelMouseEnterSubscription = on(this._panelElement, 'mouseenter', () => this.onPanelMouseEnter());
        this._panelMouseLeaveSubscription?.dispose();
        this._panelMouseLeaveSubscription = on(this._panelElement, 'mouseleave', () => this.onPanelMouseLeave());
    }
    setupAnchorListeners() {
        const anchorPart = this.shadowRoot?.querySelector('[part="anchor"]');
        if (!anchorPart) {
            return;
        }
        this._anchorMouseEnterSubscription?.dispose();
        this._anchorMouseEnterSubscription = on(anchorPart, 'mouseenter', () => this.onAnchorMouseEnter());
        this._anchorMouseLeaveSubscription?.dispose();
        this._anchorMouseLeaveSubscription = on(anchorPart, 'mouseleave', () => this.onAnchorMouseLeave());
        this._anchorFocusInSubscription?.dispose();
        this._anchorFocusInSubscription = on(anchorPart, 'focusin', () => this.onAnchorFocusIn());
        this._anchorFocusOutSubscription?.dispose();
        this._anchorFocusOutSubscription = on(anchorPart, 'focusout', () => this.onAnchorFocusOut());
    }
    clearHoverTimeout() {
        if (this._hoverTimeout !== null) {
            clearTimeout(this._hoverTimeout);
            this._hoverTimeout = null;
        }
    }
    clearFocusTimeout() {
        if (this._focusTimeout !== null) {
            clearTimeout(this._focusTimeout);
            this._focusTimeout = null;
        }
    }
};
__decorate([
    Attribute({ type: Placement }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], DropdownElement.prototype, "dropDownPlacement", null);
__decorate([
    Attribute({ type: Strategy }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], DropdownElement.prototype, "dropDownStrategy", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], DropdownElement.prototype, "dropDownDistance", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], DropdownElement.prototype, "dropDownSkidding", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "dropDownStaysOpen", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "dropDownMaxHeight", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "dropDownHeight", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "dropDownWidth", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "dropDownMaxWidth", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "isDropDownOpen", null);
__decorate([
    Attribute({ type: Placement }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], DropdownElement.prototype, "placement", null);
__decorate([
    Attribute({ type: Strategy }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], DropdownElement.prototype, "strategy", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], DropdownElement.prototype, "offset", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], DropdownElement.prototype, "skidding", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "open", null);
__decorate([
    Attribute({ type: DropdownTrigger }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], DropdownElement.prototype, "trigger", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "closeOnOutsideClick", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "closeOnEscape", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "closeOnSelect", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "restoreFocus", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "hasPopup", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], DropdownElement.prototype, "matchAnchorWidth", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "maxHeight", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "height", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "width", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], DropdownElement.prototype, "maxWidth", null);
__decorate([
    Emit({ eventName: 'openChanged' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], DropdownElement.prototype, "openChanged", null);
__decorate([
    Watch('open', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Boolean, Boolean]),
    __metadata("design:returntype", void 0)
], DropdownElement.prototype, "onOpenPropertyChanged", null);
__decorate([
    Watch('disabled', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Boolean, Boolean]),
    __metadata("design:returntype", void 0)
], DropdownElement.prototype, "onDisabledPropertyChanged", null);
__decorate([
    Watch('trigger', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", void 0)
], DropdownElement.prototype, "onTriggerPropertyChanged", null);
__decorate([
    Watch('placement', { waitUntilFirstUpdate: true }),
    Watch('strategy', { waitUntilFirstUpdate: true }),
    Watch('offset', { waitUntilFirstUpdate: true }),
    Watch('skidding', { waitUntilFirstUpdate: true }),
    Watch('matchAnchorWidth', { waitUntilFirstUpdate: true }),
    Watch('maxHeight', { waitUntilFirstUpdate: true }),
    Watch('height', { waitUntilFirstUpdate: true }),
    Watch('width', { waitUntilFirstUpdate: true }),
    Watch('maxWidth', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], DropdownElement.prototype, "onPositioningPropertyChanged", null);
DropdownElement = DropdownElement_1 = __decorate([
    Component({
        selector: 'mosaik-dropdown',
        template: dropdownElementTemplate,
        themes: {
            joy: dropdownElementJoyStyle,
            memphis: dropdownElementMemphisStyle,
            cosmopolitan: dropdownElementCosmopolitanStyle
        },
        options: {
            mode: 'open'
        }
    }),
    __metadata("design:paramtypes", [])
], DropdownElement);
export { DropdownElement };
//# sourceMappingURL=DropdownElement.js.map