Merge branch 'worktree-agent-aed4618fbd0b6aeac'
This commit is contained in:
+127
-16
@@ -13,10 +13,12 @@
|
||||
* button opens it over a scrim (`show()`), and Escape, the scrim or leaving the page closes it
|
||||
* (`hide()`). It is never remembered.
|
||||
*
|
||||
* `materialNavigationRail` is one rail's view of the store for its `mode` — see
|
||||
* resources/views/components/navigation-rail.blade.php.
|
||||
* `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.
|
||||
*
|
||||
* `materialNavigationBar` is `<x-navigation-bar hide-on-scroll>` — see the same file's sibling.
|
||||
*/
|
||||
import { from } from './breakpoints.js'
|
||||
import { from, upTo } from './breakpoints.js'
|
||||
|
||||
/*
|
||||
* The active indicator grows out of its centre when a page arrives through wire:navigate. The
|
||||
@@ -94,14 +96,29 @@ document.addEventListener('alpine:init', () => {
|
||||
// A destination chosen in the modal rail leaves the page; the next one starts with it shut.
|
||||
document.addEventListener('livewire:navigating', () => window.Alpine.store('rail').hide())
|
||||
|
||||
window.Alpine.data('materialNavigationRail', (mode) => ({
|
||||
window.Alpine.data('materialNavigationRail', (mode, hideWhenCollapsed = false) => ({
|
||||
wide: mode === 'adaptive' ? from('expanded').matches : false,
|
||||
roomy: mode === 'adaptive' ? from('large').matches : false,
|
||||
tight: mode === 'collapsible' ? upTo('medium').matches : false,
|
||||
queries: [],
|
||||
listeners: [],
|
||||
|
||||
init() {
|
||||
if (mode !== 'adaptive') {
|
||||
// Below `medium` a collapsible rail is held at its collapsed width whatever the
|
||||
// visitor chose, so `hide-when-collapsed` does not reach it there.
|
||||
// A drawer left open as the window narrows past it is shut, as the adaptive
|
||||
// rail's is below, or it would spring open again the next time the rail is away.
|
||||
if (mode === 'collapsible' && hideWhenCollapsed) {
|
||||
this.watch(upTo('medium'), (matches) => {
|
||||
this.tight = matches
|
||||
|
||||
if (matches) {
|
||||
this.$store.rail.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -134,17 +151,9 @@ document.addEventListener('alpine:init', () => {
|
||||
this.queries.forEach((query, index) => query.removeEventListener('change', this.listeners[index]))
|
||||
},
|
||||
|
||||
/** Whether this rail expands over a scrim rather than in the layout. */
|
||||
get modal() {
|
||||
return mode === 'modal' || (mode === 'adaptive' && !this.wide)
|
||||
},
|
||||
|
||||
get open() {
|
||||
return this.modal && this.$store.rail.open
|
||||
},
|
||||
|
||||
get expanded() {
|
||||
if (this.open || mode === 'expanded') {
|
||||
/** What this rail's mode and the visitor's choice make of it, before anything opens it. */
|
||||
get standing() {
|
||||
if (mode === 'expanded') {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -161,9 +170,49 @@ document.addEventListener('alpine:init', () => {
|
||||
return mode === 'collapsible'
|
||||
},
|
||||
|
||||
/** Whether the mode or the window leaves no room for an expanded rail in the layout. */
|
||||
get cramped() {
|
||||
return mode === 'modal' || (mode === 'adaptive' && !this.wide)
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the rail has left the layout altogether — `hide-when-collapsed`, once the
|
||||
* visitor collapses it. Not in the two bands where it is the window size class and not the
|
||||
* visitor that collapses a rail: M3's "collapsed rail may not hide". The same two numbers
|
||||
* are in resources/css/components/navigation.css.
|
||||
*/
|
||||
get away() {
|
||||
if (!hideWhenCollapsed || this.cramped || this.standing) {
|
||||
return false
|
||||
}
|
||||
|
||||
return mode === 'adaptive' ? this.wide : !this.tight
|
||||
},
|
||||
|
||||
/** Whether this rail expands over a scrim rather than in the layout. */
|
||||
get modal() {
|
||||
// A rail that is away has nothing left in the layout to expand, so the menu button
|
||||
// that brings it back — the app bar's — opens it over the page.
|
||||
return this.cramped || this.away
|
||||
},
|
||||
|
||||
get open() {
|
||||
return this.modal && this.$store.rail.open
|
||||
},
|
||||
|
||||
get expanded() {
|
||||
return this.open || this.standing
|
||||
},
|
||||
|
||||
/** The rail's own menu button: open or close the modal, or collapse and expand in place. */
|
||||
menu() {
|
||||
if (this.modal) {
|
||||
if (this.open && this.away) {
|
||||
// This rail is only over the page because it hid itself, so the button docks it
|
||||
// back into the layout — the same "expand" it means on a rail that is standing
|
||||
// there. Expanding drops `away`, which closes the drawer behind it.
|
||||
this.$store.rail.expand()
|
||||
this.$store.rail.hide()
|
||||
} else if (this.modal) {
|
||||
this.$store.rail.open ? this.$store.rail.hide() : this.$store.rail.show()
|
||||
} else {
|
||||
// `set`, not `toggle`: with nothing stored the store's `collapsed` is only
|
||||
@@ -172,4 +221,66 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
window.Alpine.data('materialNavigationBar', () => ({
|
||||
away: false,
|
||||
last: 0,
|
||||
frame: null,
|
||||
// Set in init(), so a second bar in the same page scope cannot take the first one's.
|
||||
schedule: null,
|
||||
|
||||
init() {
|
||||
this.last = Math.max(window.scrollY, 0)
|
||||
this.measure = this.measure.bind(this)
|
||||
this.schedule = () => {
|
||||
this.frame ??= requestAnimationFrame(this.measure)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', this.schedule, { passive: true })
|
||||
},
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('scroll', this.schedule)
|
||||
cancelAnimationFrame(this.frame)
|
||||
},
|
||||
|
||||
/** Anything that reaches the bar — the keyboard, a screen reader's focus — brings it back. */
|
||||
show() {
|
||||
this.away = false
|
||||
},
|
||||
|
||||
measure() {
|
||||
this.frame = null
|
||||
|
||||
const at = Math.max(window.scrollY, 0)
|
||||
const by = at - this.last
|
||||
|
||||
// Smaller than a finger's jitter, or the rubber band at either end of the page: not a
|
||||
// direction yet, and the bar should not flicker while one is being decided.
|
||||
if (Math.abs(by) < 8) {
|
||||
return
|
||||
}
|
||||
|
||||
this.last = at
|
||||
|
||||
// A bar that is not on screen at this width (the shell hides it from `medium`) has no
|
||||
// scroll behaviour to have; one with something anchored to its edge keeps still, so
|
||||
// the snackbar or sheet resting on it does not slide with it.
|
||||
if (this.$root.getClientRects().length === 0 || anchored()) {
|
||||
this.away = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Never before the first screenful: the bar has to be passed before it can be left.
|
||||
this.away = by > 0 && at > this.$root.offsetHeight
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* Whether something on screen is anchored to the bar's edge and would be dragged along with it: a
|
||||
* snackbar (`<x-toast>`'s, which reads --material-bottom-bar), or a bottom sheet or drawer over the
|
||||
* page. M3 lets those cover the bar; it is the bar leaving from under them that looks broken.
|
||||
*/
|
||||
const anchored = () => [...document.querySelectorAll('[data-toast], [role="dialog"]')].some((over) => over.getClientRects().length > 0)
|
||||
|
||||
Reference in New Issue
Block a user