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
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user