Skip to content

Fixed #4109 - DataTable + VirtualScroller spacer drift with rowGroupMode subheader#8579

Open
oliversluke wants to merge 1 commit into
primefaces:masterfrom
oliversluke:fix-rowgroup-spacer
Open

Fixed #4109 - DataTable + VirtualScroller spacer drift with rowGroupMode subheader#8579
oliversluke wants to merge 1 commit into
primefaces:masterfrom
oliversluke:fix-rowgroup-spacer

Conversation

@oliversluke
Copy link
Copy Markdown

Summary

Fixes #4109. Spacer math in DataTable + VirtualScroller doesn't account for rowGroupMode="subheader" header rows or hidden rows in collapsed groups, producing visible row stretching, gaps between rendered rows, and full-window blanks during fast scroll.

Diagnosis: #4109 (comment)

Approach

Make VirtualScroller variable-row-height aware via a new optional getItemSize(index: number): number callback prop. When supplied, VS builds a cumulative-size prefix-sum array and uses it for spacer total, content translation, scroll-to-index, and viewport-size calculation. When null (default), VS retains its existing uniform-height code path byte-for-byte — zero risk for current VS consumers (Listbox, Select, TreeSelect, AutoComplete, etc.).

DataTable supplies a getItemSize that returns itemSize plus an auto-measured group-header height at cluster boundaries, and 0 for hidden rows in collapsed clusters. The spacer-tbody subtraction is rewritten to sum the actually-rendered window's sizes. This corrects both drift sources from #4109 with a single mechanism.

Changes

VirtualScroller.vue / BaseVirtualScroller.vue

  • New getItemSize prop (Function, default null).
  • Cumulative-size cache + helpers: buildCumulativeSizes(), getItemOffset(index), getIndexAtOffset(offset) (binary search), getLastByCumulative(first, numToleratedItems).
  • Six math sites take the variable-aware path when cumulativeSizes is set, falling back to the existing uniform path otherwise: setSpacerSize, setContentPosition, onScrollPositionChange.calculateCurrentIndex + calculateLast, calculateOptions.calculateLast, getRenderedRange.calculateFirstInViewport, scrollToIndex / scrollInView.

DataTable.vue

  • d_rowGroupHeaderHeight measured via getOuterHeight(firstHeaderRow) in mounted / updated.
  • getItemSize(index) computed: returns itemSize + header contribution at group boundaries + zero-out for hidden rows in collapsed clusters.
  • Wired into <DTVirtualScroller> via :getItemSize.
  • Spacer-tbody style uses calc(${spacerStyle.height} - ${getRenderedWindowSize(slotProps)}px), summing the windowed getItemSize.

BodyRow.vue

  • shouldRenderRowGroupHeader / shouldRenderRowGroupFooter use this.index (windowed position) instead of this.rowIndex (absolute, from getItemOptions). Previously the comparison mixed a windowed value array with an absolute index in VS mode, dropping or misplacing headers depending on first. In non-VS mode this.index === this.rowIndex so behavior is unchanged.

TableBody.vue

  • rowGroupHeaderStyle gated on isVirtualScrollerDisabled. The CSS position: sticky rule stays; without an inline top it defaults to top: auto, deactivating sticky behavior. This avoids the multi-element sticky-stacking pile-up when groups are dense in VS mode (every header tries to stick at the same threshold and they overlap). Non-VS sticky group-header behavior is preserved.

Test plan

  • All 410 existing unit specs pass (78 components).
  • 11 new VirtualScroller specs (uniform/variable paths, cumulative correctness, binary-search edge cases, getItemSize reactivity).
  • 3 new DataTable specs (getItemSize boundary detection, collapsed-cluster handling).
  • pnpm run lint clean.
  • pnpm run format:check clean.
  • pnpm --filter primevue build clean.
  • Eyeballed against the issue's repro (i % 5 pathological, every row its own group) and a realistic 10-groups-of-12–18-rows case. Both render uniform row heights, no gaps, no doubling, clean fast scroll, correct collapse/expand behavior.

Known limitation / follow-up

Disabling sticky on row-group-headers in VS mode removes a UX cue: when scrolling deep into a long group, users can lose track of which group they're currently in. A follow-up enhancement (not in this PR) could add a single floating "currently-active group" header overlay anchored at the top of the scroll container — one sticky element instead of many, so no stacking pitfall. Mentioned here for visibility; intentionally out of scope to keep this PR focused on the spacer-math fix.

… rowGroupMode subheader

Add an optional getItemSize(index): number prop to VirtualScroller so it
can be made variable-row-height aware. When supplied, VS builds a
cumulative-size prefix-sum array and uses it for spacer total, content
translation, scroll-to-index, and viewport-size calculation. When null
(default), VS retains its existing uniform-height code path byte-for-byte.

DataTable supplies a getItemSize that returns itemSize plus an auto-
measured group-header height at cluster boundaries, and 0 for hidden
rows inside collapsed clusters. The spacer-tbody subtraction sums the
actually-rendered window's sizes. This corrects both drift sources:
collapsed clusters no longer over-contribute to the cumulative total,
and subheader rows are now accounted for in the spacer formula.

Two related pre-existing bugs are addressed in the same change:

- BodyRow.shouldRenderRowGroupHeader / shouldRenderRowGroupFooter use
  this.value[this.rowIndex - 1], which mixes a windowed value array
  with an absolute rowIndex in VS mode. Switched to this.index for
  windowed position lookup. Non-VS behavior unchanged.

- TableBody's inline top on every sticky group-header causes them to
  stack at the same threshold when groups are dense in VS mode. Gated
  on isVirtualScrollerDisabled so non-VS sticky behavior is preserved.

Tests: 11 new VirtualScroller specs (uniform/variable paths, cumulative
correctness, binary-search boundary cases, reference-change invalidation)
and 3 new DataTable specs (getItemSize boundary detection, collapsed-
cluster handling). All 410 existing specs continue to pass.

Signed-off-by: Oliver Sluke <22557015+oliversluke@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DataTable: Virtual scrolling doesn't work when rowGroup mode is enabled

1 participant