diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 38ecc56c..696f0a57 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -326,7 +326,7 @@ The snackbar host. Once per layout, near the end of ``: `` (`po materialToast('Share deleted', { type: 'success', description: null, timeout: 4000, action: { label: 'Undo', handler: () => $wire.restore() } }) ``` -`type` (`success`, `error`, `warning`, `info`) adds the state icon; `timeout: 0` keeps it until dismissed; a toast with an action or no timeout gets a close button. Hover or focus pauses the timer. +`type` (`success`, `error`, `warning`, `info`) picks the announcement role; `timeout: 0` keeps it until dismissed; a toast with an action or no timeout gets a close button. Hover or focus pauses the timer. A toast with an `action` never auto-dismisses (M3's rule) unless you write a `timeout` out. - `action`: `label`, plus `handler` (a function) and/or `event` (a name). Pressing it closes the snackbar, calls `handler`, then dispatches `new CustomEvent(event)` on `window`; give both and both run. Use `event` where a function cannot travel, such as a toast built from JSON. - `sticky: true` keeps a toast until it is dismissed or its action is pressed (any `timeout` is ignored), without holding the queue up: a toast dispatched meanwhile shows in its place, and the sticky one comes back once the queue is empty. One sticky toast is kept at a time; a newer one replaces it. Use it for a question that must be answered, not for news: diff --git a/resources/js/snackbar.js b/resources/js/snackbar.js index 8d3cf59f..87ee4515 100644 --- a/resources/js/snackbar.js +++ b/resources/js/snackbar.js @@ -3,7 +3,8 @@ * * One snackbar at a time, as M3 shows them. Each waits its turn, stays for its timeout (paused * while hovered or focused, so it is never pulled away from someone reading or reaching for its - * action) and is replaced by the next. + * action) and is replaced by the next. A snackbar carrying an action has no timeout at all — + * M3's accessibility page forbids one — unless the caller writes a timeout out. * * A `sticky` toast ("A new version is ready" with a Reload action) stays until it is answered, but * never holds the queue up: it is kept aside rather than queued, a toast that arrives while it shows @@ -52,12 +53,17 @@ document.addEventListener('alpine:init', () => { const toast = Array.isArray(detail) ? detail[0] : detail const sticky = toast.sticky === true + // M3 forbids a snackbar with an action from auto-dismissing: it has to wait for the + // person to read it and reach it. A timeout written out still wins, for a caller who + // means it; a missing one no longer falls back to the default. + const untimed = sticky || toast.timeout === 0 || toast.timeout === null || (toast.action != null && toast.timeout === undefined) + const entry = { id: ++sequence, type: toast.type ?? null, title: toast.title ?? '', description: toast.description ?? null, - timeout: sticky || toast.timeout === 0 || toast.timeout === null ? 0 : (toast.timeout ?? DEFAULT_TIMEOUT_MS), + timeout: untimed ? 0 : (toast.timeout ?? DEFAULT_TIMEOUT_MS), action: toast.action ?? null, sticky, } diff --git a/resources/views/components/toast.blade.php b/resources/views/components/toast.blade.php index fb09d3c7..1227beeb 100644 --- a/resources/views/components/toast.blade.php +++ b/resources/views/components/toast.blade.php @@ -7,9 +7,10 @@ dispatches from a Livewire component — and for `window.materialToast(title, options)` from JavaScript (`{ type, description, timeout, sticky, action: { label, handler, event } }`). Toasts queue and show in turn, each for its `timeout` (4s by default; M3 asks for 4–10s), - paused while the pointer or focus is on it. A toast with an action or no timeout gets a close - button. Pressing the action closes the snackbar, calls `handler` and dispatches `event` (a - name) on `window`; both may be given. + paused while the pointer or focus is on it. A toast with an `action` has no timeout at all, + as M3 requires — it waits to be read and acted on — unless the caller writes a `timeout` out. + A toast with an action or no timeout gets a close button. Pressing the action closes the + snackbar, calls `handler` and dispatches `event` (a name) on `window`; both may be given. `sticky: true` keeps a toast until it is dismissed or its action pressed, without holding up the queue: a toast that arrives meanwhile shows in its place, and the sticky one comes back