The side sheet, the bottom sheet's panel and the docked search's scrim and view kept `display` alive through their exit with `transition-behavior: allow-discrete`. Firefox does not transition `display` (Chrome 117 and Safari 18 do), and `x-show` sets `display: none` in the frame the exit starts, so in Firefox the sheets vanished instead of sliding out and the search view and scrim vanished instead of fading. The docked search had a second problem in every engine: neither the view nor the scrim had a closed state to transition to, so where `display` was held (Chrome, Safari) the view stood at full opacity for its duration and then disappeared. Each element now carries `x-transition:enter`/`:leave="md-transition"`, the approach the two sheet scrims already took (renamed from `md-scrim-transition` to one name for all of them). The class only switches Alpine to CSS-transition mode, so `x-show` holds `display` for the element's computed transition-duration before hiding it, in every engine, and a reopen during the exit cancels the pending hide; nothing styles it. `display` and `allow-discrete` leave the transitions so Chrome and Safari do not hold a second time. Alpine reads the first `transition-duration` listed, which is the closing slide or fade in each list (the preset panel lists translate before height). The search view now closes back into the bar (opacity 0, `scale: 1 0.9`, the reverse of its `@starting-style` entry) and its scrim fades out on close and when the search turns full screen. Under reduced motion the durations are zero and every one of them closes at once. Four browser tests sample each exit mid-way in the page, in the same round trip as the close: the sheets part of the way to their closed offset, the search scrim and view part of the way faded, each still displayed, then `display: none`. All four fail on main in Firefox (the two search tests in Chrome too) and pass in Chrome, Firefox and Safari. OverlayTest pins the drawer's new transition and the view's markup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
253 lines
9.9 KiB
CSS
253 lines
9.9 KiB
CSS
/*
|
|
* `<x-drawer>`: M3's side sheet, modal by default and co-planar as `standard` from 840px
|
|
* (docs/reference/m3/components-actions-communication-containment.md § Side sheets → Specs; no
|
|
* androidx Compose token file exists for it — Compose support is "Unavailable" per the site — so
|
|
* the numbers come from the specs page and, for the standard variant's 0dp elevation,
|
|
* material-components-android's `SideSheet.md`).
|
|
*
|
|
* [data-md-drawer] the wrapper; data-md-open, data-md-standard
|
|
* [data-md-drawer-scrim] modal only (a standard sheet's is hidden from 840px)
|
|
* [data-md-drawer-sheet] surface-container-low, elevation 1; data-md-side
|
|
* [data-md-drawer-head] title/subtitle/close, and the separator's divider
|
|
* [data-md-drawer-head-row], [data-md-drawer-heading], [data-md-drawer-title],
|
|
* [data-md-drawer-subtitle], [data-md-drawer-close]
|
|
* [data-md-drawer-body] a size container, so its contents lay out by the sheet's own
|
|
* width (`@md:`), never the viewport's
|
|
* [data-md-drawer-actions] left-aligned, 72dp (C-10 — a dialog's are trailing-aligned)
|
|
*
|
|
* 400px cap, 24dp start/end padding, a large corner on the inner edge only — from the specs
|
|
* table. It enters on the emphasized-decelerate easing rather than a spring, because a sheet
|
|
* anchored to an edge that overshot would open a gap, and leaves on emphasized accelerate: the
|
|
* view keeps `x-show`, so `@starting-style` gives the entry its start and the view's `x-transition`
|
|
* holds `display` through the exit, whose timing comes from the closed state's own rule (a
|
|
* transition takes the after-change style's). Not `allow-discrete` on `display`: Firefox does not
|
|
* transition `display`, and in the other engines it would hold a second time after Alpine's hold.
|
|
* Alpine holds for the first `transition-duration` listed, so each exit's longest transition is
|
|
* listed first. Reduced motion zeroes both duration tokens (tokens/motion.css).
|
|
* Below `expanded` it slides, from whichever edge `data-md-side` names in the document's direction;
|
|
* once `standard` makes it co-planar it only fades (an in-flow box does not need to travel). Every
|
|
* state rule matches the sheet and scrim as the root's children, so a sheet nested in an open one
|
|
* keeps its own.
|
|
*
|
|
* A `standard` sheet drops the scrim, the trap and the elevation, and swaps surface-container-low
|
|
* for `surface` with an outline-variant rule down its inner edge, in place of the scrim
|
|
* (its anatomy's "Divider (optional)"). Below `expanded` (840px) it is the modal sheet — M3 caps
|
|
* a side sheet at 400dp, and a 600px window has too little room left beside one, so the switch
|
|
* sits at `expanded` rather than `medium`. `data-md-drawer-collapsed` (the view, from the
|
|
* `wide` — window ≥ 840px — Alpine state) takes the standard sheet out of the layout entirely
|
|
* while it is closed there, so the content beside it fills the space.
|
|
*/
|
|
|
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
|
|
|
@import './button.css';
|
|
@import './divider.css';
|
|
|
|
@layer material.components {
|
|
/* 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] {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 40;
|
|
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
|
|
opacity: 0;
|
|
transition-property: opacity;
|
|
transition-duration: var(--md-sys-motion-effects-default-duration);
|
|
transition-timing-function: var(--md-sys-motion-effects-default);
|
|
}
|
|
|
|
[data-md-drawer][data-md-open] > [data-md-drawer-scrim] {
|
|
opacity: 1;
|
|
}
|
|
|
|
@starting-style {
|
|
[data-md-drawer][data-md-open] > [data-md-drawer-scrim] {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
/* Closed, the sheet waits one width past its own edge (`--sheet-offset`, mirrored in RTL) and
|
|
slides there on emphasized accelerate; open, it comes back on emphasized decelerate. */
|
|
[data-md-drawer-sheet] {
|
|
--sheet-offset: 100%;
|
|
|
|
position: fixed;
|
|
top: var(--material-safe-top, env(safe-area-inset-top));
|
|
bottom: 0;
|
|
z-index: 50;
|
|
display: flex;
|
|
width: 100%;
|
|
flex-direction: column;
|
|
overflow-y: auto;
|
|
padding: var(--md-sys-measurement-space300);
|
|
background-color: var(--md-sys-color-surface-container-low);
|
|
color: var(--md-sys-color-on-surface);
|
|
box-shadow: var(--md-sys-elevation-1);
|
|
translate: var(--sheet-offset) 0;
|
|
transition-property: translate;
|
|
transition-duration: var(--md-sys-motion-effects-default-duration);
|
|
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
|
|
}
|
|
|
|
[data-md-drawer-sheet][data-md-side='end'] {
|
|
inset-inline-end: 0;
|
|
}
|
|
|
|
[data-md-drawer-sheet][data-md-side='start'] {
|
|
--sheet-offset: -100%;
|
|
|
|
inset-inline-start: 0;
|
|
}
|
|
|
|
[data-md-drawer-sheet][data-md-side='end']:is([dir='rtl'], [dir='rtl'] *) {
|
|
--sheet-offset: -100%;
|
|
}
|
|
|
|
[data-md-drawer-sheet][data-md-side='start']:is([dir='rtl'], [dir='rtl'] *) {
|
|
--sheet-offset: 100%;
|
|
}
|
|
|
|
[data-md-drawer][data-md-open] > [data-md-drawer-sheet] {
|
|
translate: 0 0;
|
|
transition-duration: var(--md-sys-motion-spatial-default-duration);
|
|
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate);
|
|
}
|
|
|
|
@starting-style {
|
|
[data-md-drawer][data-md-open] > [data-md-drawer-sheet] {
|
|
translate: var(--sheet-offset) 0;
|
|
}
|
|
}
|
|
|
|
@media (width >= 600px) {
|
|
[data-md-drawer-sheet] {
|
|
width: var(--sheet-width);
|
|
max-width: calc(100vw - 64px);
|
|
}
|
|
|
|
[data-md-drawer-sheet][data-md-side='end'] {
|
|
border-start-start-radius: var(--md-sys-shape-corner-lg);
|
|
border-end-start-radius: var(--md-sys-shape-corner-lg);
|
|
}
|
|
|
|
[data-md-drawer-sheet][data-md-side='start'] {
|
|
border-start-end-radius: var(--md-sys-shape-corner-lg);
|
|
border-end-end-radius: var(--md-sys-shape-corner-lg);
|
|
}
|
|
}
|
|
|
|
/* Co-planar from expanded: sticky beside the content, no scrim, no elevation, flat on
|
|
`surface` with an outline-variant rule down the inner edge instead. It only fades here —
|
|
an in-flow box does not travel like an overlay does. */
|
|
@media (width >= 840px) {
|
|
[data-md-drawer][data-md-standard] {
|
|
position: sticky;
|
|
top: 0;
|
|
height: 100dvh;
|
|
flex-shrink: 0;
|
|
align-self: flex-start;
|
|
}
|
|
|
|
[data-md-drawer][data-md-standard][data-md-drawer-collapsed] {
|
|
display: none;
|
|
}
|
|
|
|
[data-md-drawer][data-md-standard] > [data-md-drawer-scrim] {
|
|
display: none;
|
|
}
|
|
|
|
[data-md-drawer][data-md-standard] > [data-md-drawer-sheet] {
|
|
position: relative;
|
|
top: 0;
|
|
z-index: auto;
|
|
height: 100%;
|
|
translate: 0 0;
|
|
border-radius: var(--md-sys-shape-corner-none);
|
|
background-color: var(--md-sys-color-surface);
|
|
box-shadow: none;
|
|
opacity: 0;
|
|
transition-property: opacity;
|
|
transition-duration: var(--md-sys-motion-effects-default-duration);
|
|
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
|
|
}
|
|
|
|
[data-md-drawer][data-md-standard][data-md-open] > [data-md-drawer-sheet] {
|
|
opacity: 1;
|
|
transition-duration: var(--md-sys-motion-spatial-default-duration);
|
|
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate);
|
|
}
|
|
|
|
@starting-style {
|
|
[data-md-drawer][data-md-standard][data-md-open] > [data-md-drawer-sheet] {
|
|
translate: 0 0;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
[data-md-drawer][data-md-standard] > [data-md-drawer-sheet][data-md-side='end'] {
|
|
border-inline-start: 1px solid var(--md-sys-color-outline-variant);
|
|
}
|
|
|
|
[data-md-drawer][data-md-standard] > [data-md-drawer-sheet][data-md-side='start'] {
|
|
border-inline-end: 1px solid var(--md-sys-color-outline-variant);
|
|
}
|
|
}
|
|
|
|
[data-md-drawer-head] {
|
|
margin-bottom: var(--md-sys-measurement-space200);
|
|
}
|
|
|
|
[data-md-drawer-head-row] {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
[data-md-drawer-heading] {
|
|
min-width: 0;
|
|
}
|
|
|
|
[data-md-drawer-title] {
|
|
font: var(--md-sys-typescale-title-lg);
|
|
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
|
}
|
|
|
|
[data-md-drawer-subtitle] {
|
|
margin-top: var(--md-sys-measurement-space50);
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
font: var(--md-sys-typescale-body-md);
|
|
letter-spacing: var(--md-sys-typescale-body-md-tracking);
|
|
}
|
|
|
|
[data-md-drawer-close] {
|
|
display: inline-flex;
|
|
flex-shrink: 0;
|
|
margin-top: calc(-1 * var(--md-sys-measurement-space100));
|
|
margin-inline-end: -12px;
|
|
}
|
|
|
|
[data-md-drawer-head] > [data-md-divider] {
|
|
margin-top: var(--md-sys-measurement-space200);
|
|
}
|
|
|
|
[data-md-drawer-body] {
|
|
container-type: inline-size;
|
|
flex: 1 1 0%;
|
|
}
|
|
|
|
[data-md-drawer-actions] {
|
|
display: flex;
|
|
min-height: var(--md-sys-measurement-space900);
|
|
flex-shrink: 0;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: var(--md-sys-measurement-space100);
|
|
padding-top: var(--md-sys-measurement-space200);
|
|
}
|
|
}
|