Keep a closed standard side sheet out of its row from the first paint

A closed standard sheet is `inline-size: 0` with a negative margin as
wide as its row's gap, but the view writes that gap (`--md-drawer-gap`)
and `data-md-drawer-collapsed` only once Alpine runs. Until then each
closed sheet was a zero-wide flex item that still cost the row one gap,
and a sheet bound to an open Livewire property had no width at all, so
the content beside them changed width when the script started - two
closed sheets beside a column made it 48px narrower, then it jumped.
Until the view has settled, drawer.css now takes a standard sheet that
is not `data-md-open` out of the layout, and the view renders
`data-md-open` on a standard sheet whose `wire:model` property is open
(truthy as Alpine reads it), so it stands at its width from the first
paint. Transitions still wait for `data-md-drawer-settled`. A browser
test measures a column beside two closed sheets (one bound to Livewire,
one to Alpine) and an open one before Alpine starts and once settled;
it fails without the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-17 06:09:29 +02:00
co-authored by Claude Opus 5
parent 207270a23d
commit a6dc213e67
4 changed files with 87 additions and 1 deletions
+14 -1
View File
@@ -37,7 +37,10 @@
`open` itself would run before `settle()` in the same flush and, already queued, not run again
once `closing` changed. Nothing moves while the page loads: `data-md-drawer-settled` comes two
frames after the state Alpine starts with, and the standard sheet has no transitions until
then, so a sheet that starts open stands open rather than growing in.
then, so a sheet that starts open stands open rather than growing in. Before the script starts
at all, a standard sheet whose `wire:model` property is open is rendered `data-md-open` and
stands at its width, and any other is out of the row, so the content beside it keeps its width
when Alpine runs.
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
@@ -82,6 +85,15 @@
// a wider one is the modal sheet's to take, not the co-planar standard sheet's.
$sheetWidth = $standard ? "min({$width}, 400px)" : $width;
// A standard sheet bound to Livewire is drawn open from the first paint when its property is
// (truthy as Alpine reads it), so it stands in the row before the script starts rather than
// growing into it; drawer.css keeps any other standard sheet out of the row until then.
$startsOpen = false;
if ($standard && $model !== null && ($component = \Livewire\Livewire::current()) !== null) {
$startsOpen = ! in_array(data_get($component, $model), [null, false, 0, 0.0, ''], true);
}
// M3 requires a close affordance; the prop can only ever add one, never take away the last
// way out of the sheet. A standard sheet keeps no scrim and no trap from `expanded`, and
// Escape leaves it open there, so it always draws one.
@@ -160,6 +172,7 @@
x-effect="settle(open)"
data-md-drawer
@if ($standard) data-md-standard @endif
@if ($startsOpen) data-md-open @endif
style="--sheet-width: {{ $sheetWidth }}"
>
{{-- `md-transition` is a class name only to turn on Alpine's CSS transition: `x-show` then keeps