diff --git a/templates/default.phtml b/templates/default.phtml index b176158..a64c83c 100644 --- a/templates/default.phtml +++ b/templates/default.phtml @@ -337,6 +337,7 @@ $componentPageConfig['moduleRuntimeComponents'] = $moduleRuntimeComponents; + diff --git a/templates/partials/app-toast-stack.phtml b/templates/partials/app-toast-stack.phtml new file mode 100644 index 0000000..ef12a9c --- /dev/null +++ b/templates/partials/app-toast-stack.phtml @@ -0,0 +1,12 @@ + +
diff --git a/web/js/components/app-async-flash.js b/web/js/components/app-async-flash.js index dbfc178..180545b 100644 --- a/web/js/components/app-async-flash.js +++ b/web/js/components/app-async-flash.js @@ -2,8 +2,12 @@ import { warnOnce } from '../core/app-dom.js'; /** * Async Flash Messages & Loading State - * Renders flash messages into the .app-toast-stack for JS-triggered notifications. - * Creates the stack container on demand if it doesn't exist yet. + * + * Renders flash messages into the .app-toast-stack for JS-triggered + * notifications. The stack container is rendered server-side by + * templates/partials/app-toast-stack.phtml (mounted once in default.phtml); + * this module only appends/removes toasts inside it. + * * Provides loading cursor state management. */ @@ -43,18 +47,13 @@ const defaultTimeouts = { const MAX_VISIBLE_TOASTS = 5; /** - * Get or create the toast stack container. - * @returns {HTMLElement} + * Resolve the server-rendered toast stack container. + * Returns null if the partial is missing — callers must handle that case. + * @returns {HTMLElement|null} */ function getToastStack() { - let stack = document.querySelector('.app-toast-stack'); - if (!stack) { - stack = document.createElement('div'); - stack.className = 'app-toast-stack'; - stack.setAttribute('aria-live', 'polite'); - document.body.appendChild(stack); - } - return stack; + const stack = document.querySelector('.app-toast-stack'); + return stack instanceof HTMLElement ? stack : null; } /** @@ -95,6 +94,12 @@ function enforceMaxVisible(stack) { */ export function showAsyncFlash(type, message, timeout) { const stack = getToastStack(); + if (!stack) { + warnOnce('UI_EL_MISSING', 'Missing .app-toast-stack — partial not mounted?', { + module: 'async-flash', + }); + return null; + } const effectiveTimeout = timeout ?? defaultTimeouts[type] ?? 5000;