feat(shared): non-blocking toast helper available to every tool
Promote classifier's local toast (classifier/css/base.css + showToast
in classifier/js/excel.js) into shared/toast.{js,css}. Every tool's
build.sh now concatenates them, so window.zddc.toast(msg, level, opts)
is callable from any tool.
API:
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. Single-toast
policy — a second call replaces the first. Click anywhere on the
toast to dismiss. ARIA: error → role=alert/aria-live=assertive,
others → role=status/aria-live=polite.
Class prefix is .zddc-toast (BEM-ish) to avoid colliding with any
tool-local .toast rules. Classifier's existing showToast now
delegates to window.zddc.toast — call sites in excel.js +
selection.js are unchanged. Classifier's local .toast CSS block
deleted in favor of the shared one.
This commit only EXPOSES the API. Replacing the ~25 alert() call
sites scattered across archive/transmittal/mdedit/classifier with
toast calls is left as follow-up — each alert needs per-call review
to decide if it's truly non-blocking.
Five Playwright tests in tests/toast.spec.js lock the contract:
API exposure, level mapping, ARIA roles, single-toast replace,
click-to-dismiss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
538167b5c8
commit
8ba029612e
15 changed files with 313 additions and 53 deletions
|
|
@ -20,6 +20,7 @@ trap cleanup EXIT
|
|||
# CSS files to concatenate in order
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/base.css" \
|
||||
"css/layout.css" \
|
||||
"css/components.css" \
|
||||
|
|
@ -38,6 +39,7 @@ concat_files \
|
|||
"../shared/zddc.js" \
|
||||
"../shared/hash.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/preview-lib.js" \
|
||||
"js/init.js" \
|
||||
"js/parser.js" \
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ trap cleanup EXIT
|
|||
# CSS files: shared base first, then browse-specific.
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/base.css" \
|
||||
"css/tree.css" \
|
||||
> "$css_temp"
|
||||
|
|
@ -34,6 +35,7 @@ concat_files \
|
|||
"../shared/zddc.js" \
|
||||
"../shared/zddc-filter.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/help.js" \
|
||||
"../shared/preview-lib.js" \
|
||||
"js/init.js" \
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ trap cleanup EXIT
|
|||
# CSS files to concatenate in order
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/base.css" \
|
||||
"css/layout.css" \
|
||||
"css/spreadsheet.css" \
|
||||
|
|
@ -37,6 +38,7 @@ concat_files \
|
|||
"../shared/hash.js" \
|
||||
"../shared/zddc-source.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/preview-lib.js" \
|
||||
"js/app.js" \
|
||||
"js/utils.js" \
|
||||
|
|
|
|||
|
|
@ -28,38 +28,5 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ── Toast notifications (classifier-only) ───────────────────────────────── */
|
||||
/* shared/base.css intentionally omits toast CSS; only classifier uses toasts. */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 0.875rem 1.25rem;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9000;
|
||||
max-width: 400px;
|
||||
font-size: 0.875rem;
|
||||
animation: zddc-toast-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
.toast-success { border-left: 4px solid var(--success); }
|
||||
.toast-error { border-left: 4px solid var(--danger); }
|
||||
.toast-info { border-left: 4px solid var(--info); }
|
||||
.toast-warning { border-left: 4px solid var(--warning); }
|
||||
|
||||
.toast-fade {
|
||||
animation: zddc-toast-out 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes zddc-toast-in {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes zddc-toast-out {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(100%); opacity: 0; }
|
||||
}
|
||||
/* Toast notifications come from shared/toast.css (.zddc-toast); the
|
||||
classifier-local .toast block was promoted there. */
|
||||
|
|
|
|||
|
|
@ -6,26 +6,19 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Show toast notification
|
||||
* Thin wrapper over the shared toast helper. Keeps the
|
||||
* window.app.modules.excel.showToast call sites in classifier
|
||||
* unchanged while delegating to the canonical implementation in
|
||||
* shared/toast.js (window.zddc.toast).
|
||||
*/
|
||||
function showToast(message, type = 'info') {
|
||||
// Remove existing toast
|
||||
const existing = document.querySelector('.toast');
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
if (window.zddc && typeof window.zddc.toast === 'function') {
|
||||
window.zddc.toast(message, type);
|
||||
} else {
|
||||
// shared/toast.js missing from the build — log so the
|
||||
// problem is visible without crashing the caller.
|
||||
console.warn('[classifier] window.zddc.toast unavailable;', type, message);
|
||||
}
|
||||
|
||||
// Create toast
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
toast.classList.add('toast-fade');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ trap cleanup EXIT
|
|||
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/form.css" \
|
||||
> "$css_temp"
|
||||
|
||||
concat_files \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/help.js" \
|
||||
"js/app.js" \
|
||||
"js/context.js" \
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ trap cleanup EXIT
|
|||
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/landing.css" \
|
||||
> "$css_temp"
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ concat_files \
|
|||
"../shared/zddc.js" \
|
||||
"../shared/zddc-filter.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/help.js" \
|
||||
"js/landing.js" \
|
||||
> "$js_raw"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ trap cleanup EXIT
|
|||
concat_files \
|
||||
"css/tailwind-utils.css" \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/base.css" \
|
||||
"css/editor.css" \
|
||||
"css/toc.css" \
|
||||
|
|
@ -41,6 +42,7 @@ concat_files \
|
|||
"../shared/zddc.js" \
|
||||
"../shared/zddc-source.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/preview-lib.js" \
|
||||
"js/app.js" \
|
||||
"js/utils.js" \
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ export default defineConfig({
|
|||
name: 'zddc-source',
|
||||
testMatch: 'zddc-source.spec.js',
|
||||
},
|
||||
{
|
||||
name: 'toast',
|
||||
testMatch: 'toast.spec.js',
|
||||
},
|
||||
{
|
||||
name: 'zddc',
|
||||
testMatch: 'zddc.spec.js',
|
||||
|
|
|
|||
40
shared/toast.css
Normal file
40
shared/toast.css
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* shared/toast.css — single-toast notification styles paired with
|
||||
shared/toast.js. Uses BEM-ish .zddc-toast prefix to avoid collisions
|
||||
with tool-local .toast classes; the old classifier rules can stay
|
||||
alongside until this file is concatenated above them in the build. */
|
||||
|
||||
.zddc-toast {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 0.875rem 1.25rem;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9000;
|
||||
max-width: 400px;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
animation: zddc-toast-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
.zddc-toast--success { border-left: 4px solid var(--success); }
|
||||
.zddc-toast--error { border-left: 4px solid var(--danger); }
|
||||
.zddc-toast--info { border-left: 4px solid var(--info); }
|
||||
.zddc-toast--warning { border-left: 4px solid var(--warning); }
|
||||
|
||||
.zddc-toast--fade {
|
||||
animation: zddc-toast-out 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes zddc-toast-in {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes zddc-toast-out {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(100%); opacity: 0; }
|
||||
}
|
||||
63
shared/toast.js
Normal file
63
shared/toast.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// 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;
|
||||
})();
|
||||
|
|
@ -19,6 +19,7 @@ trap cleanup EXIT
|
|||
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/table.css" \
|
||||
"../form/css/form.css" \
|
||||
> "$css_temp"
|
||||
|
|
@ -33,6 +34,7 @@ concat_files \
|
|||
"../shared/zddc.js" \
|
||||
"../shared/zddc-source.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/help.js" \
|
||||
"js/mode.js" \
|
||||
"js/app.js" \
|
||||
|
|
|
|||
67
tests/toast.spec.js
Normal file
67
tests/toast.spec.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Tests for shared/toast.js — the cross-tool notification helper
|
||||
// available as window.zddc.toast(msg, level, opts). Loaded into every
|
||||
// tool's bundle by build.sh.
|
||||
//
|
||||
// Strategy: load any tool's dist HTML over file:// (browse is the
|
||||
// smallest), trigger the helper, and assert DOM + ARIA shape.
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
|
||||
const HTML_PATH = path.resolve('browse/dist/browse.html');
|
||||
|
||||
test.describe('shared/toast.js', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`file://${HTML_PATH}`, { waitUntil: 'load' });
|
||||
});
|
||||
|
||||
test('exposes window.zddc.toast(msg, level)', async ({ page }) => {
|
||||
const exposed = await page.evaluate(
|
||||
() => typeof window.zddc?.toast === 'function'
|
||||
);
|
||||
expect(exposed).toBe(true);
|
||||
});
|
||||
|
||||
test('renders a single toast with the level class and ARIA role', async ({ page }) => {
|
||||
const after = await page.evaluate(() => {
|
||||
window.zddc.toast('Saved.', 'success');
|
||||
const el = document.querySelector('.zddc-toast');
|
||||
return el && {
|
||||
text: el.textContent,
|
||||
level: [...el.classList].find(c => c.startsWith('zddc-toast--')),
|
||||
role: el.getAttribute('role'),
|
||||
live: el.getAttribute('aria-live'),
|
||||
};
|
||||
});
|
||||
expect(after).toEqual({
|
||||
text: 'Saved.',
|
||||
level: 'zddc-toast--success',
|
||||
role: 'status',
|
||||
live: 'polite',
|
||||
});
|
||||
});
|
||||
|
||||
test('error level uses role=alert + aria-live=assertive', async ({ page }) => {
|
||||
const probe = await page.evaluate(() => {
|
||||
window.zddc.toast('Boom', 'error');
|
||||
const el = document.querySelector('.zddc-toast');
|
||||
return { role: el.getAttribute('role'), live: el.getAttribute('aria-live') };
|
||||
});
|
||||
expect(probe).toEqual({ role: 'alert', live: 'assertive' });
|
||||
});
|
||||
|
||||
test('a second toast replaces the first (single-toast policy)', async ({ page }) => {
|
||||
const count = await page.evaluate(() => {
|
||||
window.zddc.toast('one', 'info');
|
||||
window.zddc.toast('two', 'info');
|
||||
return document.querySelectorAll('.zddc-toast').length;
|
||||
});
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
test('clicking dismisses immediately', async ({ page }) => {
|
||||
await page.evaluate(() => window.zddc.toast('click me', 'info'));
|
||||
await page.locator('.zddc-toast').click();
|
||||
await expect(page.locator('.zddc-toast')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
|
@ -23,6 +23,7 @@ trap cleanup EXIT
|
|||
# CSS files to concatenate in order
|
||||
concat_files \
|
||||
"../shared/base.css" \
|
||||
"../shared/toast.css" \
|
||||
"css/base.css" \
|
||||
"css/layout.css" \
|
||||
"css/forms.css" \
|
||||
|
|
@ -47,6 +48,7 @@ concat_files \
|
|||
"../shared/hash.js" \
|
||||
"../shared/zddc-source.js" \
|
||||
"../shared/theme.js" \
|
||||
"../shared/toast.js" \
|
||||
"../shared/preview-lib.js" \
|
||||
"js/app.js" \
|
||||
"js/reactive.js" \
|
||||
|
|
|
|||
|
|
@ -335,6 +335,11 @@ a:hover {
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* The refresh ⟳ glyph renders slightly smaller than ◐ / ? — bump to match. */
|
||||
#refreshHeaderBtn {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Toast CSS lives in classifier/css/base.css — only that tool uses toasts. */
|
||||
|
||||
/* ── Theme and help icon buttons ─────────────────────────────────────────── */
|
||||
|
|
@ -525,6 +530,47 @@ body.help-open .app-header {
|
|||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* shared/toast.css — single-toast notification styles paired with
|
||||
shared/toast.js. Uses BEM-ish .zddc-toast prefix to avoid collisions
|
||||
with tool-local .toast classes; the old classifier rules can stay
|
||||
alongside until this file is concatenated above them in the build. */
|
||||
|
||||
.zddc-toast {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 0.875rem 1.25rem;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9000;
|
||||
max-width: 400px;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
animation: zddc-toast-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
.zddc-toast--success { border-left: 4px solid var(--success); }
|
||||
.zddc-toast--error { border-left: 4px solid var(--danger); }
|
||||
.zddc-toast--info { border-left: 4px solid var(--info); }
|
||||
.zddc-toast--warning { border-left: 4px solid var(--warning); }
|
||||
|
||||
.zddc-toast--fade {
|
||||
animation: zddc-toast-out 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes zddc-toast-in {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes zddc-toast-out {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(100%); opacity: 0; }
|
||||
}
|
||||
|
||||
/* tables/ — directory-of-YAML table view. Reuses tokens from shared/base.css. */
|
||||
|
||||
.table-main {
|
||||
|
|
@ -967,7 +1013,7 @@ body.help-open .app-header {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-09 23:35:43 · 3a4a1c7-dirty</span></span>
|
||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-10 00:02:54 · 538167b-dirty</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
|
|
@ -1965,6 +2011,70 @@ body.help-open .app-header {
|
|||
}
|
||||
}());
|
||||
|
||||
// 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;
|
||||
})();
|
||||
|
||||
/**
|
||||
* ZDDC shared help panel — open/close logic.
|
||||
* Works with all four tools regardless of their module pattern.
|
||||
|
|
|
|||
Loading…
Reference in a new issue