import { reactive } from 'vue'; import type { ContextMenuItem, ContextMenuState } from './types'; const MENU_WIDTH = 180; const MENU_ITEM_HEIGHT = 36; const MENU_DIVIDER_HEIGHT = 9; const SUBMENU_GAP = 4; let listenersBound = false; export const contextMenuState = reactive({ visible: false, x: 0, y: 0, items: [], payload: null, }); export const submenuState = reactive({ activeKey: null as string | null, items: [] as ContextMenuItem[], left: 0, top: 0, }); function bindGlobalListeners() { if (listenersBound) { return; } listenersBound = true; const hide = () => hideContextMenu(); document.addEventListener('click', hide, true); document.addEventListener('scroll', hide, true); window.addEventListener('resize', hide); } export function resetSubmenu() { submenuState.activeKey = null; submenuState.items = []; submenuState.left = 0; submenuState.top = 0; } export function showSubmenu(key: string, items: ContextMenuItem[], left: number, top: number) { submenuState.activeKey = key; submenuState.items = items; submenuState.left = left; submenuState.top = top; } export function showContextMenu( e: MouseEvent, items: ContextMenuItem[], payload?: any, ) { e.preventDefault(); hideContextMenu(); const estimatedHeight = estimateMenuHeight(items); let x = e.clientX; let y = e.clientY; if (x + MENU_WIDTH > window.innerWidth) { x = Math.max(8, window.innerWidth - MENU_WIDTH - 8); } if (y + estimatedHeight > window.innerHeight) { y = Math.max(8, window.innerHeight - estimatedHeight - 8); } contextMenuState.x = x; contextMenuState.y = y; contextMenuState.items = items; contextMenuState.payload = payload ?? null; contextMenuState.visible = true; bindGlobalListeners(); } export function hideContextMenu() { contextMenuState.visible = false; contextMenuState.items = []; contextMenuState.payload = null; resetSubmenu(); } export function estimateMenuHeight(items: ContextMenuItem[]): number { let height = 8; for (const item of items) { height += item.type === 'divider' ? MENU_DIVIDER_HEIGHT : MENU_ITEM_HEIGHT; } return height; } export function getSubmenuPosition( parentRect: DOMRect, childCount: number, ): { left: number; top: number } { const submenuHeight = childCount * MENU_ITEM_HEIGHT + 8; let left = parentRect.right + SUBMENU_GAP; let top = parentRect.top; if (left + MENU_WIDTH > window.innerWidth) { left = parentRect.left - MENU_WIDTH - SUBMENU_GAP; } if (top + submenuHeight > window.innerHeight) { top = Math.max(8, window.innerHeight - submenuHeight - 8); } return { left, top }; } export { MENU_WIDTH, MENU_ITEM_HEIGHT };