@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/build/cjs/{packages/virtual-core/src/index.js → index.js} +285 -116
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/{packages/virtual-core/src/utils.js → utils.js} +0 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +285 -185
- package/build/esm/index.js.map +1 -1
- package/build/{stats-html.html → stats.html} +1 -1
- package/build/{stats-react.json → stats.json} +14 -39
- package/build/types/index.d.ts +69 -25
- package/build/umd/index.development.js +283 -183
- 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 +4 -6
- package/src/index.ts +302 -160
- package/build/cjs/packages/virtual-core/src/index.js.map +0 -1
- package/build/cjs/packages/virtual-core/src/utils.js.map +0 -1
- package/build/types/utils.d.ts +0 -7
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
|
|
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
|
|
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 =
|
|
66
|
-
cb(
|
|
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
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
const scrollProps = {
|
|
130
|
+
element: ['scrollLeft', 'scrollTop'],
|
|
131
|
+
window: ['scrollX', 'scrollY'],
|
|
132
|
+
} as const
|
|
123
133
|
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
passive: true,
|
|
129
|
-
})
|
|
140
|
+
const propX = scrollProps[mode][0]
|
|
141
|
+
const propY = scrollProps[mode][1]
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
instance.scrollElement
|
|
133
|
-
}
|
|
134
|
-
}
|
|
143
|
+
let prevX: number = instance.scrollElement[propX]
|
|
144
|
+
let prevY: number = instance.scrollElement[propY]
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
+
cb(Math.max(0, offset - instance.options.scrollMargin))
|
|
151
|
+
}
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
scroll()
|
|
152
154
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
168
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
|
196
|
-
TItemElement
|
|
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<
|
|
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
|
-
|
|
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.
|
|
364
|
+
this.calculateRange()
|
|
307
365
|
}),
|
|
308
366
|
)
|
|
309
367
|
|
|
310
368
|
this.unsubs.push(
|
|
311
369
|
this.options.observeElementOffset(this, (offset) => {
|
|
312
|
-
this.
|
|
313
|
-
|
|
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
|
|
437
|
+
key: process.env.NODE_ENV !== 'production' && 'getMeasurements',
|
|
358
438
|
debug: () => this.options.debug,
|
|
359
439
|
},
|
|
360
440
|
)
|
|
361
441
|
|
|
362
|
-
|
|
442
|
+
calculateRange = memo(
|
|
363
443
|
() => [this.getMeasurements(), this.getSize(), this.scrollOffset],
|
|
364
444
|
(measurements, outerSize, scrollOffset) => {
|
|
365
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
399
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{
|
|
586
|
+
{
|
|
587
|
+
align = 'start',
|
|
588
|
+
smoothScroll = this.options.enableSmoothScroll,
|
|
589
|
+
}: ScrollToOffsetOptions = {},
|
|
455
590
|
) => {
|
|
456
|
-
const
|
|
457
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
{
|
|
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 = (
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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:
|
|
735
|
+
measurements: VirtualItem[]
|
|
594
736
|
outerSize: number
|
|
595
737
|
scrollOffset: number
|
|
596
738
|
}) {
|