@tanstack/virtual-core 3.0.0-beta.22 → 3.0.0-beta.26

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,8 @@
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
-
218
194
  this.getResizeObserver = (() => {
219
195
  let _ro = null;
220
196
  return () => {
@@ -231,12 +207,10 @@
231
207
  }
232
208
  };
233
209
  })();
234
-
235
210
  this.range = {
236
211
  startIndex: 0,
237
212
  endIndex: 0
238
213
  };
239
-
240
214
  this.setOptions = opts => {
241
215
  Object.entries(opts).forEach(_ref3 => {
242
216
  let [key, value] = _ref3;
@@ -253,7 +227,6 @@
253
227
  horizontal: false,
254
228
  getItemKey: defaultKeyExtractor,
255
229
  rangeExtractor: defaultRangeExtractor,
256
- enableSmoothScroll: true,
257
230
  onChange: () => {},
258
231
  measureElement,
259
232
  initialRect: {
@@ -266,42 +239,33 @@
266
239
  ...opts
267
240
  };
268
241
  };
269
-
270
242
  this.notify = () => {
271
243
  var _this$options$onChang, _this$options;
272
-
273
244
  (_this$options$onChang = (_this$options = this.options).onChange) == null ? void 0 : _this$options$onChang.call(_this$options, this);
274
245
  };
275
-
276
246
  this.cleanup = () => {
277
247
  this.unsubs.filter(Boolean).forEach(d => d());
278
248
  this.unsubs = [];
279
249
  this.scrollElement = null;
280
250
  };
281
-
282
251
  this._didMount = () => {
283
252
  return () => {
284
253
  var _this$getResizeObserv;
285
-
286
254
  (_this$getResizeObserv = this.getResizeObserver()) == null ? void 0 : _this$getResizeObserv.disconnect();
287
255
  this.measureElementCache = {};
288
256
  this.cleanup();
289
257
  };
290
258
  };
291
-
292
259
  this._willUpdate = () => {
293
260
  const scrollElement = this.options.getScrollElement();
294
-
295
261
  if (this.scrollElement !== scrollElement) {
296
262
  this.cleanup();
297
263
  this.scrollElement = scrollElement;
298
-
299
264
  this._scrollToOffset(this.scrollOffset, {
300
- canSmooth: false,
301
- sync: true,
302
- requested: false
265
+ adjustments: undefined,
266
+ behavior: undefined,
267
+ sync: true
303
268
  });
304
-
305
269
  this.unsubs.push(this.options.observeElementRect(this, rect => {
306
270
  this.scrollRect = rect;
307
271
  this.calculateRange();
@@ -311,11 +275,10 @@
311
275
  clearTimeout(this.isScrollingTimeoutId);
312
276
  this.isScrollingTimeoutId = null;
313
277
  }
314
-
315
278
  if (this.scrollOffset !== offset) {
316
279
  this.scrollOffset = offset;
317
280
  this.isScrolling = true;
318
- this.scrollDelta = 0;
281
+ this.scrollAdjustments = 0;
319
282
  this.isScrollingTimeoutId = setTimeout(() => {
320
283
  this.isScrollingTimeoutId = null;
321
284
  this.isScrolling = false;
@@ -323,25 +286,21 @@
323
286
  }, this.options.scrollingDelay);
324
287
  } else {
325
288
  this.isScrolling = false;
326
- this.scrollDelta = 0;
289
+ this.scrollAdjustments = 0;
327
290
  }
328
-
329
291
  this.calculateRange();
330
292
  }));
331
293
  } else if (!this.isScrolling) {
332
294
  this.calculateRange();
333
295
  }
334
296
  };
335
-
336
297
  this.getSize = () => {
337
298
  return this.scrollRect[this.options.horizontal ? 'width' : 'height'];
338
299
  };
339
-
340
300
  this.getMeasurements = memo(() => [this.options.count, this.options.paddingStart, this.options.getItemKey, this.itemMeasurementsCache], (count, paddingStart, getItemKey, measurementsCache) => {
341
301
  const min = this.pendingMeasuredCacheIndexes.length > 0 ? Math.min(...this.pendingMeasuredCacheIndexes) : 0;
342
302
  this.pendingMeasuredCacheIndexes = [];
343
303
  const measurements = this.measurementsCache.slice(0, min);
344
-
345
304
  for (let i = min; i < count; i++) {
346
305
  const key = getItemKey(i);
347
306
  const measuredSize = measurementsCache[key];
@@ -356,7 +315,6 @@
356
315
  key
357
316
  };
358
317
  }
359
-
360
318
  this.measurementsCache = measurements;
361
319
  return measurements;
362
320
  }, {
@@ -369,19 +327,18 @@
369
327
  outerSize,
370
328
  scrollOffset
371
329
  });
372
-
373
330
  if (range.startIndex !== this.range.startIndex || range.endIndex !== this.range.endIndex) {
374
331
  this.range = range;
375
332
  this.notify();
376
333
  }
377
-
378
334
  return this.range;
379
335
  }, {
380
336
  key: 'calculateRange',
381
337
  debug: () => this.options.debug
382
338
  });
