tests / browser (chrome, chromium) (push) Successful in 3m34s
tests / browser (safari, webkit) (push) Successful in 6m2s
tests / lint (push) Successful in 59s
tests / feature (8.4) (push) Successful in 1m6s
tests / feature (8.5) (push) Successful in 1m9s
tests / browser (firefox, firefox) (push) Successful in 4m20s
M3 Expressive top app bars (small, centered, medium and large flexible, search) that collapse with CSS sticky, docked and floating toolbars, primary and secondary tabs with a view-transition indicator, section navigation, the account menu and theme toggles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
61 lines
2.0 KiB
JavaScript
61 lines
2.0 KiB
JavaScript
/**
|
|
* `materialAppBar`: says when content is under `<x-app-bar>` (`scrolled`) and, for a medium or
|
|
* large bar, when it has collapsed to its row (`collapsed`). The collapsing itself is CSS
|
|
* (resources/css/components/app-bar.css); this only reads the bar's position and height, once per
|
|
* frame while the page scrolls or the bar resizes — its height, because a long title wraps and the
|
|
* bar must stick where exactly its row is left showing.
|
|
*/
|
|
document.addEventListener('alpine:init', () => {
|
|
window.Alpine.data('materialAppBar', () => ({
|
|
scrolled: false,
|
|
collapsed: false,
|
|
height: null,
|
|
frame: null,
|
|
|
|
init() {
|
|
this.measure = this.measure.bind(this)
|
|
this.schedule = () => {
|
|
this.frame ??= requestAnimationFrame(this.measure)
|
|
}
|
|
|
|
this.resizes = new ResizeObserver(this.schedule)
|
|
this.resizes.observe(this.$root)
|
|
window.addEventListener('scroll', this.schedule, { passive: true })
|
|
this.measure()
|
|
},
|
|
|
|
destroy() {
|
|
this.resizes.disconnect()
|
|
window.removeEventListener('scroll', this.schedule)
|
|
cancelAnimationFrame(this.frame)
|
|
},
|
|
|
|
// Bound as the bar's style, so a Livewire morph keeps it.
|
|
get measured() {
|
|
return this.height === null ? {} : { '--app-bar-measured': `${this.height}px` }
|
|
},
|
|
|
|
measure() {
|
|
this.frame = null
|
|
|
|
const bar = this.$root
|
|
this.height = bar.offsetHeight
|
|
|
|
const style = getComputedStyle(bar)
|
|
|
|
if (style.position !== 'sticky') {
|
|
this.scrolled = this.collapsed = false
|
|
|
|
return
|
|
}
|
|
|
|
const top = bar.getBoundingClientRect().top
|
|
const stuckAt = parseFloat(style.top) || 0
|
|
const stuck = window.scrollY > 0 && top <= stuckAt + 0.5
|
|
|
|
this.collapsed = stuck && stuckAt < 0
|
|
this.scrolled = stuck
|
|
},
|
|
}))
|
|
})
|