52 lines
1.9 KiB
JavaScript
52 lines
1.9 KiB
JavaScript
// DOM 小工具 + 单例浮层菜单。
|
|
import { escapeHtml } from "./format.js";
|
|
|
|
export const $ = (id) => document.getElementById(id);
|
|
|
|
// ───── floating dropdown menu (single instance) ─────
|
|
// 用 position: fixed 单例避免被 pane overflow 裁剪;按位置算出右上角对齐
|
|
let _menuItems = null;
|
|
export function showMenu(triggerEl, items) {
|
|
_menuItems = items;
|
|
const menu = $("floating-menu");
|
|
menu.innerHTML = items.map((it) => {
|
|
const cls = "dd-item " + (it.cls || "");
|
|
const dis = it.disabled ? " disabled" : "";
|
|
return `<button class="${cls}" data-act="${escapeHtml(it.act)}"${dis}>${escapeHtml(it.label)}</button>`;
|
|
}).join("");
|
|
menu.querySelectorAll(".dd-item").forEach((btn) => {
|
|
btn.onclick = (e) => {
|
|
e.stopPropagation();
|
|
const act = btn.dataset.act;
|
|
const item = _menuItems && _menuItems.find((i) => i.act === act);
|
|
hideMenu();
|
|
if (item && item.onclick) item.onclick();
|
|
};
|
|
});
|
|
// 默认右下展开;若空间不足则改向上
|
|
const rect = triggerEl.getBoundingClientRect();
|
|
menu.style.visibility = "hidden";
|
|
menu.classList.add("show");
|
|
const mh = menu.offsetHeight || 120;
|
|
menu.style.right = Math.max(4, window.innerWidth - rect.right) + "px";
|
|
menu.style.left = "auto";
|
|
if (rect.bottom + mh + 8 > window.innerHeight) {
|
|
menu.style.top = Math.max(4, rect.top - mh - 4) + "px";
|
|
} else {
|
|
menu.style.top = (rect.bottom + 4) + "px";
|
|
}
|
|
menu.style.visibility = "";
|
|
}
|
|
export function hideMenu() {
|
|
_menuItems = null;
|
|
$("floating-menu").classList.remove("show");
|
|
}
|
|
document.addEventListener("click", (e) => {
|
|
if (e.target.closest(".dd-toggle")) return;
|
|
if (e.target.closest("#floating-menu")) return;
|
|
hideMenu();
|
|
}, true);
|
|
window.addEventListener("resize", hideMenu);
|
|
// 滚动 pane 时菜单位置失效,直接关
|
|
document.addEventListener("scroll", hideMenu, true);
|