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>
168 lines
7.3 KiB
CSS
168 lines
7.3 KiB
CSS
/*
|
||
* `<x-bottom-sheet>`: M3's modal and standard bottom sheets (SheetBottomTokens.kt, androidx
|
||
* Compose Material 3, Apache-2.0; docs/reference/m3/
|
||
* components-actions-communication-containment.md § Bottom sheets → Specs).
|
||
*
|
||
* [data-md-bottom-sheet] the wrapper; data-md-open, data-md-standard
|
||
* [data-md-bottom-sheet-scrim] modal only
|
||
* [data-md-bottom-sheet-probe] hidden, measures a stop's px height for a drag to settle on
|
||
* [data-md-bottom-sheet-panel] surface-container-low, extra-large top corners, elevation 1;
|
||
* data-md-preset while `heights`/`snap` gives it stops
|
||
* [data-md-bottom-sheet-handle] the drag target and M3's 48dp hit area (C-01)
|
||
* [data-md-bottom-sheet-grip] the 32×4px bar, the shared classes' touch target
|
||
* [data-md-bottom-sheet-announce] a live region, only while stops exist
|
||
* [data-md-bottom-sheet-body], [data-md-bottom-sheet-title], [data-md-bottom-sheet-actions]
|
||
*
|
||
* 640px max width, centred; 28px top corners only (`DockedContainerShape` = CornerExtraLargeTop);
|
||
* 50dvh default open height under a ceiling of the screen less M3's 72dp top margin (C-19). It
|
||
* rises on emphasized decelerate and only falls on emphasized accelerate — the asymmetric timing a
|
||
* more specific selector for the open state supplies, since a CSS transition always takes its
|
||
* duration and easing from the *after-change* style (the technique resources/css/components/
|
||
* drawer.css also uses). `data-md-preset` adds the height-between-stops transition on the spatial
|
||
* spring, independent of the open/close slide.
|
||
*
|
||
* The handle's own 22px top/bottom padding (`SheetDefaults.kt`'s `DragHandleVerticalPadding`) is a
|
||
* literal — 22 is not on the measurement scale — matching alert.css's 12px for the same reason.
|
||
* The grip button draws the shared `md-focus-ring` and `md-touch-target` classes rather than a
|
||
* hand-rolled ring: its own visible bar is not a smaller indicator drawn inside a bigger box those
|
||
* classes cannot reach (unlike the datepicker's day) — the box they draw *is* the button's whole
|
||
* hit area. The one refinement is the ring's offset, 4px rather than 2px, as the old handle drew
|
||
* it: 2px from a 4px-tall bar leaves the ring hard to tell from the bar.
|
||
*
|
||
* The scrim fades out as it fades in, and the state rules match the root's own scrim and panel,
|
||
* so a sheet nested in an open one (a menu's sheet at compact inside a sheet) keeps its own state.
|
||
*/
|
||
|
||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||
|
||
@layer material.components {
|
||
/* The scrim fades out while the view's `x-transition` holds its `display` (see drawer.css). */
|
||
[data-md-bottom-sheet-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-bottom-sheet][data-md-open] > [data-md-bottom-sheet-scrim] {
|
||
opacity: 1;
|
||
}
|
||
|
||
@starting-style {
|
||
[data-md-bottom-sheet][data-md-open] > [data-md-bottom-sheet-scrim] {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
[data-md-bottom-sheet-probe] {
|
||
position: fixed;
|
||
inset-inline-start: 0;
|
||
top: 0;
|
||
width: 0;
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
}
|
||
|
||
[data-md-bottom-sheet-panel] {
|
||
position: fixed;
|
||
inset-inline: 0;
|
||
bottom: 0;
|
||
z-index: 50;
|
||
display: flex;
|
||
width: 100%;
|
||
max-width: 640px;
|
||
margin-inline: auto;
|
||
flex-direction: column;
|
||
max-height: var(--sheet-max-height);
|
||
touch-action: pan-y;
|
||
border-radius: var(--md-sys-shape-corner-xl) var(--md-sys-shape-corner-xl) var(--md-sys-shape-corner-none) var(--md-sys-shape-corner-none);
|
||
background-color: var(--md-sys-color-surface-container-low);
|
||
color: var(--md-sys-color-on-surface);
|
||
box-shadow: var(--md-sys-elevation-1);
|
||
padding-bottom: var(--material-safe-bottom, env(safe-area-inset-bottom));
|
||
translate: 0 100%;
|
||
transition-property: translate, display;
|
||
transition-duration: var(--md-sys-motion-effects-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate);
|
||
transition-behavior: allow-discrete;
|
||
}
|
||
|
||
[data-md-bottom-sheet][data-md-open] > [data-md-bottom-sheet-panel] {
|
||
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-bottom-sheet][data-md-open] > [data-md-bottom-sheet-panel] {
|
||
translate: 0 100%;
|
||
}
|
||
}
|
||
|
||
/* With stops the panel is the height of its current one, moving between them on the spatial
|
||
spring, independent of the translate that opens and closes the sheet. */
|
||
[data-md-bottom-sheet-panel][data-md-preset] {
|
||
height: var(--sheet-max-height);
|
||
transition-property: translate, height, display;
|
||
transition-duration: var(--md-sys-motion-effects-default-duration), var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-effects-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-easing-emphasized-accelerate), var(--md-sys-motion-spatial-default), var(--md-sys-motion-easing-emphasized-accelerate);
|
||
}
|
||
|
||
[data-md-bottom-sheet][data-md-open] > [data-md-bottom-sheet-panel][data-md-preset] {
|
||
transition-duration: var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-spatial-default-duration);
|
||
transition-timing-function: var(--md-sys-motion-easing-emphasized-decelerate), var(--md-sys-motion-spatial-default), var(--md-sys-motion-easing-emphasized-decelerate);
|
||
}
|
||
|
||
[data-md-bottom-sheet-handle] {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
justify-content: center;
|
||
padding-block: 22px;
|
||
cursor: grab;
|
||
}
|
||
|
||
[data-md-bottom-sheet-handle]:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
[data-md-bottom-sheet-grip] {
|
||
width: 32px;
|
||
height: 4px;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
background-color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-md-bottom-sheet-grip]:focus-visible {
|
||
outline-offset: 4px;
|
||
}
|
||
|
||
[data-md-bottom-sheet-body] {
|
||
min-height: 0;
|
||
flex: 1 1 0%;
|
||
overflow-y: auto;
|
||
padding-inline: var(--md-sys-measurement-space300);
|
||
padding-bottom: var(--md-sys-measurement-space300);
|
||
}
|
||
|
||
[data-md-bottom-sheet-title] {
|
||
margin-bottom: var(--md-sys-measurement-space200);
|
||
font: var(--md-sys-typescale-title-lg);
|
||
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
||
}
|
||
|
||
[data-md-bottom-sheet-actions] {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: var(--md-sys-measurement-space100);
|
||
padding-inline: var(--md-sys-measurement-space300);
|
||
padding-bottom: var(--md-sys-measurement-space300);
|
||
}
|
||
}
|