@tanstack/virtual-core 3.0.0-beta.1 → 3.0.0-beta.10

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,6 +1,4 @@
1
1
  import observeRect from '@reach/observe-rect'
2
- import { check } from 'prettier'
3
- import React from 'react'
4
2
  import { memo } from './utils'
5
3
 
6
4
  export * from './utils'
@@ -9,7 +7,7 @@ export * from './utils'
9
7
 
10
8
  type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'
11
9
 
12
- interface ScrollToOptions {
10
+ export interface ScrollToOptions {
13
11
  align: ScrollAlignment
14
12
  }
15
13
 
@@ -60,19 +58,40 @@ export const defaultRangeExtractor = (range: Range) => {
60
58
  return arr
61
59
  }
62
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
+
63
80
  export const observeElementRect = (
64
81
  instance: Virtualizer<any, any>,
65
82
  cb: (rect: Rect) => void,
66
83
  ) => {
84
+ const onResize = memoRectCallback(instance, cb)
85
+
67
86
  const observer = observeRect(instance.scrollElement as Element, (rect) => {
68
- cb(rect)
87
+ onResize(rect)
69
88
  })
70
89
 
71
90
  if (!instance.scrollElement) {
72
91
  return
73
92
  }
74
93
 
75
- cb(instance.scrollElement.getBoundingClientRect())
94
+ onResize(instance.scrollElement.getBoundingClientRect())
76
95
 
77
96
  observer.observe()
78
97
 
@@ -85,12 +104,12 @@ export const observeWindowRect = (
85
104
  instance: Virtualizer<any, any>,
86
105
  cb: (rect: Rect) => void,
87
106
  ) => {
88
- const onResize = () => {
89
- cb({
107
+ const memoizedCallback = memoRectCallback(instance, cb)
108
+ const onResize = () =>
109
+ memoizedCallback({
90
110
  width: instance.scrollElement.innerWidth,
91
111
  height: instance.scrollElement.innerHeight,
92
112
  })
93
- }
94
113
 
95
114
  if (!instance.scrollElement) {
96
115
  return
@@ -108,60 +127,58 @@ export const observeWindowRect = (
108
127
  }
109
128
  }
110
129
 
111
- export const observeElementOffset = (
112
- instance: Virtualizer<any, any>,
113
- cb: (offset: number) => void,
114
- ) => {
115
- const onScroll = () =>
116
- cb(
117
- instance.scrollElement[
118
- instance.options.horizontal ? 'scrollLeft' : 'scrollTop'
119
- ],
120
- )
130
+ type ObserverMode = 'element' | 'window'
121
131
 
122
- if (!instance.scrollElement) {
123
- return
124
- }
132
+ const scrollProps = {
133
+ element: ['scrollLeft', 'scrollTop'],
134
+ window: ['scrollX', 'scrollY'],
135
+ } as const
125
136
 
126
- onScroll()
137
+ const createOffsetObserver = (mode: ObserverMode) => {
138
+ return (instance: Virtualizer<any, any>, cb: (offset: number) => void) => {
139
+ if (!instance.scrollElement) {
140
+ return
141
+ }
127
142
 
128
- instance.scrollElement.addEventListener('scroll', onScroll, {
129
- capture: false,
130
- passive: true,
131
- })
143
+ const propX = scrollProps[mode][0]
144
+ const propY = scrollProps[mode][1]
132
145
 
133
- return () => {
134
- instance.scrollElement.removeEventListener('scroll', onScroll)
135
- }
136
- }
146
+ let prevX: number = instance.scrollElement[propX]
147
+ let prevY: number = instance.scrollElement[propY]
137
148
 
138
- export const observeWindowOffset = (
139
- instance: Virtualizer<any, any>,
140
- cb: (offset: number) => void,
141
- ) => {
142
- const onScroll = () =>
143
- cb(
144
- instance.scrollElement[
145
- instance.options.horizontal ? 'scrollX' : 'scrollY'
146
- ],
147
- )
149
+ const scroll = () => {
150
+ cb(instance.scrollElement[instance.options.horizontal ? propX : propY])
151
+ }
148
152
 
149
- if (!instance.scrollElement) {
150
- return
151
- }
153
+ scroll()
152
154
 
153
- onScroll()
155
+ const onScroll = (e: Event) => {
156
+ const target = e.currentTarget as HTMLElement & Window
157
+ const scrollX = target[propX]
158
+ const scrollY = target[propY]
154
159
 
155
- instance.scrollElement.addEventListener('scroll', onScroll, {
156
- capture: false,
157
- passive: true,
158
- })
160
+ if (instance.options.horizontal ? prevX - scrollX : prevY - scrollY) {
161
+ scroll()
162
+ }
159
163
 
160
- return () => {
161
- 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
+ }
162
176
  }
163
177
  }
164
178
 
179
+ export const observeElementOffset = createOffsetObserver('element')
180
+ export const observeWindowOffset = createOffsetObserver('window')
181
+
165
182
  export const measureElement = (
166
183
  element: unknown,
167
184
  instance: Virtualizer<any, any>,
@@ -248,6 +265,10 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
248
265
  private scrollOffset: number
249
266
  private destinationOffset: undefined | number
250
267
  private scrollCheckFrame!: ReturnType<typeof setTimeout>
268
+ private measureElementCache: Record<
269
+ number,
270
+ (measurableItem: TItemElement | null) => void
271
+ > = {}
251
272
 
252
273
  constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>) {
253
274
  this.setOptions(opts)
@@ -286,6 +307,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
286
307
  private cleanup = () => {
287
308
  this.unsubs.filter(Boolean).forEach((d) => d!())
288
309
  this.unsubs = []
310
+ this.scrollElement = null
289
311
  }
290
312
 
291
313
  _didMount = () => {
@@ -402,48 +424,61 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
402
424
  this.options.measureElement,
403
425
  ],
404
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
+
405
464
  const virtualItems: VirtualItem<TItemElement>[] = []
406
465
 
466
+ const currentMeasureElements: typeof this.measureElementCache = {}
467
+
407
468
  for (let k = 0, len = indexes.length; k < len; k++) {
408
469
  const i = indexes[k]!
409
470
  const measurement = measurements[i]!
410
471
 
411
472
  const item = {
412
473
  ...measurement,
413
- measureElement: (measurableItem: TItemElement | null) => {
414
- if (measurableItem) {
415
- const measuredItemSize = measureElement(measurableItem, this)
416
-
417
- if (measuredItemSize !== item.size) {
418
- if (item.start < this.scrollOffset) {
419
- if (
420
- process.env.NODE_ENV === 'development' &&
421
- this.options.debug
422
- )
423
- console.info('correction', measuredItemSize - item.size)
424
-
425
- if (!this.destinationOffset) {
426
- this._scrollToOffset(
427
- this.scrollOffset + (measuredItemSize - item.size),
428
- false,
429
- )
430
- }
431
- }
432
-
433
- this.pendingMeasuredCacheIndexes.push(i)
434
- this.itemMeasurementsCache = {
435
- ...this.itemMeasurementsCache,
436
- [item.key]: measuredItemSize,
437
- }
438
- this.notify()
439
- }
440
- }
441
- },
474
+ measureElement: (currentMeasureElements[i] =
475
+ this.measureElementCache[i] ?? makeMeasureElement(i)),
442
476
  }
443
-
444
477
  virtualItems.push(item)
445
478
  }
446
479
 
480
+ this.measureElementCache = currentMeasureElements
481
+
447
482
  return virtualItems
448
483
  },
449
484
  {
@@ -1,101 +0,0 @@
1
- {
2
- "version": 2,
3
- "tree": {
4
- "name": "root",
5
- "children": [
6
- {
7
- "name": "index.production.js",
8
- "children": [
9
- {
10
- "name": "node_modules/@reach/observe-rect/dist/observe-rect.esm.js",
11
- "uid": "8d36-7"
12
- },
13
- {
14
- "name": "packages/virtual-core/src",
15
- "children": [
16
- {
17
- "uid": "8d36-9",
18
- "name": "utils.ts"
19
- },
20
- {
21
- "uid": "8d36-11",
22
- "name": "index.ts"
23
- }
24
- ]
25
- }
26
- ]
27
- }
28
- ],
29
- "isRoot": true
30
- },
31
- "nodeParts": {
32
- "8d36-7": {
33
- "renderedLength": 1732,
34
- "gzipLength": 652,
35
- "brotliLength": 0,
36
- "mainUid": "8d36-6"
37
- },
38
- "8d36-9": {
39
- "renderedLength": 1432,
40
- "gzipLength": 544,
41
- "brotliLength": 0,
42
- "mainUid": "8d36-8"
43
- },
44
- "8d36-11": {
45
- "renderedLength": 12406,
46
- "gzipLength": 2777,
47
- "brotliLength": 0,
48
- "mainUid": "8d36-10"
49
- }
50
- },
51
- "nodeMetas": {
52
- "8d36-6": {
53
- "id": "/node_modules/@reach/observe-rect/dist/observe-rect.esm.js",
54
- "moduleParts": {
55
- "index.production.js": "8d36-7"
56
- },
57
- "imported": [],
58
- "importedBy": [
59
- {
60
- "uid": "8d36-10"
61
- }
62
- ]
63
- },
64
- "8d36-8": {
65
- "id": "/packages/virtual-core/src/utils.ts",
66
- "moduleParts": {
67
- "index.production.js": "8d36-9"
68
- },
69
- "imported": [],
70
- "importedBy": [
71
- {
72
- "uid": "8d36-10"
73
- }
74
- ]
75
- },
76
- "8d36-10": {
77
- "id": "/packages/virtual-core/src/index.ts",
78
- "moduleParts": {
79
- "index.production.js": "8d36-11"
80
- },
81
- "imported": [
82
- {
83
- "uid": "8d36-6"
84
- },
85
- {
86
- "uid": "8d36-8"
87
- }
88
- ],
89
- "importedBy": [],
90
- "isEntry": true
91
- }
92
- },
93
- "env": {
94
- "rollup": "2.75.4"
95
- },
96
- "options": {
97
- "gzip": true,
98
- "brotli": false,
99
- "sourcemap": false
100
- }
101
- }
@@ -1,87 +0,0 @@
1
- export * from './utils';
2
- declare type ScrollAlignment = 'start' | 'center' | 'end' | 'auto';
3
- interface ScrollToOptions {
4
- align: ScrollAlignment;
5
- }
6
- declare type ScrollToOffsetOptions = ScrollToOptions;
7
- declare type ScrollToIndexOptions = ScrollToOptions;
8
- export interface Range {
9
- startIndex: number;
10
- endIndex: number;
11
- overscan: number;
12
- count: number;
13
- }
14
- declare type Key = number | string;
15
- interface Item {
16
- key: Key;
17
- index: number;
18
- start: number;
19
- end: number;
20
- size: number;
21
- }
22
- interface Rect {
23
- width: number;
24
- height: number;
25
- }
26
- export interface VirtualItem<TItemElement> extends Item {
27
- measureElement: (el: TItemElement | null) => void;
28
- }
29
- export declare const defaultKeyExtractor: (index: number) => number;
30
- export declare const defaultRangeExtractor: (range: Range) => number[];
31
- export declare const observeElementRect: (instance: Virtualizer<any, any>, cb: (rect: Rect) => void) => (() => void) | undefined;
32
- export declare const observeWindowRect: (instance: Virtualizer<any, any>, cb: (rect: Rect) => void) => (() => void) | undefined;
33
- export declare const observeElementOffset: (instance: Virtualizer<any, any>, cb: (offset: number) => void) => (() => void) | undefined;
34
- export declare const observeWindowOffset: (instance: Virtualizer<any, any>, cb: (offset: number) => void) => (() => void) | undefined;
35
- export declare const measureElement: (element: unknown, instance: Virtualizer<any, any>) => number;
36
- export declare const windowScroll: (offset: number, canSmooth: boolean, instance: Virtualizer<any, any>) => void;
37
- export declare const elementScroll: (offset: number, canSmooth: boolean, instance: Virtualizer<any, any>) => void;
38
- export interface VirtualizerOptions<TScrollElement = unknown, TItemElement = unknown> {
39
- count: number;
40
- getScrollElement: () => TScrollElement;
41
- estimateSize: (index: number) => number;
42
- scrollToFn: (offset: number, canSmooth: boolean, instance: Virtualizer<TScrollElement, TItemElement>) => void;
43
- observeElementRect: (instance: Virtualizer<TScrollElement, TItemElement>, cb: (rect: Rect) => void) => void | (() => void);
44
- observeElementOffset: (instance: Virtualizer<TScrollElement, TItemElement>, cb: (offset: number) => void) => void | (() => void);
45
- debug?: any;
46
- initialRect?: Rect;
47
- onChange?: (instance: Virtualizer<TScrollElement, TItemElement>) => void;
48
- measureElement?: (el: TItemElement, instance: Virtualizer<TScrollElement, TItemElement>) => number;
49
- overscan?: number;
50
- horizontal?: boolean;
51
- paddingStart?: number;
52
- paddingEnd?: number;
53
- scrollPaddingStart?: number;
54
- scrollPaddingEnd?: number;
55
- initialOffset?: number;
56
- getItemKey?: (index: number) => Key;
57
- rangeExtractor?: (range: Range) => number[];
58
- enableSmoothScroll?: boolean;
59
- }
60
- export declare class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
61
- private unsubs;
62
- options: Required<VirtualizerOptions<TScrollElement, TItemElement>>;
63
- scrollElement: TScrollElement | null;
64
- private measurementsCache;
65
- private itemMeasurementsCache;
66
- private pendingMeasuredCacheIndexes;
67
- private scrollRect;
68
- private scrollOffset;
69
- private destinationOffset;
70
- private scrollCheckFrame;
71
- constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>);
72
- setOptions: (opts: VirtualizerOptions<TScrollElement, TItemElement>) => void;
73
- private notify;
74
- private cleanup;
75
- _didMount: () => () => void;
76
- _willUpdate: () => void;
77
- private getSize;
78
- private getMeasurements;
79
- private calculateRange;
80
- private getIndexes;
81
- getVirtualItems: () => VirtualItem<TItemElement>[];
82
- scrollToOffset: (toOffset: number, { align }?: ScrollToOffsetOptions) => void;
83
- scrollToIndex: (index: number, { align, ...rest }?: ScrollToIndexOptions) => void;
84
- getTotalSize: () => number;
85
- private _scrollToOffset;
86
- measure: () => void;
87
- }
@@ -1,7 +0,0 @@
1
- export declare type NoInfer<A extends any> = [A][A extends any ? 0 : never];
2
- export declare type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
3
- export declare function memo<TDeps extends readonly any[], TResult>(getDeps: () => [...TDeps], fn: (...args: NoInfer<[...TDeps]>) => TResult, opts: {
4
- key: any;
5
- debug?: () => any;
6
- onChange?: (result: TResult) => void;
7
- }): () => TResult;