Files
xk-admin/apps/web-antd/src/components/context-menu/use-context-menu.ts
李琦 69862cd982
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
1. Excel交互组件封装
2026-06-25 16:09:07 +08:00

117 lines
2.7 KiB
TypeScript

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<ContextMenuState>({
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 };