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