94 lines
2.3 KiB
TypeScript
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,
|
|
};
|
|
}
|