zcbot/web/static/js/dom.js

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