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);
};
/* eslint-disable no-restricted-syntax */
import { arrow, autoUpdate, computePosition, flip, inline as inlineMiddleware, offset, shift, size, } from '@floating-ui/dom';
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { Component } from '../../models';
import { getHostComposePath } from '../../utils/dom';
import { BackdropMixin } from '../../utils/mixins/BackdropMixin';
import { FocusTrapMixin } from '../../utils/mixins/focus/FocusTrapMixin';
import { PreventScrollMixin } from '../../utils/mixins/PreventScrollMixin';
import { Timers } from '../../utils/controllers/Timers';
import { ACTIONS, KeyToActionMixin } from '../../utils/mixins/KeyToActionMixin';
import { DepthManager } from '../../utils/controllers/DepthManager';
import { KeyDownHandledMixin } from '../../utils/mixins/KeyDownHandledMixin';
import { COLOR, DEFAULTS, POPOVER_PLACEMENT, TIMEOUTS, TRIGGER } from './popover.constants';
import { PopoverEventManager } from './popover.events';
import styles from './popover.styles';
import { PopoverUtils } from './popover.utils';
/**
 * Popover is generic overlay which can be triggered by any actionable element.
 *
 * It can be used for tooltips, dropdowns, menus or any showing any other contextual content.
 *
 * The popover automatically positions itself based on available space and
 * supports dynamic height adjustments with scrollable content when needed.
 * It uses [Floating UI](https://floating-ui.com/) for maintaining the position of the popover.
 *
 * ## Limitations
 *
 * ### On trigger for multiple popovers
 *
 * A component (button, etc.) can trigger more than one popover, but only one of them should change the
 * aria-expanded and aria-haspopup attributes on the trigger.
 *
 * To prevent unexpected attribute changes on the trigger `disable-aria-expanded` attribute must be set on all linked
 * Popovers except one.
 *
 * ### React Popover with append-to attribute
 *
 * React mounts the popover based on the virtual DOM, but when the append-to attribute is set, the popover removes itself
 * and mounts to the specified element. React will not know about the move and will not know about the
 * newly created mdc-popoverportal element either. This throws a `NotFoundError` error when the Popover is directly
 * added/removed by React, for example:
 *
 * ```tsx
 * const SomeComponent = () => {
 *    const [isOpen, setIsOpen] = useState(false);
 *    return (<div>
 *      {isOpen && <Popover append-to="some-element-id">...</mdc-popover>}
 *    </div>);
 * }
 * ```
 * As a workaround Popover need to wrap with any other element/component, for example:
 * ```tsx
 * const SomeComponent = () => {
 *    const [isOpen, setIsOpen] = useState(false);
 *    return (<div>
 *      {isOpen && <div>
 *        <Popover append-to="some-element-id">...</mdc-popover>
 *      <div>}
 *    </div>);
 * }
 * ```
 * Note the wrapper <div> around the Popover component (React.Fragment does not work).
 *
 * @dependency mdc-button
 *
 * @tagname mdc-popover
 *
 * @event shown - (React: onShown) This event is dispatched when the popover is shown
 * @event hidden - (React: onHidden) This event is dispatched when the popover is hidden
 * @event created - (React: onCreated) This event is dispatched when the popover is created (added to the DOM)
 * @event destroyed - (React: onDestroyed) This event is dispatched when the popover is destroyed (removed from the DOM)
 *
 * @cssproperty --mdc-popover-arrow-border-radius - radius of the arrow border
 * @cssproperty --mdc-popover-arrow-border - border of the arrow
 * @cssproperty --mdc-popover-primary-background-color - primary background color of the popover
 * @cssproperty --mdc-popover-border-color - border color of the popover
 * @cssproperty --mdc-popover-inverted-background-color - inverted background color of the popover
 * @cssproperty --mdc-popover-inverted-border-color - inverted border color of the popover
 * @cssproperty --mdc-popover-inverted-text-color - inverted text color of the popover
 * @cssproperty --mdc-popover-elevation-3 - elevation of the popover
 * @cssproperty --mdc-popover-max-width - max width of the popover
 * @cssproperty --mdc-popover-max-height - max height of the popover
 * @cssproperty --mdc-popover-width - width of the popover
 * @cssproperty --mdc-popover-backdrop-color - background color of the backdrop (if backdrop is enabled)
 *
 * @slot - Default slot for the popover content
 *
 * @csspart popover-close - The close button of the popover.
 * @csspart popover-content - The content of the popover.
 * @csspart popover-hover-bridge - The hover bridge of the popover.
 */
