import { type Placement, type PlacementAlign, type PlacementSide } from './placement';
/** Everything a positioning pass worked out, handed to the hooks. */
export interface FloatingPosition {
    /** Viewport coordinates for the panel's top-left corner. */
    top: number;
    left: number;
    /** The side actually used, after any flip. */
    side: PlacementSide;
    /** The cross-axis alignment actually used. */
    align: PlacementAlign;
    /** The anchor's box, in viewport coordinates. */
    anchorRect: DOMRect;
    /** The panel's box, measured before this pass moved it. */
    panelRect: DOMRect;
    /** The custom positioning container, when `containerId` names one. */
    container: HTMLElement | null;
}
export interface FloatingOptions {
    /** The element that owns the panel: holds `open`, and receives the events. */
    host: HTMLElement;
    /** The panel. Read every time, because components build theirs lazily. */
    panel: () => HTMLElement | null | undefined;
    /** What the panel is positioned against. Defaults to the host. */
    anchor?: () => HTMLElement;
    /** Preferred side, optionally with an alignment suffix (`bottom-end`). */
    placement: () => Placement;
    /** Gap between anchor and panel, in px. */
    offset?: number;
    /**
     * `id` of an element to position within instead of the viewport. Flip and
     * shift do not apply there (they reason in viewport coordinates); alignment
     * does, through the same `alignCrossAxis` the viewport path uses.
     */
    containerId?: () => string;
    /**
     * Called before the panel is measured, for a component that sizes its panel
     * from the anchor -- r-select matches the trigger's width, and the width has
     * to be in place before the height that the flip decision needs is real.
     */
    beforeMeasure?: (anchorRect: DOMRect, panel: HTMLElement) => void;
    /**
     * The panel's size, when its own box does not report it. r-select pins the
     * panel host's width to the trigger's, but a consumer can make the panel
     * *inside* that host wider (`::part(dropdown) { min-width }`, so a long option
     * is not clipped by a deliberately narrow trigger -- r-player's quality menu
     * does exactly this). The extra width simply overflows the host, invisible to
     * a measurement taken on it, and both the alignment and the flip decision
     * would then be computed against a width nobody sees.
     */
    measurePanel?: (panel: HTMLElement) => {
        width: number;
        height: number;
    };
    /**
     * Last word on the coordinates. Return a replacement to override the computed
     * position; return nothing to accept it.
     */
    adjust?: (position: FloatingPosition, panel: HTMLElement) => {
        top: number;
        left: number;
    } | void;
    /** Called once the coordinates are written, for arrow nudges and the like. */
    afterPosition?: (position: FloatingPosition, panel: HTMLElement) => void;
}
export declare class FloatingController {
    private options;
    private repositionBound;
    /**
     * Whether the panel is meant to be showing.
     *
     * Held here rather than read back from `panel.style.display`, for the same
     * reason the components stopped reading it: during the exit animation the
     * panel is still `block` while the intent is already closed, so a re-open
     * arriving in that window would look like a no-op and be swallowed. That is
     * the bug this whole controller exists to make impossible; reintroducing it
     * one layer down would be a poor joke.
     */
    private opened;
    /** Pending reposition frame, so a scroll burst coalesces into one write. */
    private repositionFrame;
    /**
     * Bumped on every transition. An async tail (waiting on animations) checks it
     * before touching the DOM, so a panel that has since been re-opened is never
     * hidden by the exit that was already in flight when it re-opened. A timeout
     * would only have guessed at the same thing.
     */
    private generation;
    constructor(options: FloatingOptions);
    /** The element the panel is positioned against. */
    private get anchor();
    /**
     * Drive the panel to `open`.
     *
     * The only writer of the panel's display, its transit class, the reposition
     * listeners and the host's `aria-expanded` -- which is why those four cannot
     * disagree with each other or with the state.
     */
    apply: (open: boolean) => void;
    /** The side of the requested placement, without its alignment suffix. */
    private side;
    /**
     * Place the panel next to the anchor.
     *
     * Deferred to the next frame so a `display: block` set this tick, and any
     * content added with it, are part of the measurement -- the flip decision
     * needs the panel's real laid-out height, which is also why the entrance
     * animation is chosen here (from the side actually used) rather than by the
     * caller (from the side merely asked for).
     */
    position: (applyEntranceTransit?: boolean) => void;
    private write;
    /**
     * The panel is portalled and positioned once on open, so it comes adrift when
     * the page or any scroll container moves under it -- a select in a sticky
     * header, a popover in a scrolling pane. Capture-phase scroll catches nested
     * scrollers, which do not bubble.
     */
    private reposition;
    private attachReposition;
    private detachReposition;
    /** Drop the listeners. Call from `disconnectedCallback`. */
    destroy: () => void;
}
