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

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
@@ -7,7 +7,7 @@ export * from './utils'
7
7
 
8
8
  type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'
9
9
 
10
- interface ScrollToOptions {
10
+ export interface ScrollToOptions {
11
11
  align: ScrollAlignment
12
12
  }
13
13
 
@@ -58,19 +58,40 @@ export const defaultRangeExtractor = (range: Range) => {
58
58
  return arr
59
59
  }
60
60
 
61
+ const memoRectCallback = (
62
+ instance: Virtualizer<any, any>,
63
+ cb: (rect: Rect) => void,
64
+ ) => {
65
+ let prev: Rect = { height: -1, width: -1 }
66
+
67
+ return (rect: Rect) => {
68
+ if (
69
+ instance.options.horizontal
70
+ ? rect.width !== prev.width
71
+ : rect.height !== prev.height
72
+ ) {
73
+ cb(rect)
74
+ }
75
+
76
+ prev = rect
77
+ }
78
+ }
79
+
61
80
  export const observeElementRect = (
62
81
  instance: Virtualizer<any, any>,
63
82
  cb: (rect: Rect) => void,
64
83
  ) => {
84
+ const onResize = memoRectCallback(instance, cb)
85
+
65
86
  const observer = observeRect(instance.scrollElement as Element, (rect) => {
66
- cb(rect)
87
+ onResize(rect)
67
88
  })
68
89
 
69
90
  if (!instance.scrollElement) {
70
91
  return
71
92
  }
72
93
 
73
- cb(instance.scrollElement.getBoundingClientRect())
94
+ onResize(instance.scrollElement.getBoundingClientRect())
74
95
 
75
96
  observer.observe()
76
97
 
@@ -83,12 +104,12 @@ export const observeWindowRect = (
83
104
  instance: Virtualizer<any, any>,
84
105
  cb: (rect: Rect) => void,
85
106
  ) => {
86
- const onResize = () => {
87
- cb({
107
+ const memoizedCallback = memoRectCallback(instance, cb)
108
+ const onResize = () =>
109
+ memoizedCallback({
88
110
  width: instance.scrollElement.innerWidth,
89
111
  height: instance.scrollElement.innerHeight,
90
112
  })
91
- }
92
113
 
93
114
  if (!instance.scrollElement) {
94
115
  return
@@ -106,60 +127,58 @@ export const observeWindowRect = (
106
127
  }
107
128
  }
108
129
 
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
- )
130
+ type ObserverMode = 'element' | 'window'
119
131
 
120
- if (!instance.scrollElement) {
121
- return
122
- }
132
+ const scrollProps = {
133
+ element: ['scrollLeft', 'scrollTop'],
134
+ window: ['scrollX', 'scrollY'],
135
+ } as const
123
136
 
124
- onScroll()
137
+ const createOffsetObserver = (mode: ObserverMode) => {
138
+ return (instance: Virtualizer<any, any>, cb: (offset: number) => void) => {
139
+ if (!instance.scrollElement) {
140
+ return
141
+ }
125
142
 
126
- instance.scrollElement.addEventListener('scroll', onScroll, {
127
- capture: false,
128
- passive: true,
129
- })
143
+ const propX = scrollProps[mode][0]
144
+ const propY = scrollProps[mode][1]
130
145
 
131
- return () => {
132
- instance.scrollElement.removeEventListener('scroll', onScroll)
133
- }
134
- }
146
+ let prevX: number = instance.scrollElement[propX]
147
+ let prevY: number = instance.scrollElement[propY]
135
148
 
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
- )
149
+ const scroll = () => {
150
+ cb(instance.scrollElement[instance.options.horizontal ? propX : propY])
151
+ }
146
152
 
147
- if (!instance.scrollElement) {
148
- return
149
- }
153
+ scroll()
150
154
 
151
- onScroll()
155
+ const onScroll = (e: Event) => {
156
+ const target = e.currentTarget as HTMLElement & Window
157
+ const scrollX = target[propX]
158
+ const scrollY = target[propY]
152
159
 
153
- instance.scrollElement.addEventListener('scroll', onScroll, {
154
- capture: false,
155
- passive: true,
156
- })
160
+ if (instance.options.horizontal ? prevX - scrollX : prevY - scrollY) {
161
+ scroll()
162
+ }
157
163
 
158
- return () => {
159
- instance.scrollElement.removeEventListener('scroll', onScroll)
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
 
179
+ export const observeElementOffset = createOffsetObserver('element')
180
+ export const observeWindowOffset = createOffsetObserver('window')
181
+
163
182
  export const measureElement = (
164
183
  element: unknown,
165
184
  instance: Virtualizer<any, any>,
@@ -246,6 +265,10 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
246
265
  private scrollOffset: number
247
266
  private destinationOffset: undefined | number
248
267
  private scrollCheckFrame!: ReturnType<typeof setTimeout>
268
+ private measureElementCache: Record<
269
+ number,
270
+ (measurableItem: TItemElement | null) => void
271
+ > = {}
249
272
 
250
273
  constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>) {
251
274
  this.setOptions(opts)
@@ -284,6 +307,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
284
307
  private cleanup = () => {
285
308
  this.unsubs.filter(Boolean).forEach((d) => d!())
286
309
  this.unsubs = []
310
+ this.scrollElement = null
287
311
  }
288
312
 
289
313
  _didMount = () => {
@@ -400,48 +424,61 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
400
424
  this.options.measureElement,
401
425
  ],
402
426
  (indexes, measurements, measureElement) => {
427
+ const makeMeasureElement =
428
+ (index: number) => (measurableItem: TItemElement | null) => {
429
+ const item = this.measurementsCache[index]!
430
+
431
+ if (!measurableItem) {
432
+ return
433
+ }
434
+
435
+ const measuredItemSize = measureElement(measurableItem, this)
436
+ const itemSize = this.itemMeasurementsCache[item.key] ?? item.size
437
+
438
+ if (measuredItemSize !== itemSize) {
439
+ if (item.start < this.scrollOffset) {
440
+ if (
441
+ process.env.NODE_ENV === 'development' &&
442
+ this.options.debug
443
+ ) {
444
+ console.info('correction', measuredItemSize - itemSize)
445
+ }
446
+
447
+ if (!this.destinationOffset) {
448
+ this._scrollToOffset(
449
+ this.scrollOffset + (measuredItemSize - itemSize),
450
+ false,
451
+ )
452
+ }
453
+ }
454
+
455
+ this.pendingMeasuredCacheIndexes.push(index)
456
+ this.itemMeasurementsCache = {
457
+ ...this.itemMeasurementsCache,
458
+ [item.key]: measuredItemSize,
459
+ }
460
+ this.notify()
461
+ }
462
+ }
463
+
403
464
  const virtualItems: VirtualItem<TItemElement>[] = []
404
465
 
466
+ const currentMeasureElements: typeof this.measureElementCache = {}
467
+
405
468
  for (let k = 0, len = indexes.length; k < len; k++) {
406
469
  const i = indexes[k]!
407
470
  const measurement = measurements[i]!
408
471
 
409
472
  const item = {
410
473
  ...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
- },
474
+ measureElement: (currentMeasureElements[i] =
475
+ this.measureElementCache[i] ?? makeMeasureElement(i)),
440
476
  }
441
-
442
477
  virtualItems.push(item)
443
478
  }
444
479
 
480
+ this.measureElementCache = currentMeasureElements
481
+
445
482
  return virtualItems
446
483
  },
447
484
  {