/** * Toast notification state using Svelte 5 runes. * * Provides success/info/warning/error notifications with auto-dismiss. * Toasts stack vertically and animate in/out. */ export type ToastType = 'success' | 'info' | 'warning' | 'error'; export interface Toast { id: string; type: ToastType; message: string; duration: number; dismissible: boolean; timer?: ReturnType; } /** Default auto-dismiss durations by type (milliseconds). */ const DEFAULT_DURATIONS: Record = { success: 3000, info: 4000, warning: 5000, error: 8000 }; let nextId = 0; class ToastState { toasts = $state([]); /** Add a success toast (green, 3s auto-dismiss). */ success(message: string, duration?: number): string { return this.add('success', message, duration); } /** Add an info toast (blue, 4s auto-dismiss). */ info(message: string, duration?: number): string { return this.add('info', message, duration); } /** Add a warning toast (yellow, 5s auto-dismiss). */ warning(message: string, duration?: number): string { return this.add('warning', message, duration); } /** Add an error toast (red, 8s auto-dismiss). */ error(message: string, duration?: number): string { return this.add('error', message, duration); } /** Dismiss a toast by ID. */ dismiss(id: string): void { const toast = this.toasts.find((t) => t.id === id); if (toast?.timer) { clearTimeout(toast.timer); } this.toasts = this.toasts.filter((t) => t.id !== id); } /** Dismiss all toasts. */ dismissAll(): void { for (const t of this.toasts) { if (t.timer) clearTimeout(t.timer); } this.toasts = []; } private add(type: ToastType, message: string, duration?: number): string { const id = `toast-${++nextId}`; const dur = duration ?? DEFAULT_DURATIONS[type]; const toast: Toast = { id, type, message, duration: dur, dismissible: type === 'error' }; // Auto-dismiss after duration toast.timer = setTimeout(() => { this.dismiss(id); }, dur); this.toasts = [...this.toasts, toast]; return id; } } /** Singleton toast state instance. */ export const toast = new ToastState();