Pin the two-line snackbar, and reach it with Alt+G
Plan step 22, actions.md § Missing (two-line snackbar height, snackbar keyboard shortcut): the container grew organically from `min-h-12` rather than sitting at SnackbarTokens.TwoLinesContainerHeight, and there was no way for a keyboard to reach an actioned snackbar at all. A `description` is M3's second line, so the snackbar is pinned to 68px whenever one is there, and below `medium` a two-line snackbar with an action wraps the action under the text — M3's "two lines with longer action" configuration. Alt+G, the shortcut M3 suggests for the web, moves the focus to a snackbar that carries an action from wherever the page had it; `event.code`, because Alt rewrites `event.key` on some layouts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
6af501c966
commit
44ae9ecea4
@@ -10,6 +10,11 @@
|
||||
* never holds the queue up: it is kept aside rather than queued, a toast that arrives while it shows
|
||||
* takes its place, and it comes back once the queue is empty. Only one is kept — a newer sticky
|
||||
* toast replaces it. Dismissing it, or pressing its action, lets it go.
|
||||
*
|
||||
* Two keys are watched on the whole document: Escape dismisses the snackbar while the focus is in
|
||||
* it, and Alt+G moves the focus to a snackbar that carries an action from wherever the page had it
|
||||
* — M3 asks the web for a documented shortcut of that kind, since a snackbar never takes the focus
|
||||
* on its own and a keyboard would otherwise have no way to reach the action.
|
||||
*/
|
||||
const DEFAULT_TIMEOUT_MS = 4000
|
||||
|
||||
@@ -35,25 +40,40 @@ document.addEventListener('alpine:init', () => {
|
||||
timer: null,
|
||||
remaining: 0,
|
||||
startedAt: 0,
|
||||
escape: null,
|
||||
keys: null,
|
||||
|
||||
init() {
|
||||
host = this
|
||||
pending.splice(0).forEach((detail) => this.add(detail))
|
||||
|
||||
// M3: Esc dismisses the focused snackbar. Only the focused one — a key pressed
|
||||
// anywhere else on the page belongs to whatever has the focus there.
|
||||
this.escape = (event) => {
|
||||
this.keys = (event) => {
|
||||
// M3: Esc dismisses the focused snackbar. Only the focused one — a key pressed
|
||||
// anywhere else on the page belongs to whatever has the focus there.
|
||||
if (event.key === 'Escape' && this.current && this.$el.contains(document.activeElement)) {
|
||||
this.dismiss()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// M3 asks the web for a documented shortcut that moves the focus to a snackbar
|
||||
// carrying an action, and suggests Alt+G: a snackbar never takes the focus by
|
||||
// itself, so without one the keyboard cannot reach the action at all. `event.code`
|
||||
// rather than `event.key`, which Alt rewrites to another character on some layouts.
|
||||
if (event.altKey && !event.ctrlKey && !event.metaKey && event.code === 'KeyG') {
|
||||
const action = this.$el.querySelector('[data-toast-action]')
|
||||
|
||||
if (action) {
|
||||
event.preventDefault()
|
||||
action.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', this.escape)
|
||||
document.addEventListener('keydown', this.keys)
|
||||
},
|
||||
|
||||
destroy() {
|
||||
document.removeEventListener('keydown', this.escape)
|
||||
document.removeEventListener('keydown', this.keys)
|
||||
document.documentElement.style.removeProperty('--material-snackbar-height')
|
||||
|
||||
if (host === this) {
|
||||
|
||||
Reference in New Issue
Block a user