Fade the side and bottom sheet scrims out in Firefox too

Both scrims are shown and hidden by Alpine's `x-show`, and faded by CSS:
closing set `display: none` inline in the same frame as the opacity
change, and `transition-behavior: allow-discrete` on `display` held the
element for the fade. Firefox does not transition `display`, so no
transition started there and the scrim vanished at once — what
ContainmentTest's two fade-out tests caught.

Alpine now holds `display` itself: `x-transition` with a class string
waits for the element's computed transition-duration (the effects token,
zero under reduced motion) before hiding it, the same in every engine,
and registering `enter` too means reopening during the fade cancels the
pending hide. `display` leaves the scrims' CSS transitions, so Chrome and
Safari do not hold it a second time after Alpine's hold.

The Alpine transition also holds `$nextTick` until the sheet is shown,
which menu.js's openSheet() relies on ("Alpine holds $nextTick until
then") and which had been lost when the bottom sheet moved off
x-transition. Without it `focusItem()` could run while the sheet was
still `display: none`, so ActionsTest's sheet-at-compact menu and its
filter field did not take the focus in Firefox and Safari; both pass
there again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-16 12:24:19 +02:00
co-authored by Claude Opus 5
parent 921c8cef04
commit 6a133c2c67
4 changed files with 14 additions and 7 deletions
+2 -2
View File
@@ -36,16 +36,16 @@
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@layer material.components { @layer material.components {
/* The scrim fades out while the view's `x-transition` holds its `display` (see drawer.css). */
[data-md-bottom-sheet-scrim] { [data-md-bottom-sheet-scrim] {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 40; z-index: 40;
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
opacity: 0; opacity: 0;
transition-property: opacity, display; transition-property: opacity;
transition-duration: var(--md-sys-motion-effects-default-duration); transition-duration: var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-effects-default); transition-timing-function: var(--md-sys-motion-effects-default);
transition-behavior: allow-discrete;
} }
[data-md-bottom-sheet][data-md-open] > [data-md-bottom-sheet-scrim] { [data-md-bottom-sheet][data-md-open] > [data-md-bottom-sheet-scrim] {
+5 -3
View File
@@ -41,17 +41,19 @@
@import './divider.css'; @import './divider.css';
@layer material.components { @layer material.components {
/* The scrim fades in and, while `display` is held by `allow-discrete`, back out. */ /* The scrim fades in from `@starting-style` and back out while the view's `x-transition` holds
its `display` for this duration. Not `allow-discrete` on `display`: Firefox does not transition
`display`, so the scrim vanished there, and in the other engines it would hold a second time
after Alpine's hold. */
[data-md-drawer-scrim] { [data-md-drawer-scrim] {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 40; z-index: 40;
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent); background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
opacity: 0; opacity: 0;
transition-property: opacity, display; transition-property: opacity;
transition-duration: var(--md-sys-motion-effects-default-duration); transition-duration: var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-effects-default); transition-timing-function: var(--md-sys-motion-effects-default);
transition-behavior: allow-discrete;
} }
[data-md-drawer][data-md-open] > [data-md-drawer-scrim] { [data-md-drawer][data-md-open] > [data-md-drawer-scrim] {
@@ -104,7 +104,8 @@
@if ($standard) data-md-standard @endif @if ($standard) data-md-standard @endif
> >
@unless ($standard) @unless ($standard)
<div data-md-bottom-sheet-scrim x-cloak x-show="open" x-on:click="close()" aria-hidden="true"></div> {{-- Alpine's CSS transition holds the scrim for its fade, as in drawer.blade.php. --}}
<div data-md-bottom-sheet-scrim x-cloak x-show="open" x-transition:enter="md-scrim-transition" x-transition:leave="md-scrim-transition" x-on:click="close()" aria-hidden="true"></div>
@endunless @endunless
@if ($stops !== []) @if ($stops !== [])
+5 -1
View File
@@ -93,7 +93,11 @@
data-md-drawer data-md-drawer
@if ($standard) data-md-standard @endif @if ($standard) data-md-standard @endif
> >
<div data-md-drawer-scrim x-cloak x-show="open" @if (! $withoutBackdropClose) x-on:click="close()" @endif aria-hidden="true"></div> {{-- The class names only turn on Alpine's CSS transition: it keeps the scrim displayed for the
computed transition-duration (drawer.css's effects token) before hiding it, because Firefox
does not transition `display`, even with `allow-discrete`. Both stages, so reopening during
the fade cancels the pending hide. --}}
<div data-md-drawer-scrim x-cloak x-show="open" x-transition:enter="md-scrim-transition" x-transition:leave="md-scrim-transition" @if (! $withoutBackdropClose) x-on:click="close()" @endif aria-hidden="true"></div>
<aside <aside
x-cloak x-cloak