383
339
  this.getIndexes = memo(() => [this.options.rangeExtractor, this.range, this.options.overscan, this.options.count], (rangeExtractor, range, overscan, count) => {
384
- return rangeExtractor({ ...range,
340
+ return rangeExtractor({
341
+ ...range,
385
342
  overscan,
386
343
  count: count
387
344
  });
@@ -389,110 +346,84 @@
389
346
  key: 'getIndexes',
390
347
  debug: () => this.options.debug
391
348
  });
392
-
393
349
  this.indexFromElement = node => {
394
350
  const attributeName = this.options.indexAttribute;
395
351
  const indexStr = node.getAttribute(attributeName);
396
-
397
352
  if (!indexStr) {
398
- console.warn("Missing attribute name '" + attributeName + "={index}' on measured element.");
353
+ console.warn(`Missing attribute name '${attributeName}={index}' on measured element.`);
399
354
  return -1;
400
355
  }
401
-
402
356
  return parseInt(indexStr, 10);
403
357
  };
404
-
405
358
  this._measureElement = (node, _sync) => {
406
- var _this$itemMeasurement;
407
-
408
359
  const index = this.indexFromElement(node);
409
360
  const item = this.measurementsCache[index];
410
-
411
361
  if (!item) {
412
362
  return;
413
363
  }
414
-
415
364
  const prevNode = this.measureElementCache[item.key];
416
365
  const ro = this.getResizeObserver();
417
-
418
366
  if (!node.isConnected) {
419
367
  if (prevNode) {
420
368
  ro == null ? void 0 : ro.unobserve(prevNode);
421
369
  delete this.measureElementCache[item.key];
422
370
  }
423
-
424
371
  return;
425
372
  }
426
-
427
373
  if (!prevNode || prevNode !== node) {
428
374
  if (prevNode) {
429
375
  ro == null ? void 0 : ro.unobserve(prevNode);
430
376
  }
431
-
432
377
  this.measureElementCache[item.key] = node;
433
378
  ro == null ? void 0 : ro.observe(node);
434
379
  }
435
-
436
380
  const measuredItemSize = this.options.measureElement(node, this);
437
- const itemSize = (_this$itemMeasurement = this.itemMeasurementsCache[item.key]) != null ? _this$itemMeasurement : item.size;
438
-
439
- if (measuredItemSize !== itemSize) {
440
- if (item.start < this.scrollOffset) {
381
+ const itemSize = this.itemMeasurementsCache[item.key] ?? item.size;
382
+ const delta = measuredItemSize - itemSize;
383
+ if (delta !== 0) {
384
+ if (item.start < this.scrollOffset && this.isScrolling) {
441
385
  if (this.options.debug) {
442
- console.info('correction', measuredItemSize - itemSize);
443
- }
444
-
445
- if (this.destinationOffset === undefined) {
446
- this.scrollDelta += measuredItemSize - itemSize;
447
-
448
- this._scrollToOffset(this.scrollOffset + this.scrollDelta, {
449
- canSmooth: false,
450
- sync: false,
451
- requested: false
452
- });
386
+ console.info('correction', delta);
453
387
  }
388
+ this._scrollToOffset(this.scrollOffset, {
389
+ adjustments: this.scrollAdjustments += delta,
390
+ behavior: undefined,
391
+ sync: false
392
+ });
454
393
  }
455
-
456
394
  this.pendingMeasuredCacheIndexes.push(index);
457
- this.itemMeasurementsCache = { ...this.itemMeasurementsCache,
395
+ this.itemMeasurementsCache = {
396
+ ...this.itemMeasurementsCache,
458
397
  [item.key]: measuredItemSize
459
398
  };
460
399
  this.notify();
461
400
  }
462
401
  };
463
-
464
402
  this.measureElement = node => {
465
403
  if (!node) {
466
404
  return;
467
405
  }
468
-
469
406
  this._measureElement(node, true);
470
407
  };
471
-
472
408
  this.getVirtualItems = memo(() => [this.getIndexes(), this.getMeasurements()], (indexes, measurements) => {
473
409
  const virtualItems = [];
474
-
475
410
  for (let k = 0, len = indexes.length; k < len; k++) {
476
411
  const i = indexes[k];
477
412
  const measurement = measurements[i];
478
413
  virtualItems.push(measurement);
479
414
  }
480
-
481
415
  return virtualItems;
482
416
  }, {
483
417
  key: 'getIndexes',
484
418
  debug: () => this.options.debug
485
419
  });
486
-
487
420
  this.scrollToOffset = function (toOffset, _temp) {
488
421
  let {
489
422
  align = 'start',
490
- smoothScroll = _this.options.enableSmoothScroll
423
+ behavior
491
424
  } = _temp === void 0 ? {} : _temp;
492
425
  const offset = _this.scrollOffset;
493
-
494
426
  const size = _this.getSize();
495
-
496
427
  if (align === 'auto') {
497
428
  if (toOffset <= offset) {
498
429
  align = 'start';
@@ -502,13 +433,11 @@
502
433
  align = 'start';
503
434
  }
504
435
  }
505
-
506
436
  const options = {
507
- canSmooth: smoothScroll,
508
- sync: false,
509
- requested: true
437
+ adjustments: undefined,
438
+ behavior,
439
+ sync: false
510
440
  };
511
-
512
441
  if (align === 'start') {
513
442
  _this._scrollToOffset(toOffset, options);
514
443
  } else if (align === 'end') {
@@ -517,29 +446,21 @@
517
446
  _this._scrollToOffset(toOffset - size / 2, options);
518
447
  }
519
448
  };
520
-
521
449
  this.scrollToIndex = function (index, _temp2) {
522
450
  let {
523
451
  align = 'auto',
524
- smoothScroll = _this.options.enableSmoothScroll,
525
452
  ...rest
526
453
  } = _temp2 === void 0 ? {} : _temp2;
527
-
528
454
  const measurements = _this.getMeasurements();
529
-
530
455
  const offset = _this.scrollOffset;
531
-
532
456
  const size = _this.getSize();
533
-
534
457
  const {
535
458
  count
536
459
  } = _this.options;
537
460
  const measurement = measurements[Math.max(0, Math.min(index, count - 1))];
538
-
539
461
  if (!measurement) {
540
462
  return;
541
463
  }
542
-
543
464
  if (align === 'auto') {
544
465
  if (measurement.end >= offset + size - _this.options.scrollPaddingEnd) {
545
466
  align = 'end';
@@ -549,78 +470,42 @@
549
470
  return;
550
471
  }
551
472
  }
552
-
553
473
  const toOffset = align === 'end' ? measurement.end + _this.options.scrollPaddingEnd : measurement.start - _this.options.scrollPaddingStart;
554
-
555
474
  _this.scrollToOffset(toOffset, {
556
475
  align,
557
- smoothScroll,
558
476
  ...rest
559
477
  });
560
478
  };
561
-
562
479
  this.getTotalSize = () => {
563
480
  var _this$getMeasurements;
564
-
565
481
  return (((_this$getMeasurements = this.getMeasurements()[this.options.count - 1]) == null ? void 0 : _this$getMeasurements.end) || this.options.paddingStart) + this.options.paddingEnd;
566
482
  };
567
-
568
483
  this._scrollToOffset = (offset, _ref4) => {
569
484
  let {
570
- requested,
571
- canSmooth,
485
+ adjustments,
486
+ behavior,
572
487
  sync
573
488
  } = _ref4;
574
- clearTimeout(this.scrollCheckFrame);
575
-
576
- if (requested) {
577
- this.destinationOffset = offset;
578
- }
579
-
580
489
  this.options.scrollToFn(offset, {
581
- canSmooth,
582
- sync
490
+ behavior,
491
+ sync,
492
+ adjustments
583
493
  }, this);
584
- let scrollCheckFrame;
585
-
586
- const check = () => {
587
- let lastOffset = this.scrollOffset;
588
- this.scrollCheckFrame = scrollCheckFrame = setTimeout(() => {
589
- if (this.scrollCheckFrame !== scrollCheckFrame) {
590
- return;
591
- }
592
-
593
- if (this.scrollOffset === lastOffset) {
594
- this.destinationOffset = undefined;
595
- return;
596
- }
597
-
598
- lastOffset = this.scrollOffset;
599
- check();
600
- }, 100);
601
- };
602
-
603
- check();
604
494
  };
605
-
606
495
  this.measure = () => {
607
496
  this.itemMeasurementsCache = {};
608
497
  this.notify();
609
498
  };
610
-
611
499
  this.setOptions(_opts);
612
500
  this.scrollRect = this.options.initialRect;
613
501
  this.scrollOffset = this.options.initialOffset;
614
502
  this.calculateRange();
615
503
  }
616
-
617
504
  }
618
-
619
505
  const findNearestBinarySearch = (low, high, getCurrentValue, value) => {
620
506
  while (low <= high) {
621
507
  const middle = (low + high) / 2 | 0;
622
508
  const currentValue = getCurrentValue(middle);
623
-
624
509
  if (currentValue < value) {
625
510
  low = middle + 1;
626
511
  } else if (currentValue > value) {
@@ -629,14 +514,12 @@
629
514
  return middle;
630
515
  }
631
516
  }
632
-
633
517
  if (low > 0) {
634
518
  return low - 1;
635
519
  } else {
636
520
  return 0;
637
521
  }
638
522
  };
639
-
640
523
  function calculateRange(_ref5) {
641
524
  let {
642
525
  measurements,
@@ -644,16 +527,12 @@
644
527
  scrollOffset
645
528
  } = _ref5;
646
529
  const count = measurements.length - 1;
647
-
648
530
  const getOffset = index => measurements[index].start;
649
-
650
531
  const startIndex = findNearestBinarySearch(0, count, getOffset, scrollOffset);
651
532
  let endIndex = startIndex;
652
-
653
533
  while (endIndex < count && measurements[endIndex].end < scrollOffset + outerSize) {
654
534
  endIndex++;
655
535
  }
656
-
657
536
  return {
658
537
  startIndex,
659
538
  endIndex