diff --git a/resources/css/components/drawer.css b/resources/css/components/drawer.css index 4795bf53..6fb1f3b1 100644 --- a/resources/css/components/drawer.css +++ b/resources/css/components/drawer.css @@ -195,6 +195,13 @@ display: none; } + /* Until the view settles (`data-md-drawer-settled`, two frames after Alpine starts), the + state the page loads with is drawn at once: a sheet that starts open does not grow in. */ + [data-md-drawer][data-md-standard]:not([data-md-drawer-settled]), + [data-md-drawer][data-md-standard]:not([data-md-drawer-settled]) > [data-md-drawer-sheet] { + transition: none; + } + [data-md-drawer][data-md-standard] > [data-md-drawer-scrim] { display: none; } diff --git a/resources/views/components/drawer.blade.php b/resources/views/components/drawer.blade.php index 04d44c34..043aea01 100644 --- a/resources/views/components/drawer.blade.php +++ b/resources/views/components/drawer.blade.php @@ -30,7 +30,9 @@ 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. + 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. For the second pane of a list-detail layout use `` instead (step 35's canonical layout); the two are not the same thing — a pane shows what the list beside it selected, a @@ -85,6 +87,7 @@ x-data="{ @if ($model !== null) open: @entangle($attributes->wire('model')).live, @endif wide: false, + settled: false, collapsed: false, closing: false, closings: 0, @@ -93,9 +96,17 @@ settle(open) { open = Boolean(open); - if (this.wasOpen === null || open === this.wasOpen) { + if (this.wasOpen === null) { this.wasOpen = open; + // The state Alpine starts with is drawn, not animated: two frames on, once it has + // been painted, the sheet may move. + requestAnimationFrame(() => requestAnimationFrame(() => this.settled = true)); + + return; + } + + if (open === this.wasOpen) { return; } @@ -141,6 +152,7 @@ @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="collapsed ? '' : null" + x-bind:data-md-drawer-settled="settled ? '' : null" x-effect="settle(open)" data-md-drawer @if ($standard) data-md-standard @endif diff --git a/tests/Browser/ContainmentTest.php b/tests/Browser/ContainmentTest.php index c9256538..cbe7ed27 100644 --- a/tests/Browser/ContainmentTest.php +++ b/tests/Browser/ContainmentTest.php @@ -515,8 +515,8 @@ it('gives a closing standard side sheet\'s room back to the content beside it as $column = "{$root}.previousElementSibling"; $toggle = "Array.from(document.querySelectorAll('#containment button')).find((button) => button.textContent.trim() === 'Show or hide the filters')"; - // The example starts open, and grows in once Alpine opens it: its entry has run when the sheet - // stands at its full 400px and fully shown, with nothing on it still animating. + // The example starts open, and stands open from the first frame Alpine draws: full 400px, fully + // shown, nothing on it animating. $page = containment()->resize(1000, 800) ->assertScript("{$root}.hasAttribute('data-md-open') && Math.round({$root}.getBoundingClientRect().width) === 400 && getComputedStyle({$sheet}).opacity === '1' && {$root}.getAnimations({ subtree: true }).length === 0"); @@ -565,6 +565,42 @@ it('gives a closing standard side sheet\'s room back to the content beside it as ->assertScript("{$root}.hasAttribute('data-md-open') && ! {$root}.hasAttribute('data-md-drawer-collapsed') && Math.abs({$root}.getBoundingClientRect().width - 400) < 1 && getComputedStyle({$sheet}).opacity === '1'"); }); +it('draws a standard side sheet that starts open standing open, without growing it in on load', function () { + // Slow tokens, so a load-time entry would still be running when the page is first read. + Route::middleware('web')->get('/standard-sheet-load-probe', fn () => Blade::render(<<<'BLADE' + + + + + @vite(config('livewire-material.showcase.vite')) + @livewireStyles + + + + + + + + Filter the list + + @livewireScripts + + + BLADE)); + + $root = "document.querySelector('[data-md-drawer]')"; + + $page = visit('/standard-sheet-load-probe')->resize(1000, 800)->waitForEvent('networkidle') + ->assertScript("typeof window.Alpine !== 'undefined' && {$root}.hasAttribute('data-md-drawer-settled')") + ->assertScript("{$root}.hasAttribute('data-md-open') && Math.abs({$root}.getBoundingClientRect().width - 400) < 1") + ->assertScript("{$root}.getAnimations({ subtree: true }).length === 0") + ->assertScript("getComputedStyle({$root}.querySelector('[data-md-drawer-sheet]')).opacity === '1'"); + + // Settled, it still moves when someone closes it. + $page->script("document.getElementById('toggle-sheet').click()"); + $page->assertScript("{$root}.getAnimations({ subtree: true }).length > 0"); +}); + it('opens a row\'s opener from a press anywhere on the row, but not from its own buttons', function () { $page = containment();