Send a carousel scroll on when it comes to rest short of its item
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
CI's WebKit twice left the carousel on its first item after Next. A scroll started by the buttons or keys that comes to rest anywhere but its item is now sent there once more; a scroll the person starts themselves cancels that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
889f8a8aa1
commit
7ebcb0565f
@@ -69,6 +69,9 @@ const MEDIUM_ITEM_FLEX_PERCENTAGE = 0.1
|
|||||||
/** A programmatic scroll still counts as where the carousel is going for this long. */
|
/** A programmatic scroll still counts as where the carousel is going for this long. */
|
||||||
const TARGET_MS = 700
|
const TARGET_MS = 700
|
||||||
|
|
||||||
|
// How long the row must go without a scroll event to count as having come to rest.
|
||||||
|
const SETTLE_MS = 150
|
||||||
|
|
||||||
const ITEM = '[data-material-carousel-item]'
|
const ITEM = '[data-material-carousel-item]'
|
||||||
const INTERACTIVE = 'a[href], button, input, select, textarea, summary, [contenteditable], [tabindex]:not([tabindex="-1"])'
|
const INTERACTIVE = 'a[href], button, input, select, textarea, summary, [contenteditable], [tabindex]:not([tabindex="-1"])'
|
||||||
|
|
||||||
@@ -805,6 +808,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
frame: null,
|
frame: null,
|
||||||
target: null,
|
target: null,
|
||||||
targetAt: 0,
|
targetAt: 0,
|
||||||
|
settle: null,
|
||||||
listeners: [],
|
listeners: [],
|
||||||
mutations: null,
|
mutations: null,
|
||||||
resizes: null,
|
resizes: null,
|
||||||
@@ -817,7 +821,12 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
state.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')
|
state.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')
|
||||||
|
|
||||||
this.listen(scroller, 'scroll', () => this.schedule(), { passive: true })
|
this.listen(scroller, 'scroll', () => this.scrolled(), { passive: true })
|
||||||
|
|
||||||
|
// A scroll the person makes themselves is theirs to end wherever it ends.
|
||||||
|
for (const type of ['pointerdown', 'wheel', 'touchstart']) {
|
||||||
|
this.listen(scroller, type, () => (state.target = null), { passive: true })
|
||||||
|
}
|
||||||
this.listen(scroller, 'keydown', (event) => this.navigate(event))
|
this.listen(scroller, 'keydown', (event) => this.navigate(event))
|
||||||
this.listen(scroller, 'focusin', (event) => this.reveal(event))
|
this.listen(scroller, 'focusin', (event) => this.reveal(event))
|
||||||
this.listen(scroller, 'click', (event) => this.open(event))
|
this.listen(scroller, 'click', (event) => this.open(event))
|
||||||
@@ -913,6 +922,33 @@ document.addEventListener('alpine:init', () => {
|
|||||||
state.frame ??= requestAnimationFrame(() => this.render())
|
state.frame ??= requestAnimationFrame(() => this.render())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each scroll frame, and once the row comes to rest: a scroll the buttons or keys started
|
||||||
|
* must end on its item. WebKit on Linux can re-snap a smooth scroll to the item it left
|
||||||
|
* when the masks change the layout under it, so an arrival somewhere else is sent on
|
||||||
|
* again, once, at once.
|
||||||
|
*/
|
||||||
|
scrolled() {
|
||||||
|
this.schedule()
|
||||||
|
|
||||||
|
clearTimeout(state.settle)
|
||||||
|
state.settle = setTimeout(() => {
|
||||||
|
const target = state.target
|
||||||
|
|
||||||
|
if (target === null || performance.now() - state.targetAt > TARGET_MS * 3) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.target = null
|
||||||
|
|
||||||
|
const left = state.snaps[target]
|
||||||
|
|
||||||
|
if (left !== undefined && Math.abs(this.scrollOffset() - left) > 1) {
|
||||||
|
this.$refs.scroller.scrollTo({ left: state.rtl ? -left : left, behavior: 'instant' })
|
||||||
|
}
|
||||||
|
}, SETTLE_MS)
|
||||||
|
},
|
||||||
|
|
||||||
/** Carousel.kt's carouselItem layer block, for every item. */
|
/** Carousel.kt's carouselItem layer block, for every item. */
|
||||||
render() {
|
render() {
|
||||||
cancelAnimationFrame(state.frame)
|
cancelAnimationFrame(state.frame)
|
||||||
@@ -1076,6 +1112,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
cancelAnimationFrame(state.frame)
|
cancelAnimationFrame(state.frame)
|
||||||
|
clearTimeout(state.settle)
|
||||||
state.listeners.forEach((remove) => remove())
|
state.listeners.forEach((remove) => remove())
|
||||||
state.resizes?.disconnect()
|
state.resizes?.disconnect()
|
||||||
state.mutations?.disconnect()
|
state.mutations?.disconnect()
|
||||||
|
|||||||
Reference in New Issue
Block a user