import { CSSProperties, RefObject } from 'react'; type Side = "top" | "bottom" | "left" | "right"; type Alignment = "start" | "end"; type Placement = Side | `${Side}-${Alignment}` | "auto" | "center"; type BoundaryEdge = "top" | "right" | "bottom" | "left"; interface OffsetXY { x?: number; y?: number; } interface AvoidElement { /** Ref to the DOM element to avoid overlapping. */ ref: RefObject; /** * Which side of the safe zone this element eats from. * 'left' = "my right edge becomes the new left boundary." * 'top' = "my bottom edge becomes the new top boundary." */ edge: BoundaryEdge; } interface FloatingPositionConfig { triggerRef: RefObject; floatingRef: RefObject; placement?: Placement; /** * Gap en px entre trigger y floating (forma numérica), o traslación en ejes de * viewport (forma objeto). Con `{ x, y }`, el desplazamiento se aplica en * coordenadas de pantalla antes del clamp — puede absorberse cerca de los bordes. */ offset?: number | OffsetXY; matchTriggerWidth?: boolean; viewportMargin?: number; attachToParent?: boolean; enabled?: boolean; closeOnScroll?: boolean; onClose?: () => void; /** * Si es true, el guard de "no ocultar/cerrar si el floating tiene foco * adentro" se ignora — el out-of-viewport siempre oculta y dispara onClose. * Pensado para consumidores donde el foco adentro es automático (autofocus * al abrir), no una señal de interacción real (p. ej. Menu/Select). */ ignoreFocusGuard?: boolean; /** Increment to force reposition (e.g. when trigger moves without a prop change) */ repositionKey?: number; /** * DOM elements the floating element must not overlap. * * Each entry declares a ref and which side of the viewport it "eats" space from. * The floating element will flip, shift, and clamp to stay in the remaining safe zone. * * @example * avoidElements={[ * { ref: sidebarRef, edge: 'left' }, * { ref: toolbarRef, edge: 'top' }, * ]} * * Pass a stable array reference (useMemo or module-level const) to avoid recalculations. */ avoidElements?: AvoidElement[]; /** * Optional element used exclusively for getBoundingClientRect when positioning * the floating element. When provided, it overrides `triggerRef` for measurement * only — `triggerRef` still handles click-outside, focus, and scroll detection. * * Use this when the visual anchor is a sub-element of the trigger (e.g. an input * field inside a wrapper that also renders helper text below it). */ anchorRef?: RefObject; /** * Cuando es `true`, el scroll no cierra por out-of-viewport ni por `closeOnScroll`. * Usarlo durante scrolls programáticos (p. ej. destapar el trigger). */ suppressCloseOnScrollRef?: RefObject; } interface FloatingPositionResult { floatingStyles: CSSProperties; resolvedPlacement: Placement; isPositioned: boolean; /** * false mientras el rect de referencia está completamente fuera del layout * viewport (no del visualViewport/teclado) y el update vino de un scroll. */ isAnchorVisible: boolean; } declare function useFloatingPosition({ triggerRef, floatingRef, placement, offset, matchTriggerWidth, viewportMargin, attachToParent, enabled, closeOnScroll, onClose, ignoreFocusGuard, repositionKey, avoidElements, anchorRef, suppressCloseOnScrollRef, }: FloatingPositionConfig): FloatingPositionResult; export { useFloatingPosition }; export type { AvoidElement, BoundaryEdge, FloatingPositionConfig, FloatingPositionResult, OffsetXY, Placement, Side, Alignment, };