class Popover extends KeyDownHandledMixin(KeyToActionMixin(BackdropMixin(PreventScrollMixin(FocusTrapMixin(Component))))) {
    /**
     * The effective z-index of the popover.
     *
     * If no explicit `z-index` value is provided, then it automatically calculated
     * to ensure proper stacking order among multiple overlays.
     */
    get zIndex() {
        if (!Number.isInteger(this.internalZIndex)) {
            return this.depthManager.getHostZIndex();
        }
        return this.internalZIndex;
    }
    set zIndex(value) {
        this.internalZIndex = value;
    }
    /** @internal */
    get connectedTooltip() {
        const connectedTooltips = this.getRootNode().querySelectorAll(`mdc-tooltip[triggerID="${this.triggerID}"]`);
        return Array.from(connectedTooltips).find(t => t !== this);
    }
    /**
     * Get trigger element on-demand
     * It is necessary because trigger might appear later in the DOM, or it could be replaced completely.
     */
    get triggerElement() {
        return this.getRootNode().querySelector(`[id="${this.triggerID}"]`);
    }
    constructor() {
        super();
        /** track the depth of the popover for z-index calculation
         * @internal
         */
        this.depthManager = new DepthManager(this);
        /**
         * The unique ID of the popover.
         */
        this.id = '';
        /**
         * The ID of the element that triggers the popover.
         * This attribute is required for the popover to work.
         */
        this.triggerID = '';
        /**
         * Determines the events that cause the Popover to show.
         * Multiple event names should be separated by spaces.
         * For example to allow both click and hover, use 'click mouseenter' as the trigger.
         * - **click**
         * - **mouseenter**
         * - **focusin**
         * - **manual**
         *
         * @default click
         */
        this.trigger = DEFAULTS.TRIGGER;
        /**
         * The placement of the popover.
         * - **top**
         * - **top-start**
         * - **top-end**
         * - **bottom**
         * - **bottom-start**
         * - **bottom-end**
         * - **left**
         * - **left-start**
         * - **left-end**
         * - **right**
         * - **right-start**
         * - **right-end**
         * @default bottom
         */
        this.placement = DEFAULTS.PLACEMENT;
        /**
         * Color of the popover
         * - **tonal**
         * - **contrast**
         * @default tonal
         */
        this.color = DEFAULTS.COLOR;
        /**
         * The visibility of the popover.
         * @default false
         */
        this.visible = DEFAULTS.VISIBLE;
        /**
         * The offset of the popover.
         *
         * By default, offset is just a number which represents the distance on the main axis in pixels
         * between the reference element and the popover. Main axis is determined by the placement of the popover.
         * For example, if the placement is top or bottom, the main axis is vertical, and
         * if the placement is left or right, the main axis is horizontal.
         *
         * Alternatively the offset property accepts other values as well:
         * - with an object value, `crossAxis` and `alignmentAxis` can be changed as well
         * - with a function value, it provides the opportunity to fully control the offset logic
         *
         * Note: only the number value reflected in the `offset` attribute
         *
         * @default 4
         * @see [Floating UI - offset](https://floating-ui.com/docs/offset)
         */
        this.offset = DEFAULTS.OFFSET;
        /**
         * This describes the clipping element(s) or area that overflow will be checked relative to.
         * The default is 'clippingAncestors', which are the overflow ancestors which will cause the
         * element to be clipped.
         *
         * Possible values:
         *  - 'clippingAncestors'
         *  - any css selector
         *
         * @default 'clippingAncestors'
         *
         * @see [Floating UI - boundary](https://floating-ui.com/docs/detectOverflow#boundary)
         */
        this.boundary = DEFAULTS.BOUNDARY;
        /**
         * This describes the root boundary that the element will be checked for overflow relative to.
         * The default is 'viewport', which is the area of the page the user can see on the screen.
         *
         * The other string option is 'document', which is the entire page outside the viewport.
         *
         * @default 'viewport'
         *
         * @see [Floating UI - rootBoundary](https://floating-ui.com/docs/detectOverflow#rootboundary)
         */
        this.boundaryRoot = DEFAULTS.BOUNDARY_ROOT;
        /**
         * Determines whether the focus trap is enabled.
         * If true, focus will be restricted to the content within this component.
         * @default false
         */
        this.focusTrap = DEFAULTS.FOCUS_TRAP;
        /**
         * Prevent outside scrolling when popover is shown.
         * @default false
         */
        this.preventScroll = DEFAULTS.PREVENT_SCROLL;
        /**
         * The arrow visibility of the popover.
         * @default false
         */
        this.showArrow = DEFAULTS.ARROW;
        /**
         * The close button visibility of the popover.
         * @default false
         */
        this.closeButton = DEFAULTS.CLOSE_BUTTON;
        /**
         * Determines whether the popover is interactive.
         * Make sure to set focusTrap to true to keep the focus inside the popover in case necessary.
         * Setting interactive to true will not automatically set focusTrap!
         *
         * @default false
         */
        this.interactive = DEFAULTS.INTERACTIVE;
        /**
         * The delay of the show/hide popover.
         * @default 0,0
         */
        this.delay = DEFAULTS.DELAY;
        /**
         * Hide popover on escape key press.
         * @default false
         */
        this.hideOnEscape = DEFAULTS.HIDE_ON_ESCAPE;
        /**
         * Propagates the event, when the escape key is pressed (only when pressed inside the popover)
         * If true, the escape key press close the popover and will propagate the keydown event.
         * If false, the escape key press will close the popover but will not propagate the keydown event.
         * (set to false to prevent the event from bubbling up to the document).
         *
         * This only works when `hideOnEscape` is true.
         *
         * @default false
         */
        this.propagateEventOnEscape = DEFAULTS.PROPAGATE_EVENT_ON_ESCAPE;
        /**
         * Hide popover on blur.
         * @default false
         */
        this.hideOnBlur = DEFAULTS.HIDE_ON_BLUR;
        /**
         * Hide on outside click of the popover.
         * @default false
         */
        this.hideOnOutsideClick = DEFAULTS.HIDE_ON_CLICK_OUTSIDE;
        /**
         * The focus back to trigger after the popover hide.
         * @default false
         */
        this.focusBackToTrigger = DEFAULTS.FOCUS_BACK;
        /**
         * Determines whether the popover with backdrop.
         * Other than popover and trigger element, the rest of the screen will be covered with a backdrop.
         * @default false
         */
        this.backdrop = DEFAULTS.BACKDROP;
        /**
         * Set this property to false to make the backdrop a dimmed visible backdrop (if `backdrop` is true).
         * This is useful for cases where the popovers backdrop should get dimmed.
         *
         * @default true - default is to have a invisible backdrop
         */
        this.isBackdropInvisible = DEFAULTS.IS_BACKDROP_INVISIBLE;
        /**
         * Changes the placement of popover to keep it in view when scrolling.
         * @default false
         */
        this.disableFlip = DEFAULTS.DISABLE_FLIP;
        /**
         * Changes the size of popover to keep it in view when scrolling.
         * @default false
         */
        this.size = DEFAULTS.SIZE;
        /**
         * aria-label attribute to be set for close button accessibility.
         * @default null
         */
        this.closeButtonAriaLabel = null;
        /**
         * The strategy of the popover.
         * This determines how the popover is positioned in the DOM.
         * - **absolute**: The popover is positioned absolutely relative to the nearest positioned ancestor.
         * - **fixed**: The popover is positioned fixed relative to the viewport.
         *
         * Default as `absolute` is recommended for most cases.
         * In cases where the popover gets clipped by a scrollable container,
         * you can set this to `fixed` to avoid clipping.
         *
         * @default absolute
         * @see [Floating UI - strategy](https://floating-ui.com/docs/computePosition#strategy)
         */
        this.strategy = DEFAULTS.STRATEGY;
        /**
         * Role of the popover
         * @default dialog
         */
        this.role = DEFAULTS.ROLE;
        /**
         * aria-labelledby for an interactive popover only, defaults to the trigger component id.
         * Used in nested cases where the triggerComponent isn't the actual button.
         */
        this.ariaLabelledby = null;
        /**
         * aria-describedby of the popover.
         */
        this.ariaDescribedby = null;
        /**
         * Disable setting the `aria-expanded` attribute on trigger element.
         *
         * Note, when `disable-aria-expanded` is true
         *  - when its value change after first update to
         *    - `true`: popover will not remove the `aria-expanded` to avoid conflicts when there are more than one popover
         *      registered to the same trigger
         *      - `false`: check `aria-expanded` value and update it if necessary.
         *  - aria-haspopup will be disabled as well
         * @default false
         */
        this.disableAriaExpanded = DEFAULTS.DISABLE_ARIA_EXPANDED;
        /**
         * Controls the visibility of a connected tooltip when this popover is open.
         * - If set to `true`, the tooltip remains open alongside the popover.
         * - If set to `false`, the tooltip will be closed when the popover opens.
         * Useful for scenarios where both a popover and a tooltip are linked to the same trigger element.
         */
        this.keepConnectedTooltipOpen = DEFAULTS.KEEP_CONNECTED_TOOLTIP_OPEN;
        /**
         * Whether to update the position of the Popover on every animation frame if required.
         * While optimized for performance, it should be used sparingly and with caution.
         *
         * @default false
         */
        this.animationFrame = DEFAULTS.ANIMATION_FRAME;
        /**
         * Improves positioning for inline reference elements that span over multiple lines,
         * such as hyperlinks or range selections.
         *
         * When enabled, the floating-ui `inline` middleware is used to position the popover
         * based on the individual client rects of the inline element, rather than its full bounding box.
         *
         * @default false
         * @see [Floating UI - inline](https://floating-ui.com/docs/inline)
         */
        this.inline = DEFAULTS.INLINE;
        /**
         * The index of the interactive element to receive focus when the popover opens with focus trap enabled.
         * The index is based on the order of interactive elements in the popover, starting from 0.
         * If not set, the first interactive element (index 0) will receive focus by default.
         * This is useful for cases where you want a specific element to receive focus when the popover opens, instead of the default first element.
         * @default null
         */
        this.elementIndexToReceiveFocus = null;
        /**
         * The name of the stack group this popover belongs to.
         * Popovers with the same stack group name will be grouped together.
         */
        this.stackGroupName = '';
        /** @internal */
        this.arrowElement = null;
        /** @internal */
        this.isHovered = false;
        /** @internal */
        this.openDelay = 0;
        /** @internal */
        this.closeDelay = 0;
        /** @internal */
        this.floatingUICleanupFunction = null;
        /** @internal */
        this.shouldSuppressOpening = false;
        /** @internal */
        this.timers = new Timers(this);
        /** @internal */
        this.parseTrigger = () => {
            var _a;
            const triggers = ((_a = this.trigger) === null || _a === void 0 ? void 0 : _a.split(' ')) || [];
            const validTriggers = triggers.filter(trigger => Object.values(TRIGGER).includes(trigger));
            let newTrigger = validTriggers.length > 0 ? this.trigger : DEFAULTS.TRIGGER;
            if (newTrigger === 'mouseenter') {
                if (this.interactive) {
                    // if the popover is interactive, there is interactive content inside the popover
                    // so we can't use the focusin trigger, since after closing with escape key, the
                    // popover keeps opening. So we need to use the click trigger instead.
                    newTrigger = 'mouseenter click';
                }
                else {
                    // non-interactive popovers with trigger mouseenter (like a tooltip) should also open
                    // when focusing to the trigger element
                    newTrigger = 'mouseenter focusin';
                }
            }
            this.trigger = newTrigger;
        };
        /**
         * Sets up the trigger related event listeners, based on the trigger type.
         * Includes fallback for mouseenter trigger to also handle focusin for non-interactive popovers.
         *
         * We use capture phase on `document` for all trigger listeners to make sure we capture trigger
         * events even when they are not propagated during the bubble phase (e.g.: buttons in list item).
         *
         * Hover is detected via `mouseover`/`mouseout` (not `mouseenter`/`mouseleave`). The latter are
         * spec'd as `composed: false` and do not bubble, so a document-level listener never sees them
         * when the trigger lives inside a shadow root (e.g. wrapped in `mdc-iconprovider` or
         * `mdc-themeprovider`). `mouseover`/`mouseout` are `composed: true` and bubble, so they cross
         * shadow boundaries and reach `document`, letting us keep event delegation (no direct reference
         * to the trigger element and no dependency on its mount/unmount lifecycle). The handlers filter
         * out movements that stay within the trigger's subtree, recreating enter/leave semantics.
         * @internal
         */
        this.setupTriggerListeners = () => {
            if (this.trigger.includes('click')) {
                document.addEventListener('click', this.togglePopoverVisible, { capture: true });
                if (this.hideOnBlur) {
                    document.addEventListener('focusout', this.handleFocusOut, { capture: true });
                }
            }
            if (this.trigger.includes('mouseenter')) {
                const hoverBridge = this.renderRoot.querySelector('div[part="popover-hover-bridge"]');
                hoverBridge === null || hoverBridge === void 0 ? void 0 : hoverBridge.addEventListener('mouseenter', this.show);
                this.addEventListener('mouseenter', this.cancelCloseDelay);
                this.addEventListener('mouseleave', this.startCloseDelay);
                document.addEventListener('mouseover', this.handleMouseEnter, { capture: true });
                document.addEventListener('mouseout', this.handleMouseLeave, { capture: true });
            }
            if (this.trigger.includes('focusin')) {
                document.addEventListener('focusin', this.handleFocusIn, { capture: true });
                if (!this.interactive) {
                    document.addEventListener('focusout', this.handleFocusOut, { capture: true });
                }
            }
        };
        /**
         * Removes the trigger related event listeners.
         * @internal
         */
        this.removeTriggerListeners = () => {
            // click trigger
            document.removeEventListener('click', this.togglePopoverVisible, { capture: true });
            // mouseenter trigger
            const hoverBridge = this.renderRoot.querySelector('div[part="popover-hover-bridge"]');
            hoverBridge === null || hoverBridge === void 0 ? void 0 : hoverBridge.removeEventListener('mouseenter', this.show);
            document.removeEventListener('mouseover', this.handleMouseEnter, { capture: true });
            document.removeEventListener('mouseout', this.handleMouseLeave, { capture: true });
            this.removeEventListener('mouseenter', this.cancelCloseDelay);
            this.removeEventListener('mouseleave', this.startCloseDelay);
            // focusin trigger
            document.removeEventListener('focusin', this.handleFocusIn, { capture: true });
            document.removeEventListener('focusout', this.handleFocusOut, { capture: true });
        };
        /**
         * Removes all event listeners related to the popover.
         * @internal
         */
        this.removeAllListeners = () => {
            this.removeTriggerListeners();
            if (this.hideOnOutsideClick) {
                document.removeEventListener('click', this.onOutsidePopoverClick, { capture: true });
            }
            if (this.hideOnEscape) {
                this.removeEventListener('keydown', this.onEscapeKeydown);
                document.removeEventListener('keydown', this.onEscapeKeydown);
            }
            if (this.hideOnBlur) {
                this.removeEventListener('focusout', this.onPopoverFocusOut);
            }
        };
        /**
         * Handles the outside click event to close the popover.
         *
         * @param event - The mouse event.
         * @internal
         */
        this.onOutsidePopoverClick = (event) => {
            if (!this.depthManager.isHostOnTop())
                return;
            const path = event.composedPath();
            const insidePopoverClick = this.contains(event.target) || path.includes(this.triggerElement) || path.includes(this);
            const clickedOnBackdrop = this.backdropElement ? path.includes(this.backdropElement) : false;
            if (!insidePopoverClick || clickedOnBackdrop) {
                this.hide();
                PopoverEventManager.onClickOutside(this);
                if (clickedOnBackdrop) {
                    event.stopPropagation();
                }
            }
        };
        /**
         * Handles the escape keydown event to close the popover.
         *
         * This method is attached to the document.
         *
         * @param event - The keyboard event.
         * @internal
         */
        this.onEscapeKeydown = (event) => {
            if (!this.visible || this.getActionForKeyEvent(event) !== ACTIONS.ESCAPE || !this.depthManager.isHostOnTop()) {
                return;
            }
            if (!this.propagateEventOnEscape) {
                // If propagateEventOnEscape is false, we don't want to allow the event to bubble up
                event.stopPropagation();
            }
            event.preventDefault();
            this.hide();
            PopoverEventManager.onEscapeKeyPressed(this);
            this.keyDownEventHandled();
        };
        /**
         * Handles the popover focus out event.
         *
         * @param event - The focus event.
         * @internal
         */
        this.onPopoverFocusOut = (event) => {
            if (!this.contains(event.relatedTarget)) {
                this.hide();
            }
        };
        /**
         * Determines whether a delegated `mouseover`/`mouseout` event is movement that stays within the
         * trigger's subtree rather than a genuine enter/leave of the trigger.
         *
         * Both events fire repeatedly while the pointer moves between elements inside the trigger
         * (e.g. an icon with internal SVG elements). `relatedTarget` is the element on the other side of
         * the boundary (the element being left for `mouseover`, or entered for `mouseout`); if it
         * resolves to the trigger or one of its shadow hosts, the pointer has not crossed the trigger
         * boundary and the event should be ignored.
         * @internal
         */
        this.isHoverWithinTrigger = (event) => {
            const { triggerElement } = this;
            const { relatedTarget } = event;
            if (triggerElement && relatedTarget instanceof Element) {
                return getHostComposePath(relatedTarget).includes(triggerElement);
            }
            return false;
        };
        /**
         * Handles the pointer moving over the trigger element (delegated `mouseover`).
         * This method sets the `isHovered` flag to true and shows the popover.
         * @internal
         */
        this.handleMouseEnter = (event) => {
            if (!this.isEventFromTrigger(event))
                return;
            if (this.isHoverWithinTrigger(event))
                return;
            this.isHovered = true;
            this.show();
        };
        /**
         * Handles the pointer leaving the trigger element (delegated `mouseout`).
         * This method sets the `isHovered` flag to false and starts the close delay
         * timer to hide the popover.
         * @internal
         */
        this.handleMouseLeave = (event) => {
            if (!this.isEventFromTrigger(event))
                return;
            if (this.isHoverWithinTrigger(event))
                return;
            this.isHovered = false;
            this.startCloseDelay();
        };
        /**
         * Closes based on hideOnBlur property or hover state.
         * @internal
         */
        this.handleFocusOut = (event) => {
            if (!this.isEventFromTrigger(event))
                return;
            if (this.hideOnBlur) {
                const { relatedTarget } = event;
                // Don't hide if focus moves within the component
                if (!this.contains(relatedTarget)) {
                    this.hide();
                }
                return;
            }
            if (!this.isHovered) {
                this.hide();
            }
        };
        /**
         * Handles focus in event on the trigger element.
         * This method checks if the trigger element has visible focus or is being hovered.
         * @internal
         */
        this.handleFocusIn = (event) => {
            var _a, _b, _c, _d;
            if (!this.isEventFromTrigger(event))
                return;
            if (((_a = this.triggerElement) === null || _a === void 0 ? void 0 : _a.matches(':focus-visible')) ||
                ((_d = (_c = (_b = this.triggerElement) === null || _b === void 0 ? void 0 : _b.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector('.mdc-focus-ring')) === null || _d === void 0 ? void 0 : _d.matches(':focus-visible')) ||
                this.isHovered) {
                this.show();
            }
        };
        /**
         * Cancels the open delay timer.
         * @internal
         */
        this.cancelOpenDelay = () => {
            this.timers.clearTimeout(TIMEOUTS.OPEN);
        };
        /**
         * Starts the close delay timer.
         * If the popover is not interactive, it will close the popover after the delay.
         * @internal
         */
        this.startCloseDelay = () => {
            this.cancelOpenDelay();
            if (!this.interactive) {
                this.hide();
            }
            else {
                this.timers.setTimeout(TIMEOUTS.HOVER, () => {
                    this.visible = false;
                }, this.closeDelay);
            }
        };
        /**
         * Cancels the close delay timer.
         * @internal
         */
        this.cancelCloseDelay = () => {
            this.timers.clearTimeout(TIMEOUTS.HOVER);
            this.timers.clearTimeout(TIMEOUTS.CLOSE);
        };
        /**
         * Hides the popover.
         */
        this.hide = () => {
            this.cancelOpenDelay();
            const callback = () => {
                this.visible = false;
            };
            if (this.closeDelay > 0) {
                this.timers.setTimeout(TIMEOUTS.CLOSE, callback, this.closeDelay);
            }
            else {
                this.timers.clearTimeout(TIMEOUTS.CLOSE);
                callback();
            }
        };
        /**
         * Toggles the popover visibility.
         */
        this.togglePopoverVisible = (event) => {
            if (!this.isEventFromTrigger(event))
                return;
            if (this.visible) {
                this.hide();
            }
            else {
                this.show();
            }
        };
        /**
         * Positions the popover based on the trigger element.
         * It also handles the flip, size and arrow placement.
         * It uses the floating-ui/dom library to calculate the position.
         * @internal
         */
        this.positionPopover = () => {
            const { triggerElement } = this;
            if (!triggerElement)
                return;
            const boundary = !this.boundary || this.boundary === 'clippingAncestors'
                ? 'clippingAncestors'
                : Array.from(document.querySelectorAll(this.boundary));
            const rootBoundary = this.boundaryRoot;
            const middleware = [];
            if (this.inline) {
                middleware.push(inlineMiddleware());
            }
            middleware.push(shift({
                boundary,
                rootBoundary,
                padding: this.boundaryPadding,
            }));
            if (!this.disableFlip) {
                middleware.push(flip({
                    boundary,
                    rootBoundary,
                }));
            }
            if (this.size) {
                // expose a CSS variable for the available height
                // so that it can be used in other components styles
                const setInternalAvailableHeight = (availableHeight) => {
                    this.style.setProperty('--mdc-popover-internal-available-height', `${availableHeight}px`);
                };
                const popoverContent = this.renderRoot.querySelector('[part="popover-content"]');
                middleware.push(size({
                    boundary,
                    rootBoundary,
                    apply({ availableHeight }) {
                        if (!popoverContent)
                            return;
                        Object.assign(popoverContent.style, {
                            maxHeight: `${availableHeight}px`,
                            overflowY: 'auto',
                        });
                        setInternalAvailableHeight(availableHeight);
                    },
                    padding: 50,
                }));
            }
            let arrowOffset = 0;
            if (this.showArrow) {
                this.arrowElement = this.renderRoot.querySelector('div[part="popover-arrow"]');
                if (this.arrowElement) {
                    const arrowLen = this.arrowElement.offsetHeight;
                    arrowOffset = Math.sqrt(2 * arrowLen ** 2) / 2;
                    middleware.push(arrow({ element: this.arrowElement, padding: 12 }));
                }
            }
            middleware.push(offset(typeof this.offset === 'number' ? this.offset + arrowOffset : this.offset));
            this.floatingUICleanupFunction = autoUpdate(triggerElement, this, async () => {
                const { triggerElement } = this;
                if (!triggerElement)
                    return;
                const adjustedPlacement = this.adjustPlacementForRtl(this.placement);
                const { x, y, middlewareData, placement } = await computePosition(triggerElement, this, {
                    placement: adjustedPlacement,
                    middleware,
                    strategy: this.strategy,
                });
                this.utils.updatePopoverStyle(x, y);
                if (middlewareData.arrow && this.arrowElement) {
                    this.utils.updateArrowStyle(middlewareData.arrow, placement);
                }
                if (this.trigger.includes('mouseenter')) {
                    this.utils.setupHoverBridge(placement);
                }
            }, {
                animationFrame: this.animationFrame,
            });
        };
        this.show = this.show.bind(this);
        this.utils = new PopoverUtils(this);
        this.parseTrigger();
        [this.openDelay, this.closeDelay] = this.utils.setupDelay();
    }
    async firstUpdated(changedProperties) {
        super.firstUpdated(changedProperties);
        PopoverEventManager.onCreatedPopover(this);
    }
    connectedCallback() {
        super.connectedCallback();
        this.utils.setupAppendTo();
        this.setupTriggerListeners();
    }
    async disconnectedCallback() {
        var _a, _b;
        super.disconnectedCallback();
        this.removeAllListeners();
        (_a = this.deactivateFocusTrap) === null || _a === void 0 ? void 0 : _a.call(this);
        this.deactivatePreventScroll();
        this.moveElementBackAfterBackdropRemoval(this.triggerElement);
        this.removeBackdrop();
        (_b = this.floatingUICleanupFunction) === null || _b === void 0 ? void 0 : _b.call(this);
        if (!this.keepConnectedTooltipOpen) {
            if (this.connectedTooltip) {
                this.connectedTooltip.shouldSuppressOpening = false;
            }
        }
        this.utils.cleanupAppendTo();
        PopoverEventManager.onDestroyedPopover(this);
    }
    async updated(changedProperties) {
        super.updated(changedProperties);
        if (changedProperties.has('visible')) {
            const oldValue = changedProperties.get('visible') || false;
            await this.isOpenUpdated(oldValue, this.visible);
            this.utils.updateAriaExpandedAttribute();
        }
        if (changedProperties.has('trigger')) {
            this.parseTrigger();
            this.removeTriggerListeners();
            this.setupTriggerListeners();
        }
        if (changedProperties.has('placement')) {
            this.setAttribute('placement', Object.values(POPOVER_PLACEMENT).includes(this.placement) ? this.placement : DEFAULTS.PLACEMENT);
        }
        if (changedProperties.has('delay')) {
            [this.openDelay, this.closeDelay] = this.utils.setupDelay();
        }
        if (changedProperties.has('color')) {
            this.setAttribute('color', Object.values(COLOR).includes(this.color) ? this.color : DEFAULTS.COLOR);
        }
        if (changedProperties.has('appendTo')) {
            // only setup appendTo when the component is connected to the DOM
            // this fixes an issue which causes popovers to be orphaned in the DOM without the appendTo logic being cleaned up,
            // cause of Lit deferring the updated() to fire after a disconnect
            if (this.appendTo && this.isConnected) {
                this.utils.setupAppendTo();
            }
            else {
                this.utils.cleanupAppendTo();
            }
        }
        if (changedProperties.has('interactive') ||
            changedProperties.has('aria-label') ||
            changedProperties.has('aria-labelledby')) {
            this.utils.updateAriaLabels();
        }
        if (changedProperties.has('role')) {
            // If the role is changed to an empty string, set it to null
            // to avoid setting an invalid role on the popover element.
            if (this.role === '') {
                this.role = null;
            }
            this.utils.updateAriaModal();
        }
        if (changedProperties.has('disableAriaExpanded')) {
            this.utils.updateAriaExpandedAttribute();
        }
        if (changedProperties.has('focusTrap')) {
            // if focusTrap turned false and the popover is visible, deactivate the focus trap
            if (!this.focusTrap && this.visible) {
                this.deactivateFocusTrap();
            }
        }
        if (changedProperties.has('preventScroll')) {
            // if preventScroll turned false and the popover is visible, deactivate the prevent scroll
            if (!this.preventScroll && this.visible) {
                this.deactivatePreventScroll();
            }
            else if (this.preventScroll && this.visible) {
                this.activatePreventScroll();
            }
        }
    }
    /** @internal */
    onComponentStackChanged(changed) {
        if (changed === 'removed') {
            this.hide();
        }
        else if (changed === 'moved') {
            this.requestUpdate('zIndex');
        }
    }
    /**
     * Handles the popover visibility change and position the popover.
     * Handles the exit event to close the popover.
     *
     * @param oldValue - The old value of the visible property.
     * @param newValue - The new value of the visible property.
     */
    async isOpenUpdated(oldValue, newValue) {
        var _a, _b;
        const { triggerElement } = this;
        if (oldValue === newValue || !triggerElement) {
            return;
        }
        if (newValue && !this.shouldSuppressOpening) {
            if (this.depthManager.pushHost()) {
                // request update to trigger zIndex recalculation
                this.requestUpdate('zIndex');
            }
            if (!this.keepConnectedTooltipOpen) {
                // If this popover gets visible and keepConnectedTooltipsClosed is true,
                // we need to close the connected tooltip.
                if (this.connectedTooltip) {
                    this.connectedTooltip.visible = false;
                    this.connectedTooltip.shouldSuppressOpening = true;
                }
            }
            // create backdrop if it doesn't exist
            if (this.backdrop && !this.backdropElement) {
                this.createBackdrop('popover');
                this.keepElementAboveBackdrop(triggerElement);
            }
            this.positionPopover();
            if (this.hideOnBlur) {
                this.addEventListener('focusout', this.onPopoverFocusOut);
                if (this.trigger === 'click') {
                    triggerElement.style.pointerEvents = 'none';
                }
            }
            if (this.hideOnEscape) {
                this.addEventListener('keydown', this.onEscapeKeydown);
                document.addEventListener('keydown', this.onEscapeKeydown);
            }
            this.activatePreventScroll();
            if (this.hideOnOutsideClick) {
                document.addEventListener('click', this.onOutsidePopoverClick, { capture: true });
            }
            // make sure popover is fully rendered before activating focus trap
            setTimeout(() => {
                var _a, _b, _c;
                if (this.interactive && this.focusTrap) {
                    (_a = this.activateFocusTrap) === null || _a === void 0 ? void 0 : _a.call(this);
                    (_b = this.setInitialFocus) === null || _b === void 0 ? void 0 : _b.call(this, (_c = this.elementIndexToReceiveFocus) !== null && _c !== void 0 ? _c : 0);
                }
            }, 0);
            PopoverEventManager.onShowPopover(this);
        }
        else {
            this.depthManager.popHost();
            // cleanup floating-ui on closing the popover
            (_a = this.floatingUICleanupFunction) === null || _a === void 0 ? void 0 : _a.call(this);
            if (this.backdrop) {
                this.moveElementBackAfterBackdropRemoval(triggerElement);
                this.removeBackdrop();
            }
            if (this.hideOnBlur) {
                this.removeEventListener('focusout', this.onPopoverFocusOut);
                if (this.trigger === 'click') {
                    triggerElement.style.pointerEvents = '';
                }
            }
            if (this.hideOnOutsideClick) {
                document.removeEventListener('click', this.onOutsidePopoverClick, { capture: true });
            }
            if (this.hideOnEscape) {
                this.removeEventListener('keydown', this.onEscapeKeydown);
                document.removeEventListener('keydown', this.onEscapeKeydown);
            }
            this.deactivatePreventScroll();
            (_b = this.deactivateFocusTrap) === null || _b === void 0 ? void 0 : _b.call(this);
            if (this.focusBackToTrigger) {
                triggerElement === null || triggerElement === void 0 ? void 0 : triggerElement.focus();
            }
            if (!this.keepConnectedTooltipOpen) {
                if (this.connectedTooltip) {
                    this.connectedTooltip.shouldSuppressOpening = false;
                }
            }
            PopoverEventManager.onHidePopover(this);
        }
    }
    /**
     * Shows the popover.
     */
    show() {
        if (this.shouldSuppressOpening) {
            return;
        }
        this.cancelCloseDelay();
        if (this.visible) {
            return;
        }
        const callback = () => {
            this.visible = true;
        };
        if (this.openDelay > 0) {
            this.timers.setTimeout(TIMEOUTS.OPEN, callback, this.openDelay);
        }
        else {
            this.timers.clearTimeout(TIMEOUTS.OPEN);
            callback();
        }
    }
    isEventFromTrigger(event) {
        var _a;
        if (!this.triggerID)
            return false;
        if (event.composed) {
            return event.composedPath().some(el => (el === null || el === void 0 ? void 0 : el.id) === this.triggerID);
        }
        return ((_a = event.target) === null || _a === void 0 ? void 0 : _a.id) === this.triggerID;
    }
    /**
     * Detects if the current layout is in RTL (right-to-left) mode.
     * @returns True if RTL, false if LTR
     * @internal
     */
    isRtl() {
        return window.getComputedStyle(this).direction === 'rtl';
    }
    /**
     * Adjusts the placement for RTL layouts by flipping left/right directions.
     * @param placement - The original placement value
     * @returns The adjusted placement for RTL or original placement for LTR
     * @internal
     */
    adjustPlacementForRtl(placement) {
        if (!this.isRtl()) {
            return placement;
        }
        switch (placement) {
            case POPOVER_PLACEMENT.LEFT:
                return POPOVER_PLACEMENT.RIGHT;
            case POPOVER_PLACEMENT.LEFT_START:
                return POPOVER_PLACEMENT.RIGHT_START;
            case POPOVER_PLACEMENT.LEFT_END:
                return POPOVER_PLACEMENT.RIGHT_END;
            case POPOVER_PLACEMENT.RIGHT:
                return POPOVER_PLACEMENT.LEFT;
            case POPOVER_PLACEMENT.RIGHT_START:
                return POPOVER_PLACEMENT.LEFT_START;
            case POPOVER_PLACEMENT.RIGHT_END:
                return POPOVER_PLACEMENT.LEFT_END;
            default:
                return placement;
        }
    }
    render() {
        this.style.zIndex = `${this.zIndex}`;
        return html `
      <div part="popover-hover-bridge"></div>
      ${this.closeButton
            ? html ` <mdc-button
            part="popover-close"
            prefix-icon="cancel-bold"
            variant="tertiary"
            size="20"
            aria-label=${ifDefined(this.closeButtonAriaLabel) || ''}
            @click="${this.hide}"
          ></mdc-button>`
            : nothing}
      ${this.showArrow ? html `<div class="popover-arrow" part="popover-arrow"></div>` : nothing}
      <div part="popover-content">
        <slot></slot>
      </div>
    `;
    }
}
Popover.styles = [...Component.styles, ...styles];
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "id", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "triggerID", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "trigger", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "placement", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "color", void 0);
__decorate([
    property({ type: Boolean, reflect: true }),
    __metadata("design:type", Boolean)
], Popover.prototype, "visible", void 0);
__decorate([
    property({
        type: Number,
        reflect: true,
        converter: {
            toAttribute(value) {
                return typeof value === 'number' ? value.toString() : '';
            },
        },
    }),
    __metadata("design:type", Object)
], Popover.prototype, "offset", void 0);
__decorate([
    property({ type: String, reflect: true, attribute: 'boundary' }),
    __metadata("design:type", String)
], Popover.prototype, "boundary", void 0);
__decorate([
    property({ type: String, reflect: true, attribute: 'boundary-root' }),
    __metadata("design:type", String)
], Popover.prototype, "boundaryRoot", void 0);
__decorate([
    property({ type: Number, reflect: true, attribute: 'boundary-padding' }),
    __metadata("design:type", Number)
], Popover.prototype, "boundaryPadding", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'focus-trap' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "focusTrap", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'prevent-scroll' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "preventScroll", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'show-arrow' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "showArrow", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'close-button' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "closeButton", void 0);
__decorate([
    property({ type: Boolean, reflect: true }),
    __metadata("design:type", Boolean)
], Popover.prototype, "interactive", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "delay", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'hide-on-escape' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "hideOnEscape", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'propagate-event-on-escape' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "propagateEventOnEscape", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'hide-on-blur' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "hideOnBlur", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'hide-on-outside-click' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "hideOnOutsideClick", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'focus-back-to-trigger' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "focusBackToTrigger", void 0);
__decorate([
    property({ type: Boolean, reflect: true }),
    __metadata("design:type", Boolean)
], Popover.prototype, "backdrop", void 0);
__decorate([
    property({ type: String, reflect: true, attribute: 'backdrop-append-to' }),
    __metadata("design:type", String)
], Popover.prototype, "backdropAppendTo", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'is-backdrop-invisible' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "isBackdropInvisible", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'disable-flip' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "disableFlip", void 0);
__decorate([
    property({ type: Boolean, reflect: true }),
    __metadata("design:type", Boolean)
], Popover.prototype, "size", void 0);
__decorate([
    property({ type: Number, reflect: true, attribute: 'z-index' }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], Popover.prototype, "zIndex", null);
__decorate([
    property({ type: String, reflect: true, attribute: 'append-to' }),
    __metadata("design:type", String)
], Popover.prototype, "appendTo", void 0);
__decorate([
    property({ type: String, attribute: 'close-button-aria-label', reflect: true }),
    __metadata("design:type", Object)
], Popover.prototype, "closeButtonAriaLabel", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "strategy", void 0);
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", Object)
], Popover.prototype, "role", void 0);
__decorate([
    property({ type: String, reflect: true, attribute: 'aria-labelledby' }),
    __metadata("design:type", Object)
], Popover.prototype, "ariaLabelledby", void 0);
__decorate([
    property({ type: String, reflect: true, attribute: 'aria-describedby' }),
    __metadata("design:type", Object)
], Popover.prototype, "ariaDescribedby", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'disable-aria-expanded' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "disableAriaExpanded", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'keep-connected-tooltip-open' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "keepConnectedTooltipOpen", void 0);
__decorate([
    property({ type: Boolean, reflect: true, attribute: 'animation-frame' }),
    __metadata("design:type", Boolean)
], Popover.prototype, "animationFrame", void 0);
__decorate([
    property({ type: Boolean, reflect: true }),
    __metadata("design:type", Boolean)
], Popover.prototype, "inline", void 0);
__decorate([
    property({ type: Number, attribute: 'element-index-to-receive-focus', reflect: true }),
    __metadata("design:type", Object)
], Popover.prototype, "elementIndexToReceiveFocus", void 0);
__decorate([
    property({ type: String, attribute: 'stack-group-name', reflect: true }),
    __metadata("design:type", String)
], Popover.prototype, "stackGroupName", void 0);
export default Popover;
