import { Injectable, signal } from '@angular/core'; export interface Toast { id: number; kind: 'error' | 'info' | 'success'; text: string; ttlMs: number; } @Injectable({ providedIn: 'root' }) export class ToastService { readonly toasts = signal([]); private nextId = 1; private readonly timers = new Map< number, { timer: ReturnType; expiresAt: number; remaining: number } >(); error(text: string) { this.show('error', text, 6000); } info(text: string) { this.show('info', text, 3000); } success(text: string) { this.show('success', text, 3000); } dismiss(id: number) { const entry = this.timers.get(id); if (entry) clearTimeout(entry.timer); this.timers.delete(id); this.toasts.update((list) => list.filter((t) => t.id !== id)); } /** Freeze the auto-dismiss countdown (hover). */ pause(id: number) { const entry = this.timers.get(id); if (!entry) return; clearTimeout(entry.timer); entry.remaining = Math.max(0, entry.expiresAt - Date.now()); } /** Resume the countdown with whatever time was left when paused. */ resume(id: number) { const entry = this.timers.get(id); if (!entry) return; entry.expiresAt = Date.now() + entry.remaining; entry.timer = setTimeout(() => this.dismiss(id), entry.remaining); } private show(kind: Toast['kind'], text: string, ttlMs: number) { const toast = { id: this.nextId++, kind, text, ttlMs }; this.toasts.update((list) => [...list, toast]); this.timers.set(toast.id, { timer: setTimeout(() => this.dismiss(toast.id), ttlMs), expiresAt: Date.now() + ttlMs, remaining: ttlMs, }); } }