@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/build/cjs/packages/virtual-core/src/index.js +103 -55
- package/build/cjs/packages/virtual-core/src/index.js.map +1 -1
- package/build/esm/index.js +103 -55
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/{stats-react.json → stats.json} +21 -21
- package/build/types/index.d.ts +2 -1
- package/build/umd/index.development.js +103 -55
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +115 -78
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
|
-
|
|
87
|
+
onResize(rect)
|
|
67
88
|
})
|
|
68
89
|
|
|
69
90
|
if (!instance.scrollElement) {
|
|
70
91
|
return
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
|
|
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
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
const scrollProps = {
|
|
133
|
+
element: ['scrollLeft', 'scrollTop'],
|
|
134
|
+
window: ['scrollX', 'scrollY'],
|
|
135
|
+
} as const
|
|
123
136
|
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
passive: true,
|
|
129
|
-
})
|
|
143
|
+
const propX = scrollProps[mode][0]
|
|
144
|
+
const propY = scrollProps[mode][1]
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
instance.scrollElement
|
|
133
|
-
}
|
|
134
|
-
}
|
|
146
|
+
let prevX: number = instance.scrollElement[propX]
|
|
147
|
+
let prevY: number = instance.scrollElement[propY]
|
|
135
148
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
148
|
-
return
|
|
149
|
-
}
|
|
153
|
+
scroll()
|
|
150
154
|
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
})
|
|
160
|
+
if (instance.options.horizontal ? prevX - scrollX : prevY - scrollY) {
|
|
161
|
+
scroll()
|
|
162
|
+
}
|
|
157
163
|
|
|
158
|
-
|
|
159
|
-
|
|
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: (
|
|
412
|
-
|
|
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
|
{
|