Flip what the rail on the page draws from $store.rail.toggle()

toggle() was set(!collapsed), and while nothing is stored `collapsed`
is only `rail.default`: an adaptive rail between 840 and 1199px is
drawn collapsed whatever that says, so an application's shortcut
collapsed a collapsed rail and the first press changed nothing. Each
interactive rail now registers its root, and toggle() asks the first
one on the page, the way its menu button asks itself: an open rail is
hidden, a rail with no room to expand (modal, or adaptive below
expanded) opens over its scrim without storing anything, and any
other rail stores the opposite of what it draws, so a rail that hid
itself docks back into the layout as before. With no rail on the
page it still flips the choice. collapse() and expand() stay the
choice itself. The browser tests toggle the scaffold's rail at 960
and 1512px (in place) and at 768 and 393px (the modal), and a hiding
rail back into the layout; the first two fail 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 03:50:00 +02:00
co-authored by Claude Opus 5
parent 252c649c45
commit 394c9326ac
4 changed files with 128 additions and 4 deletions
+88
View File
@@ -117,6 +117,72 @@ it('starts the rail collapsed across the expanded class and expands it in the la
navigationReady($page->refresh())->assertScript(railWidth(256));
});
const TOGGLE_RAIL = "window.eval(\"Alpine.store('rail').toggle()\")";
it('flips the standing rail from $store.rail.toggle() as drawn, whatever is stored', function () {
// The expanded class with nothing stored: `data-rail` says `expanded` (`rail.default`), and
// the rail is drawn collapsed all the same — so an application's own shortcut expands it on
// the first press, as the rail's menu button does, rather than collapsing a collapsed rail.
$page = shellPage(960)
->assertScript(railWidth(96))
->assertScript("document.documentElement.getAttribute('data-rail') === 'expanded'")
->assertScript("document.documentElement.hasAttribute('data-rail-auto')");
$page->script(TOGGLE_RAIL);
$page->assertScript(railWidth(256))
->assertScript('! '.RAIL.".hasAttribute('data-md-open')")
->assertScript("localStorage.getItem('material-rail') === 'expanded'")
->assertAttribute('[data-md-navigation-rail-menu]', 'aria-expanded', 'true');
$page->script(TOGGLE_RAIL);
$page->assertScript(railWidth(96))
->assertScript("localStorage.getItem('material-rail') === 'collapsed'")
->assertNoJavaScriptErrors();
// Large: expanded to begin with, so the first press collapses it.
$page = shellPage(1512)->assertScript(railWidth(256));
$page->script(TOGGLE_RAIL);
$page->assertScript(railWidth(96))
->assertScript("localStorage.getItem('material-rail') === 'collapsed'")
->assertNoJavaScriptErrors();
});
it('opens and closes the modal rail from $store.rail.toggle() below expanded, storing nothing', function () {
// Medium: collapsed in the layout, and expanding it means the scrim, as its menu button does.
$page = shellPage(768)->assertScript(railWidth(96));
$page->script(TOGGLE_RAIL);
$page->assertScript(RAIL.".hasAttribute('data-md-open')")
->assertScript('Math.round('.RAIL_PANEL.'.getBoundingClientRect().width) === 256')
->assertScript(railWidth(96));
$page->script(TOGGLE_RAIL);
$page->assertScript('! '.RAIL.".hasAttribute('data-md-open')")
->assertScript("localStorage.getItem('material-rail') === null")
->assertNoJavaScriptErrors();
// Compact: nothing is drawn until the modal rail slides in, as `$store.rail.show()` brings it.
$page = shellPage(393, 852)->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'");
$page->script(TOGGLE_RAIL);
$page->assertScript(RAIL.".hasAttribute('data-md-open')")
->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'flex'");
$page->script(TOGGLE_RAIL);
$page->assertScript('! '.RAIL.".hasAttribute('data-md-open')")
->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'")
->assertScript("localStorage.getItem('material-rail') === null")
->assertNoJavaScriptErrors();
});
it('morphs the rail header FAB between collapsed (icon only) and extended (with its label)', function () {
$fab = "document.querySelector('[data-md-navigation-rail-fab-row] [data-md-fab]')";
$label = "document.querySelector('[data-md-navigation-rail-fab-row] [data-md-fab] > span')";
@@ -399,6 +465,28 @@ it('takes a rail out of the layout entirely when it hides collapsed, and back ov
->assertNoJavaScriptErrors();
});
it('docks a rail that hid itself back into the layout from $store.rail.toggle(), not over the page', function () {
$page = navigationExtrasProbe();
$rail = "document.querySelector('#hiding-rail [data-md-navigation-rail]')";
$railWidth = "Math.round({$rail}.getBoundingClientRect().width)";
$page->click('#hiding-rail [data-md-navigation-rail-menu]')
->assertScript("{$railWidth} === 0");
// The first interactive rail on the page is this one: the narrow rail above it is fixed.
$page->script(TOGGLE_RAIL);
$page->assertScript("{$railWidth} === 256")
->assertScript("! {$rail}.hasAttribute('data-md-open')")
->assertScript("localStorage.getItem('material-rail') === 'expanded'");
$page->script(TOGGLE_RAIL);
$page->assertScript("{$railWidth} === 0")
->assertScript("! {$rail}.hasAttribute('data-md-open')")
->assertNoJavaScriptErrors();
});
it('takes an adaptive rail out of the layout across the expanded class when it hides collapsed, standing again from large', function () {
$page = navigationExtrasProbe();
$rail = "document.querySelector('#adaptive-hiding-rail [data-md-navigation-rail]')";