feat(modals): close, popstate, dismiss, Esc, backdrop, response-error
Centralizes state cleanup on the dialog's native 'close' event: every close source (Esc cancel event, backdrop click, [data-modal-dismiss], browser back, htmx:responseError on the modal fetch, or programmatic closeModal()) just calls dialog.close() and the single 'close' listener clears ?modal= from the URL and resets currentModalPath. This avoids the trap where legacy modal.js's backdrop close didn't sync our URL, and the trap where a 4xx response opened an empty modal. window.closeModal exposed for callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3de68b7539
commit
6e66375233
1 changed files with 70 additions and 0 deletions
|
|
@ -34,6 +34,55 @@
|
|||
});
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
const dlg = document.getElementById("modal-container");
|
||||
if (dlg && dlg.open) dlg.close();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const dlg = document.getElementById("modal-container");
|
||||
if (!dlg) return;
|
||||
|
||||
// Single source of truth: every close path funnels through the dialog's
|
||||
// native 'close' event, so URL/state cleanup runs exactly once no matter
|
||||
// who triggered the close (Esc, backdrop, dismiss button, response error,
|
||||
// or the legacy modal.js backdrop handler).
|
||||
dlg.addEventListener("close", () => {
|
||||
currentModalPath = null;
|
||||
const url = new URL(window.location.href);
|
||||
if (url.searchParams.has("modal")) {
|
||||
url.searchParams.delete("modal");
|
||||
history.pushState({}, "", url.toString());
|
||||
}
|
||||
});
|
||||
|
||||
// Esc key fires 'cancel' on a <dialog>; preventDefault so we control the
|
||||
// close path (the default action would close() too, but we want one path).
|
||||
dlg.addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
dlg.close();
|
||||
});
|
||||
|
||||
// Backdrop click on our own slot. The legacy modal.js handler also
|
||||
// matches dialog.modal and closes; this listener is harmless when the
|
||||
// legacy one wins because dialog.close() is idempotent.
|
||||
dlg.addEventListener("click", (event) => {
|
||||
if (event.target === dlg) dlg.close();
|
||||
});
|
||||
});
|
||||
|
||||
// Browser back/forward: re-evaluate URL state and either fetch+show a new
|
||||
// modal or close the current one. Closing routes through dialog.close() so
|
||||
// the close-event cleanup fires (no separate state update here).
|
||||
window.addEventListener("popstate", () => {
|
||||
const path = new URL(window.location.href).searchParams.get("modal");
|
||||
if (path) {
|
||||
fetchAndShow(path);
|
||||
} else {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const link = event.target.closest("a[data-modal]");
|
||||
if (!link) return;
|
||||
|
|
@ -45,7 +94,28 @@
|
|||
openModal(href);
|
||||
});
|
||||
|
||||
// Dismiss triggers inside modal content (e.g. the editor's × and Cancel
|
||||
// buttons render with data-modal-dismiss). Document-level delegation so
|
||||
// HTMX-swapped content gets the behavior without re-binding.
|
||||
document.addEventListener("click", (event) => {
|
||||
if (event.target.closest("[data-modal-dismiss]")) {
|
||||
event.preventDefault();
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// HTMX response error (4xx/5xx from the modal fetch): close the modal so
|
||||
// the user isn't left with an error fragment open and a stale ?modal= URL.
|
||||
document.body.addEventListener("htmx:responseError", (event) => {
|
||||
const target = event.detail && event.detail.target;
|
||||
if (target && target.id === "modal-content") {
|
||||
console.warn("[modal-router] server returned error, closing modal", event.detail.xhr && event.detail.xhr.status);
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Public API — used by files-overlay.js to open the editor from row clicks
|
||||
// that aren't a literal <a data-modal> (existing event delegation).
|
||||
window.openModal = openModal;
|
||||
window.closeModal = closeModal;
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue