export type ToastType = 'success' | 'error'; interface ToastAction { label: string; callback: () => void; } interface ToastItem { id: number; type: ToastType; message: string; } const ICON: Record = { success: 'check_circle', error: 'error', }; const DURATION: Record = { success: 3000, error: 5000, }; const DURATION_WITH_ACTION = 10_000; const MAX_VISIBLE = 3; let nextId = 0; let container: HTMLElement | null = null; const items: ToastItem[] = []; export const Toast = { mount(parent: HTMLElement): void { if (container?.isConnected) return; // déjà monté container = document.createElement('div'); container.id = 'toast-container'; parent.appendChild(container); }, show(type: ToastType, message: string, action?: ToastAction): void { if (!container?.isConnected) return; const id = nextId++; items.push({ id, type, message }); // Evincer les plus anciens si > MAX_VISIBLE while (items.length > MAX_VISIBLE) { const old = items.shift()!; const oldEl = container.querySelector(`[data-toast-id="${old.id}"]`); if (oldEl) oldEl.remove(); } const el = document.createElement('div'); el.className = `toast toast-${type}`; el.dataset['toastId'] = String(id); const iconSpan = document.createElement('span'); iconSpan.className = 'toast-icon material-symbols-outlined'; iconSpan.textContent = ICON[type]; const msgSpan = document.createElement('span'); msgSpan.className = 'toast-msg'; msgSpan.textContent = message; el.appendChild(iconSpan); el.appendChild(msgSpan); if (action) { const btn = document.createElement('button'); btn.className = 'toast-action'; btn.textContent = action.label; btn.addEventListener('click', () => { action.callback(); cleanup(); }, { once: true }); el.appendChild(btn); } container.appendChild(el); // Trigger animation requestAnimationFrame(() => el.classList.add('toast-visible')); const cleanup = () => { if (!el.parentNode) return; el.remove(); const idx = items.findIndex(i => i.id === id); if (idx !== -1) items.splice(idx, 1); }; const duration = action ? DURATION_WITH_ACTION : DURATION[type]; setTimeout(() => { el.classList.remove('toast-visible'); el.classList.add('toast-exit'); el.addEventListener('animationend', cleanup, { once: true }); // Fallback si animationend ne se declenche pas setTimeout(cleanup, 500); }, duration); }, };