virtua 0.52.5 → 0.52.7

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.
@@ -283,6 +283,13 @@ var createVirtualStore = ({ $getRange: getRange, $findIndex: findIndex, $getItem
283
283
  //#endregion
284
284
  //#region src/core/cache.ts
285
285
  var UNCACHED = -1;
286
+ var fill = (array, length, prepend) => {
287
+ const key = prepend ? "unshift" : "push";
288
+ for (let i = 0; i < length; i++) {
289
+ array[key](-1);
290
+ }
291
+ return array;
292
+ };
286
293
  var findIndex = (getOffset, length, offset, low = 0, high = length - 1) => {
287
294
  let found = low;
288
295
  while (low <= high) {
@@ -299,13 +306,6 @@ var findIndex = (getOffset, length, offset, low = 0, high = length - 1) => {
299
306
 
300
307
  //#endregion
301
308
  //#region src/core/layouts/list.ts
302
- var fill = (array, length, prepend) => {
303
- const key = prepend ? "unshift" : "push";
304
- for (let i = 0; i < length; i++) {
305
- array[key](-1);
306
- }
307
- return array;
308
- };
309
309
  var createListLayout = (length, itemSize, snapshot) => {
310
310
  let defaultItemSize = snapshot && snapshot[1] || itemSize || 40;
311
311
  let computedOffsetIndex = -1;
@@ -405,99 +405,149 @@ var createListLayout = (length, itemSize, snapshot) => {
405
405
  var getAxisLength = (axis) => typeof axis === "number" ? axis : axis.length;
406
406
  var getAxisItem = (axis, index) => typeof axis === "number" ? index : axis[index];
407
407
  var createGridLayout = (axis, size, gap = 0) => {
408
+ let length = getAxisLength(axis);
409
+ const isUniform = typeof size === "number";
410
+ const isAuto = size === "auto";
411
+ let defaultItemSize = (isUniform ? size : 40) + gap;
412
+ let computedOffsetIndex = -1;
408
413
  let header = 0;
409
414
  let footer = 0;
410
- const setPinned = (nextHeader = 0, nextFooter = 0) => {
411
- header = nextHeader;
412
- footer = nextFooter;
415
+ let currentSizes = null;
416
+ const sizes = [];
417
+ const offsets = [];
418
+ const autos = [];
419
+ const isMeasurable = (index) => isAuto || !!autos[index];
420
+ const materialize = () => {
421
+ if (!sizes.length) {
422
+ fill(sizes, length);
423
+ }
413
424
  };
414
- const getPinnedStart = (length) => min(header, length);
415
- const getTrailStart = (length) => max(length - footer, getPinnedStart(length));
416
- const pinRange = (getRange, getOffset, length, startOffset, endOffset) => {
417
- const insetStart = getOffset(getPinnedStart(length));
418
- const insetEnd = getOffset(length) - getOffset(getTrailStart(length));
419
- const start = startOffset + (insetStart ? insetStart - gap : 0);
420
- return getRange(start, max(start, endOffset - (insetEnd ? insetEnd - gap : 0)));
425
+ const getSize = (index) => {
426
+ const size = sizes[index];
427
+ return size === -1 ? defaultItemSize : size;
421
428
  };
422
- if (typeof size === "number") {
423
- let length = getAxisLength(axis);
424
- let itemSize = size;
425
- const findIndex = (offset) => itemSize + gap ? clamp(floor(offset / (itemSize + gap)), 0, length - 1) : 0;
426
- const getItemOffset = (index) => index * (itemSize + gap);
427
- const findRange = (startOffset, endOffset) => [findIndex(startOffset), findIndex(endOffset)];
428
- return {
429
- $getRange: (startOffset, endOffset) => pinRange(findRange, getItemOffset, length, startOffset, endOffset),
430
- $findIndex: findIndex,
431
- $getItemOffset: getItemOffset,
432
- $getItemSize: () => itemSize,
433
- $setItemSize: () => false,
434
- $isSizeEqual: () => false,
435
- $getTotalSize: () => length ? length * (itemSize + gap) - gap : 0,
436
- $getLength: () => length,
437
- $setLength: (nextLength) => {
438
- length = nextLength;
439
- return 0;
440
- },
441
- $isMeasurable: () => false,
442
- $setPinned: setPinned,
443
- $getPinnedStart: () => getPinnedStart(length),
444
- $getTrailStart: () => getTrailStart(length),
445
- $setAxis: (_axis, nextSize, scrollOffset) => {
446
- if (typeof nextSize !== "number" || nextSize === itemSize) {
447
- return;
429
+ const getOffset = (index) => {
430
+ if (!length) return 0;
431
+ if (!sizes.length) {
432
+ return index * defaultItemSize;
433
+ }
434
+ if (computedOffsetIndex >= index) {
435
+ return offsets[index];
436
+ }
437
+ if (computedOffsetIndex < 0) {
438
+ offsets[0] = 0;
439
+ computedOffsetIndex = 0;
440
+ }
441
+ let i = computedOffsetIndex;
442
+ let top = offsets[i];
443
+ while (i < index) {
444
+ top += getSize(i);
445
+ offsets[++i] = top;
446
+ }
447
+ computedOffsetIndex = index;
448
+ return top;
449
+ };
450
+ const setSize = (index, size) => {
451
+ sizes[index] = size;
452
+ computedOffsetIndex = min(index, computedOffsetIndex);
453
+ };
454
+ const find = (offset) => sizes.length ? findIndex(getOffset, length, offset) : defaultItemSize ? clamp(floor(offset / defaultItemSize), 0, length - 1) : 0;
455
+ const getPinnedStart = () => min(header, length);
456
+ const getTrailStart = () => max(length - footer, getPinnedStart());
457
+ return {
458
+ $getRange: (startOffset, endOffset) => {
459
+ const insetStart = getOffset(getPinnedStart());
460
+ const insetEnd = getOffset(length) - getOffset(getTrailStart());
461
+ const start = startOffset + (insetStart ? insetStart - gap : 0);
462
+ return [find(start), find(max(start, endOffset - (insetEnd ? insetEnd - gap : 0)))];
463
+ },
464
+ $findIndex: find,
465
+ $getItemOffset: getOffset,
466
+ $getItemSize: (index) => (sizes.length ? getSize(index) : defaultItemSize) - gap,
467
+ $setItemSize: (index, size) => {
468
+ if (!isMeasurable(index)) {
469
+ return false;
470
+ }
471
+ materialize();
472
+ const isInitialMeasurement = sizes[index] === -1;
473
+ setSize(index, size + gap);
474
+ return isInitialMeasurement;
475
+ },
476
+ $isSizeEqual: (index, size) => (sizes.length ? sizes[index] : -1) === size + gap,
477
+ $getTotalSize: () => length ? getOffset(length) - gap : 0,
478
+ $getLength: () => length,
479
+ $setLength: (nextLength) => {
480
+ const diff = nextLength - length;
481
+ computedOffsetIndex = min(nextLength - 1, computedOffsetIndex);
482
+ length = nextLength;
483
+ if (sizes.length) {
484
+ if (diff > 0) {
485
+ fill(sizes, diff);
486
+ } else {
487
+ sizes.splice(diff);
448
488
  }
449
- const jump = findIndex(scrollOffset) * (nextSize - itemSize);
450
- itemSize = nextSize;
451
- return jump;
452
489
  }
453
- };
454
- }
455
- const isAuto = size === "auto";
456
- let currentAxis;
457
- let currentSize = size;
458
- const length = getAxisLength(axis);
459
- const autos = [];
460
- const inner = createListLayout(length, undefined, [[], 40 + gap]);
461
- const isMeasurable = (index) => isAuto || !!autos[index];
462
- const layout = {
463
- $getRange: (startOffset, endOffset) => pinRange(inner.$getRange, inner.$getItemOffset, inner.$getLength(), startOffset, endOffset),
464
- $findIndex: inner.$findIndex,
465
- $getItemOffset: inner.$getItemOffset,
466
- $getItemSize: (index) => inner.$getItemSize(index) - gap,
467
- $setItemSize: (index, s) => isMeasurable(index) && inner.$setItemSize(index, s + gap),
468
- $isSizeEqual: (index, s) => inner.$isSizeEqual(index, s + gap),
469
- $getTotalSize: () => inner.$getLength() ? inner.$getTotalSize() - gap : 0,
470
- $getLength: inner.$getLength,
471
- $setLength: inner.$setLength,
472
- $estimateDefaultSize: isAuto ? inner.$estimateDefaultSize : undefined,
490
+ return 0;
491
+ },
492
+ $estimateDefaultSize: isAuto ? (startIndex) => {
493
+ let measuredCountBeforeStart = 0;
494
+ const measuredSizes = [];
495
+ sizes.forEach((s, i) => {
496
+ if (s !== -1) {
497
+ if (s) {
498
+ measuredSizes.push(s);
499
+ }
500
+ if (i < startIndex) {
501
+ measuredCountBeforeStart++;
502
+ }
503
+ }
504
+ });
505
+ computedOffsetIndex = -1;
506
+ sort(measuredSizes);
507
+ const len = measuredSizes.length;
508
+ const mid = len / 2 | 0;
509
+ const median = len % 2 === 0 ? (measuredSizes[mid - 1] + measuredSizes[mid]) / 2 : measuredSizes[mid];
510
+ const prevDefaultItemSize = defaultItemSize;
511
+ return ((defaultItemSize = median) - prevDefaultItemSize) * max(startIndex - measuredCountBeforeStart, 0);
512
+ } : undefined,
473
513
  $isMeasurable: isMeasurable,
474
- $setPinned: setPinned,
475
- $getPinnedStart: () => getPinnedStart(inner.$getLength()),
476
- $getTrailStart: () => getTrailStart(inner.$getLength()),
477
- $setAxis: (nextAxis, requestedSize, scrollOffset, mutable) => {
478
- if (isAuto) {
514
+ $setPinned: (nextHeader = 0, nextFooter = 0) => {
515
+ header = nextHeader;
516
+ footer = nextFooter;
517
+ },
518
+ $getPinnedStart: getPinnedStart,
519
+ $getTrailStart: getTrailStart,
520
+ $getSizes: (nextAxis, nextSize) => typeof nextSize === "number" ? isUniform ? nextSize : null : isUniform || isAuto || nextSize === "auto" ? null : nextAxis.map((item) => item != null ? item[nextSize] : null),
521
+ $setAxis: (nextSizes, scrollOffset) => {
522
+ if (nextSizes == null) {
479
523
  return;
480
524
  }
481
- const nextSize = typeof requestedSize === "number" || requestedSize === "auto" ? currentSize : requestedSize;
482
- const length = inner.$getLength();
483
- if (!mutable && nextAxis === currentAxis && nextSize === currentSize && length === autos.length) {
525
+ if (typeof nextSizes === "number") {
526
+ const nextDefaultItemSize = nextSizes + gap;
527
+ if (nextDefaultItemSize === defaultItemSize) {
528
+ return;
529
+ }
530
+ const jump = find(scrollOffset) * (nextDefaultItemSize - defaultItemSize);
531
+ defaultItemSize = nextDefaultItemSize;
532
+ return jump;
533
+ }
534
+ if (nextSizes === currentSizes && length === autos.length) {
484
535
  return;
485
536
  }
486
- currentAxis = nextAxis;
487
- currentSize = nextSize;
537
+ currentSizes = nextSizes;
488
538
  autos.length = length;
489
- const anchorIndex = inner.$findIndex(scrollOffset);
490
- const prevOffset = inner.$getItemOffset(anchorIndex);
539
+ materialize();
540
+ const anchorIndex = find(scrollOffset);
541
+ const prevOffset = getOffset(anchorIndex);
491
542
  let changed;
492
543
  for (let i = 0; i < length; i++) {
493
- const item = nextAxis[i];
494
- const itemSize = item !== null && typeof item === "object" ? item[nextSize] : null;
544
+ const itemSize = nextSizes[i];
495
545
  const isAutoItem = typeof itemSize !== "number";
496
546
  if (!isAutoItem || !autos[i]) {
497
547
  autos[i] = isAutoItem;
498
548
  const target = isAutoItem ? -1 : itemSize + gap;
499
- if (!inner.$isSizeEqual(i, target)) {
500
- inner.$setItemSize(i, target);
549
+ if (sizes[i] !== target) {
550
+ setSize(i, target);
501
551
  changed = true;
502
552
  }
503
553
  }
@@ -505,28 +555,40 @@ var createGridLayout = (axis, size, gap = 0) => {
505
555
  if (!changed) {
506
556
  return;
507
557
  }
508
- return inner.$getItemOffset(anchorIndex) - prevOffset;
558
+ return getOffset(anchorIndex) - prevOffset;
509
559
  }
510
560
  };
511
- layout.$setAxis(axis, size, 0);
512
- return layout;
513
561
  };
514
562
 
515
563
  //#endregion
516
564
  //#region src/core/grid.ts
517
- var updateGridAxis = (store, layout, axis, size, header, footer, mutable) => {
565
+ var updateGridAxis = (store, layout, axis, sizes, header, footer) => {
518
566
  const length = getAxisLength(axis);
519
567
  layout.$setPinned(header, footer);
520
568
  if (length !== store.$getItemsLength()) {
521
569
  store.$update(5, [length]);
522
570
  }
523
- const jump = layout.$setAxis(axis, size, store.$getScrollOffset() - store.$getItemOffset(0), mutable);
571
+ const jump = layout.$setAxis(sizes, store.$getScrollOffset() - store.$getItemOffset(0));
524
572
  if (jump != null) {
525
573
  store.$update(9, jump);
526
574
  }
527
575
  };
528
576
  var rowStatesCache = /*#__PURE__*/ new WeakMap();
529
- var hasVisibleTrack = (from, to, pinnedStart, start, end, trailStart) => from < pinnedStart || max(from, start) < min(to, end + 1) || trailStart < to;
577
+ var createGridSpanIndex = (spans = EMPTY) => {
578
+ const index = new Map();
579
+ for (const span of spans) {
580
+ const rowTo = span.rowIndex + (span.rowSpan || 1);
581
+ for (let rowIndex = span.rowIndex; rowIndex < rowTo; rowIndex++) {
582
+ const rowSpans = index.get(rowIndex);
583
+ if (rowSpans) {
584
+ rowSpans.push(span);
585
+ } else {
586
+ index.set(rowIndex, [span]);
587
+ }
588
+ }
589
+ }
590
+ return index;
591
+ };
530
592
  var getSectionStarts = (sectionRows, pinnedStart, trailStart) => {
531
593
  const starts = [];
532
594
  for (const rowIndex of sectionRows) {
@@ -615,17 +677,17 @@ var gridStyleToString = (style) => {
615
677
  }
616
678
  return css;
617
679
  };
618
- var createGridPlan = (rowLayout, colLayout, rowRange, colRange, sectionRows = EMPTY, spans = EMPTY, kept = EMPTY, sortedCell) => {
680
+ var createGridPlan = (rowLayout, colLayout, [rowRangeStart, rowRangeEnd], [colRangeStart, colRangeEnd], spanIndex, sectionRows = EMPTY, kept = EMPTY, sortedCell) => {
619
681
  const totalRowCount = rowLayout.$getLength();
620
682
  const totalColCount = colLayout.$getLength();
621
683
  const rowPinnedStart = rowLayout.$getPinnedStart();
622
684
  const rowTrailStart = rowLayout.$getTrailStart();
623
685
  const colPinnedStart = colLayout.$getPinnedStart();
624
686
  const colTrailStart = colLayout.$getTrailStart();
625
- const rowRangeStart = clamp(rowRange[0], rowPinnedStart, rowTrailStart);
626
- const rowRangeEnd = min(rowRange[1], rowTrailStart - 1);
627
- const colRangeStart = clamp(colRange[0], colPinnedStart, colTrailStart);
628
- const colRangeEnd = min(colRange[1], colTrailStart - 1);
687
+ rowRangeStart = clamp(rowRangeStart, rowPinnedStart, rowTrailStart);
688
+ rowRangeEnd = min(rowRangeEnd, rowTrailStart - 1);
689
+ colRangeStart = clamp(colRangeStart, colPinnedStart, colTrailStart);
690
+ colRangeEnd = min(colRangeEnd, colTrailStart - 1);
629
691
  const sectionStarts = getSectionStarts(sectionRows, rowPinnedStart, rowTrailStart);
630
692
  const rowHeaderCol = max(colPinnedStart - 1, 0);
631
693
  const extraRows = [];
@@ -634,28 +696,30 @@ var createGridPlan = (rowLayout, colLayout, rowRange, colRange, sectionRows = EM
634
696
  for (const cell of kept) {
635
697
  if (cell.rowIndex < totalRowCount && cell.colIndex < totalColCount) {
636
698
  extraCells.push(cell);
699
+ extraRows.push(cell.rowIndex);
637
700
  }
638
701
  }
639
702
  const keptLength = extraCells.length;
640
- const sectionLength = sectionStarts.length;
641
703
  const firstSection = getSectionIndex(sectionStarts, rowRangeStart, rowTrailStart);
642
704
  const firstSectionStart = firstSection < 0 ? -1 : sectionStarts[firstSection];
643
705
  if (firstSectionStart >= 0) {
644
706
  extraRows.push(firstSectionStart);
645
707
  }
646
- for (const span of spans) {
647
- const row = span.rowIndex;
648
- const col = span.colIndex;
649
- const rowTo = getSpanRowEnd(span, totalRowCount);
650
- const colTo = getSpanColEnd(span, totalColCount);
651
- if (row < totalRowCount && col < totalColCount && (rowTo - row > 1 || colTo - col > 1)) {
652
- let isLaid = (hasVisibleTrack(row, rowTo, rowPinnedStart, rowRangeStart, rowRangeEnd, rowTrailStart) || firstSectionStart >= row && firstSectionStart < rowTo) && hasVisibleTrack(col, colTo, colPinnedStart, colRangeStart, colRangeEnd, colTrailStart);
653
- for (let k = 0; !isLaid && k < keptLength; k++) {
654
- const { rowIndex, colIndex } = extraCells[k];
655
- isLaid = rowIndex >= row && rowIndex < rowTo && colIndex >= col && colIndex < colTo;
656
- }
657
- if (isLaid) {
658
- extraCells.push(span);
708
+ const seenRows = getTrackIndexes(sort(extraRows), totalRowCount, rowPinnedStart, rowRangeStart, rowRangeEnd, rowTrailStart);
709
+ for (let s = 0; s < seenRows.length; s++) {
710
+ for (const span of spanIndex.get(seenRows[s]) || EMPTY) {
711
+ const { rowIndex, colIndex } = span;
712
+ const rowTo = getSpanRowEnd(span, totalRowCount);
713
+ const colTo = getSpanColEnd(span, totalColCount);
714
+ if ((s < 1 || seenRows[s - 1] < rowIndex) && colIndex < totalColCount && (rowTo - rowIndex > 1 || colTo - colIndex > 1)) {
715
+ let isLaid = (rowIndex < rowPinnedStart || max(rowIndex, rowRangeStart) < min(rowTo, rowRangeEnd + 1) || rowTrailStart < rowTo || firstSectionStart >= rowIndex && firstSectionStart < rowTo) && (colIndex < colPinnedStart || max(colIndex, colRangeStart) < min(colTo, colRangeEnd + 1) || colTrailStart < colTo);
716
+ for (let k = 0; !isLaid && k < keptLength; k++) {
717
+ const cell = extraCells[k];
718
+ isLaid = cell.rowIndex >= rowIndex && cell.rowIndex < rowTo && cell.colIndex >= colIndex && cell.colIndex < colTo;
719
+ }
720
+ if (isLaid) {
721
+ extraCells.push(span);
722
+ }
659
723
  }
660
724
  }
661
725
  }
@@ -705,7 +769,7 @@ var createGridPlan = (rowLayout, colLayout, rowRange, colRange, sectionRows = EM
705
769
  const section = getSectionIndex(sectionStarts, rowIndex, rowTrailStart);
706
770
  const groupStart = isRowPinnedStart ? 0 : isRowPinnedEnd ? rowTrailStart : section < 0 ? -1 : sectionStarts[section];
707
771
  if (rowIndex === groupStart) {
708
- const groupEnd = isRowPinnedStart ? rowPinnedStart : isRowPinnedEnd ? totalRowCount : section + 1 < sectionLength ? sectionStarts[section + 1] : rowTrailStart;
772
+ const groupEnd = isRowPinnedStart ? rowPinnedStart : isRowPinnedEnd ? totalRowCount : section + 1 < sectionStarts.length ? sectionStarts[section + 1] : rowTrailStart;
709
773
  rowCuts.push(groupEnd);
710
774
  const groupedRows = [];
711
775
  groups.push({
@@ -746,7 +810,7 @@ var createGridPlan = (rowLayout, colLayout, rowRange, colRange, sectionRows = EM
746
810
  let measureColIndex;
747
811
  rowEnd = max(rowEnd, rowTo);
748
812
  const stickyStart = isColPinnedStart ? colLayout.$getItemOffset(colIndex) : null;
749
- const stickyEnd = isColPinnedEnd ? colLayout.$getTotalSize() - colLayout.$getItemOffset(colTo - 1) - colLayout.$getItemSize(colTo - 1) : null;
813
+ const stickyEnd = isColPinnedEnd ? colLayout.$getItemOffset(totalColCount) - colLayout.$getItemOffset(colTo) : null;
750
814
  if (measuredRow === false && !isSpanningRows) {
751
815
  measureRowIndex = rowIndex;
752
816
  measuredRow = true;
@@ -1714,6 +1778,9 @@ var VGrid = (props) => {
1714
1778
  ]);
1715
1779
  const rowLayout = createGridLayout(rows, rowHeight, gap);
1716
1780
  const colLayout = createGridLayout(cols, colWidth, gap);
1781
+ const rowSizes = createMemo(() => rowLayout.$getSizes(props.rows, props.rowHeight));
1782
+ const colSizes = createMemo(() => colLayout.$getSizes(props.cols, props.colWidth));
1783
+ const spanIndex = createMemo(() => createGridSpanIndex(props.spans));
1717
1784
  const rowStore = createVirtualStore(rowLayout);
1718
1785
  const colStore = createVirtualStore(colLayout);
1719
1786
  const driver = createContainerGridDriver(rowStore, colStore);
@@ -1740,16 +1807,16 @@ var VGrid = (props) => {
1740
1807
  rowStore.$subscribe(8, notifyScrollEnd);
1741
1808
  colStore.$subscribe(8, notifyScrollEnd);
1742
1809
  createComputed(() => {
1743
- updateGridAxis(rowStore, rowLayout, props.rows, props.rowHeight, props.headerRows, props.footerRows, true);
1810
+ updateGridAxis(rowStore, rowLayout, props.rows, rowSizes(), props.headerRows, props.footerRows);
1744
1811
  });
1745
1812
  createComputed(() => {
1746
- updateGridAxis(colStore, colLayout, props.cols, props.colWidth, props.headerCols, props.footerCols, true);
1813
+ updateGridAxis(colStore, colLayout, props.cols, colSizes(), props.headerCols, props.footerCols);
1747
1814
  });
1748
1815
  const rowCount = createMemo(() => stateVersion() && rowStore.$getItemsLength());
1749
1816
  const colCount = createMemo(() => stateVersion() && colStore.$getItemsLength());
1750
1817
  const plan = createMemo(() => {
1751
1818
  stateVersion();
1752
- return createGridPlan(rowLayout, colLayout, rowStore.$getRange(props.bufferSize), colStore.$getRange(props.bufferSize), props.sectionRows, props.spans, props.keepMounted, props.ariaSort);
1819
+ return createGridPlan(rowLayout, colLayout, rowStore.$getRange(props.bufferSize), colStore.$getRange(props.bufferSize), spanIndex(), props.sectionRows, props.keepMounted, props.ariaSort);
1753
1820
  });
1754
1821
  const keys = createMemo(() => plan().$groups.map((state) => "$rows" in state ? state.$key : state.$row));
1755
1822
  const isScrolling = createMemo(() => stateVersion() && (rowStore.$isScrolling() || colStore.$isScrolling()));