Repository navigation
fix: Keep sticky header cols in sync with grouped table columns - #5194
NathanZlion wants to merge 6 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5194 +/- ##
=======================================
Coverage 97.73% 97.74%
=======================================
Files 996 998 +2
Lines 32226 32312 +86
Branches 11910 11949 +39
=======================================
+ Hits 31497 31582 +85
+ Misses 723 685 -38
- Partials 6 45 +39 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Explicit widths let the grouped sticky header alignment fix be checked by widening the window after load, and a low minWidth on Memory lets the group resize minimum fix be checked by shrinking the Performance group.
There was a problem hiding this comment.
🟡 Changes recommended
Sticky synchronization introduces unnecessary synchronous layout reads for tables without sticky headers.
1 open issue
What changed in this PR
This PR fixes grouped, resizable table columns so sticky headers track body widths and group resize limits respect the last column’s minimum width.
Changes:
- Register sticky
<col>elements for width synchronization. - Share minimum-width logic between column resizing and group headers.
- Add regression tests and update the demo with explicit widths.
| File | Description |
|---|---|
src/table/use-column-widths.tsx |
Synchronizes sticky columns and exposes shared minimum-width logic. |
src/table/header-cell/group-header-cell.tsx |
Uses the last child’s minimum width for group resizing. |
src/table/column-groups/col-group.tsx |
Registers sticky and primary column elements. |
src/table/__tests__/resizable-columns.test.tsx |
Adds regression coverage for both fixes. |
src/table/__tests__/columns-width.test.tsx |
Updates context default checks. |
pages/table/column-groups.page.tsx |
Configures widths for manual verification. |
Review effort: Balanced
syncStickyColumnWidths measured every primary column before checking for a sticky cell or <col>, adding layout reads on resize even without a sticky header. Only measure when there is something to update.
| const lastChild = childColumnIds[childColumnIds.length - 1]; | ||
| const groupMinWidth = childColumnIds.reduce<number>((sum, id) => { | ||
| if (id === lastChild) { | ||
| return sum + DEFAULT_COLUMN_WIDTH; |
There was a problem hiding this comment.
the bug 2, this used to use the 120 default width every time. That's what made the tracker and the column edge drift apart. using the actual min width instead.
| const cellsRef = useRef(new Map<PropertyKey, HTMLElement>()); | ||
| const stickyCellsRef = useRef(new Map<PropertyKey, HTMLElement>()); | ||
| const colsRef = useRef(new Map<PropertyKey, HTMLElement>()); | ||
| const stickyColsRef = useRef(new Map<PropertyKey, HTMLElement>()); |
There was a problem hiding this comment.
How is this different from stickyCellsRef we already have?
There was a problem hiding this comment.
stickyCellsRef holds the sticky header cells, and this holds the sticky s. When s exist, the browser takes column widths from them, not from the cells. We were only updating the cells, so the s kept their first-render width, and that's the bug.
we're using col for grouped ones: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/col
There was a problem hiding this comment.
If I understand correctly, when we have grouped columns the widths measurement should be done differently - with respect to the groups. When grouping is not used - then technically stickyCellsRef and stickyColsRef should be identical. If thats the case, case we remove the stickyCellsRef then?
If we cannot do this optimisation and we have to keep both - let's add code comments to explain why is that needed.
There was a problem hiding this comment.
agree, I'll add a comment. They're not quite the identical, the sticky table only renders a <colgroup> when there are column groups, one of the ways we used back then to make column groups work well with the resizing feature and all. so without groups stickyColsRef stays empty and the cells are what carry the widths.
| {hasSelection && ( | ||
| <col | ||
| style={sticky && selectionColumnId ? { width: getColumnStyles(true, selectionColumnId).width } : undefined} | ||
| ref={sticky && selectionColumnId ? node => setCol(true, selectionColumnId, node) : undefined} |
There was a problem hiding this comment.
Why do we set this column only if it is sticky? Should it be:
ref={selectionColumnId ? node => setCol(sticky, selectionColumnId, node) : undefined}
Also, isn't selectionColumnId guaranteed? Can we remove the && selectionColumnId check? Otherwise, there is a potential issue possible where the column reference is stored, but then not removed if selectionColumnId gets removed.
There was a problem hiding this comment.
Why do we set this column only if it is sticky?
Only the sticky one needs it. The sticky header is a separate table, so its cols have to be told the width the main table actually rendered. The main table's checkbox column already gets its 54px from its own cell, so its col never needed a ref (it didn't have one before this change either). With setCol(sticky, ...)
selectionColumnId
yeah this one is for typescript since selectionColumnId is optional. I realize now we can make selectionColumnId a required field. Will address in next commtit.
…ColumnId is always there

Description
This PR fixes two bugs in table that are related to column groups with resizable columns.
First one is the sticky header being out of line with the column widths when on wide screens. This is a reported bug. When the container is wider than the summed column widths, the last column stretches to fill it. The sticky header's widths were set once at render and never updated, so the header drifted away from the body columns. The sticky s now register through setCol and are updated in syncStickyColumnWidths, together with the sticky header cells.

Before
Second one is the Group resizer stopping at the wrong minimum. This was caused by a hardcoded 120px for the right most instead of just looking into it's actual min width. this means tracker stopped shrinking but the columns would keep shrinking and columns would shrink upto the correct min width. it's just that the tracker wasn't in sync after some point. Fix is to make the header use the new function getColumnMinWidth which uses the same rule updateWidths already applies and this fixed the issue.
Related links, issue #, if available: n/a
How has this been tested?
Review checklist
The following items are to be evaluated by the author(s) and the reviewer(s).
Correctness
CONTRIBUTING.md.CONTRIBUTING.md.Security
checkSafeUrlfunction.Testing
Added two unit tests in resizable-columns.test.tsx, one per bug. Each fails without its fix and passes with it:
Checked manually in Chrome at 1280, 1920 and 2560px, with and without groups, the sticky header, selection and expandable rows. The sticky header lines up with the body columns, and the group resize tracker stays on the column edge when shrinking with the resize buttons.
You can check it out at table/column-groups page also, in a wide screen.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.