import type { ToastOptions, ToastInstance, ToastAPI } from './types'; /** * Toast notification system for bare-v2 * Provides non-blocking notifications with stacking support */ class Toast implements ToastAPI { private container: HTMLElement | null = null; private toastId = 0; private toasts = new Map(); private getContainer(): HTMLElement { if (!this.container) { this.container = document.createElement('div'); this.container.className = 'toast-container'; document.body.appendChild(this.container); } return this.container; } private removeToast(id: number): void { const data = this.toasts.get(id); if (!data) return; const { element, timeoutId } = data; if (timeoutId) clearTimeout(timeoutId); element.classList.add('toast-exit'); element.addEventListener('animationend', () => { element.remove(); this.toasts.delete(id); }, { once: true }); } private getIconHtml(variant: string, customIcon?: string): string { if (customIcon !== undefined) return customIcon; const iconMap: Record = { success: '', error: '', destructive: '', warning: '', info: '', default: '', }; return iconMap[variant] || iconMap.default; } show(options: ToastOptions = {}): number { const { title, description, variant = 'default', duration = 5000, action, icon, } = options; const id = this.toastId++; const element = document.createElement('div'); element.className = `toast toast-${variant}`; element.setAttribute('role', 'alert'); element.setAttribute('aria-live', 'polite'); const iconHtml = this.getIconHtml(variant, icon); element.innerHTML = ` ${iconHtml ? `
${iconHtml}
` : ''}
${title ? `
${title}
` : ''} ${description ? `
${description}
` : ''} ${action ? `
${action}
` : ''}
`; // Add close handler const closeBtn = element.querySelector('.toast-close'); closeBtn?.addEventListener('click', () => this.removeToast(id)); // Insert at the top (newest first) const containerEl = this.getContainer(); containerEl.insertBefore(element, containerEl.firstChild); // Auto-dismiss only if duration > 0 let timeoutId: number | null = null; if (duration > 0) { timeoutId = window.setTimeout(() => this.removeToast(id), duration); } this.toasts.set(id, { id, element, timeoutId }); return id; } success(title: string, description?: string, options: Partial = {}): number { return this.show({ ...options, title, description, variant: 'success' }); } error(title: string, description?: string, options: Partial = {}): number { return this.show({ ...options, title, description, variant: 'error' }); } warning(title: string, description?: string, options: Partial = {}): number { return this.show({ ...options, title, description, variant: 'warning' }); } info(title: string, description?: string, options: Partial = {}): number { return this.show({ ...options, title, description, variant: 'info' }); } dismiss(id: number): void { this.removeToast(id); } } export const createToast = (): ToastAPI => new Toast();