From ea529bf785674e85513bdd1ec9fd2d92394ea87a Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Sun, 13 Sep 2026 20:31:33 +0200 Subject: [PATCH] Let a list-detail pane close on Escape when asked A drawer that is a pane from xl ignored Escape, whatever close-on-escape said: the page beside a pane stays in use, so that is the right default. An application whose pane is a transient detail (ReStride's activity and workout panes) can now pass pane-close-on-escape and have Escape close it at every width. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2 --- .../boost/skills/livewire-material-development/SKILL.md | 2 +- resources/views/components/drawer.blade.php | 7 +++++-- tests/Feature/Components/OverlayTest.php | 9 +++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 50782461..3c35741e 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -462,7 +462,7 @@ Props: `title`, `subtitle`, `icon` (centred hero icon), `separator`, `persistent ### `` -An M3 side sheet, bound like ``; `close()` in scope. Props: `title`, `subtitle`, `separator`, `side` (`end` default, `start`), `width` (`25rem`), `with-close-button`, `close-on-escape` (default true), `without-backdrop-close`, `actions` slot. `pane` (with `pane-width`) turns it into a list-detail pane from `xl`: render it after the list inside `
`. Its body is a size container — lay out inside with `@md:` etc., not `sm:`. +An M3 side sheet, bound like ``; `close()` in scope. Props: `title`, `subtitle`, `separator`, `side` (`end` default, `start`), `width` (`25rem`), `with-close-button`, `close-on-escape` (default true), `without-backdrop-close`, `actions` slot. `pane` (with `pane-width`) turns it into a list-detail pane from `xl`: render it after the list inside `
`. Escape leaves a pane open unless `pane-close-on-escape`. Its body is a size container — lay out inside with `@md:` etc., not `sm:`. ### `` diff --git a/resources/views/components/drawer.blade.php b/resources/views/components/drawer.blade.php index 0a66c90b..e83ad5a0 100644 --- a/resources/views/components/drawer.blade.php +++ b/resources/views/components/drawer.blade.php @@ -15,7 +15,9 @@ As a pane (`pane`, from `xl`) nothing is covered: the page renders the drawer after its list in an `xl:flex xl:items-start xl:gap-6` row, the drawer sticks under the top of the viewport, the list stays usable and another row swaps what it shows — no scrim, no trap, no inert page. While - closed it takes no room. `pane-width` sizes the pane (the sheet's width by default). The body is + closed it takes no room. `pane-width` sizes the pane (the sheet's width by default). Escape closes + the sheet but leaves a pane open, since the page beside it is still in use; `pane-close-on-escape` + closes the pane on Escape too, for a pane that is a transient detail. The body is a size container, so its contents lay out by the room the sheet or pane actually has (`@md:`), never by the viewport. @@ -34,6 +36,7 @@ 'width' => '25rem', 'pane' => false, 'paneWidth' => null, + 'paneCloseOnEscape' => false, ]) @php @@ -55,7 +58,7 @@ }, @endif }" - @if ($closeOnEscape) x-on:keydown.window.escape="if (open && ! wide) close()" @endif + @if ($closeOnEscape) x-on:keydown.window.escape="{{ $paneCloseOnEscape ? 'if (open) close()' : 'if (open && ! wide) close()' }}" @endif data-sheet="{{ $id }}" @if ($pane) x-bind:class="! open && 'xl:hidden'" diff --git a/tests/Feature/Components/OverlayTest.php b/tests/Feature/Components/OverlayTest.php index 1539a211..34211e00 100644 --- a/tests/Feature/Components/OverlayTest.php +++ b/tests/Feature/Components/OverlayTest.php @@ -55,6 +55,15 @@ it('keeps a full-screen dialog\'s subtitle on a phone, where its bar carries the ->and((string) $this->blade('Text'))->toContain('

Only a subtitle

'); }); +it('leaves a pane open on Escape unless it is asked to close then too', function () { + expect((string) $this->blade('Body')) + ->toContain('x-on:keydown.window.escape="if (open && ! wide) close()"') + ->and((string) $this->blade('Body')) + ->toContain('x-on:keydown.window.escape="if (open) close()"') + ->and((string) $this->blade('Body')) + ->not->toContain('keydown.window.escape'); +}); + it('slides a side sheet in from either edge, and is a pane from xl when asked', function () { expect((string) $this->blade('Body')) ->toContain('x-trap.inert.noscroll="open && ! wide"')