@tanstack/virtual-core 3.0.0-beta.17 → 3.0.0-beta.19
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/index.js +65 -18
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.js +65 -18
- package/build/esm/index.js.map +1 -1
- package/build/stats.html +1 -1
- package/build/stats.json +14 -14
- package/build/types/index.d.ts +26 -7
- package/build/umd/index.development.js +65 -18
- 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 +71 -25
package/src/index.ts
CHANGED
|
@@ -84,7 +84,7 @@ export const observeElementRect = (
|
|
|
84
84
|
const observer = new ResizeObserver((entries) => {
|
|
85
85
|
cb({
|
|
86
86
|
width: entries[0]?.contentRect.width as number,
|
|
87
|
-
height: entries[0]?.contentRect.height as number
|
|
87
|
+
height: entries[0]?.contentRect.height as number,
|
|
88
88
|
})
|
|
89
89
|
})
|
|
90
90
|
|
|
@@ -148,7 +148,10 @@ const createOffsetObserver = (mode: ObserverMode) => {
|
|
|
148
148
|
let prevY: number = instance.scrollElement[propY]
|
|
149
149
|
|
|
150
150
|
const scroll = () => {
|
|
151
|
-
|
|
151
|
+
const offset =
|
|
152
|
+
instance.scrollElement[instance.options.horizontal ? propX : propY]
|
|
153
|
+
|
|
154
|
+
cb(Math.max(0, offset - instance.options.scrollMargin))
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
scroll()
|
|
@@ -191,22 +194,26 @@ export const measureElement = (
|
|
|
191
194
|
|
|
192
195
|
export const windowScroll = (
|
|
193
196
|
offset: number,
|
|
194
|
-
canSmooth: boolean,
|
|
197
|
+
{ canSmooth, sync }: { canSmooth: boolean; sync: boolean },
|
|
195
198
|
instance: Virtualizer<any, any>,
|
|
196
199
|
) => {
|
|
200
|
+
const toOffset = sync ? offset : offset + instance.options.scrollMargin
|
|
201
|
+
|
|
197
202
|
;(instance.scrollElement as Window)?.scrollTo?.({
|
|
198
|
-
[instance.options.horizontal ? 'left' : 'top']:
|
|
203
|
+
[instance.options.horizontal ? 'left' : 'top']: toOffset,
|
|
199
204
|
behavior: canSmooth ? 'smooth' : undefined,
|
|
200
205
|
})
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
export const elementScroll = (
|
|
204
209
|
offset: number,
|
|
205
|
-
canSmooth: boolean,
|
|
210
|
+
{ canSmooth, sync }: { canSmooth: boolean; sync: boolean },
|
|
206
211
|
instance: Virtualizer<any, any>,
|
|
207
212
|
) => {
|
|
213
|
+
const toOffset = sync ? offset : offset + instance.options.scrollMargin
|
|
214
|
+
|
|
208
215
|
;(instance.scrollElement as Element)?.scrollTo?.({
|
|
209
|
-
[instance.options.horizontal ? 'left' : 'top']:
|
|
216
|
+
[instance.options.horizontal ? 'left' : 'top']: toOffset,
|
|
210
217
|
behavior: canSmooth ? 'smooth' : undefined,
|
|
211
218
|
})
|
|
212
219
|
}
|
|
@@ -223,7 +230,7 @@ export interface VirtualizerOptions<
|
|
|
223
230
|
// Required from the framework adapter (but can be overridden)
|
|
224
231
|
scrollToFn: (
|
|
225
232
|
offset: number,
|
|
226
|
-
canSmooth: boolean,
|
|
233
|
+
options: { canSmooth: boolean; sync: boolean },
|
|
227
234
|
instance: Virtualizer<TScrollElement, TItemElement>,
|
|
228
235
|
) => void
|
|
229
236
|
observeElementRect: (
|
|
@@ -253,12 +260,16 @@ export interface VirtualizerOptions<
|
|
|
253
260
|
getItemKey?: (index: number) => Key
|
|
254
261
|
rangeExtractor?: (range: Range) => number[]
|
|
255
262
|
enableSmoothScroll?: boolean
|
|
263
|
+
scrollMargin?: number
|
|
264
|
+
scrollingDelay?: number
|
|
256
265
|
}
|
|
257
266
|
|
|
258
267
|
export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
259
268
|
private unsubs: (void | (() => void))[] = []
|
|
260
269
|
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
|
|
261
270
|
scrollElement: TScrollElement | null = null
|
|
271
|
+
isScrolling: boolean = false
|
|
272
|
+
private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
262
273
|
private measurementsCache: Item[] = []
|
|
263
274
|
private itemMeasurementsCache: Record<Key, number> = {}
|
|
264
275
|
private pendingMeasuredCacheIndexes: number[] = []
|
|
@@ -270,7 +281,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
270
281
|
number,
|
|
271
282
|
(measurableItem: TItemElement | null) => void
|
|
272
283
|
> = {}
|
|
273
|
-
|
|
284
|
+
range: { startIndex: number; endIndex: number } = {
|
|
274
285
|
startIndex: 0,
|
|
275
286
|
endIndex: 0,
|
|
276
287
|
}
|
|
@@ -303,6 +314,8 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
303
314
|
onChange: () => {},
|
|
304
315
|
measureElement,
|
|
305
316
|
initialRect: { width: 0, height: 0 },
|
|
317
|
+
scrollMargin: 0,
|
|
318
|
+
scrollingDelay: 150,
|
|
306
319
|
...opts,
|
|
307
320
|
}
|
|
308
321
|
}
|
|
@@ -330,6 +343,10 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
330
343
|
this.cleanup()
|
|
331
344
|
|
|
332
345
|
this.scrollElement = scrollElement
|
|
346
|
+
this._scrollToOffset(this.scrollOffset, {
|
|
347
|
+
canSmooth: false,
|
|
348
|
+
sync: true,
|
|
349
|
+
})
|
|
333
350
|
|
|
334
351
|
this.unsubs.push(
|
|
335
352
|
this.options.observeElementRect(this, (rect) => {
|
|
@@ -340,10 +357,30 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
340
357
|
|
|
341
358
|
this.unsubs.push(
|
|
342
359
|
this.options.observeElementOffset(this, (offset) => {
|
|
343
|
-
this.
|
|
360
|
+
if (this.isScrollingTimeoutId !== null) {
|
|
361
|
+
clearTimeout(this.isScrollingTimeoutId)
|
|
362
|
+
this.isScrollingTimeoutId = null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (this.scrollOffset !== offset) {
|
|
366
|
+
this.isScrolling = true
|
|
367
|
+
this.scrollOffset = offset
|
|
368
|
+
|
|
369
|
+
this.isScrollingTimeoutId = setTimeout(() => {
|
|
370
|
+
this.isScrollingTimeoutId = null
|
|
371
|
+
this.isScrolling = false
|
|
372
|
+
|
|
373
|
+
this.notify()
|
|
374
|
+
}, this.options.scrollingDelay)
|
|
375
|
+
} else {
|
|
376
|
+
this.isScrolling = false
|
|
377
|
+
}
|
|
378
|
+
|
|
344
379
|
this.calculateRange()
|
|
345
380
|
}),
|
|
346
381
|
)
|
|
382
|
+
} else if (!this.isScrolling) {
|
|
383
|
+
this.calculateRange()
|
|
347
384
|
}
|
|
348
385
|
}
|
|
349
386
|
|
|
@@ -390,7 +427,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
390
427
|
},
|
|
391
428
|
)
|
|
392
429
|
|
|
393
|
-
|
|
430
|
+
calculateRange = memo(
|
|
394
431
|
() => [this.getMeasurements(), this.getSize(), this.scrollOffset],
|
|
395
432
|
(measurements, outerSize, scrollOffset) => {
|
|
396
433
|
const range = calculateRange({
|
|
@@ -460,7 +497,10 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
460
497
|
if (!this.destinationOffset) {
|
|
461
498
|
this._scrollToOffset(
|
|
462
499
|
this.scrollOffset + (measuredItemSize - itemSize),
|
|
463
|
-
|
|
500
|
+
{
|
|
501
|
+
canSmooth: false,
|
|
502
|
+
sync: false,
|
|
503
|
+
},
|
|
464
504
|
)
|
|
465
505
|
}
|
|
466
506
|
}
|
|
@@ -502,7 +542,10 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
502
542
|
|
|
503
543
|
scrollToOffset = (
|
|
504
544
|
toOffset: number,
|
|
505
|
-
{
|
|
545
|
+
{
|
|
546
|
+
align = 'start',
|
|
547
|
+
smoothScroll = this.options.enableSmoothScroll,
|
|
548
|
+
}: ScrollToOffsetOptions = {},
|
|
506
549
|
) => {
|
|
507
550
|
const offset = this.scrollOffset
|
|
508
551
|
const size = this.getSize()
|
|
@@ -517,18 +560,22 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
517
560
|
}
|
|
518
561
|
}
|
|
519
562
|
|
|
563
|
+
const options = {
|
|
564
|
+
canSmooth: smoothScroll,
|
|
565
|
+
sync: false,
|
|
566
|
+
}
|
|
520
567
|
if (align === 'start') {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
568
|
+
this._scrollToOffset(toOffset, options)
|
|
569
|
+
} else if (align === 'end') {
|
|
570
|
+
this._scrollToOffset(toOffset - size, options)
|
|
571
|
+
} else if (align === 'center') {
|
|
572
|
+
this._scrollToOffset(toOffset - size / 2, options)
|
|
526
573
|
}
|
|
527
574
|
}
|
|
528
575
|
|
|
529
576
|
scrollToIndex = (
|
|
530
577
|
index: number,
|
|
531
|
-
{ align = 'auto',
|
|
578
|
+
{ align = 'auto', ...rest }: ScrollToIndexOptions = {},
|
|
532
579
|
) => {
|
|
533
580
|
const measurements = this.getMeasurements()
|
|
534
581
|
const offset = this.scrollOffset
|
|
@@ -559,22 +606,21 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
559
606
|
? measurement.end + this.options.scrollPaddingEnd
|
|
560
607
|
: measurement.start - this.options.scrollPaddingStart
|
|
561
608
|
|
|
562
|
-
this.scrollToOffset(toOffset, { align,
|
|
609
|
+
this.scrollToOffset(toOffset, { align, ...rest })
|
|
563
610
|
}
|
|
564
611
|
|
|
565
612
|
getTotalSize = () =>
|
|
566
613
|
(this.getMeasurements()[this.options.count - 1]?.end ||
|
|
567
614
|
this.options.paddingStart) + this.options.paddingEnd
|
|
568
615
|
|
|
569
|
-
private _scrollToOffset = (
|
|
616
|
+
private _scrollToOffset = (
|
|
617
|
+
offset: number,
|
|
618
|
+
options: { canSmooth: boolean; sync: boolean },
|
|
619
|
+
) => {
|
|
570
620
|
clearTimeout(this.scrollCheckFrame)
|
|
571
621
|
|
|
572
622
|
this.destinationOffset = offset
|
|
573
|
-
this.options.scrollToFn(
|
|
574
|
-
offset,
|
|
575
|
-
canSmooth,
|
|
576
|
-
this,
|
|
577
|
-
)
|
|
623
|
+
this.options.scrollToFn(offset, options, this)
|
|
578
624
|
|
|
579
625
|
let scrollCheckFrame: ReturnType<typeof setTimeout>
|
|
580
626
|
|