import { ReactiveController, ReactiveControllerHost } from 'lit'; import { Placement } from '@floating-ui/dom'; export interface PopoverControllerConfig { getTriggerElement: () => HTMLElement | null; getFloatingElement: () => HTMLElement | null; getArrowElement: () => HTMLElement | null; onPlacementChange?: (placement: string) => void; } /** * Outcome of {@link PopoverController.reposition}. `stale` means the host * closed or retargeted again while `computePosition` was in flight — the caller * must not act on it, because whatever superseded it owns the element now. */ export type RepositionResult = 'repositioned' | 'no-anchor' | 'stale'; export interface PositionOptions { placement: Placement; distance: number; /** Offset along the cross axis, in pixels (e.g. to align a submenu's first item with its trigger). */ skidding?: number; fullWidth?: boolean; /** Floor the floating element's width at the trigger's width (`'trigger'`). */ minWidth?: 'trigger'; } type TriggerHandlers = { onPointerEnter?: (e: PointerEvent) => void; onPointerLeave?: (e: PointerEvent) => void; onFocusIn?: (e: FocusEvent) => void; onFocusOut?: (e: FocusEvent) => void; onClick?: (e: MouseEvent) => void; onKeyDown?: (e: KeyboardEvent) => void; }; export declare class PopoverController implements ReactiveController { private _host; private _config; private _cleanupAutoUpdate?; private _cleanupSafePolygon?; private _handlers?; private _triggerElement?; private _currentPlacement; constructor(host: ReactiveControllerHost & HTMLElement, config: PopoverControllerConfig); hostConnected(): void; hostDisconnected(): void; private _markedTrigger; private _markedAttribute; /** * Write an ARIA attribute on the current trigger, remembering the element * written to and settling the previously marked one at `resting`. * * Remembering matters: re-resolving the trigger from `for` at teardown time * is wrong the moment `for` moved in between — the attribute gets stripped * from the new anchor, which never had it, and stays on the old one for good. */ markTrigger(attribute: string, value: string, resting: string | null): void; /** * Undo `markTrigger` on the element that actually received it. Pass a resting * value when the trigger keeps its relationship in a closed state * (`aria-expanded="false"`), or `null` to remove the attribute outright. */ releaseTrigger(value: string | null): void; private _settleMarked; get currentPlacement(): string; updatePosition(options: PositionOptions): Promise; startPositioning(options: PositionOptions): void; /** * Re-anchor an already-open floating element onto the current trigger. * * `startPositioning`'s `autoUpdate` observers are bound to whichever element * was the trigger when it ran, so they never see a retarget — the loop keeps * watching the old anchor while `updatePosition` resolves the new one. Tear * it down before recomputing, or the stale cleanup leaks and two loops drive * the same floating element. * * `isCurrent` is re-checked after the await, which is a real gap: * `computePosition` is async, so the host may have closed or retargeted again * meanwhile. Re-arming `autoUpdate` past that point would strand a loop on a * hidden element — the host's close path has already run its own * `stopPositioning` and will never run another. */ reposition(options: PositionOptions, isCurrent: () => boolean): Promise; stopPositioning(): void; handlePointerLeave(e: PointerEvent, onHide: (reason: 'exit' | 'settled') => void): void; cleanupSafePolygon(): void; addTriggerListeners(handlers: TriggerHandlers): void; removeTriggerListeners(triggerOverride?: HTMLElement | null): void; animateShow(el: HTMLElement, duration: number, keyframes?: Keyframe[]): Promise; animateHide(el: HTMLElement, duration: number, keyframes?: Keyframe[]): Promise; } export {}; //# sourceMappingURL=popover.d.ts.map