66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
/**
|
|
* Menu position calculation utility
|
|
* Calculates optimal position for dropdown menus to avoid viewport clipping
|
|
*/
|
|
|
|
export interface MenuPosition {
|
|
x: number;
|
|
y: number;
|
|
placement: 'bottom' | 'top';
|
|
}
|
|
|
|
/**
|
|
* Calculate the optimal position for a menu dropdown
|
|
* @param triggerElement - The button/element that triggers the menu
|
|
* @param menuWidth - Estimated or actual menu width (default: 160px)
|
|
* @param menuHeight - Estimated or actual menu height (default: 120px)
|
|
* @returns Position object with x, y coordinates and placement direction
|
|
*/
|
|
export function calculateMenuPosition(
|
|
triggerElement: HTMLElement,
|
|
menuWidth: number = 160,
|
|
menuHeight: number = 120
|
|
): MenuPosition {
|
|
const rect = triggerElement.getBoundingClientRect();
|
|
const viewportHeight = window.innerHeight;
|
|
const viewportWidth = window.innerWidth;
|
|
|
|
// Determine vertical placement (below or above trigger)
|
|
const spaceBelow = viewportHeight - rect.bottom;
|
|
const spaceAbove = rect.top;
|
|
const fitsBelow = spaceBelow >= menuHeight + 8; // 8px margin
|
|
const fitsAbove = spaceAbove >= menuHeight + 8;
|
|
|
|
let y: number;
|
|
let placement: 'bottom' | 'top';
|
|
|
|
if (fitsBelow) {
|
|
// Prefer below if there's space
|
|
y = rect.bottom + 4; // 4px gap
|
|
placement = 'bottom';
|
|
} else if (fitsAbove) {
|
|
// Show above if no space below
|
|
y = rect.top - menuHeight - 4; // 4px gap
|
|
placement = 'top';
|
|
} else {
|
|
// Not enough space either way - prefer below and let it extend
|
|
y = rect.bottom + 4;
|
|
placement = 'bottom';
|
|
}
|
|
|
|
// Horizontal positioning - align right edge of menu with right edge of button
|
|
let x = rect.right - menuWidth;
|
|
|
|
// Ensure menu doesn't overflow left edge of viewport
|
|
if (x < 8) {
|
|
x = 8; // 8px margin from left edge
|
|
}
|
|
|
|
// Ensure menu doesn't overflow right edge of viewport
|
|
if (x + menuWidth > viewportWidth - 8) {
|
|
x = viewportWidth - menuWidth - 8; // 8px margin from right edge
|
|
}
|
|
|
|
return { x, y, placement };
|
|
}
|