Skip to content

Commit 7f53251

Browse files
committed
fix(virtual-core): scroll to index doesn't scroll to bottom correctly
1 parent 2c70382 commit 7f53251

2 files changed

Lines changed: 36 additions & 41 deletions

File tree

‎packages/virtual-core/src/index.ts‎

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,6 @@ export class Virtualizer<
359359
scrollElement: TScrollElement | null = null
360360
targetWindow: (Window & typeof globalThis) | null = null
361361
isScrolling = false
362-
private scrollToIndexTimeoutId: number | null = null
363362
measurementsCache: Array<VirtualItem> = []
364363
private itemSizeCache = new Map<Key, number>()
365364
private pendingMeasuredCacheIndexes: Array<number> = []
@@ -904,7 +903,7 @@ export class Virtualizer<
904903
toOffset -= size
905904
}
906905

907-
const maxOffset = this.getTotalSize() - size
906+
const maxOffset = this.getTotalSize() + this.options.scrollMargin - size
908907

909908
return Math.max(Math.min(maxOffset, toOffset), 0)
910909
}
@@ -943,19 +942,10 @@ export class Virtualizer<
943942

944943
private isDynamicMode = () => this.elementsCache.size > 0
945944

946-
private cancelScrollToIndex = () => {
947-
if (this.scrollToIndexTimeoutId !== null && this.targetWindow) {
948-
this.targetWindow.clearTimeout(this.scrollToIndexTimeoutId)
949-
this.scrollToIndexTimeoutId = null
950-
}
951-
}
952-
953945
scrollToOffset = (
954946
toOffset: number,
955947
{ align = 'start', behavior }: ScrollToOffsetOptions = {},
956948
) => {
957-
this.cancelScrollToIndex()
958-
959949
if (behavior === 'smooth' && this.isDynamicMode()) {
960950
console.warn(
961951
'The `smooth` scroll behavior is not fully supported with dynamic size.',
@@ -972,50 +962,55 @@ export class Virtualizer<
972962
index: number,
973963
{ align: initialAlign = 'auto', behavior }: ScrollToIndexOptions = {},
974964
) => {
975-
index = Math.max(0, Math.min(index, this.options.count - 1))
976-
977-
this.cancelScrollToIndex()
978-
979965
if (behavior === 'smooth' && this.isDynamicMode()) {
980966
console.warn(
981967
'The `smooth` scroll behavior is not fully supported with dynamic size.',
982968
)
983969
}
984970

985-
const offsetAndAlign = this.getOffsetForIndex(index, initialAlign)
986-
if (!offsetAndAlign) return
987-
988-
const [offset, align] = offsetAndAlign
989-
990-
this._scrollToOffset(offset, { adjustments: undefined, behavior })
991-
992-
if (behavior !== 'smooth' && this.isDynamicMode() && this.targetWindow) {
993-
this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => {
994-
this.scrollToIndexTimeoutId = null
971+
index = Math.max(0, Math.min(index, this.options.count - 1))
995972

996-
const elementInDOM = this.elementsCache.has(
997-
this.options.getItemKey(index),
998-
)
973+
let attempts = 0
974+
const maxAttempts = 10
999975

1000-
if (elementInDOM) {
1001-
const result = this.getOffsetForIndex(index, align)
1002-
if (!result) return
1003-
const [latestOffset] = result
976+
const tryScroll = (currentAlign: ScrollAlignment) => {
977+
const offsetInfo = this.getOffsetForIndex(index, currentAlign)
978+
if (!offsetInfo) {
979+
scheduleRetry(currentAlign)
980+
return
981+
}
982+
const [offset, align] = offsetInfo
983+
this._scrollToOffset(offset, { adjustments: undefined, behavior })
984+
985+
requestAnimationFrame(() => {
986+
const currentOffset = this.getScrollOffset()
987+
const afterInfo = this.getOffsetForIndex(index, align)
988+
if (!afterInfo) {
989+
scheduleRetry(align)
990+
return
991+
}
1004992

1005-
const currentScrollOffset = this.getScrollOffset()
1006-
if (!approxEqual(latestOffset, currentScrollOffset)) {
1007-
this.scrollToIndex(index, { align, behavior })
1008-
}
1009-
} else {
1010-
this.scrollToIndex(index, { align, behavior })
993+
if (!approxEqual(afterInfo[0], currentOffset)) {
994+
scheduleRetry(align)
1011995
}
1012996
})
1013997
}
998+
999+
function scheduleRetry(align: ScrollAlignment) {
1000+
attempts++
1001+
if (attempts < maxAttempts) {
1002+
requestAnimationFrame(() => tryScroll(align))
1003+
} else {
1004+
console.warn(
1005+
`Failed to scroll to index ${index} after ${maxAttempts} attempts.`,
1006+
)
1007+
}
1008+
}
1009+
1010+
tryScroll(initialAlign)
10141011
}
10151012

10161013
scrollBy = (delta: number, { behavior }: ScrollToOffsetOptions = {}) => {
1017-
this.cancelScrollToIndex()
1018-
10191014
if (behavior === 'smooth' && this.isDynamicMode()) {
10201015
console.warn(
10211016
'The `smooth` scroll behavior is not fully supported with dynamic size.',

‎packages/virtual-core/src/utils.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function notUndefined<T>(value: T | undefined, msg?: string): T {
8383
}
8484
}
8585

86-
export const approxEqual = (a: number, b: number) => Math.abs(a - b) <= 1
86+
export const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1.01
8787

8888
export const debounce = (
8989
targetWindow: Window & typeof globalThis,

0 commit comments

Comments
 (0)