86 lines
2.8 KiB
JavaScript
86 lines
2.8 KiB
JavaScript
/* ObsiGate — generic themed context menu for the XLSX grid (#155).
|
|
*
|
|
* Builds a small menu anchored at a screen position, closes it on outside
|
|
* click / Escape / window blur. Labels and callbacks come from the caller, so
|
|
* no i18n lives here.
|
|
*/
|
|
let _menu = null;
|
|
let _openedAt = 0;
|
|
let _dismissBound = false;
|
|
|
|
export function closeContextMenu() {
|
|
if (_menu) {
|
|
_menu.remove();
|
|
_menu = null;
|
|
}
|
|
}
|
|
|
|
export function isContextMenuOpen() {
|
|
return _menu !== null;
|
|
}
|
|
|
|
function bindDismiss() {
|
|
if (_dismissBound) return;
|
|
_dismissBound = true;
|
|
document.addEventListener("click", (e) => {
|
|
if (!_menu) return;
|
|
// Ignore the very gesture that opened the menu (touch long-press can emit
|
|
// a click right after).
|
|
if (Date.now() - _openedAt < 250) return;
|
|
if (e.target && e.target.closest && e.target.closest(".xlsx-context-menu")) return;
|
|
closeContextMenu();
|
|
});
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape") closeContextMenu();
|
|
});
|
|
if (typeof window !== "undefined") window.addEventListener("blur", closeContextMenu);
|
|
}
|
|
|
|
/**
|
|
* @param {number} x - viewport X of the pointer
|
|
* @param {number} y - viewport Y of the pointer
|
|
* @param {Array<{label?: string, separator?: boolean, danger?: boolean, disabled?: boolean, onClick?: Function}>} items
|
|
* @returns {HTMLElement} the menu element
|
|
*/
|
|
export function openContextMenu(x, y, items) {
|
|
bindDismiss();
|
|
closeContextMenu();
|
|
const menu = document.createElement("div");
|
|
menu.className = "xlsx-context-menu";
|
|
menu.setAttribute("role", "menu");
|
|
for (const item of items) {
|
|
if (item.separator) {
|
|
const sep = document.createElement("div");
|
|
sep.className = "xlsx-context-sep";
|
|
menu.appendChild(sep);
|
|
continue;
|
|
}
|
|
const btn = document.createElement("button");
|
|
btn.type = "button";
|
|
btn.className = `btn-action xlsx-context-item${item.danger ? " xlsx-context-danger" : ""}`;
|
|
btn.setAttribute("role", "menuitem");
|
|
btn.textContent = item.label;
|
|
btn.disabled = Boolean(item.disabled);
|
|
btn.addEventListener("click", () => {
|
|
closeContextMenu();
|
|
if (!item.disabled && item.onClick) item.onClick();
|
|
});
|
|
menu.appendChild(btn);
|
|
}
|
|
document.body.appendChild(menu);
|
|
|
|
// Keep the menu inside the viewport (getBoundingClientRect is 0 in JSDOM,
|
|
// which simply leaves the requested position).
|
|
const rect = menu.getBoundingClientRect();
|
|
const vw = (typeof window !== "undefined" && window.innerWidth) || 1024;
|
|
const vh = (typeof window !== "undefined" && window.innerHeight) || 768;
|
|
const left = Math.max(4, Math.min(x, vw - rect.width - 4));
|
|
const top = Math.max(4, Math.min(y, vh - rect.height - 4));
|
|
menu.style.left = `${left}px`;
|
|
menu.style.top = `${top}px`;
|
|
|
|
_menu = menu;
|
|
_openedAt = Date.now();
|
|
return menu;
|
|
}
|