export type FloatingPlacement = "bottom-start" | "bottom-end" | "top-start" | "top-end"; export interface RectLike { top: number; right: number; bottom: number; left: number; width: number; height: number; } export interface FloatingPositionOptions { anchor: RectLike; surfaceWidth: number; surfaceHeight: number; placement?: FloatingPlacement; offset?: number; viewportWidth?: number; viewportHeight?: number; viewportPadding?: number; } export interface FloatingPosition { top: number; left: number; maxHeight: number; resolvedPlacement: FloatingPlacement; } export function computeFloatingPosition({ anchor, surfaceWidth, surfaceHeight, placement = "bottom-start", offset = 8, viewportWidth = typeof window === "undefined" ? 1920 : window.innerWidth, viewportHeight = typeof window === "undefined" ? 1080 : window.innerHeight, viewportPadding = 8, }: FloatingPositionOptions): FloatingPosition { const roomBelow = viewportHeight - anchor.bottom - offset - viewportPadding; const roomAbove = anchor.top - offset - viewportPadding; const requestedTop = placement.startsWith("top"); const shouldFlip = requestedTop ? surfaceHeight > roomAbove && roomBelow > roomAbove : surfaceHeight > roomBelow && roomAbove > roomBelow; const resolvedPlacement = shouldFlip ? (`${requestedTop ? "bottom" : "top"}-${placement.endsWith("end") ? "end" : "start"}` as FloatingPlacement) : placement; const alignedLeft = resolvedPlacement.endsWith("end") ? anchor.right - surfaceWidth : anchor.left; const left = Math.min( Math.max(viewportPadding, alignedLeft), Math.max(viewportPadding, viewportWidth - surfaceWidth - viewportPadding), ); const availableHeight = resolvedPlacement.startsWith("top") ? roomAbove : roomBelow; const maxHeight = Math.max(96, availableHeight); const visibleHeight = Math.min(surfaceHeight, maxHeight); const top = resolvedPlacement.startsWith("top") ? Math.max(viewportPadding, anchor.top - offset - visibleHeight) : Math.min(anchor.bottom + offset, viewportHeight - visibleHeight - viewportPadding); return { top, left, maxHeight, resolvedPlacement }; }