// shared/toast.js — non-blocking notification helper available to every // tool via window.zddc.toast(msg, level, opts). Originated as classifier's // local showToast (classifier/js/excel.js); promoted here so tools that // today use alert() or silent console.error can switch to a uniform // non-blocking surface. // // Usage: // window.zddc.toast('Saved.', 'success'); // window.zddc.toast('Could not load: ' + err.message, 'error'); // window.zddc.toast('Note', 'info', { durationMs: 3000 }); // // Levels: 'info' (default) | 'success' | 'warning' | 'error'. // Each tool may also expose app.notify(msg, level) as a thin wrapper — // see ARCHITECTURE.md for the convention. (function () { 'use strict'; if (!window.zddc) window.zddc = {}; // Don't overwrite if a tool defined its own first. if (typeof window.zddc.toast === 'function') return; var DEFAULT_DURATION_MS = 5000; var FADE_MS = 300; function toast(message, level, opts) { opts = opts || {}; var lvl = (level === 'success' || level === 'error' || level === 'warning') ? level : 'info'; // Single-toast policy: dismiss any existing toast immediately // so the new one is always the most recent. Matches the // classifier's prior behavior and avoids stack-of-toasts UX. var existing = document.querySelector('.zddc-toast'); if (existing) existing.remove(); var el = document.createElement('div'); el.className = 'zddc-toast zddc-toast--' + lvl; // ARIA: errors get assertive (interrupts SR queue), others polite. el.setAttribute('role', lvl === 'error' ? 'alert' : 'status'); el.setAttribute('aria-live', lvl === 'error' ? 'assertive' : 'polite'); el.textContent = message == null ? '' : String(message); document.body.appendChild(el); var dur = typeof opts.durationMs === 'number' ? opts.durationMs : DEFAULT_DURATION_MS; var timer = setTimeout(function () { el.classList.add('zddc-toast--fade'); setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, FADE_MS); }, dur); // Click-to-dismiss. Useful for sticky errors the user wants gone. el.addEventListener('click', function () { clearTimeout(timer); if (el.parentNode) el.parentNode.removeChild(el); }); return el; } window.zddc.toast = toast; // Route window.alert() calls into the toast helper. Every tool has // accumulated some `alert(...)` sites for error reporting; rather // than touch each one, intercept globally so they're non-blocking // and ARIA-announced consistently. Native alert is preserved on // window.alertNative for the rare case where a truly modal block // is needed (e.g. before navigating away with unsaved changes). if (typeof window.alert === 'function' && !window.alertNative) { window.alertNative = window.alert.bind(window); window.alert = function (msg) { toast(String(msg == null ? '' : msg), 'error'); }; } })();