Skip to content

Add frozen: 'start' | 'end' for end-edge column pinning#4034

Merged
amanmahajan7 merged 8 commits into
Comcast:mainfrom
PaloSP:feat/end-frozen-columns
Jul 9, 2026
Merged

Add frozen: 'start' | 'end' for end-edge column pinning#4034
amanmahajan7 merged 8 commits into
Comcast:mainfrom
PaloSP:feat/end-frozen-columns

Conversation

@PaloSP

@PaloSP PaloSP commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Adds right-edge column pinning — long-standing ask (#2873, #3671).

  • Column.frozen widens to boolean | 'start' | 'end'. true stays an alias for 'start' (backwards compatible).
  • 'end' pins to the inline-end edge; symmetric infra: --rdg-frozen-end-${idx} CSS var, rdg-cell-frozen-end class, end-shadow + top/bottom-summary variants, logical properties throughout.

Credit to @robert-luoqing for #3671 which informed the approach. API per @amanmahajan7 and @nstepien's 'start' | 'end' refinement.

One check: "we want to prevent the shadow logic" (#3671 2026-04-21) — I interpreted as "preserve" and mirrored the start-shadow symmetrically. If the intent was otherwise, happy to drop it.

Tests: new test/browser/column/frozenEnd.test.ts + additions to virtualization.test.ts and direction.test.ts. Demo updated in CommonFeatures.

@CLAassistant

CLAassistant commented Apr 24, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@PaloSP PaloSP mentioned this pull request Apr 24, 2026
@codecov-commenter

codecov-commenter commented Apr 24, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.54%. Comparing base (150a6f8) to head (9d10cce).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4034      +/-   ##
==========================================
+ Coverage   97.48%   97.54%   +0.06%     
==========================================
  Files          38       39       +1     
  Lines        1549     1588      +39     
  Branches      497      515      +18     
==========================================
+ Hits         1510     1549      +39     
  Misses         39       39              
Files with missing lines Coverage Δ
src/DataGrid.tsx 98.88% <100.00%> (+0.02%) ⬆️
src/GroupRow.tsx 100.00% <100.00%> (ø)
src/HeaderRow.tsx 100.00% <ø> (ø)
src/hooks/useCalculatedColumns.ts 100.00% <100.00%> (ø)
src/hooks/useViewportColumns.ts 100.00% <100.00%> (ø)
src/utils/activePositionUtils.ts 98.92% <100.00%> (ø)
src/utils/colSpanUtils.ts 100.00% <100.00%> (ø)
src/utils/frozenColumnUtils.ts 100.00% <100.00%> (ø)
src/utils/index.ts 90.90% <ø> (ø)
src/utils/styleUtils.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@amanmahajan7 amanmahajan7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR. A few quick comments. I can do a detailed review in 2 weeks (on a vacation)

Comment thread src/hooks/useCalculatedColumns.ts Outdated
}

const frozen = rawColumn.frozen ?? false;
const frozen: boolean | 'start' | 'end' = rawColumn.frozen ?? false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a type and reuse it

Comment thread src/style/cell.ts Outdated
Comment thread src/style/core.ts Outdated
Comment thread src/style/row.ts Outdated
Comment thread src/style/row.ts Outdated
border-inline-start: var(--rdg-selection-width) solid var(--rdg-selection-color);
}

& > .${cellFrozenEnd}:last-child::after {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, let's dedupe

& > .${cellFrozen}:first-child::before,
& > .${cellFrozenEnd}:last-child::after { }

& > .${cellFrozen}:first-child::before { }

& > .${cellFrozenEnd}:last-child::after { }

@PaloSP PaloSP force-pushed the feat/end-frozen-columns branch from 5943b18 to 5016075 Compare April 24, 2026 20:57
@PaloSP

PaloSP commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

@amanmahajan7 pushed a revision addressing all 5 comments.

On comment 4: intentional divergence from the literal suggestion. Comment 2's dedup removed the separate cellFrozenEnd export, so row.ts no longer imports from ./cell at all — its selectors use the DOM marker classes (.rdg-cell-frozen / .rdg-cell-frozen-end) directly. cellFrozen kept as the shared identifier per your phrasing in #2. Happy to rename cellFrozencellFrozenStart and restore an import if you prefer.

Two open questions, both breaking — your call:

  1. Rename CSS var --rdg-frozen-left-${idx}--rdg-frozen-start-${idx} for symmetry with new --rdg-frozen-end-${idx}?
  2. Rename DOM marker .rdg-cell-frozen.rdg-cell-frozen-start for symmetry with new .rdg-cell-frozen-end?

Both currently kept as-is (non-breaking). No rush — enjoy the vacation.

@PaloSP PaloSP force-pushed the feat/end-frozen-columns branch 4 times, most recently from c51325e to d88ce9d Compare May 6, 2026 18:14
@PaloSP

PaloSP commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

Hi @amanmahajan7 — gentle nudge, no rush. Hope the vacation was good.

Branch is up-to-date with main (CI green) and the two open questions from my revision reply are still waiting on your call:

  1. Rename CSS var --rdg-frozen-left-${idx}--rdg-frozen-start-${idx} for symmetry?
  2. Rename DOM marker .rdg-cell-frozen.rdg-cell-frozen-start for symmetry?

Both are breaking, so I held off. Happy to address whenever you have a window.

@amanmahajan7 amanmahajan7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more cleanup comments. I will do a detailed review soon.

Comment thread src/hooks/useCalculatedColumns.ts Outdated
Comment thread src/hooks/useCalculatedColumns.ts
Comment thread src/hooks/useCalculatedColumns.ts Outdated
Comment thread src/hooks/useCalculatedColumns.ts Outdated
Comment thread src/style/row.ts Outdated
}

