@tanstack/virtual-core 3.0.0-beta.23 → 3.0.0-beta.28

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.
@@ -15,19 +15,19 @@ Object.defineProperty(exports, '__esModule', { value: true });
15
15
  var utils = require('./utils.js');
16
16
 
17
17
  //
18
+
19
+ //
20
+
18
21
  const defaultKeyExtractor = index => index;
19
22
  const defaultRangeExtractor = range => {
20
23
  const start = Math.max(range.startIndex - range.overscan, 0);
21
24
  const end = Math.min(range.endIndex + range.overscan, range.count - 1);
22
25
  const arr = [];
23
-
24
26
  for (let i = start; i <= end; i++) {
25
27
  arr.push(i);
26
28
  }
27
-
28
29
  return arr;
29
30
  };
30
-
31
31
  const memoRectCallback = (instance, cb) => {
32
32
  let prev = {
33
33
  height: -1,
@@ -37,25 +37,20 @@ const memoRectCallback = (instance, cb) => {
37
37
  if (instance.options.horizontal ? rect.width !== prev.width : rect.height !== prev.height) {
38
38
  cb(rect);
39
39
  }
40
-
41
40
  prev = rect;
42
41
  };
43
42
  };
44
-
45
43
  const observeElementRect = (instance, cb) => {
46
44
  const observer = new ResizeObserver(entries => {
47
45
  var _entries$, _entries$2;
48
-
49
46
  cb({
50
47
  width: (_entries$ = entries[0]) == null ? void 0 : _entries$.contentRect.width,
51
48
  height: (_entries$2 = entries[0]) == null ? void 0 : _entries$2.contentRect.height
52
49
  });
53
50
  });
54
-
55
51
  if (!instance.scrollElement) {
56
52
  return;
57
53
  }
58
-
59
54
  cb(instance.scrollElement.getBoundingClientRect());
60
55
  observer.observe(instance.scrollElement);
61
56
  return () => {
@@ -64,16 +59,13 @@ const observeElementRect = (instance, cb) => {
64
59
  };
65
60
  const observeWindowRect = (instance, cb) => {
66
61
  const memoizedCallback = memoRectCallback(instance, cb);
67
-
68
62
  const onResize = () => memoizedCallback({
69
63
  width: instance.scrollElement.innerWidth,
70
64
  height: instance.scrollElement.innerHeight
71
65
  });
72
-
73
66
  if (!instance.scrollElement) {
74
67
  return;
75
68
  }
76
-
77
69
  onResize();
78
70
  instance.scrollElement.addEventListener('resize', onResize, {
79
71
  capture: false,
@@ -87,38 +79,30 @@ const scrollProps = {
87
79
  element: ['scrollLeft', 'scrollTop'],
88
80
  window: ['scrollX', 'scrollY']
89
81
  };
90
-
91
82
  const createOffsetObserver = mode => {
92
83
  return (instance, cb) => {
93
84
  if (!instance.scrollElement) {
94
85
  return;
95
86
  }
96
-
97
87
  const propX = scrollProps[mode][0];
98
88
  const propY = scrollProps[mode][1];
99
89
  let prevX = instance.scrollElement[propX];
100
90
  let prevY = instance.scrollElement[propY];
101
-
102
91
  const scroll = () => {
103
92
  const offset = instance.scrollElement[instance.options.horizontal ? propX : propY];
104
93
  cb(Math.max(0, offset - instance.options.scrollMargin));
105
94
  };
106
-
107
95
  scroll();
108
-
109
96
  const onScroll = e => {
110
97
  const target = e.currentTarget;
111
98
  const scrollX = target[propX];
112
99
  const scrollY = target[propY];
113
-
114
100
  if (instance.options.horizontal ? prevX - scrollX : prevY - scrollY) {
115
101
  scroll();
116
102
  }
117
-
118
103
  prevX = scrollX;
119
104
  prevY = scrollY;
120
105
  };
121
-
122
106
  instance.scrollElement.addEventListener('scroll', onScroll, {
123
107
  capture: false,
124
108
  passive: true
@@ -128,7 +112,6 @@ const createOffsetObserver = mode => {
128
112
  };
129
113
  };
130
114
  };
131
-
132
115
  const observeElementOffset = createOffsetObserver('element');
133
116
  const observeWindowOffset = createOffsetObserver('window');
134
117
  const measureElement = (element, instance) => {
@@ -136,34 +119,33 @@ const measureElement = (element, instance) => {
136
119
  };
137
120
  const windowScroll = (offset, _ref, instance) => {
138
121
  var _instance$scrollEleme;
139
-
140
122
  let {
141
- canSmooth,
123
+ adjustments,
124
+ behavior,
142
125
  sync
143
126
  } = _ref;
144
- const toOffset = sync ? offset : offset + instance.options.scrollMargin;
127
+ const toOffset = (sync ? offset : offset + instance.options.scrollMargin) + (adjustments ?? 0);
145
128
  (_instance$scrollEleme = instance.scrollElement) == null ? void 0 : _instance$scrollEleme.scrollTo == null ? void 0 : _instance$scrollEleme.scrollTo({
146
129
  [instance.options.horizontal ? 'left' : 'top']: toOffset,
147
- behavior: canSmooth ? 'smooth' : undefined
130
+ behavior
148
131
  });
149
132
  };
150
133
  const elementScroll = (offset, _ref2, instance) => {
151
134
  var _instance$scrollEleme2;
152
-
153
135
  let {
154
- canSmooth,
136
+ adjustments,
137
+ behavior,
155
138
  sync
156
139
  } = _ref2;
157
- const toOffset = sync ? offset : offset + instance.options.scrollMargin;
140
+ const toOffset = (sync ? offset : offset + instance.options.scrollMargin) + (adjustments ?? 0);
158
141
  (_instance$scrollEleme2 = instance.scrollElement) == null ? void 0 : _instance$scrollEleme2.scrollTo == null ? void 0 : _instance$scrollEleme2.scrollTo({
159
142
  [instance.options.horizontal ? 'left' : 'top']: toOffset,
160
- behavior: canSmooth ? 'smooth' : undefined
143
+ behavior
161
144
  });
162
145
  };
163
146
  class Virtualizer {
164
147
  constructor(_opts) {
165
148
  var _this = this;
166
-
167
149
  this.unsubs = [];
168
150
  this.scrollElement = null;
169
151
  this.isScrolling = false;
@@ -171,9 +153,9 @@ class Virtualizer {
171
153
  this.measurementsCache = [];
172
154
  this.itemMeasurementsCache = {};
173
155
  this.pendingMeasuredCacheIndexes = [];
174
- this.scrollDelta = 0;
156
+ this.scrollAdjustments = 0;
175
157
  this.measureElementCache = {};
176
-
158
+ this.pendingScrollToIndexCallback = null;
177
159
  this.getResizeObserver = (() => {
178
160
  let _ro = null;
179
161
  return () => {
@@ -190,12 +172,10 @@ class Virtualizer {
190
172
  }
191
173
  };
192
174
  })();
193
-
194
175
  this.range = {
195
176
  startIndex: 0,
196
177
  endIndex: 0
197
178
  };
198
-
199
179
  this.setOptions = opts => {
200
180
  Object.entries(opts).forEach(_ref3 => {
201
181
  let [key, value] = _ref3;
@@ -212,7 +192,6 @@ class Virtualizer {
212
192
  horizontal: false,
213
193
  getItemKey: defaultKeyExtractor,
214
194
  rangeExtractor: defaultRangeExtractor,
215
- enableSmoothScroll: true,
216
195
  onChange: () => {},
217
196
  measureElement,
218
197
  initialRect: {
@@ -225,42 +204,35 @@ class Virtualizer {
225
204
  ...opts
226
205
  };
227
206
  };
228
-
229
207
  this.notify = () => {
230
208
  var _this$options$onChang, _this$options;
231
-
232
209
  (_this$options$onChang = (_this$options = this.options).onChange) == null ? void 0 : _this$options$onChang.call(_this$options, this);
233
210
  };
234
-
235
211
  this.cleanup = () => {
236
212
  this.unsubs.filter(Boolean).forEach(d => d());
237
213
  this.unsubs = [];
238
214
  this.scrollElement = null;
239
215
  };
240
-
241
216
  this._didMount = () => {
242
217
  return () => {
243
218
  var _this$getResizeObserv;
244
-
245
219
  (_this$getResizeObserv = this.getResizeObserver()) == null ? void 0 : _this$getResizeObserv.disconnect();
246
220
  this.measureElementCache = {};
247
221
  this.cleanup();
248
222
  };
249
223
  };
250
-
251
224
  this._willUpdate = () => {
225
+ var _this$pendingScrollTo;
226
+ (_this$pendingScrollTo = this.pendingScrollToIndexCallback) == null ? void 0 : _this$pendingScrollTo.call(this);
252
227
  const scrollElement = this.options.getScrollElement();
253
-
254
228
  if (this.scrollElement !== scrollElement) {
255
229
  this.cleanup();
256
230
  this.scrollElement = scrollElement;
257
-
258
231
  this._scrollToOffset(this.scrollOffset, {
259
- canSmooth: false,
260
- sync: true,
261
- requested: false
232
+ adjustments: undefined,
233
+ behavior: undefined,
234
+ sync: true
262
235
  });
263
-
264
236
  this.unsubs.push(this.options.observeElementRect(this, rect => {
265
237
  this.scrollRect = rect;
266
238
  this.calculateRange();
@@ -270,11 +242,10 @@ class Virtualizer {
270
242
  clearTimeout(this.isScrollingTimeoutId);
271
243
  this.isScrollingTimeoutId = null;
272
244
  }
273
-
274
245
  if (this.scrollOffset !== offset) {
275
246
  this.scrollOffset = offset;
276
247
  this.isScrolling = true;
277
- this.scrollDelta = 0;
248
+ this.scrollAdjustments = 0;
278
249
  this.isScrollingTimeoutId = setTimeout(() => {
279
250
  this.isScrollingTimeoutId = null;
280
251
  this.isScrolling = false;
@@ -282,25 +253,21 @@ class Virtualizer {
282
253
  }, this.options.scrollingDelay);
283
254
  } else {
284
255
  this.isScrolling = false;
285
- this.scrollDelta = 0;
256
+ this.scrollAdjustments = 0;
286
257
  }
287
-
288
258
  this.calculateRange();
289
259
  }));
290
260
  } else if (!this.isScrolling) {
291
261
  this.calculateRange();
292
262
  }
293
263
  };
294
-
295
264
  this.getSize = () => {
296
265
  return this.scrollRect[this.options.horizontal ? 'width' : 'height'];
297
266
  };
298
-
299
267
  this.getMeasurements = utils.memo(() => [this.options.count, this.options.paddingStart, this.options.getItemKey, this.itemMeasurementsCache], (count, paddingStart, getItemKey, measurementsCache) => {
300
268
  const min = this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
301
269
  this.pendingMeasuredCacheIndexes = [];
302
270
  const measurements = this.measurementsCache.slice(0, min);
303
-
304
271
  for (let i = min; i < count; i++) {
305
272
  const key = getItemKey(i);
306
273
  const measuredSize = measurementsCache[key];
@@ -315,7 +282,6 @@ class Virtualizer {
315
282
  key
316
283
  };
317
284
  }
318
-
319
285
  this.measurementsCache = measurements;
320
286
  return measurements;
321
287
  }, {
@@ -328,19 +294,18 @@ class Virtualizer {
328
294
  outerSize,
329
295
  scrollOffset
330
296
  });
331
-
332
297
  if (range.startIndex !== this.range.startIndex || range.endIndex !== this.range.endIndex) {
333
298
  this.range = range;
334
299
  this.notify();
335
300
  }
336
-
337
301
  return this.range;
338
302
  }, {
339
303
  key: process.env.NODE_ENV !== 'production' && 'calculateRange',
340
304
  debug: () => this.options.debug
341
305
  });
342
306
  this.getIndexes = utils.memo(() => [this.options.rangeExtractor, this.range, this.options.overscan, this.options.count], (rangeExtractor, range, overscan, count) => {
343
- return rangeExtractor({ ...range,
307
+ return rangeExtractor({
308
+ ...range,
344
309
  overscan,
345
310
  count: count
346
311
  });
@@ -348,109 +313,84 @@ class Virtualizer {
348
313
  key: process.env.NODE_ENV !== 'production' && 'getIndexes',
349
314
  debug: () => this.options.debug
350
315
  });
351
-
352
316
  this.indexFromElement = node => {
353
317
  const attributeName = this.options.indexAttribute;
354
318
  const indexStr = node.getAttribute(attributeName);
355
-
356
319
  if (!indexStr) {
357
- console.warn("Missing attribute name '" + attributeName + "={index}' on measured element.");
320
+ console.warn(`Missing attribute name '${attributeName}={index}' on measured element.`);
358
321
  return -1;
359
322
  }
360
-
361
323
  return parseInt(indexStr, 10);
362
324
  };
363
-
364
325
  this._measureElement = (node, _sync) => {
365
- var _this$itemMeasurement;
366
-
367
326
  const index = this.indexFromElement(node);
368
327
  const item = this.measurementsCache[index];
369
-
370
328
  if (!item) {
371
329
  return;
372
330
  }
373
-
374
331
  const prevNode = this.measureElementCache[item.key];
375
332
  const ro = this.getResizeObserver();
376
-
377
333
  if (!node.isConnected) {
378
334
  if (prevNode) {
379
335
  ro == null ? void 0 : ro.unobserve(prevNode);
380
336
  delete this.measureElementCache[item.key];
381
337
  }
382
-
383
338
  return;
384
339
  }
385
-
386
340
  if (!prevNode || prevNode !== node) {
387
341
  if (prevNode) {
388
342
  ro == null ? void 0 : ro.unobserve(prevNode);
389
343
  }
390
-
391
344
  this.measureElementCache[item.key] = node;
392
345
  ro == null ? void 0 : ro.observe(node);
393
346
  }
394
-
395
347
  const measuredItemSize = this.options.measureElement(node, this);
396
- const itemSize = (_this$itemMeasurement = this.itemMeasurementsCache[item.key]) != null ? _this$itemMeasurement : item.size;
348
+ const itemSize = this.itemMeasurementsCache[item.key] ?? item.size;
397
349
  const delta = measuredItemSize - itemSize;
398
-
399
350
  if (delta !== 0) {
400
- if (item.start < this.scrollOffset && this.isScrolling && this.destinationOffset === undefined) {
351
+ if (item.start < this.scrollOffset && this.isScrolling) {
401
352
  if (process.env.NODE_ENV !== 'production' && this.options.debug) {
402
353
  console.info('correction', delta);
403
354
  }
404
-
405
- this.scrollDelta += delta;
406
-
407
- this._scrollToOffset(this.scrollOffset + this.scrollDelta, {
408
- canSmooth: false,
409
- sync: false,
410
- requested: false
355
+ this._scrollToOffset(this.scrollOffset, {
356
+ adjustments: this.scrollAdjustments += delta,
357
+ behavior: undefined,
358
+ sync: false
411
359
  });
412
360
  }
413
-
414
361
  this.pendingMeasuredCacheIndexes.push(index);
415
- this.itemMeasurementsCache = { ...this.itemMeasurementsCache,
362
+ this.itemMeasurementsCache = {
363
+ ...this.itemMeasurementsCache,
416
364
  [item.key]: measuredItemSize
417
365
  };
418
366
  this.notify();
419
367
  }
420
368
  };
421
-
422
369
  this.measureElement = node => {
423
370
  if (!node) {
424
371
  return;
425
372
  }
426
-
427
373
  this._measureElement(node, true);
428
374
  };
429
-
430
375
  this.getVirtualItems = utils.memo(() => [this.getIndexes(), this.getMeasurements()], (indexes, measurements) => {
431
376
  const virtualItems = [];
432
-
433
377
  for (let k = 0, len = indexes.length; k < len; k++) {
434
378
  const i = indexes[k];
435
379
  const measurement = measurements[i];
436
380
  virtualItems.push(measurement);
437
381
  }
438
-
439
382
  return virtualItems;
440
383
  }, {
441
384
  key: process.env.NODE_ENV !== 'production' && 'getIndexes',
442
385
  debug: () => this.options.debug
443
386
  });
444
-
445
387
  this.scrollToOffset = function (toOffset, _temp) {
446
388
  let {
447
389
  align = 'start',
448
- smoothScroll = _this.options.enableSmoothScroll
390
+ behavior
449
391
  } = _temp === void 0 ? {} : _temp;
450
392
  const offset = _this.scrollOffset;
451
-
452
393
  const size = _this.getSize();
453
-
454
394
  if (align === 'auto') {
455
395
  if (toOffset <= offset) {
456
396
  align = 'start';
@@ -460,13 +400,11 @@ class Virtualizer {
460
400
  align = 'start';
461
401
  }
462
402
  }
463
-
464
403
  const options = {
465
- canSmooth: smoothScroll,
466
- sync: false,
467
- requested: true
404
+ adjustments: undefined,
405
+ behavior,
406
+ sync: false
468
407
  };
469
-
470
408
  if (align === 'start') {
471
409
  _this._scrollToOffset(toOffset, options);
472
410
  } else if (align === 'end') {
@@ -475,29 +413,22 @@ class Virtualizer {
475
413
  _this._scrollToOffset(toOffset - size / 2, options);
476
414
  }
477
415
  };
478
-
479
416
  this.scrollToIndex = function (index, _temp2) {
480
417
  let {
481
418
  align = 'auto',
482
- smoothScroll = _this.options.enableSmoothScroll,
483
419
  ...rest
484
420
  } = _temp2 === void 0 ? {} : _temp2;
485
-
421
+ _this.pendingScrollToIndexCallback = null;
486
422
  const measurements = _this.getMeasurements();
487
-
488
423
  const offset = _this.scrollOffset;
489
-
490
424
  const size = _this.getSize();
491
-
492
425
  const {
493
426
  count
494
427
  } = _this.options;
495
428
  const measurement = measurements[Math.max(0, Math.min(index, count - 1))];
496
-
497
429
  if (!measurement) {
498
430
  return;
499
431
  }
500
-
501
432
  if (align === 'auto') {
502
433
  if (measurement.end >= offset + size - _this.options.scrollPaddingEnd) {
503
434
  align = 'end';
@@ -507,78 +438,57 @@ class Virtualizer {
507
438
  return;
508
439
  }
509
440
  }
510
-
511
441
  const toOffset = align === 'end' ? measurement.end + _this.options.scrollPaddingEnd : measurement.start - _this.options.scrollPaddingStart;
512
-
513
442
  _this.scrollToOffset(toOffset, {
514
443
  align,
515
- smoothScroll,
516
444
  ...rest
517
445
  });
446
+ const isDynamic = Object.keys(_this.measureElementCache).length > 0;
447
+ if (isDynamic) {
448
+ const didSeen = () => typeof _this.itemMeasurementsCache[_this.options.getItemKey(index)] === 'number';
449
+ if (!didSeen()) {
450
+ _this.pendingScrollToIndexCallback = () => {
451
+ if (didSeen()) {
452
+ _this.pendingScrollToIndexCallback = null;
453
+ _this.scrollToIndex(index, {
454
+ align,
455
+ ...rest
456
+ });
457
+ }
458
+ };
459
+ }
460
+ }
518
461
  };
519
-
520
462
  this.getTotalSize = () => {
521
463
  var _this$getMeasurements;
522
-
523
464
  return (((_this$getMeasurements = this.getMeasurements()[this.options.count - 1]) == null ? void 0 : _this$getMeasurements.end) || this.options.paddingStart) + this.options.paddingEnd;
524
465
  };
525
-
526
466
  this._scrollToOffset = (offset, _ref4) => {
527
467
  let {
528
- requested,
529
- canSmooth,
468
+ adjustments,
469
+ behavior,
530
470
  sync
531
471
  } = _ref4;
532
- clearTimeout(this.scrollCheckFrame);
533
-
534
- if (requested) {
535
- this.destinationOffset = offset;
536
- }
537
-
538
472
  this.options.scrollToFn(offset, {
539
- canSmooth,
540
- sync
473
+ behavior,
474
+ sync,
475
+ adjustments
541
476
  }, this);
542
- let scrollCheckFrame;
543
-
544
- const check = () => {
545
- let lastOffset = this.scrollOffset;
546
- this.scrollCheckFrame = scrollCheckFrame = setTimeout(() => {
547
- if (this.scrollCheckFrame !== scrollCheckFrame) {
548
- return;
549
- }
550
-
551
- if (this.scrollOffset === lastOffset) {
552
- this.destinationOffset = undefined;
553
- return;
554
- }
555
-
556
- lastOffset = this.scrollOffset;
557
- check();
558
- }, 100);
559
- };
560
-
561
- check();
562
477
  };
563
-
564
478
  this.measure = () => {
565
479
  this.itemMeasurementsCache = {};
566
480
  this.notify();
567
481
  };
568
-
569
482
  this.setOptions(_opts);
570
483
  this.scrollRect = this.options.initialRect;
571
484
  this.scrollOffset = this.options.initialOffset;
572
485
  this.calculateRange();
573
486
  }
574
-
575
487
  }
576
-
577
488
  const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
578
489
  while (low <= high) {
579
490
  const middle = (low + high) / 2 | 0;
580
491
  const currentValue = getCurrentValue(middle);
581
-
582
492
  if (currentValue < value) {
583
493
  low = middle + 1;
584
494
  } else if (currentValue > value) {
@@ -587,14 +497,12 @@ const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
587
497
  return middle;
588
498
  }
589
499
  }
590
-
591
500
  if (low > 0) {
592
501
  return low - 1;
593
502
  } else {
594
503
  return 0;
595
504
  }
596
505
  };
597
-
598
506
  function calculateRange(_ref5) {
599
507
  let {
600
508
  measurements,
@@ -602,16 +510,12 @@ function calculateRange(_ref5) {
602
510
  scrollOffset
603
511
  } = _ref5;
604
512
  const count = measurements.length - 1;
605
-
606
513
  const getOffset = index => measurements[index].start;
607
-
608
514
  const startIndex = findNearestBinarySearch(0, count, getOffset, scrollOffset);
609
515
  let endIndex = startIndex;
610
-
611
516
  while (endIndex < count && measurements[endIndex].end < scrollOffset + outerSize) {
612
517
  endIndex++;
613
518
  }
614
-
615
519
  return {
616
520
  startIndex,
617
521
  endIndex