import { type AnchoredPosition, type AnchoredPositionInput } from './anchored-position'; /** * How the scroll/resize path reschedules. Each consumer family measured * differently before consolidation, and the differences are preserved verbatim * so placement timing is unchanged. * * - `sync` — measure inside the listener. Selects. * - `frame` — coalesce and measure on the next frame. Tooltips. * - `double-frame` — coalesce, measure next frame, then measure once more the * frame after. Scroll-driven owners such as ShellPage commit a compact-header * render in the frame after the scroll event, so a single frame reads a stale * anchor rect. Menus. */ export type AnchoredLiveUpdateMode = 'sync' | 'frame' | 'double-frame'; export interface AnchoredPositionControllerOptions { /** Resolve the anchor at measure time; positioning retries while this is null. */ getAnchor: () => HTMLElement | null; /** Resolve the popup at measure time; positioning retries while this is null. */ getPopup: () => HTMLElement | null; /** * Build the layout input for one measurement pass. * * The owning component keeps its own anchor semantics here — inner-cell align * offsets, minimum widths, and token resolution stay with the component rather * than moving into this controller. Return `null` when the component's own * guards say the popup is not measurable yet, which schedules a retry. */ measure: (anchor: HTMLElement, popup: HTMLElement) => AnchoredPositionInput | null; /** Commit a changed position. Not called when the result is unchanged. */ apply: (position: AnchoredPosition) => void; /** * Move a `popover="manual"` popup into the native top layer before measuring. * * Use this for popups that remain in their owner's DOM tree. The top layer * gives `position: fixed` viewport coordinates even when an application * ancestor establishes a fixed-position containing block through containment, * transforms, or filters. */ topLayer?: boolean; /** Fired once per `start()` when the first measurement succeeds. */ onReady?: () => void; /** Scroll/resize behavior. Defaults to `sync`. */ liveUpdate?: AnchoredLiveUpdateMode; /** * Re-measure when the popup or anchor resizes. Tooltips need this because the * label's `ds-text` upgrade changes the measured width after mount. */ observeResize?: boolean; retryBudget?: number; } /** * Lifecycle owner for an element-anchored popup: listener binding, measurement * retries, frame coalescing, and teardown. * * Pairs with `computeAnchoredPosition`, which owns the geometry. Split this way * because the geometry is pure and exhaustively testable while the lifecycle is * timing-dependent; keeping them separate means the risky half lives in exactly * one place. * * Not suitable for overlays that place several popups in one shared pass — Toast * positions N anchored records inside a single layout commit and keeps its own * scheduler, using only the geometry function. */ export declare class AnchoredPositionController { private readonly options; private scrollResizeHandler; private resizeObserver; private retryRaf; private liveRaf; private last; private isReady; constructor(options: AnchoredPositionControllerOptions); /** `true` once a measurement has succeeded, for gating popup reveal. */ get ready(): boolean; /** * Bind scroll/resize tracking for an opening popup. * * Deliberately does not schedule placement: every consumer binds listeners and * then schedules in its own order, and folding both into one call produced two * overlapping retry loops. Call `schedule()` or `update()` separately. */ observe(): void; /** Unbind tracking, cancel pending frames, and clear the cached position. */ unobserve(): void; /** * Retry measurement each frame until it succeeds or the budget runs out, so a * popup is never revealed at 0,0 after a failed first pass. */ schedule(onReady?: () => void): void; /** Measure and commit once. @returns `false` when the popup is not measurable. */ update(): boolean; /** Reposition an already-open popup without hiding it. */ scheduleLiveUpdate(): void; /** Cancel pending retry and live frames without unbinding listeners. */ cancel(): void; private cancelRetry; private bindListeners; private unbindListeners; /** * Re-attach the resize observer after the popup remounts. Safe to call * repeatedly; `ResizeObserver.observe` ignores duplicate targets. */ observeResizeTargets(): void; } //# sourceMappingURL=anchored-position-controller.d.ts.map