Files
jellytau/src/lib/utils/menuPosition.ts
T
2026-01-26 22:21:54 +01:00

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