// Global confirm-dialog replacement (issue #5): ONE styled modal // answers every hx-confirm sitewide (via the htmx:confirm event) and // exposes a promise API for the legacy JS call sites. No more native // window.confirm anywhere. let activeResolve: ((ok: boolean) => void) | null = null; function modalElements(): { overlay: HTMLElement | null; message: HTMLElement | null } { return { overlay: document.getElementById("confirm-modal"), message: document.getElementById("confirm-modal-message"), }; } function hide(ok: boolean): void { const { overlay } = modalElements(); if (overlay) overlay.classList.add("hidden"); const resolve = activeResolve; activeResolve = null; if (resolve) resolve(ok); } /** * confirmDialog shows the global modal and resolves with the answer. * Safe to call from any module (import this instead of window.confirm). */ export function confirmDialog(message: string): Promise { const { overlay, message: messageEl } = modalElements(); if (!overlay || !messageEl) { // Header missing (bare layouts): degrade to the native dialog // rather than silently swallowing the confirmation. return Promise.resolve(window.confirm(message)); } messageEl.textContent = message; overlay.classList.remove("hidden"); return new Promise((resolve) => { activeResolve = resolve; }); } // Wire the static buttons once. function wireButtons(): void { const { overlay } = modalElements(); const accept = document.getElementById("confirm-modal-accept"); const dismiss = document.getElementById("confirm-modal-cancel"); if (accept && !accept.hasAttribute("data-wired")) { accept.setAttribute("data-wired", "1"); accept.addEventListener("click", () => hide(true)); } if (dismiss && !dismiss.hasAttribute("data-wired")) { dismiss.setAttribute("data-wired", "1"); dismiss.addEventListener("click", () => hide(false)); } // Backdrop click cancels. if (overlay && !overlay.hasAttribute("data-wired")) { overlay.setAttribute("data-wired", "1"); overlay.addEventListener("click", (e) => { if (e.target === overlay) hide(false); }); } } // htmx:confirm interceptor: every element with hx-confirm gets the // modal instead of the browser dialog; confirming issues the request. // (Canonical htmx pattern — see htmx docs, "confirming with a modal".) document.body.addEventListener("htmx:confirm", function (evt) { const custom = evt as CustomEvent<{ element: HTMLElement; issueRequest: (override?: boolean) => void; }>; const elt = custom.detail.element; if (!elt) return; const question = elt.getAttribute("hx-confirm"); if (question === null) return; evt.preventDefault(); wireButtons(); confirmDialog(question).then((ok) => { if (ok) custom.detail.issueRequest(true); }); });