From 0ff6bbb3f1669c646f331c8932d59f4d13ee5f02 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Sun, 26 Apr 2026 23:33:05 +0200 Subject: [PATCH] fix(modal): add _isClosing guard and async-safe closeModal listener Co-Authored-By: Claude Sonnet 4.6 --- public/components/modal.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/public/components/modal.js b/public/components/modal.js index 31f723f..bbd7d66 100644 --- a/public/components/modal.js +++ b/public/components/modal.js @@ -17,6 +17,7 @@ let activeOverlay = null; let previouslyFocused = null; let focusTrapHandler = null; let _initialFormSnapshot = null; +let _isClosing = false; // Overlay-Dimming: theme-color abdunkeln im Standalone-Modus const OVERLAY_THEME_COLOR = '#1A1A1A'; @@ -285,7 +286,7 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {} // Close-Button activeOverlay.querySelector('[data-action="close-modal"]') - ?.addEventListener('click', closeModal); + ?.addEventListener('click', () => closeModal()); // Escape document.addEventListener('keydown', onEscape); @@ -304,16 +305,23 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {} // -------------------------------------------------------- export async function closeModal({ force = false } = {}) { - if (!activeOverlay) return; + if (!activeOverlay || _isClosing) return; + _isClosing = true; if (!force) { const panel = activeOverlay.querySelector('.modal-panel'); if (panel && isFormDirty(panel)) { - const confirmed = await confirmModal(t('modal.unsavedChanges'), { - danger: false, - confirmLabel: t('modal.discardChanges'), - }); - if (!confirmed) return; + let confirmed; + try { + confirmed = await confirmModal(t('modal.unsavedChanges'), { + danger: false, + confirmLabel: t('modal.discardChanges'), + }); + } catch (err) { + _isClosing = false; + throw err; + } + if (!confirmed) { _isClosing = false; return; } } } @@ -341,14 +349,16 @@ export async function closeModal({ force = false } = {}) { if (isMobile && panel) { panel.classList.add('modal-panel--closing'); // Fallback-Timer falls animationend nicht feuert (prefers-reduced-motion, Tab-Wechsel etc.) - const fallback = setTimeout(() => _doClose(capturedOverlay), 300); + const fallback = setTimeout(() => { _isClosing = false; _doClose(capturedOverlay); }, 300); panel.addEventListener('animationend', () => { clearTimeout(fallback); + _isClosing = false; _doClose(capturedOverlay); }, { once: true }); return; } + _isClosing = false; _doClose(capturedOverlay); }