& > .${cellFrozen}:first-child::before {
& > .rdg-cell-frozen:first-child::before,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets rename this variable also

Suggested change
& > .rdg-cell-frozen:first-child::before,
& > .rdg-cell-frozen-start:first-child::before,

@PaloSP PaloSP force-pushed the feat/end-frozen-columns branch 2 times, most recently from 8c52472 to e9b24ff Compare May 17, 2026 10:03
@PaloSP

PaloSP commented May 17, 2026

Copy link
Copy Markdown
Contributor Author

@amanmahajan7 thanks — applied all 5 inline comments plus extended the rename principle consistently across the codebase.

Your 5 comments:

  1. lastFrozenColumnIndexlastStartFrozenColumnIndex
  2. Restored the // TODO: sort columns to keep them grouped if they have a parent comment ✓
  3. lastMetriclastColumnMetric
  4. firstEndMetricfirstEndFrozenColumnMetric
  5. .rdg-cell-frozen.rdg-cell-frozen-start (DOM marker, breaking) ✓

Same principle applied to similar names (where ambiguous "Frozen" meant "start-frozen"):

  • cellFrozencellFrozenStart
  • cellFrozenClassnamecellFrozenStartClassname
  • totalFrozenColumnWidthtotalStartFrozenColumnWidth
  • frozenColumnShadowTopClassnamefrozenColumnShadowStartTopClassname
  • columnMetric (inside the second useMemo) → lastStartFrozenColumnMetric
  • CSS var --rdg-frozen-left-${idx}--rdg-frozen-start-${idx} (breaking — this also resolves my open Q1 implicitly, since keeping the asymmetric -left after renaming the DOM marker to -start would be glaring)

All 351 tests pass on chromium + firefox.

@PaloSP PaloSP force-pushed the feat/end-frozen-columns branch from e9b24ff to f1e732e Compare May 25, 2026 16:42
@amanmahajan7

amanmahajan7 commented May 26, 2026

Copy link
Copy Markdown
Collaborator

I haven't had a chance to review this PR. I will review is as soon as I can. Thanks for your patience.

@yorikcv

yorikcv commented Jun 5, 2026

Copy link
Copy Markdown

@amanmahajan7 @PaloSP

Hi guys,

Could you please consider releasing this pull request to production? I’m currently waiting for this functionality on my side and would greatly appreciate your help.

Thanks in advance!

@luisfmsouza

Copy link
Copy Markdown

Thanks for your work on this @PaloSP, we are looking forward to have this functionality.

@amanmahajan7 I know you are busy, so how can we help to speed this release up?

@PaloSP PaloSP force-pushed the feat/end-frozen-columns branch from ca48030 to fb064a4 Compare June 26, 2026 08:51
@amanmahajan7

Copy link
Copy Markdown
Collaborator

I am planing on reviewing this PR next week. Hopefully we can merge and release a new version early next week.

@amanmahajan7 amanmahajan7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall. A few comments and I think we can add 2 more tests

Comment thread src/DataGrid.tsx Outdated
scrollToCell({ idx, rowIdx }) {
const scrollToIdx =
idx != null && idx > lastFrozenColumnIndex && idx < columns.length ? idx : undefined;
idx != null && idx > lastStartFrozenColumnIndex && idx < columns.length ? idx : undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
idx != null && idx > lastStartFrozenColumnIndex && idx < columns.length ? idx : undefined;
idx != null &&
idx > lastStartFrozenColumnIndex &&
(firstEndFrozenColumnIndex === -1 || idx < firstEndFrozenColumnIndex) &&
idx < columns.length

We can skip scrolling for end frozen columns

Comment thread src/hooks/useCalculatedColumns.ts Outdated
Comment thread src/style/cell.ts Outdated
// Single shared sticky/z-index rule reused by both edge-frozen variants.
// Selectors that need to distinguish start vs end use the marker classes
// (`.rdg-cell-frozen-start` / `.rdg-cell-frozen-end`) directly rather than this ecij identifier.
export const cellFrozenStart = css`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const cellFrozenStart = css`
export const cellFrozen = css`

nit

or

Suggested change
export const cellFrozenStart = css`
export const cellFrozenBase = css`

Comment thread src/index.ts
Comment thread test/browser/column/frozenEnd.test.ts Outdated
await expect.element(cell3).toHaveClass(cellClassname, cellFrozenEndClassname, { exact: true });
});

test('end-frozen cells expose insetInlineEnd and --rdg-frozen-end CSS var', async () => {

@amanmahajan7 amanmahajan7 Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we are testing implementation details? Can we replicate what we did for start?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can rename this file to startFrozen

@PaloSP PaloSP force-pushed the feat/end-frozen-columns branch from fb064a4 to 24478ed Compare July 6, 2026 05:20
@PaloSP

PaloSP commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@amanmahajan7 all applied, thanks for the review.

  1. scrollToCell — applied your suggestion; end-frozen columns are skipped the same way start-frozen ones are. Added a test (scrollToCell with end-frozen columns) asserting the no-op + that unfrozen targets still scroll.
  2. viewportRight — you're right, applied (- totalEndFrozenColumnWidth, mirroring viewportLeft) + added it to the memo deps. The new index-based virtualization test below locks this in — fewer unfrozen columns render now that the effective viewport is narrower.
  3. cellFrozenStartcellFrozenBase — went with Base; the block is shared by both edge variants, so Start was misleading. cellFrozen alone felt too close to cellFrozenStartClassname/cellFrozenEndClassname semantics.
  4. Type name — went with ColumnFrozenState. FrozenColumnPosition reads odd for the false member (not-frozen isn't a position). Happy to change if you prefer something else.
  5. frozenEnd tests — replaced both CSS-var/computed-style tests with a behavior test: end-frozen cells stay flush with the grid's inline-end edge and stack correctly, before and after horizontal scroll (getBoundingClientRect, no implementation details).
  6. File renamesfrozen.test.tsstartFrozen.test.ts, and symmetrically frozenEnd.test.tsendFrozen.test.ts.

New tests: scrollToCell with end-frozen columns (scrollToCell.test.tsx) and virtualization is enabled with 2 end-frozen columns (virtualization.test.ts, exact-index assertions in the style of the 4-frozen-columns test, replacing the weaker presence-based one).

Also rebased on latest main. 351 tests green on chromium + firefox.

@PaloSP PaloSP requested a review from amanmahajan7 July 6, 2026 05:36

@amanmahajan7 amanmahajan7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments. I will do one final review and local testing after this change

Comment thread src/utils/colSpanUtils.ts Outdated
Comment thread src/index.ts Outdated
Comment thread README.md
Comment thread src/GroupRow.tsx Outdated
@PaloSP

PaloSP commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@amanmahajan7 done, all four:

  1. Barrel import — extracted src/utils/frozenColumnUtils.ts with isStartFrozen; colSpanUtils and styleUtils now import from it directly (barrel re-exports it for external consumers).
  2. ColumnFrozen — agreed; the alias is gone, the type is named ColumnFrozen everywhere now.
  3. RTL docs — updated the direction section as suggested.
  4. GroupRow selector — good catch, applied. My :nth-child version was wrong: it kept the border on the first end-frozen cell (a visible separator inside the band) and dropped it on the row's last cell. :nth-last-child gives the mirror of the start band — internal separators removed, row-edge border kept.

While at it, swept the whole diff for leftovers of the same patterns and found one: frozenShadowStylesfrozenStartShadowStyles (local var in DataGrid.tsx, same ambiguous-Frozen naming you flagged earlier).

CI green, 351 tests passing. Ready for your final review.

@PaloSP PaloSP requested a review from amanmahajan7 July 6, 2026 16:02

@amanmahajan7 amanmahajan7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last comment otherwise ready.

Comment thread src/utils/styleUtils.ts Outdated
insetInlineStart: isStartFrozen(column.frozen)
? `var(--rdg-frozen-start-${column.idx})`
: undefined,
insetInlineEnd: column.frozen === 'end' ? `var(--rdg-frozen-end-${column.idx})` : undefined

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
insetInlineEnd: column.frozen === 'end' ? `var(--rdg-frozen-end-${column.idx})` : undefined
insetInlineEnd: column.frozen === 'end' ? `var(--rdg-frozen-end-${column.idx + colSpan - 1})` : undefined

I think we need this. Can you double check

@PaloSP

PaloSP commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@amanmahajan7 good catch, confirmed it's a real bug. --rdg-frozen-end-${idx} measures from a column's right edge, so a colSpanned end-frozen cell must anchor by its last spanned column → column.idx + colSpan - 1. Start side unaffected (anchors left). Applied + added a regression test (fails on old code with the spanning cell landing 60px short, passes now, chromium + firefox). Also de-flaked the sibling scroll-pin test (cached element refs → re-query). CI green.

@PaloSP PaloSP requested a review from amanmahajan7 July 8, 2026 09:05

@amanmahajan7 amanmahajan7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Thank you for adding this highly requested feature.

@amanmahajan7 amanmahajan7 merged commit 3c9a765 into Comcast:main Jul 9, 2026
2 checks passed
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.

6 participants