/* const ctx = new ContextMenu(); ctx.setItems([ { label: "Öffnen", disable: true, onClick: () => alert("Öffnen!") }, { label: "Öffnen", onClick: () => alert("Öffnen!") }, { label: "Bearbeiten", onClick: () => alert("Bearbeiten…") }, { type: "divider" }, { label: "Mehr Optionen", color: 'rgb()|color-name|color-value', children: [ { label: "Duplizieren", onClick: () => alert("Dupliziert!") }, { label: "Umbenennen", onClick: () => alert("Rename…") }, { label: "Exportieren", children: [ { label: "PDF", onClick: () => alert("PDF export") }, { label: "CSV", onClick: () => alert("CSV export") }, ] } ] }, { label: "Löschen", onClick: () => alert("Gelöscht!") }, ]); ctx.show(element, or location, { position: "right" }); */ class ContextMenu { constructor() { this.menu = document.createElement("div"); this.menu.className = "ctx-menu hidden"; document.body.appendChild(this.menu); // close on click outside → sofort document.addEventListener("click", (e) => { if (!this.menu.contains(e.target)) { this.closeAllSubmenus(); this.hide(); } }); window.addEventListener("resize", () => this.hide()); window.addEventListener("scroll", () => this.hide()); } setItems(items) { this.menu.innerHTML = ""; this.menu.appendChild(this.buildMenu(items)); } buildMenu(items) { const ul = document.createElement("ul"); ul.className = "ctx-list"; items.forEach(item => { if (item.type === "divider") { const divider = document.createElement("li"); divider.className = "ctx-divider"; ul.appendChild(divider); return; } const li = document.createElement("li"); li.className = "ctx-item"; if (item.color) li.style.boxShadow = `inset 5px 0px 0px 0px ${item.color}`; if (item.disabled) li.classList.add("ctx-disabled"); li.innerHTML = ` ${item.label} ${item.children ? "" : ""} `; if (item.onClick && !item.children && !item.disabled) { li.addEventListener("click", e => { e.stopPropagation(); item.onClick(e); this.hide(); // Hauptmenü sofort schließen }); } if (item.children) { const submenu = this.buildMenu(item.children); submenu.classList.add("ctx-submenu"); li.appendChild(submenu); let hideTimeout; const openSubmenu = () => { clearTimeout(hideTimeout); submenu.classList.add("open"); submenu.style.left = ""; submenu.style.top = ""; const liRect = li.getBoundingClientRect(); const submenuRect = submenu.getBoundingClientRect(); let left = li.offsetWidth; if (liRect.right + submenuRect.width > window.innerWidth) left = -submenuRect.width; submenu.style.left = left + "px"; let top = 0; if (liRect.top + submenuRect.height > window.innerHeight) { top = window.innerHeight - (liRect.top + submenuRect.height) - 4; } submenu.style.top = top + "px"; }; const closeSubmenu = (delay = 500) => { clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { submenu.classList.remove("open"); submenu.style.left = ""; submenu.style.top = ""; }, delay); }; li.addEventListener("mouseenter", openSubmenu); li.addEventListener("mouseleave", () => closeSubmenu(500)); submenu.addEventListener("mouseenter", () => clearTimeout(hideTimeout)); submenu.addEventListener("mouseleave", () => closeSubmenu(500)); } ul.appendChild(li); }); return ul; } closeAllSubmenus() { this.menu.querySelectorAll(".ctx-submenu.open").forEach(sm => { sm.classList.remove("open"); sm.style.left = ""; sm.style.top = ""; }); } show(target, options = {}) { // this.hide(); this.closeAllSubmenus(); let x, y; const position = options.position || "right"; // default const offset = options.offset || 4; // 🖱️ Mausposition if (typeof target === "number") { x = target; y = options.y; } // 📦 Element als Anchor else if (target instanceof HTMLElement) { const rect = target.getBoundingClientRect(); switch (position) { case "right": x = rect.right + offset; y = rect.top; break; case "left": x = rect.left - this.menu.offsetWidth - offset; y = rect.top; break; case "top": x = rect.left; y = rect.top - this.menu.offsetHeight - offset; break; case "bottom": default: x = rect.left; y = rect.bottom + offset; break; } } // ❌ invalid fallback else { return; } // 👉 erstmal anzeigen (wichtig für width/height!) this.menu.style.left = "0px"; this.menu.style.top = "0px"; this.menu.classList.add("show"); this.menu.classList.remove("hidden"); const rect = this.menu.getBoundingClientRect(); // 🧠 Reposition nach echten Maßen if (target instanceof HTMLElement) { const anchor = target.getBoundingClientRect(); switch (position) { case "right": x = anchor.right + offset; y = anchor.top; break; case "left": x = anchor.left - rect.width - offset; y = anchor.top; break; case "top": x = anchor.left; y = anchor.top - rect.height - offset; break; case "bottom": x = anchor.left; y = anchor.bottom + offset; break; } } // 🧱 Screen Bounds if (x + rect.width > window.innerWidth) { x = window.innerWidth - rect.width - 4; } if (y + rect.height > window.innerHeight) { y = window.innerHeight - rect.height - 4; } if (x < 0) x = 4; if (y < 0) y = 4; this.menu.style.left = x + "px"; this.menu.style.top = y + "px"; } hide() { this.menu.classList.remove("show"); setTimeout(() => this.menu.classList.add("hidden"), 200); } }