NODEDC_DESIGN_GUIDELINE/packages/ui-core/src/floating.ts

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 };
}