@tanstack/virtual-core 3.0.0-beta.2 → 3.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import observeRect from '@reach/observe-rect'
2
1
  import { memo } from './utils'
3
2
 
4
3
  export * from './utils'
@@ -7,8 +6,9 @@ export * from './utils'
7
6
 
8
7
  type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'
9
8
 
10
- interface ScrollToOptions {
11
- align: ScrollAlignment
9
+ export interface ScrollToOptions {
10
+ align?: ScrollAlignment
11
+ smoothScroll?: boolean
12
12
  }
13
13
 
14
14
  type ScrollToOffsetOptions = ScrollToOptions
@@ -24,7 +24,7 @@ export interface Range {
24
24
 
25
25
  type Key = number | string
26
26
 
27
- interface Item {
27
+ export interface VirtualItem {
28
28
  key: Key
29
29
  index: number
30
30
  start: number
@@ -37,10 +37,6 @@ interface Rect {
37
37
  height: number
38
38
  }
39
39
 
40
- export interface VirtualItem<TItemElement> extends Item {
41
- measureElement: (el: TItemElement | null) => void
42
- }
43
-
44
40
  //
45
41
 
46
42
  export const defaultKeyExtractor = (index: number) => index
@@ -58,12 +54,34 @@ export const defaultRangeExtractor = (range: Range) => {
58
54
  return arr
59
55
  }
60
56
 
57
+ const memoRectCallback = (
58
+ instance: Virtualizer<any, any>,
59
+ cb: (rect: Rect) => void,
60
+ ) => {
61
+ let prev: Rect = { height: -1, width: -1 }
62
+
63
+ return (rect: Rect) => {
64
+ if (
65
+ instance.options.horizontal
66
+ ? rect.width !== prev.width
67
+ : rect.height !== prev.height
68
+ ) {
69
+ cb(rect)
70
+ }
71
+
72
+ prev = rect
73
+ }
74
+ }
75
+
61
76
  export const observeElementRect = (
62
77
  instance: Virtualizer<any, any>,
63
78
  cb: (rect: Rect) => void,
64
79
  ) => {
65
- const observer = observeRect(instance.scrollElement as Element, (rect) => {
66
- cb(rect)
80
+ const observer = new ResizeObserver((entries) => {
81
+ cb({
82
+ width: entries[0]?.contentRect.width as number,
83
+ height: entries[0]?.contentRect.height as number,
84
+ })
67
85
  })
68
86
 
69
87
  if (!instance.scrollElement) {
@@ -72,10 +90,10 @@ export const observeElementRect = (
72
90
 
73
91
  cb(instance.scrollElement.getBoundingClientRect())
74
92
 
75
- observer.observe()
93
+ observer.observe(instance.scrollElement)
76
94
 
77
95
  return () => {
78
- observer.unobserve()
96
+ observer.unobserve(instance.scrollElement)
79
97
  }
80
98
  }
81
99
 
@@ -83,12 +101,12 @@ export const observeWindowRect = (
83
101
  instance: Virtualizer<any, any>,
84
102
  cb: (rect: Rect) => void,
85
103
  ) => {
86
- const onResize = () => {
87
- cb({
104
+ const memoizedCallback = memoRectCallback(instance, cb)
105
+ const onResize = () =>
106
+ memoizedCallback({
88
107
  width: instance.scrollElement.innerWidth,
89
108
  height: instance.scrollElement.innerHeight,
90
109
  })
91
- }
92
110
 
93
111
  if (!instance.scrollElement) {
94
112
  return
@@ -106,104 +124,111 @@ export const observeWindowRect = (
106
124
  }
107
125
  }
108
126
 
109
- export const observeElementOffset = (
110
- instance: Virtualizer<any, any>,
111
- cb: (offset: number) => void,
112
- ) => {
113
- const onScroll = () =>
114
- cb(
115
- instance.scrollElement[
116
- instance.options.horizontal ? 'scrollLeft' : 'scrollTop'
117
- ],
118
- )
127
+ type ObserverMode = 'element' | 'window'
119
128
 
120
- if (!instance.scrollElement) {
121
- return
122
- }
129
+ const scrollProps = {
130
+ element: ['scrollLeft', 'scrollTop'],
131
+ window: ['scrollX', 'scrollY'],
132
+ } as const
123
133
 
124
- onScroll()
134
+ const createOffsetObserver = (mode: ObserverMode) => {
135
+ return (instance: Virtualizer<any, any>, cb: (offset: number) => void) => {
136
+ if (!instance.scrollElement) {
137
+ return
138
+ }
125
139
 
126
- instance.scrollElement.addEventListener('scroll', onScroll, {
127
- capture: false,
128
- passive: true,
129
- })
140
+ const propX = scrollProps[mode][0]
141
+ const propY = scrollProps[mode][1]
130
142
 
131
- return () => {
132
- instance.scrollElement.removeEventListener('scroll', onScroll)
133
- }
134
- }
143
+ let prevX: number = instance.scrollElement[propX]
144
+ let prevY: number = instance.scrollElement[propY]
135
145
 
136
- export const observeWindowOffset = (
137
- instance: Virtualizer<any, any>,
138
- cb: (offset: number) => void,
139
- ) => {
140
- const onScroll = () =>
141
- cb(
142
- instance.scrollElement[
143
- instance.options.horizontal ? 'scrollX' : 'scrollY'
144
- ],
145
- )
146
+ const scroll = () => {
147
+ const offset =
148
+ instance.scrollElement[instance.options.horizontal ? propX : propY]
146
149
 
147
- if (!instance.scrollElement) {
148
- return
149
- }
150
+ cb(Math.max(0, offset - instance.options.scrollMargin))
151
+ }
150
152
 
151
- onScroll()
153
+ scroll()
152
154
 
153
- instance.scrollElement.addEventListener('scroll', onScroll, {
154
- capture: false,
155
- passive: true,
156
- })
155
+ const onScroll = (e: Event) => {
156
+ const target = e.currentTarget as HTMLElement & Window
157
+ const scrollX = target[propX]
158
+ const scrollY = target[propY]
157
159
 
158
- return () => {
159
- instance.scrollElement.removeEventListener('scroll', onScroll)
160
+ if (instance.options.horizontal ? prevX - scrollX : prevY - scrollY) {
161
+ scroll()
162
+ }
163
+
164
+ prevX = scrollX
165
+ prevY = scrollY
166
+ }
167
+
168
+ instance.scrollElement.addEventListener('scroll', onScroll, {
169
+ capture: false,
170
+ passive: true,
171
+ })
172
+
173
+ return () => {
174
+ instance.scrollElement.removeEventListener('scroll', onScroll)
175
+ }
160
176
  }
161
177
  }
162
178
 
163
- export const measureElement = (
164
- element: unknown,
165
- instance: Virtualizer<any, any>,
179
+ export const observeElementOffset = createOffsetObserver('element')
180
+ export const observeWindowOffset = createOffsetObserver('window')
181
+
182
+ export const measureElement = <TItemElement extends Element>(
183
+ element: TItemElement,
184
+ instance: Virtualizer<any, TItemElement>,
166
185
  ) => {
167
- return (element as Element).getBoundingClientRect()[
168
- instance.options.horizontal ? 'width' : 'height'
169
- ]
186
+ return Math.round(
187
+ element.getBoundingClientRect()[
188
+ instance.options.horizontal ? 'width' : 'height'
189
+ ],
190
+ )
170
191
  }
171
192
 
172
193
  export const windowScroll = (
173
194
  offset: number,
174
- canSmooth: boolean,
195
+ { canSmooth, sync }: { canSmooth: boolean; sync: boolean },
175
196
  instance: Virtualizer<any, any>,
176
197
  ) => {
177
- ;(instance.scrollElement as Window)?.scrollTo({
178
- [instance.options.horizontal ? 'left' : 'top']: offset,
198
+ const toOffset = sync ? offset : offset + instance.options.scrollMargin
199
+
200
+ ;(instance.scrollElement as Window)?.scrollTo?.({
201
+ [instance.options.horizontal ? 'left' : 'top']: toOffset,
179
202
  behavior: canSmooth ? 'smooth' : undefined,
180
203
  })
181
204
  }
182
205
 
183
206
  export const elementScroll = (
184
207
  offset: number,
185
- canSmooth: boolean,
208
+ { canSmooth, sync }: { canSmooth: boolean; sync: boolean },
186
209
  instance: Virtualizer<any, any>,
187
210
  ) => {
188
- ;(instance.scrollElement as Element)?.scrollTo({
189
- [instance.options.horizontal ? 'left' : 'top']: offset,
211
+ const toOffset = sync ? offset : offset + instance.options.scrollMargin
212
+
213
+ ;(instance.scrollElement as Element)?.scrollTo?.({
214
+ [instance.options.horizontal ? 'left' : 'top']: toOffset,
190
215
  behavior: canSmooth ? 'smooth' : undefined,
191
216
  })
192
217
  }
193
218
 
194
219
  export interface VirtualizerOptions<
195
- TScrollElement = unknown,
196
- TItemElement = unknown,
220
+ TScrollElement extends unknown,
221
+ TItemElement extends Element,
197
222
  > {
198
223
  // Required from the user
199
224
  count: number
200
- getScrollElement: () => TScrollElement
225
+ getScrollElement: () => TScrollElement | null
201
226
  estimateSize: (index: number) => number
202
227
 
203
228
  // Required from the framework adapter (but can be overridden)
204
229
  scrollToFn: (
205
230
  offset: number,
206
- canSmooth: boolean,
231
+ options: { canSmooth: boolean; sync: boolean },
207
232
  instance: Virtualizer<TScrollElement, TItemElement>,
208
233
  ) => void
209
234
  observeElementRect: (
@@ -233,24 +258,45 @@ export interface VirtualizerOptions<
233
258
  getItemKey?: (index: number) => Key
234
259
  rangeExtractor?: (range: Range) => number[]
235
260
  enableSmoothScroll?: boolean
261
+ scrollMargin?: number
262
+ scrollingDelay?: number
263
+ indexAttribute?: string
236
264
  }
237
265
 
238
- export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
266
+ export class Virtualizer<
267
+ TScrollElement extends unknown,
268
+ TItemElement extends Element,
269
+ > {
239
270
  private unsubs: (void | (() => void))[] = []
240
271
  options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
241
272
  scrollElement: TScrollElement | null = null
242
- private measurementsCache: Item[] = []
273
+ isScrolling: boolean = false
274
+ private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
275
+ measurementsCache: VirtualItem[] = []
243
276
  private itemMeasurementsCache: Record<Key, number> = {}
244
277
  private pendingMeasuredCacheIndexes: number[] = []
245
278
  private scrollRect: Rect
246
279
  private scrollOffset: number
280
+ private scrollDelta: number = 0
247
281
  private destinationOffset: undefined | number
248
282
  private scrollCheckFrame!: ReturnType<typeof setTimeout>
283
+ private measureElementCache: Record<string, TItemElement> = {}
284
+ private ro = new ResizeObserver((entries) => {
285
+ entries.forEach((entry) => {
286
+ this._measureElement(entry.target as TItemElement, false)
287
+ })
288
+ })
289
+ range: { startIndex: number; endIndex: number } = {
290
+ startIndex: 0,
291
+ endIndex: 0,
292
+ }
249
293
 
250
294
  constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>) {
251
295
  this.setOptions(opts)
252
296
  this.scrollRect = this.options.initialRect
253
297
  this.scrollOffset = this.options.initialOffset
298
+
299
+ this.calculateRange()
254
300
  }
255
301
 
256
302
  setOptions = (opts: VirtualizerOptions<TScrollElement, TItemElement>) => {
@@ -273,6 +319,9 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
273
319
  onChange: () => {},
274
320
  measureElement,
275
321
  initialRect: { width: 0, height: 0 },
322
+ scrollMargin: 0,
323
+ scrollingDelay: 150,
324
+ indexAttribute: 'data-index',
276
325
  ...opts,
277
326
  }
278
327
  }
@@ -284,10 +333,14 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
284
333
  private cleanup = () => {
285
334
  this.unsubs.filter(Boolean).forEach((d) => d!())
286
335
  this.unsubs = []
336
+ this.scrollElement = null
287
337
  }
288
338
 
289
339
  _didMount = () => {
290
340
  return () => {
341
+ this.ro.disconnect()
342
+ this.measureElementCache = {}
343
+
291
344
  this.cleanup()
292
345
  }
293
346
  }
@@ -299,20 +352,47 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
299
352
  this.cleanup()
300
353
 
301
354
  this.scrollElement = scrollElement
355
+ this._scrollToOffset(this.scrollOffset, {
356
+ canSmooth: false,
357
+ sync: true,
358
+ requested: false,
359
+ })
302
360
 
303
361
  this.unsubs.push(
304
362
  this.options.observeElementRect(this, (rect) => {
305
363
  this.scrollRect = rect
306
- this.notify()
364
+ this.calculateRange()
307
365
  }),
308
366
  )
309
367
 
310
368
  this.unsubs.push(
311
369
  this.options.observeElementOffset(this, (offset) => {
312
- this.scrollOffset = offset
313
- this.notify()
370
+ if (this.isScrollingTimeoutId !== null) {
371
+ clearTimeout(this.isScrollingTimeoutId)
372
+ this.isScrollingTimeoutId = null
373
+ }
374
+
375
+ if (this.scrollOffset !== offset) {
376
+ this.scrollOffset = offset
377
+ this.isScrolling = true
378
+ this.scrollDelta = 0
379
+
380
+ this.isScrollingTimeoutId = setTimeout(() => {
381
+ this.isScrollingTimeoutId = null
382
+ this.isScrolling = false
383
+
384
+ this.notify()
385
+ }, this.options.scrollingDelay)
386
+ } else {
387
+ this.isScrolling = false
388
+ this.scrollDelta = 0
389
+ }
390
+
391
+ this.calculateRange()
314
392
  }),
315
393
  )
394
+ } else if (!this.isScrolling) {
395
+ this.calculateRange()
316
396
  }
317
397
  }
318
398
 
@@ -354,22 +434,30 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
354
434
  return measurements
355
435
  },
356
436
  {
357
- key: process.env.NODE_ENV === 'development' && 'getMeasurements',
437
+ key: process.env.NODE_ENV !== 'production' && 'getMeasurements',
358
438
  debug: () => this.options.debug,
359
439
  },
360
440
  )
361
441
 
362
- private calculateRange = memo(
442
+ calculateRange = memo(
363
443
  () => [this.getMeasurements(), this.getSize(), this.scrollOffset],
364
444
  (measurements, outerSize, scrollOffset) => {
365
- return calculateRange({
445
+ const range = calculateRange({
366
446
  measurements,
367
447
  outerSize,
368
448
  scrollOffset,
369
449
  })
450
+ if (
451
+ range.startIndex !== this.range.startIndex ||
452
+ range.endIndex !== this.range.endIndex
453
+ ) {
454
+ this.range = range
455
+ this.notify()
456
+ }
457
+ return this.range
370
458
  },
371
459
  {
372
- key: process.env.NODE_ENV === 'development' && 'calculateRange',
460
+ key: process.env.NODE_ENV !== 'production' && 'calculateRange',
373
461
  debug: () => this.options.debug,
374
462
  },
375
463
  )
@@ -377,7 +465,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
377
465
  private getIndexes = memo(
378
466
  () => [
379
467
  this.options.rangeExtractor,
380
- this.calculateRange(),
468
+ this.range,
381
469
  this.options.overscan,
382
470
  this.options.count,
383
471
  ],
@@ -389,102 +477,151 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
389
477
  })
390
478
  },
391
479
  {
392
- key: process.env.NODE_ENV === 'development' && 'getIndexes',
480
+ key: process.env.NODE_ENV !== 'production' && 'getIndexes',
481
+ debug: () => this.options.debug,
393
482
  },
394
483
  )
395
484
 
485
+ indexFromElement = (node: TItemElement) => {
486
+ const attributeName = this.options.indexAttribute
487
+ const indexStr = node.getAttribute(attributeName)
488
+
489
+ if (!indexStr) {
490
+ console.warn(
491
+ `Missing attribute name '${attributeName}={index}' on measured element.`,
492
+ )
493
+ return -1
494
+ }
495
+
496
+ return parseInt(indexStr, 10)
497
+ }
498
+
499
+ _measureElement = (node: TItemElement, _sync: boolean) => {
500
+ const index = this.indexFromElement(node)
501
+
502
+ const item = this.measurementsCache[index]
503
+ if (!item) {
504
+ return
505
+ }
506
+ const key = String(item.key)
507
+
508
+ const prevNode = this.measureElementCache[key]
509
+
510
+ if (!node.isConnected) {
511
+ if (prevNode) {
512
+ this.ro.unobserve(prevNode)
513
+ delete this.measureElementCache[key]
514
+ }
515
+ return
516
+ }
517
+
518
+ if (!prevNode || prevNode !== node) {
519
+ if (prevNode) {
520
+ this.ro.unobserve(prevNode)
521
+ }
522
+ this.measureElementCache[key] = node
523
+ this.ro.observe(node)
524
+ }
525
+
526
+ const measuredItemSize = this.options.measureElement(node, this)
527
+
528
+ const itemSize = this.itemMeasurementsCache[item.key] ?? item.size
529
+
530
+ if (measuredItemSize !== itemSize) {
531
+ if (item.start < this.scrollOffset) {
532
+ if (process.env.NODE_ENV !== 'production' && this.options.debug) {
533
+ console.info('correction', measuredItemSize - itemSize)
534
+ }
535
+
536
+ if (this.destinationOffset === undefined) {
537
+ this.scrollDelta += measuredItemSize - itemSize
538
+
539
+ this._scrollToOffset(this.scrollOffset + this.scrollDelta, {
540
+ canSmooth: false,
541
+ sync: false,
542
+ requested: false,
543
+ })
544
+ }
545
+ }
546
+
547
+ this.pendingMeasuredCacheIndexes.push(index)
548
+ this.itemMeasurementsCache = {
549
+ ...this.itemMeasurementsCache,
550
+ [item.key]: measuredItemSize,
551
+ }
552
+ this.notify()
553
+ }
554
+ }
555
+
556
+ measureElement = (node: TItemElement | null) => {
557
+ if (!node) {
558
+ return
559
+ }
560
+
561
+ this._measureElement(node, true)
562
+ }
563
+
396
564
  getVirtualItems = memo(
397
- () => [
398
- this.getIndexes(),
399
- this.getMeasurements(),
400
- this.options.measureElement,
401
- ],
402
- (indexes, measurements, measureElement) => {
403
- const virtualItems: VirtualItem<TItemElement>[] = []
565
+ () => [this.getIndexes(), this.getMeasurements()],
566
+ (indexes, measurements) => {
567
+ const virtualItems: VirtualItem[] = []
404
568
 
405
569
  for (let k = 0, len = indexes.length; k < len; k++) {
406
570
  const i = indexes[k]!
407
571
  const measurement = measurements[i]!
408
572
 
409
- const item = {
410
- ...measurement,
411
- measureElement: (measurableItem: TItemElement | null) => {
412
- if (measurableItem) {
413
- const measuredItemSize = measureElement(measurableItem, this)
414
-
415
- if (measuredItemSize !== item.size) {
416
- if (item.start < this.scrollOffset) {
417
- if (
418
- process.env.NODE_ENV === 'development' &&
419
- this.options.debug
420
- )
421
- console.info('correction', measuredItemSize - item.size)
422
-
423
- if (!this.destinationOffset) {
424
- this._scrollToOffset(
425
- this.scrollOffset + (measuredItemSize - item.size),
426
- false,
427
- )
428
- }
429
- }
430
-
431
- this.pendingMeasuredCacheIndexes.push(i)
432
- this.itemMeasurementsCache = {
433
- ...this.itemMeasurementsCache,
434
- [item.key]: measuredItemSize,
435
- }
436
- this.notify()
437
- }
438
- }
439
- },
440
- }
441
-
442
- virtualItems.push(item)
573
+ virtualItems.push(measurement)
443
574
  }
444
575
 
445
576
  return virtualItems
446
577
  },
447
578
  {
448
- key: process.env.NODE_ENV === 'development' && 'getIndexes',
579
+ key: process.env.NODE_ENV !== 'production' && 'getIndexes',
580
+ debug: () => this.options.debug,
449
581
  },
450
582
  )
451
583
 
452
584
  scrollToOffset = (
453
585
  toOffset: number,
454
- { align }: ScrollToOffsetOptions = { align: 'start' },
586
+ {
587
+ align = 'start',
588
+ smoothScroll = this.options.enableSmoothScroll,
589
+ }: ScrollToOffsetOptions = {},
455
590
  ) => {
456
- const attempt = () => {
457
- const offset = this.scrollOffset
458
- const size = this.getSize()
459
-
460
- if (align === 'auto') {
461
- if (toOffset <= offset) {
462
- align = 'start'
463
- } else if (toOffset >= offset + size) {
464
- align = 'end'
465
- } else {
466
- align = 'start'
467
- }
468
- }
591
+ const offset = this.scrollOffset
592
+ const size = this.getSize()
469
593
 
470
- if (align === 'start') {
471
- this._scrollToOffset(toOffset, true)
472
- } else if (align === 'end') {
473
- this._scrollToOffset(toOffset - size, true)
474
- } else if (align === 'center') {
475
- this._scrollToOffset(toOffset - size / 2, true)
594
+ if (align === 'auto') {
595
+ if (toOffset <= offset) {
596
+ align = 'start'
597
+ } else if (toOffset >= offset + size) {
598
+ align = 'end'
599
+ } else {
600
+ align = 'start'
476
601
  }
477
602
  }
478
603
 
479
- attempt()
480
- requestAnimationFrame(() => {
481
- attempt()
482
- })
604
+ const options = {
605
+ canSmooth: smoothScroll,
606
+ sync: false,
607
+ requested: true,
608
+ }
609
+ if (align === 'start') {
610
+ this._scrollToOffset(toOffset, options)
611
+ } else if (align === 'end') {
612
+ this._scrollToOffset(toOffset - size, options)
613
+ } else if (align === 'center') {
614
+ this._scrollToOffset(toOffset - size / 2, options)
615
+ }
483
616
  }
484
617
 
485
618
  scrollToIndex = (
486
619
  index: number,
487
- { align, ...rest }: ScrollToIndexOptions = { align: 'auto' },
620
+ {
621
+ align = 'auto',
622
+ smoothScroll = this.options.enableSmoothScroll,
623
+ ...rest
624
+ }: ScrollToIndexOptions = {},
488
625
  ) => {
489
626
  const measurements = this.getMeasurements()
490
627
  const offset = this.scrollOffset
@@ -515,22 +652,27 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
515
652
  ? measurement.end + this.options.scrollPaddingEnd
516
653
  : measurement.start - this.options.scrollPaddingStart
517
654
 
518
- this.scrollToOffset(toOffset, { align, ...rest })
655
+ this.scrollToOffset(toOffset, { align, smoothScroll, ...rest })
519
656
  }
520
657
 
521
658
  getTotalSize = () =>
522
659
  (this.getMeasurements()[this.options.count - 1]?.end ||
523
660
  this.options.paddingStart) + this.options.paddingEnd
524
661
 
525
- private _scrollToOffset = (offset: number, canSmooth: boolean) => {
662
+ private _scrollToOffset = (
663
+ offset: number,
664
+ {
665
+ requested,
666
+ canSmooth,
667
+ sync,
668
+ }: { canSmooth: boolean; sync: boolean; requested: boolean },
669
+ ) => {
526
670
  clearTimeout(this.scrollCheckFrame)
527
671
 
528
- this.destinationOffset = offset
529
- this.options.scrollToFn(
530
- offset,
531
- this.options.enableSmoothScroll && canSmooth,
532
- this,
533
- )
672
+ if (requested) {
673
+ this.destinationOffset = offset
674
+ }
675
+ this.options.scrollToFn(offset, { canSmooth, sync }, this)
534
676
 
535
677
  let scrollCheckFrame: ReturnType<typeof setTimeout>
536
678
 
@@ -590,7 +732,7 @@ function calculateRange({
590
732
  outerSize,
591
733
  scrollOffset,
592
734
  }: {
593
- measurements: Item[]
735
+ measurements: VirtualItem[]
594
736
  outerSize: number
595
737
  scrollOffset: number
596
738
  }) {