Slide the navigation rail and fade its scrim out in Firefox too
A rail that was open over the page left it on `transition: … display … allow-discrete`: the compact adaptive rail's slide-out, the slide-out of a rail that hides when collapsed, and every rail scrim's fade. Firefox does not transition `display`, even with `allow-discrete` (Chrome 117 and Safari 18 do), so there the panel and the scrim vanished on the first frame. Unlike the sheets' scrims these are drawn by attribute rules on `data-md-open`, not `x-show`, so Alpine had nothing to hold. navigation.js now marks a rail that closes `data-md-closing` — `sheet` when the panel leaves the window, `scrim` when it stands in the layout again — and drops it once the panel's `translate` and the scrim's `opacity` transitions have finished (at once under reduced motion, or when the rail opens again). navigation-rail.css keeps what is leaving displayed, and a sliding panel in its open geometry, while the attribute is set, and no longer transitions `display` anywhere, so Chrome and Safari do not hold a second time. The view sets it from `x-effect`, beside the `x-bind` that drops `data-md-open`, so both attributes change in one flush: a `$watch` a microtask later let a style read in between settle the scrim as already hidden. The collapsed branches and `--md-navigation-rail-value` are untouched. NavigationTest samples each exit part-way in the page (the compact slide, the hide-when-collapsed slide, a modal rail's scrim) and checks it ends hidden; all three fail on main in Firefox and pass in Chrome, Firefox and Safari. They wait for the entry to finish first: Firefox creates a transition on its next refresh tick, so straight after a change its computed style already reads the end value. NavigationRailTest pins the holds and that no rule transitions `display`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7db522826b
commit
92e560bef6
@@ -6,7 +6,8 @@
|
||||
* docs/reference/m3/components-navigation-selection-inputs.md § Navigation Rail).
|
||||
*
|
||||
* [data-md-navigation-rail="collapsed|expanded|collapsible|modal|adaptive"] data-md-open
|
||||
* [data-md-navigation-rail-scrim] modal and adaptive rails, open only
|
||||
* data-md-closing="sheet|scrim", while it exits
|
||||
* [data-md-navigation-rail-scrim] modal and adaptive rails, open or closing only
|
||||
* [data-md-navigation-rail-panel] the <nav>
|
||||
* [data-md-navigation-rail-header] never scrolls
|
||||
* [data-md-navigation-rail-menu-row] the menu button and the brand
|
||||
@@ -33,6 +34,15 @@
|
||||
* `resources/js/navigation.js` reads the same numbers, so the menu button and the drawing agree at
|
||||
* every width.
|
||||
*
|
||||
* **Closing**: nothing here transitions `display`, which Firefox cannot do even with
|
||||
* `allow-discrete`, so a panel sliding off the window and a fading scrim were cut to nothing there.
|
||||
* `resources/js/navigation.js` sets `data-md-closing` on a rail that was open over the page until
|
||||
* its exit transitions have run — `sheet` when the panel leaves the window (a compact adaptive
|
||||
* rail, or one that hides when collapsed), `scrim` when the panel stands in the layout again — and
|
||||
* the rules near the end of the file keep what is leaving displayed meanwhile, in every engine.
|
||||
* The collapsed branches do not read it: a closing rail is already collapsed, which is what
|
||||
* `--md-navigation-rail-value` says the moment it starts to close.
|
||||
*
|
||||
* The first set of branches also publishes the answer: `--md-navigation-rail-value` is `expanded`
|
||||
* on every rail and `collapsed` wherever those branches hold — M3's two rail values, Compose's
|
||||
* WideNavigationRailValue — so what an application puts in a rail reads it with
|
||||
@@ -306,16 +316,17 @@
|
||||
content, placed on the content-adjacent edge" — also its answer to a page scrolling under a
|
||||
fixed rail — and "container fill can be turned off (transparent) as long as items keep ≥3:1
|
||||
contrast" (N-22). Neither applies to a rail open over a scrim, a surface over the page. */
|
||||
[data-md-navigation-rail][data-md-divider]:not([data-md-open]) > [data-md-navigation-rail-panel] {
|
||||
[data-md-navigation-rail][data-md-divider]:not([data-md-open], [data-md-closing='sheet']) > [data-md-navigation-rail-panel] {
|
||||
border-inline-end: 1px solid var(--md-sys-color-outline-variant);
|
||||
}
|
||||
|
||||
[data-md-navigation-rail][data-md-fill='false']:not([data-md-open]) > [data-md-navigation-rail-panel] {
|
||||
[data-md-navigation-rail][data-md-fill='false']:not([data-md-open], [data-md-closing='sheet']) > [data-md-navigation-rail-panel] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Open: expanded over a scrim, in surface-container with a large corner at its inner edge. */
|
||||
[data-md-navigation-rail][data-md-open] > [data-md-navigation-rail-panel] {
|
||||
/* Open: expanded over a scrim, in surface-container with a large corner at its inner edge — and
|
||||
still, while a panel that is leaving the window slides off it (`data-md-closing="sheet"`). */
|
||||
[data-md-navigation-rail]:is([data-md-open], [data-md-closing='sheet']) > [data-md-navigation-rail-panel] {
|
||||
position: fixed;
|
||||
inset-block: 0;
|
||||
inset-inline-start: 0;
|
||||
@@ -350,8 +361,7 @@
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
translate: -100% 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
|
||||
|
||||
&:is([dir='rtl'], [dir='rtl'] *) {
|
||||
translate: 100% 0;
|
||||
@@ -362,8 +372,7 @@
|
||||
display: flex;
|
||||
translate: 0 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-easing-emphasized-decelerate),
|
||||
display var(--md-sys-motion-spatial-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-easing-emphasized-decelerate);
|
||||
|
||||
@starting-style {
|
||||
translate: -100% 0;
|
||||
@@ -395,8 +404,7 @@
|
||||
display: none;
|
||||
translate: -100% 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
|
||||
|
||||
&:is([dir='rtl'], [dir='rtl'] *) {
|
||||
translate: 100% 0;
|
||||
@@ -408,8 +416,7 @@
|
||||
display: none;
|
||||
translate: -100% 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
|
||||
|
||||
&:is([dir='rtl'], [dir='rtl'] *) {
|
||||
translate: 100% 0;
|
||||
@@ -422,8 +429,7 @@
|
||||
display: none;
|
||||
translate: -100% 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
|
||||
|
||||
&:is([dir='rtl'], [dir='rtl'] *) {
|
||||
translate: 100% 0;
|
||||
@@ -439,8 +445,7 @@
|
||||
display: none;
|
||||
translate: -100% 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
|
||||
|
||||
&:is([dir='rtl'], [dir='rtl'] *) {
|
||||
translate: 100% 0;
|
||||
@@ -456,8 +461,7 @@
|
||||
display: none;
|
||||
translate: -100% 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-easing-emphasized-accelerate);
|
||||
|
||||
&:is([dir='rtl'], [dir='rtl'] *) {
|
||||
translate: 100% 0;
|
||||
@@ -470,8 +474,7 @@
|
||||
display: flex;
|
||||
translate: 0 0;
|
||||
transition:
|
||||
translate var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-easing-emphasized-decelerate),
|
||||
display var(--md-sys-motion-spatial-default-duration) allow-discrete;
|
||||
translate var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-easing-emphasized-decelerate);
|
||||
|
||||
@starting-style {
|
||||
translate: -100% 0;
|
||||
@@ -486,6 +489,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Closing: what leaves stays drawn while it animates out. The exits above transition `translate`
|
||||
and the scrim's `opacity` only — not `display`, which Firefox cannot transition even with
|
||||
`allow-discrete`, so the slide and the fade were cut to nothing there — and
|
||||
resources/js/navigation.js sets `data-md-closing` on the rail until those transitions have
|
||||
run: `sheet` when the panel leaves the window, `scrim` when only the scrim was over the page.
|
||||
One attribute more than every collapsed branch, so a hold outranks the `display: none` those
|
||||
branches draw. */
|
||||
[data-md-navigation-rail][data-md-hide-when-collapsed][data-md-closing='sheet'] > [data-md-navigation-rail-panel] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (width < 600px) {
|
||||
[data-md-navigation-rail='adaptive'][data-md-closing='sheet'] > [data-md-navigation-rail-panel] {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* The two bands the configuration above does not reach, because there it is the window and not
|
||||
the visitor that collapses the rail: below `medium` for a collapsible rail — already held at
|
||||
its collapsed width above — and `medium` itself for the adaptive one, which M3 gives a
|
||||
@@ -520,9 +540,11 @@
|
||||
display: none;
|
||||
background-color: color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent);
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default),
|
||||
display var(--md-sys-motion-effects-default-duration) allow-discrete;
|
||||
transition: opacity var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);
|
||||
}
|
||||
|
||||
[data-md-navigation-rail][data-md-closing] > [data-md-navigation-rail-scrim] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[data-md-navigation-rail][data-md-open] > [data-md-navigation-rail-scrim] {
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
* (`hide()`). It is never remembered.
|
||||
*
|
||||
* `materialNavigationRail` is one rail's view of the store for its `mode` and whether it hides
|
||||
* when collapsed — see resources/views/components/navigation-rail.blade.php.
|
||||
* when collapsed — see resources/views/components/navigation-rail.blade.php. It also holds a
|
||||
* closing rail on screen for its exit (`closing`, below).
|
||||
*
|
||||
* `materialNavigationBar` is `<x-navigation-bar hide-on-scroll>` — see the same file's sibling.
|
||||
*/
|
||||
@@ -104,6 +105,20 @@ document.addEventListener('alpine:init', () => {
|
||||
queries: [],
|
||||
listeners: [],
|
||||
|
||||
/**
|
||||
* `data-md-closing` while a rail that was open over the page leaves it: `sheet` when the
|
||||
* panel slides away (a compact adaptive rail, or one that hides when collapsed), `scrim`
|
||||
* when only the scrim fades and the panel stands in the layout again. navigation-rail.css
|
||||
* keeps what is leaving displayed while it is set, because the exit cannot hold `display`
|
||||
* itself: Firefox does not transition `display`, even with `allow-discrete`, so the slide
|
||||
* and the fade were cut to nothing there. It is dropped once the exit's own transitions
|
||||
* have finished — none under reduced motion, whose durations are zero — or at once when
|
||||
* the rail opens again.
|
||||
*/
|
||||
closing: null,
|
||||
closings: 0,
|
||||
wasOpen: false,
|
||||
|
||||
init() {
|
||||
if (mode !== 'adaptive') {
|
||||
// Below `medium` a collapsible rail is held at its collapsed width whatever the
|
||||
@@ -152,6 +167,47 @@ document.addEventListener('alpine:init', () => {
|
||||
this.queries.forEach((query, index) => query.removeEventListener('change', this.listeners[index]))
|
||||
},
|
||||
|
||||
/**
|
||||
* Holds a rail that has just closed on screen until its exit has run; see `closing`. The
|
||||
* view calls it from `x-effect`, beside the `x-bind` that drops `data-md-open`, so both
|
||||
* attributes change in the same flush: a `$watch` would set `data-md-closing` a microtask
|
||||
* later, and a style read in between would settle what is leaving as already hidden.
|
||||
*/
|
||||
settle(open) {
|
||||
if (open === this.wasOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
this.wasOpen = open
|
||||
|
||||
const closing = ++this.closings
|
||||
|
||||
if (open) {
|
||||
this.closing = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Whether the panel leaves the window, rather than returning to the layout: a compact
|
||||
// adaptive rail has no place in the layout, and a rail that hides when collapsed has
|
||||
// left it. Decided from the state, not measured: reading the panel's style between the
|
||||
// two attribute changes would settle it as hidden, and nothing would transition.
|
||||
this.closing = this.away || (mode === 'adaptive' && upTo('medium').matches) ? 'sheet' : 'scrim'
|
||||
|
||||
// A frame on, the attributes have met the style, and the exit's transitions exist.
|
||||
requestAnimationFrame(() => {
|
||||
const exits = [...this.$root.querySelectorAll(':scope > :is([data-md-navigation-rail-panel], [data-md-navigation-rail-scrim])')]
|
||||
.flatMap((element) => element.getAnimations())
|
||||
.filter((animation) => ['translate', 'opacity'].includes(animation.transitionProperty))
|
||||
|
||||
Promise.allSettled(exits.map((animation) => animation.finished)).then(() => {
|
||||
if (closing === this.closings) {
|
||||
this.closing = null
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/** What this rail's mode and the visitor's choice make of it, before anything opens it. */
|
||||
get standing() {
|
||||
if (mode === 'expanded') {
|
||||
|
||||
@@ -81,8 +81,11 @@
|
||||
(`false`) drops the container colour for a transparent rail over the page's own background,
|
||||
which M3 allows as long as the items keep a 3:1 contrast against what is behind them. A rail
|
||||
open over a scrim keeps its fill and drops the divider whatever those say: it is a surface
|
||||
over the page then. The rail does not scroll with the page: in a flex row it sticks to the top
|
||||
of the viewport, as tall as the viewport at most.
|
||||
over the page then. As it closes it keeps `data-md-closing` (`sheet` while the panel slides off
|
||||
the window, `scrim` while only the scrim fades) until the exit has run, which is what holds it
|
||||
on screen in Firefox, where `display` cannot transition (resources/js/navigation.js). The rail
|
||||
does not scroll with the page: in a flex row it sticks to the top of the viewport, as tall as
|
||||
the viewport at most.
|
||||
|
||||
Values from androidx Compose Material 3 (Apache-2.0), androidx-main
|
||||
27cf9a7d5788aa0f5f2d8b6699ce279560daf326: NavigationRailCollapsedTokens.kt,
|
||||
@@ -128,6 +131,8 @@
|
||||
@if ($interactive)
|
||||
x-data="materialNavigationRail('{{ $mode }}', {{ $hideWhenCollapsed ? 'true' : 'false' }})"
|
||||
x-bind:data-md-open="open"
|
||||
x-bind:data-md-closing="closing"
|
||||
x-effect="settle(open)"
|
||||
@endif
|
||||
{{ $attributes->merge(['style' => "--navigation-rail-width: {$width}"]) }}
|
||||
>
|
||||
|
||||
@@ -555,3 +555,90 @@ it('reads expanded while a modal rail is open over the page, and collapsed once
|
||||
->assertScript('! '.railShows('expanded-only'))
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
/**
|
||||
* Closes a rail with the given script and reports whether, sampling every few ms in the page
|
||||
* itself, it was ever caught part-way: the whole close in one round trip, since a separate
|
||||
* `script` and `assertScript` apiece already take as long as the 200ms exit (see ContainmentTest's
|
||||
* scrim fades).
|
||||
*/
|
||||
function caughtMidClose(mixed $page, string $close, string $partWay): bool
|
||||
{
|
||||
return $page->script(<<<JS
|
||||
(async () => {
|
||||
{$close}
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
if ({$partWay}) return true
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
}
|
||||
return false
|
||||
})()
|
||||
JS) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opened and done opening: no transition left on the element. Firefox creates a transition on its
|
||||
* next refresh tick, so straight after the change its computed style already reads the end value
|
||||
* while the entry has not begun, and a close then would start from where the entry did.
|
||||
*/
|
||||
function settled(string $element): string
|
||||
{
|
||||
return "{$element}.getAnimations().length === 0";
|
||||
}
|
||||
|
||||
/** Still drawn, and strictly between resting at the window's edge and gone past it. */
|
||||
function slidingOut(string $panel): string
|
||||
{
|
||||
return "(getComputedStyle({$panel}).display !== 'none' && {$panel}.getBoundingClientRect().left < -2 && {$panel}.getBoundingClientRect().right > 2)";
|
||||
}
|
||||
|
||||
it('slides the compact rail out on close, rather than making it vanish', function () {
|
||||
$page = shellPage(599, 860)
|
||||
->click('@shell-menu')
|
||||
->assertScript(RAIL.".hasAttribute('data-md-open')")
|
||||
->assertScript(settled(RAIL_PANEL).' && Math.round('.RAIL_PANEL.'.getBoundingClientRect().left) === 0');
|
||||
|
||||
expect(caughtMidClose($page, "document.querySelector('[data-md-navigation-rail-scrim]').click()", slidingOut(RAIL_PANEL)))->toBeTrue();
|
||||
|
||||
$page->assertScript('getComputedStyle('.RAIL_PANEL.").display === 'none'")
|
||||
->assertScript('! '.RAIL.".hasAttribute('data-md-closing')")
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
it('slides a rail that hides when collapsed out on close, rather than making it vanish', function () {
|
||||
$page = navigationExtrasProbe();
|
||||
$rail = "document.querySelector('#hiding-rail [data-md-navigation-rail]')";
|
||||
$panel = "{$rail}.querySelector(':scope > [data-md-navigation-rail-panel]')";
|
||||
|
||||
$page->click('#hiding-rail [data-md-navigation-rail-menu]')
|
||||
->assertScript("Math.round({$rail}.getBoundingClientRect().width) === 0")
|
||||
->click('#open-hiding-rail')
|
||||
->assertScript("{$rail}.hasAttribute('data-md-open')")
|
||||
->assertScript(settled($panel)." && Math.round({$panel}.getBoundingClientRect().left) === 0 && Math.round({$panel}.getBoundingClientRect().width) === 256");
|
||||
|
||||
expect(caughtMidClose($page, "{$rail}.querySelector(':scope > [data-md-navigation-rail-scrim]').click()", slidingOut($panel)))->toBeTrue();
|
||||
|
||||
$page->assertScript("getComputedStyle({$panel}).display === 'none'")
|
||||
->assertScript("Math.round({$rail}.getBoundingClientRect().width) === 0")
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
it('fades a modal rail\'s scrim out on close while the rail stands collapsed in the layout', function () {
|
||||
$scrim = RAIL.".querySelector(':scope > [data-md-navigation-rail-scrim]')";
|
||||
|
||||
$page = railValueProbe('modal', 1000)
|
||||
->click('#open-rail')
|
||||
->assertScript(RAIL.".hasAttribute('data-md-open')")
|
||||
->assertScript(settled($scrim)." && getComputedStyle({$scrim}).opacity === '1'");
|
||||
|
||||
$fading = "(getComputedStyle({$scrim}).display !== 'none' && parseFloat(getComputedStyle({$scrim}).opacity) > 0.02 && parseFloat(getComputedStyle({$scrim}).opacity) < 0.98)";
|
||||
|
||||
expect(caughtMidClose($page, "{$scrim}.click()", $fading))->toBeTrue();
|
||||
|
||||
// The panel goes straight back into the layout: only the scrim was over the page.
|
||||
$page->assertScript("getComputedStyle({$scrim}).display === 'none'")
|
||||
->assertScript('getComputedStyle('.RAIL_PANEL.").position === 'sticky'")
|
||||
->assertScript(railWidth(96))
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
@@ -63,10 +63,11 @@ it('takes M3\'s optional divider and turns the container fill off', function ()
|
||||
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// Neither reaches a rail open over a scrim, which is a surface over the page (N-22).
|
||||
expect($css->declarations('[data-md-navigation-rail][data-md-divider]:not([data-md-open]) > [data-md-navigation-rail-panel]'))
|
||||
// Neither reaches a rail open over a scrim, which is a surface over the page (N-22), nor one
|
||||
// sliding off the window as it closes.
|
||||
expect($css->declarations('[data-md-navigation-rail][data-md-divider]:not([data-md-open], [data-md-closing=\'sheet\']) > [data-md-navigation-rail-panel]'))
|
||||
->toBe(['border-inline-end' => '1px solid var(--md-sys-color-outline-variant)'])
|
||||
->and($css->declarations('[data-md-navigation-rail][data-md-fill=\'false\']:not([data-md-open]) > [data-md-navigation-rail-panel]'))
|
||||
->and($css->declarations('[data-md-navigation-rail][data-md-fill=\'false\']:not([data-md-open], [data-md-closing=\'sheet\']) > [data-md-navigation-rail-panel]'))
|
||||
->toBe(['background-color' => 'transparent'])
|
||||
// A collapsible rail is held to its collapsed width where M3 asks for a bar instead (N-24).
|
||||
->and($css->declarations('[data-md-navigation-rail=\'collapsible\']', ['@media (width < 600px)']))
|
||||
@@ -151,6 +152,31 @@ it('hides a collapsible or adaptive rail entirely when told to, and only those',
|
||||
* every branch sets, and for `--md-navigation-rail-value`, which each branch turns from `expanded`
|
||||
* to `collapsed` so an application's style query answers exactly when the rail's own shape does.
|
||||
*/
|
||||
it('holds a closing rail on screen for its exit rather than transitioning display', function () {
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// Firefox cannot transition `display`, so no rule tries: the view sets `data-md-closing` beside
|
||||
// `data-md-open`, in the same flush, and navigation.js drops it once the exit has run.
|
||||
$discrete = collect($css->rules())
|
||||
->flatMap(fn (array $rule): array => array_values($rule['declarations']))
|
||||
->filter(fn (string $value): bool => str_contains($value, 'allow-discrete'));
|
||||
|
||||
expect($discrete)->toBeEmpty()
|
||||
->and((string) $this->blade('<x-navigation-rail mode="modal" />'))
|
||||
->toContain('x-bind:data-md-closing="closing"')
|
||||
->toContain('x-effect="settle(open)"')
|
||||
// The panel that leaves the window stays drawn, and in its open geometry, while it slides.
|
||||
->and($css->declarations("[data-md-navigation-rail][data-md-hide-when-collapsed][data-md-closing='sheet'] > [data-md-navigation-rail-panel]"))
|
||||
->toBe(['display' => 'flex'])
|
||||
->and($css->declarations("[data-md-navigation-rail='adaptive'][data-md-closing='sheet'] > [data-md-navigation-rail-panel]", ['@media (width < 600px)']))
|
||||
->toBe(['display' => 'flex'])
|
||||
->and($css->declarations("[data-md-navigation-rail]:is([data-md-open], [data-md-closing='sheet']) > [data-md-navigation-rail-panel]"))
|
||||
->toMatchArray(['position' => 'fixed', 'width' => 'var(--navigation-rail-expanded-width)'])
|
||||
// The scrim stays drawn for its fade whichever way the rail closes.
|
||||
->and($css->declarations('[data-md-navigation-rail][data-md-closing] > [data-md-navigation-rail-scrim]'))
|
||||
->toBe(['display' => 'block']);
|
||||
});
|
||||
|
||||
it('reproduces every branch of the old rail-collapsed variant for the rail\'s own width and its value', function () {
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
$collapsed = ['width' => 'var(--navigation-rail-collapsed-width)', '--md-navigation-rail-value' => 'collapsed'];
|
||||
|
||||
Reference in New Issue
Block a user