Corp/frontend/src/lib/dropdown-position.ts

94 lines
2.3 KiB
TypeScript

type ClampDropdownToViewportOptions = {
anchorLeft: number;
anchorWidth: number;
viewportWidth: number;
viewportPadding?: number;
};
export type ClampedDropdownHorizontalPosition = {
left: number;
width: number;
};
export function clampDropdownToViewport({
anchorLeft,
anchorWidth,
viewportWidth,
viewportPadding = 8,
}: ClampDropdownToViewportOptions): ClampedDropdownHorizontalPosition {
const availableWidth = Math.max(0, viewportWidth - viewportPadding * 2);
const width = Math.min(Math.max(0, anchorWidth), availableWidth);
const left = Math.min(
Math.max(viewportPadding, anchorLeft),
Math.max(viewportPadding, viewportWidth - width - viewportPadding),
);
return { left, width };
}
type FloatingMenuPositionOptions = {
anchorTop: number;
menuHeight: number;
menuLeft: number;
menuTop: number;
menuWidth: number;
viewportHeight: number;
viewportPadding?: number;
viewportWidth: number;
verticalGap?: number;
};
export type FloatingMenuPosition = {
maxHeight: number | null;
maxWidth: number;
translateX: number;
translateY: number;
};
export function constrainFloatingMenuToViewport({
anchorTop,
menuHeight,
menuLeft,
menuTop,
menuWidth,
viewportHeight,
viewportPadding = 12,
viewportWidth,
verticalGap = 8,
}: FloatingMenuPositionOptions): FloatingMenuPosition {
const maxWidth = Math.max(0, viewportWidth - viewportPadding * 2);
const renderedWidth = Math.min(menuWidth, maxWidth);
const { left } = clampDropdownToViewport({
anchorLeft: menuLeft,
anchorWidth: renderedWidth,
viewportPadding,
viewportWidth,
});
const translateX = left - menuLeft;
const viewportBottom = viewportHeight - viewportPadding;
if (menuTop + menuHeight <= viewportBottom) {
return { maxHeight: null, maxWidth, translateX, translateY: 0 };
}
const spaceBelow = Math.max(0, viewportBottom - menuTop);
const spaceAbove = Math.max(0, anchorTop - viewportPadding - verticalGap);
if (spaceAbove > spaceBelow) {
const maxHeight = Math.min(menuHeight, spaceAbove);
return {
maxHeight,
maxWidth,
translateX,
translateY: anchorTop - verticalGap - maxHeight - menuTop,
};
}
return {
maxHeight: Math.min(menuHeight, spaceBelow),
maxWidth,
translateX,
translateY: 0,
};
}