68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
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 };
|
|
}
|
|
|