There are 76 alert() call sites across the eight tools — three different ad-hoc error-surfacing patterns (alert, console.error, classifier's own showToast). Touching every site is a sweep with no judgment payoff: every alert is "something went wrong, the user should know," which is exactly what toast at level='error' is for. Shim is one if-block at the bottom of shared/toast.js. It saves the native window.alert as window.alertNative (so any truly modal-blocking call site can opt back in by name), then replaces window.alert with a function that forwards through window.zddc.toast(msg, 'error'). Effect is global — every existing alert in every tool becomes a non-blocking, ARIA-announced (aria-live=assertive) toast that the user can click to dismiss. handler/tables.html refreshed by ./build as a side effect (it bakes the current tables/dist/ into the binary every build). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
3.1 KiB
JavaScript
76 lines
3.1 KiB
JavaScript
// 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');
|
|
};
|
|
}
|
|
})();
|