Give a closing standard side sheet's room back as it leaves
From `expanded` a standard side sheet sits in the layout beside the content, and the view took it out of the layout the moment it closed: `data-md-drawer-collapsed` (`display: none` on the root) followed `! open` at once, so its fade was never seen and the content beside it took the sheet's width, and the gap, in one jump — in every engine. M3's side sheets: opening a standard sheet shrinks the body beside it, and closing gives the room back. drawer.css: the standard root is a clipping flex box whose `inline-size` springs between none and the sheet's width, with a negative margin as wide as its flex parent's gap (`--md-drawer-gap`), on the sheet's own exit and entry timings; the sheet keeps its width at the root's far edge, so it is uncovered from its inner edge while it fades. drawer.blade.php: `settle()`, from `x-effect`, measures the gap on each change and holds the collapse (`closing`) for the root's and the sheet's closing durations, read a frame on; reopening lets a pending end go by. The binding reads `collapsed`, which only `settle()` and the window's width write: a binding reading `open` ran before `settle()` in the same flush and, already queued, did not run again once `closing` changed. ContainmentTest closes the showcase's sheet at 1000px and samples, in the page, for the root part-way to none while the column beside it has grown part of the way, then checks the collapse, the column at the row's full width, and a reopen part-way through the exit ending fully open; it fails on the previous code in Chrome, Firefox and Safari. OverlayTest pins the root's declarations and the view's bindings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
db6023bf80
commit
99c18e01d8
@@ -23,7 +23,14 @@
|
||||
modal sheet — M3 calls the standard sheet "supplementary surfaces mainly for medium to
|
||||
expanded breakpoints" and the modal one "preferred at compact breakpoints", and the switch
|
||||
sits at `expanded` rather than `medium` because M3 also caps a side sheet at 400dp and a
|
||||
600px window has too little left beside one.
|
||||
600px window has too little left beside one. Opening one shrinks the body beside it and closing
|
||||
one gives the room back (M3's side sheets, "Adaptive"): the root's inline size, and a negative
|
||||
margin as wide as its flex parent's gap, spring between none and the sheet's width while the
|
||||
sheet fades, and `data-md-drawer-collapsed` — which takes the closed sheet out of the layout —
|
||||
waits for that exit (`closing`, `settle()`) instead of cutting it off on its first frame. It
|
||||
binds `collapsed`, which only `settle()` and the window's width write: a binding that read
|
||||
`open` itself would run before `settle()` in the same flush and, already queued, not run again
|
||||
once `closing` changed.
|
||||
|
||||
For the second pane of a list-detail layout use `<x-list-detail>` instead (step 35's canonical
|
||||
layout); the two are not the same thing — a pane shows what the list beside it selected, a
|
||||
@@ -78,20 +85,66 @@
|
||||
x-data="{
|
||||
@if ($model !== null) open: @entangle($attributes->wire('model')).live, @endif
|
||||
wide: false,
|
||||
collapsed: false,
|
||||
closing: false,
|
||||
closings: 0,
|
||||
wasOpen: null,
|
||||
close() { this.open = typeof this.open === 'boolean' ? false : null; },
|
||||
settle(open) {
|
||||
open = Boolean(open);
|
||||
|
||||
if (this.wasOpen === null || open === this.wasOpen) {
|
||||
this.wasOpen = open;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.wasOpen = open;
|
||||
|
||||
const closing = ++this.closings;
|
||||
const gap = parseFloat(getComputedStyle(this.$el.parentElement).columnGap);
|
||||
|
||||
this.$el.style.setProperty('--md-drawer-gap', Number.isNaN(gap) ? '0px' : gap + 'px');
|
||||
this.closing = ! open;
|
||||
|
||||
if (open) {
|
||||
this.collapsed = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const ms = (value) => parseFloat(value) * (value.trim().endsWith('ms') ? 1 : 1000);
|
||||
const longest = (element) => Math.max(0, ...getComputedStyle(element).transitionDuration.split(',').map(ms));
|
||||
const duration = Math.max(longest(this.$el), ...[...this.$el.children].map(longest));
|
||||
|
||||
setTimeout(() => {
|
||||
if (closing === this.closings) {
|
||||
this.closing = false;
|
||||
this.collapsed = this.wide;
|
||||
}
|
||||
}, duration);
|
||||
});
|
||||
},
|
||||
@if ($standard)
|
||||
init() {
|
||||
const query = window.matchMedia('(width >= 840px)');
|
||||
this.wide = query.matches;
|
||||
query.addEventListener('change', (event) => this.wide = event.matches);
|
||||
this.collapsed = ! this.open && this.wide;
|
||||
query.addEventListener('change', (event) => {
|
||||
this.wide = event.matches;
|
||||
this.collapsed = ! this.open && this.wide && ! this.closing;
|
||||
});
|
||||
},
|
||||
@endif
|
||||
}"
|
||||
@if ($closeOnEscape) x-on:keydown.window.escape="if (open && ! wide) close()" @endif
|
||||
x-bind:data-md-open="open ? '' : null"
|
||||
x-bind:data-md-drawer-collapsed="(! open && wide) ? '' : null"
|
||||
x-bind:data-md-drawer-collapsed="collapsed ? '' : null"
|
||||
x-effect="settle(open)"
|
||||
data-md-drawer
|
||||
@if ($standard) data-md-standard @endif
|
||||
style="--sheet-width: {{ $sheetWidth }}"
|
||||
>
|
||||
{{-- `md-transition` is a class name only to turn on Alpine's CSS transition: `x-show` then keeps
|
||||
the scrim and the sheet displayed for their computed transition-duration (drawer.css's
|
||||
@@ -114,7 +167,6 @@
|
||||
id="{{ $id }}"
|
||||
data-md-drawer-sheet
|
||||
data-md-side="{{ $side }}"
|
||||
style="--sheet-width: {{ $sheetWidth }}"
|
||||
{{ $attributes->whereDoesntStartWith('wire:model')->except(['id']) }}
|
||||
>
|
||||
@if (filled($title) || $closeButton)
|
||||
|
||||
Reference in New Issue
Block a user