import { warnOnce } from '../core/app-dom.js'; /** * Async Flash Messages & Loading State * Renders flash messages into #async-messages for JS-triggered notifications * Provides loading cursor state management */ /** * Set global loading state (cursor + pointer-events) * @param {boolean} loading - Whether to show loading state */ export function setLoading(loading) { if (loading) { document.body.classList.add('is-loading'); } else { document.body.classList.remove('is-loading'); } } /** * Execute an async function with loading state * @param {Function} fn - Async function to execute * @returns {Promise} - Result of the function */ export async function withLoading(fn) { setLoading(true); try { return await fn(); } finally { setLoading(false); } } const defaultTimeouts = { success: 10000, info: 10000, warning: 10000, error: 0 }; /** * Show an async flash message * @param {string} type - Message type: 'success', 'info', 'warning', 'error' * @param {string} message - The message text * @param {number} [timeout] - Auto-dismiss timeout in ms (0 = no auto-dismiss) */ export function showAsyncFlash(type, message, timeout) { const container = document.getElementById('async-messages'); if (!container) { warnOnce('UI_EL_MISSING', 'Missing #async-messages container', { module: 'async-flash' }); return null; } const effectiveTimeout = timeout ?? defaultTimeouts[type] ?? 5000; const notice = document.createElement('div'); notice.className = 'notice'; notice.setAttribute('data-variant', type); const isAssertive = type === 'error' || type === 'warning'; notice.setAttribute('role', isAssertive ? 'alert' : 'status'); notice.setAttribute('aria-live', isAssertive ? 'assertive' : 'polite'); notice.setAttribute('aria-atomic', 'true'); const messageSpan = document.createElement('span'); messageSpan.textContent = message; notice.appendChild(messageSpan); const closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.className = 'contrast outline small'; closeButton.innerHTML = ''; closeButton.addEventListener('click', () => notice.remove()); notice.appendChild(closeButton); container.appendChild(notice); if (effectiveTimeout > 0) { notice.style.setProperty('--flash-timeout', `${effectiveTimeout}ms`); notice.classList.add('flash-timed'); setTimeout(() => notice.remove(), effectiveTimeout); } return notice; } /** * Clear all async flash messages */ export function clearAsyncFlash() { const container = document.getElementById('async-messages'); if (container) { container.innerHTML = ''; } }