/** Minimal rect shape consumed by {@link computePosition}. */
export interface AnchorRect {
    top: number;
    left: number;
    right: number;
    bottom: number;
    width: number;
    height: number;
}
/**
 * Whether the floating element sits below or above the anchor.
 * @group Positioning
 */
export type LLSelectPlacement = 'below' | 'above';
/**
 * How the floating element decides its width. See `LLSelectBaseSettings`
 * (`popupWidthPolicy` field) for the user-facing contract.
 * @group Positioning
 */
export type LLSelectWidthPolicy = 'fit-content' | 'match-trigger';
/** Input to the pure positioning calculation. */
export interface PositionInput {
    anchorRect: AnchorRect;
    viewportWidth: number;
    viewportHeight: number;
    /**
     * Origin of the VISIBLE viewport in layout-viewport (client) coordinates.
     * `0` except under pinch zoom, where the visual viewport shrinks AND pans
     * (`visualViewport.offsetLeft/offsetTop`); anchor rects stay in client
     * coordinates, so clamping against `[0, viewportWidth]` alone would drag
     * the floating element toward the layout origin. Default `0`.
     */
    viewportLeft?: number;
    /** See {@link PositionInput.viewportLeft}. Default `0`. */
    viewportTop?: number;
    /** Measured height of the floating element. Pass 0 if unknown. */
    floatingHeight: number;
    /** Width policy. Optional; default `'fit-content'`. */
    widthPolicy?: LLSelectWidthPolicy;
    /**
     * Floating element's natural (max-content) width in px. Only consulted when
     * `widthPolicy === 'fit-content'`. Default `0`.
     */
    floatingNaturalWidth?: number;
    /**
     * Writing direction of the anchor's context. Only consulted when
     * `widthPolicy === 'fit-content'`: `'rtl'` right-aligns the floating element
     * to the anchor and grows LEFTWARD (the mirror of ltr). Default `'ltr'`.
     * `'match-trigger'` is position-identical in both directions.
     */
    direction?: 'ltr' | 'rtl';
    /**
     * Placement currently in effect, for stickiness across repositions of one
     * open cycle. When set and the content still fits on that side, it is kept
     * even if the other side would also fit - so a transient content shrink
     * (e.g. a filter query matching nothing) does not flip the popup back and
     * forth. Omit / `undefined` (first placement) to pick fresh.
     */
    currentPlacement?: LLSelectPlacement | undefined;
}
/** Result of {@link computePosition}: coordinates and chosen placement. */
export interface PositionResult {
    top: number;
    left: number;
    width: number;
    /** Maximum height the floating element may occupy. */
    maxHeight: number;
    placement: LLSelectPlacement;
}
/**
 * Compute where to place the floating element relative to the anchor.
 *
 * Vertical: prefers placing below; flips above when it does not fit below and
 * either fits above or has more room above. When neither side fits, picks the
 * side with more space and clamps `maxHeight` accordingly. A
 * `currentPlacement` that still fits is kept (stickiness) - re-preferring
 * "below" on every content change would make the popup jump sides whenever
 * the list shrinks and regrows.
 *
 * Horizontal: `widthPolicy === 'fit-content'` (default) returns
 * `width = max(anchor.width, floatingNaturalWidth)`, clamps to
 * `viewport - 2 * VIEWPORT_PADDING`, and keeps the popup inside the viewport
 * margins. Growth direction follows `direction`: ltr aligns left edges and
 * grows rightward; rtl aligns RIGHT edges and grows leftward (the mirror).
 *
 * The "viewport" here is the VISIBLE window in client coordinates:
 * `[viewportLeft, viewportLeft + viewportWidth]` x
 * `[viewportTop, viewportTop + viewportHeight]`. The offsets are 0 except
 * under pinch zoom (see {@link PositionInput.viewportLeft}).
 * `widthPolicy === 'match-trigger'` returns `width = anchor.width` and
 * `left = anchor.left` (no collision handling - popup is the same width as
 * trigger; direction-independent).
 */
export declare function computePosition(input: PositionInput): PositionResult;
/**
 * Whether `anchor` is currently hidden (scrolled out of the layout viewport or
 * clipped by a scrollable ancestor). Exposed so a caller can refuse to open a
 * popup against an off-screen trigger BEFORE building a positioner, rather than
 * opening and then hiding re-entrantly.
 */
export declare function isAnchorHidden(anchor: HTMLElement): boolean;
/** Controls the lifecycle of an active positioner. */
export interface Positioner {
    /** Force a re-position now. Normally called automatically. */
    reposition(): void;
    /**
     * Stop tracking and clear all inline styles + `data-placement` from the
     * floating element. Idempotent. Call once when the floating element is
     * dismissed.
     */
    detach(): void;
}
/** Options passed to {@link createPositioner}. */
export interface PositionerOptions {
    /**
     * Called when the anchor becomes invisible (fully outside the layout
     * viewport, or fully clipped by a scrollable ancestor). Typical use: close the
     * floating element so it does not hang in space without a visible trigger.
     */
    onHide?: () => void;
    /**
     * Width policy. Default `'fit-content'`, matching the `popupWidthPolicy`
     * setting default.
     */
    widthPolicy?: LLSelectWidthPolicy;
    /**
     * The floating element's inner scroll container (the popup list). Under an
     * active `maxHeight` clamp the floating element's overflow is absorbed as
     * this element's internal scrolling, so `offsetHeight` alone under-reports
     * the natural height; its `scrollHeight - clientHeight` restores the
     * difference WITHOUT lifting the clamp to re-measure (a lift-and-restore
     * would clamp this element's scrollTop to 0 mid-frame - losing the
     * scrolled-to-chosen position - and caused a visible window-scroll jolt on
     * Firefox). Omit when the floating element has no inner scroller.
     *
     * INVARIANT: this element must have no author-set height cap of its own -
     * the positioner owns the popup's `maxHeight`. The reconstruction adds back
     * ALL of its overflow, so an independent `max-height` on the inner list
     * (theme or consumer CSS) is read as extra natural height and can pick a
     * side as if the popup were taller than it can render. Shipped themes honor
     * this; consumer themes must clamp the popup, not the inner list.
     */
    innerScrollEl?: HTMLElement;
}
/**
 * Attach a positioner that keeps `floating` placed relative to `anchor`.
 *
 * Behavior: sets `floating` to `position: fixed`, listens to window scroll
 * (capture phase, so any ancestor scroll is caught), window resize, and
 * `ResizeObserver` on both elements. On every reposition: if the anchor is
 * outside the layout viewport or clipped by a scrollable ancestor and `onHide` is
 * provided, calls `onHide` and skips style updates. Otherwise applies the
 * coordinates from {@link computePosition} and sets `data-placement` on
 * `floating` for CSS hooks.
 *
 * Caller is responsible for calling `detach()` when the floating element is
 * dismissed; otherwise listeners leak.
 */
export declare function createPositioner(anchor: HTMLElement, floating: HTMLElement, options?: PositionerOptions): Positioner;
//# sourceMappingURL=positioning.d.ts.map