| 1 |
// Toast notifications ported from mnw.js:44-68. |
| 2 |
|
| 3 |
/** Maximum simultaneously-visible toasts. Beyond this the oldest drops first so |
| 4 |
* a burst of HTMX errors can't bury the viewport. */ |
| 5 |
const TOAST_MAX_VISIBLE = 5; |
| 6 |
|
| 7 |
interface ToastDetail { |
| 8 |
message?: string; |
| 9 |
type?: string; |
| 10 |
} |
| 11 |
|
| 12 |
/** Fire a toast. Rendered by the delegated listener installed by `initToasts`. */ |
| 13 |
export function showToast(message: string, type = 'error'): void { |
| 14 |
document.body.dispatchEvent(new CustomEvent<ToastDetail>('showToast', { detail: { message, type } })); |
| 15 |
} |
| 16 |
|
| 17 |
/** Install the toast renderer (caps the stack, auto-dismisses). Call once at |
| 18 |
* startup. Ported from mnw.js:47. */ |
| 19 |
export function initToasts(): void { |
| 20 |
document.body.addEventListener('showToast', (evt) => { |
| 21 |
const detail = (evt as CustomEvent<ToastDetail>).detail; |
| 22 |
const container = document.getElementById('notifications'); |
| 23 |
if (!container) return; |
| 24 |
while (container.childElementCount >= TOAST_MAX_VISIBLE) { |
| 25 |
container.firstElementChild?.remove(); |
| 26 |
} |
| 27 |
const toast = document.createElement('div'); |
| 28 |
toast.className = 'toast toast-' + (detail.type || 'info'); |
| 29 |
toast.textContent = detail.message || 'Action completed'; |
| 30 |
container.appendChild(toast); |
| 31 |
setTimeout(() => { |
| 32 |
toast.classList.add('fade-out'); |
| 33 |
setTimeout(() => toast.remove(), 300); |
| 34 |
}, 3000); |
| 35 |
}); |
| 36 |
} |
| 37 |
|