materialDatepicker set its first day, format, min, max and year range in init() without declaring them, and materialAppBar did the same with its resize observer and scroll handler. Alpine writes an undeclared property to the outermost x-data scope, so two pickers inside one page scope (ReStride's plan setup wraps its form in x-data) shared the last one's min and first day, and a second app bar would have taken the first one's observer. Both now declare them; a browser test puts two pickers in an outer scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
65 lines
2.2 KiB
JavaScript
65 lines
2.2 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,
|
|
// Set in init(). Declared here, or Alpine writes them to the outermost x-data scope,
|
|
// where a second app bar in the same page scope would take the first one's observer.
|
|
schedule: null,
|
|
resizes: 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
|
|
},
|
|
}))
|
|
})
|