fix(virtual-core): stop iOS deferred scroll adjustments from replaying stale deltas by piecyk · Pull Request #1235 · TanStack/virtual · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/ios-deferral-stale-replay.md
20 changes: 20 additions & 0 deletions packages/virtual-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,14 @@ export class Virtualizer<
const cur = this.getScrollOffset()
const max = this.getMaxScrollOffset()
if (cur < 0 || cur > max) return
// At the end clamp the browser already absorbed a shrink above the
// viewport (it clamped scrollTop onto the new bottom), so replaying our
// deferred negative delta would lift the view off the bottom — drop it.
// Positive deltas still replay: growth above doesn't clamp. (#1233)
if (this._iosDeferredAdjustment < 0 && cur >= max - 1) {
this._iosDeferredAdjustment = 0
return
}
const delta = this._iosDeferredAdjustment
this._iosDeferredAdjustment = 0
// Roll the deferred delta into the running accumulator so any resize
Expand Down Expand Up @@ -1730,6 +1738,14 @@ export class Virtualizer<
toOffset: number,
{ align = 'start', behavior = 'auto' }: ScrollToOffsetOptions = {},
) => {
// An absolute scroll command derives its target from current
// measurements, so any iOS-deferred compensation still pending is stale by
// definition — the command already accounts for the measurements the delta
// was compensating for. Drop it so _flushIosDeferredIfReady doesn't replay
// it onto the just-established position (relative commands like scrollBy
// intentionally keep the deferral, since they build on the current offset).
this._iosDeferredAdjustment = 0

const offset = this.getOffsetForAlignment(toOffset, align)

const now = this.now()
Expand All @@ -1754,6 +1770,10 @@ export class Virtualizer<
behavior = 'auto',
}: ScrollToIndexOptions = {},
) => {
// See scrollToOffset: an absolute target invalidates any pending
// iOS-deferred compensation.
this._iosDeferredAdjustment = 0

index = Math.max(0, Math.min(index, this.options.count - 1))

const offsetInfo = this.getOffsetForIndex(index, initialAlign)
Expand Down
152 changes: 152 additions & 0 deletions packages/virtual-core/tests/index.test.ts
Loading