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. */
|
||||
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 INTERACTIVE = 'a[href], button, input, select, textarea, summary, [contenteditable], [tabindex]:not([tabindex="-1"])'
|
||||
|
||||
@@ -805,6 +808,7 @@ document.addEventListener('alpine:init', () => {
|
||||
frame: null,
|
||||
target: null,
|
||||
targetAt: 0,
|
||||
settle: null,
|
||||
listeners: [],
|
||||
mutations: null,
|
||||
resizes: null,
|
||||
@@ -817,7 +821,12 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
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, 'focusin', (event) => this.reveal(event))
|
||||
this.listen(scroller, 'click', (event) => this.open(event))
|
||||
@@ -913,6 +922,33 @@ document.addEventListener('alpine:init', () => {
|
||||
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. */
|
||||
render() {
|
||||
cancelAnimationFrame(state.frame)
|
||||
@@ -1076,6 +1112,7 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
destroy() {
|
||||
cancelAnimationFrame(state.frame)
|
||||
clearTimeout(state.settle)
|
||||
state.listeners.forEach((remove) => remove())
|
||||
state.resizes?.disconnect()
|
||||
state.mutations?.disconnect()
|
||||
|
||||
Reference in New Issue
Block a user