// retoor export class ModalManager { constructor() { this.stack = []; document.addEventListener("click", (event) => this.onClick(event)); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && this.stack.length) { this.close(this.stack[this.stack.length - 1]); } }); window.addEventListener("popstate", () => { if (this.stack.length) { this.close(this.stack[this.stack.length - 1], false); } }); new MutationObserver(() => this.wire()).observe(document.body, { childList: true, subtree: true }); this.wire(); } wire() { document.querySelectorAll(".modal-overlay:not([data-wired])").forEach((overlay) => { overlay.dataset.wired = "1"; overlay.setAttribute("role", overlay.getAttribute("role") || "dialog"); overlay.setAttribute("aria-modal", "true"); overlay.addEventListener("keydown", (event) => this.trap(event, overlay)); }); } onClick(event) { const trigger = event.target.closest("[data-modal]"); if (trigger) { event.preventDefault(); this.open(document.getElementById(trigger.dataset.modal)); return; } if (event.target.closest(".modal-close")) { this.close(event.target.closest(".modal-overlay")); return; } if (event.target.classList?.contains("modal-overlay")) { this.close(event.target); } } open(overlay) { if (!overlay) { return; } overlay.classList.add("visible"); this.stack.push(overlay); history.pushState({ modal: overlay.id }, ""); overlay.querySelector("button, a, input, select, textarea")?.focus(); } close(overlay, popHistory = true) { if (!overlay) { return; } overlay.classList.remove("visible"); this.stack = this.stack.filter((item) => item !== overlay); if (popHistory && history.state?.modal === overlay.id) { history.back(); } } trap(event, overlay) { if (event.key !== "Tab") { return; } const focusable = [...overlay.querySelectorAll("button, a[href], input, select, textarea")].filter((el) => !el.disabled); if (!focusable.length) { return; } const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } }