@shival99/z-ui 2.0.79 → 2.0.80

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.
@@ -6701,8 +6701,8 @@ class ZTableComponent {
6701
6701
  },
6702
6702
  ];
6703
6703
  _bulkBarExitDuration = 140;
6704
- /** Stable identity key per virtual index (group/row id, not array index) so cached measured
6705
- * heights follow row identity — tránh co giật chiều cao khi scroll nhanh do index slot bị tái dùng. */
6704
+ /** Stable identity key per virtual index (row/group id, not array index) so cached measured
6705
+ * heights follow row identity — prevents height jitter when scrolling fast. */
6706
6706
  _virtualItemKeys = computed(() => {
6707
6707
  if (!this.isVirtual()) {
6708
6708
  return [];
@@ -6717,6 +6717,10 @@ class ZTableComponent {
6717
6717
  }
6718
6718
  return Array.from({ length: this._virtualGroupCount() }, (_, index) => index);
6719
6719
  }, ...(ngDevMode ? [{ debugName: "_virtualItemKeys" }] : []));
6720
+ /** Estimate height learned from real rendered rows (dynamicSize); `virtual.size` is the fallback
6721
+ * until a row is measured. Lets not-yet-measured rows estimate close to reality, reducing layout
6722
+ * shift. Plain field (non-reactive) so updating it won't rebuild the virtualizer. */
6723
+ _dynamicEstimateHeight = null;
6720
6724
  /** Virtualizer instance — operates on groups rather than individual rows */
6721
6725
  virtualizer = injectVirtualizer(() => {
6722
6726
  const groups = this.dynamicGroups();
@@ -6727,6 +6731,9 @@ class ZTableComponent {
6727
6731
  const flatItems = this.virtualGroupedFlatItems();
6728
6732
  const isGrouping = flatItems.length > 0;
6729
6733
  const itemKeys = this._virtualItemKeys();
6734
+ const isDynamic = this.dynamicSize();
6735
+ // Estimate for not-yet-measured rows: learned height if available, else virtual.size.
6736
+ const dynamicRowEstimate = isDynamic && this._dynamicEstimateHeight !== null ? this._dynamicEstimateHeight : rowHeight;
6730
6737
  return {
6731
6738
  scrollElement,
6732
6739
  count: isGrouping ? flatItems.length : groups.length > 0 ? groups.length : this._virtualGroupCount(),
@@ -6737,16 +6744,16 @@ class ZTableComponent {
6737
6744
  if (isGrouping) {
6738
6745
  return flatItems[index]?.type === 'group-header'
6739
6746
  ? Math.max(rowHeight, Z_VIRTUAL_GROUP_HEADER_HEIGHT)
6740
- : rowHeight;
6747
+ : dynamicRowEstimate;
6741
6748
  }
6742
6749
  if (groups.length > 0 && groups[index]) {
6743
6750
  const groupHeight = groups[index].height;
6744
6751
  if (groupHeight !== null) {
6745
6752
  return groupHeight;
6746
6753
  }
6747
- return groups[index].rowCount * rowHeight;
6754
+ return groups[index].rowCount * dynamicRowEstimate;
6748
6755
  }
6749
- return groupSize * rowHeight;
6756
+ return groupSize * dynamicRowEstimate;
6750
6757
  },
6751
6758
  scrollToFn: (offset, options, instance) => {
6752
6759
  instance.scrollOffset = offset;
@@ -6775,14 +6782,45 @@ class ZTableComponent {
6775
6782
  if (!view) {
6776
6783
  return;
6777
6784
  }
6778
- // Gom phép đo vào frame kế tiếp để tránh cập nhật vị trí nhiều lần trong cùng một frame scroll.
6785
+ // Only sample heights until the estimate is locked; afterwards just measure (no extra reads).
6786
+ const needsEstimate = this._dynamicEstimateHeight === null;
6787
+ // Batch measurements into the next frame to avoid repositioning multiple times per scroll frame.
6779
6788
  const frameId = view.requestAnimationFrame(() => {
6789
+ let measuredSum = 0;
6790
+ let measuredCount = 0;
6780
6791
  for (const el of elements) {
6781
- this.virtualizer.measureElement(el.nativeElement);
6792
+ const node = el.nativeElement;
6793
+ this.virtualizer.measureElement(node);
6794
+ if (needsEstimate) {
6795
+ // Layout already flushed by measureElement above, so this read hits the cache (no reflow).
6796
+ const { height } = node.getBoundingClientRect();
6797
+ if (height > 0) {
6798
+ measuredSum += height;
6799
+ measuredCount++;
6800
+ }
6801
+ }
6802
+ }
6803
+ if (needsEstimate) {
6804
+ this._learnDynamicEstimate(measuredSum, measuredCount);
6782
6805
  }
6783
6806
  });
6784
6807
  onCleanup(() => view.cancelAnimationFrame(frameId));
6785
6808
  }, ...(ngDevMode ? [{ debugName: "_measureVirtualItems" }] : []));
6809
+ /** Learn estimate height from rendered rows. Locks ONCE so the estimate doesn't jump every scroll
6810
+ * frame (each row is still measured exactly on render). Re-flows via virtualizer.measure(). */
6811
+ _learnDynamicEstimate(measuredSum, measuredCount) {
6812
+ if (this._dynamicEstimateHeight !== null || measuredCount === 0) {
6813
+ return;
6814
+ }
6815
+ const average = Math.round(measuredSum / measuredCount);
6816
+ // Skip re-flow when already equal to the default size.
6817
+ if (average <= 0 || average === this.virtualRowHeight()) {
6818
+ this._dynamicEstimateHeight = average > 0 ? average : null;
6819
+ return;
6820
+ }
6821
+ this._dynamicEstimateHeight = average;
6822
+ this.virtualizer.measure();
6823
+ }
6786
6824
  _rowDragAutoScroll = effect(onCleanup => {
6787
6825
  const scrollbar = this.tbodyScrollbar();
6788
6826
  if (!scrollbar?.adapter.initialized()) {
@@ -7038,6 +7076,8 @@ class ZTableComponent {
7038
7076
  }
7039
7077
  });
7040
7078
  explicitEffect([this.columnFilters, this.globalFilter, this.pagination, this.sorting, this._data], () => {
7079
+ // New dataset may have different row heights -> relearn the estimate.
7080
+ this._dynamicEstimateHeight = null;
7041
7081
  queueMicrotask(() => {
7042
7082
  this._checkVerticalScroll();
7043
7083
  this._checkHorizontalScroll();