Draw the snackbar host without Tailwind

Plan step 36 (actions). <x-toast> renders data-md-toast with
data-md-position, its snackbar data-md-toast-snackbar with a bound
data-md-two-line, data-md-toast-content with a bound
data-md-toast-wrap, and data-md-toast-action/-dismiss; no class list.
toast.css draws SnackbarTokens' inverse surface, 48/68px container,
the two 40px controls reaching 48px targets with their own state
layer and touch target (ACT-18), the enter transition and the
position-start margin that grows from medium.

data-toast becomes data-md-toast-snackbar and data-toast-action
becomes data-md-toast-action (snackbar.js, navigation.js's
hide-on-scroll guard, NavigationBarTest, CommunicationTest); a new
data-md-toast-dismiss hook names the close button. ToastTest is
rewritten on the hooks and ComponentStylesheet. CommunicationTest
gains the owed tests: an actioned snackbar past the default timeout,
Escape on a focused one, the live region before any message, and the
two-line height with Alt+G reaching the action from elsewhere on the
page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 16:42:05 +02:00
co-authored by Claude Opus 5
parent 8cb8727f75
commit ce8ac4d316
9 changed files with 329 additions and 53 deletions
+1 -1
View File
@@ -283,4 +283,4 @@ document.addEventListener('alpine:init', () => {
* snackbar (`<x-toast>`'s, which reads --material-bottom-bar), or a bottom sheet or drawer over the
* page. M3 lets those cover the bar; it is the bar leaving from under them that looks broken.
*/
const anchored = () => [...document.querySelectorAll('[data-toast], [role="dialog"]')].some((over) => over.getClientRects().length > 0)
const anchored = () => [...document.querySelectorAll('[data-md-toast-snackbar], [role="dialog"]')].some((over) => over.getClientRects().length > 0)
+2 -2
View File
@@ -60,7 +60,7 @@ document.addEventListener('alpine:init', () => {
// 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]')
const action = this.$el.querySelector('[data-md-toast-action]')
if (action) {
event.preventDefault()
@@ -142,7 +142,7 @@ document.addEventListener('alpine:init', () => {
*/
measure() {
this.$nextTick(() => {
const snackbar = this.$el.querySelector('[data-toast]')
const snackbar = this.$el.querySelector('[data-md-toast-snackbar]')
const root = document.documentElement.style
if (snackbar) {