Skip to content

Comments

selectedPosition -> activePosition, column/row iterators#3979

Open
nstepien wants to merge 14 commits intomainfrom
nomenclature
Open

selectedPosition -> activePosition, column/row iterators#3979
nstepien wants to merge 14 commits intomainfrom
nomenclature

Conversation

@nstepien
Copy link
Collaborator

@nstepien nstepien commented Feb 24, 2026

To help differentiate between "selected rows" (rows that get selected via checkbox),
and "selected rows/cells/position" (rows or cells that are focused, or remain active after blurring),
I've done the work to rename "selected rows/cells/position" to "active position".

While working on it I also tried to refactor how to iterate over rows,
which lead me to refactor how we iterate over columns as well.

@nstepien nstepien self-assigned this Feb 24, 2026
@nstepien nstepien changed the title selectedPosition -> activePosition`, column/row iterators selectedPosition -> activePosition, column/row iterators Feb 24, 2026
@codecov-commenter
Copy link

codecov-commenter commented Feb 24, 2026

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

Codecov Report

❌ Patch coverage is 99.00498% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.51%. Comparing base (dd8eb88) to head (80ea0f5).

Files with missing lines Patch % Lines
src/DataGrid.tsx 99.09% 1 Missing ⚠️
src/GroupedColumnHeaderCell.tsx 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3979      +/-   ##
==========================================
- Coverage   97.53%   97.51%   -0.03%     
==========================================
  Files          36       36              
  Lines        1463     1449      -14     
  Branches      472      457      -15     
==========================================
- Hits         1427     1413      -14     
  Misses         36       36              
Files with missing lines Coverage Δ
src/Cell.tsx 100.00% <100.00%> (ø)
src/EditCell.tsx 88.09% <100.00%> (ø)
src/GroupCell.tsx 100.00% <100.00%> (ø)
src/GroupRow.tsx 100.00% <100.00%> (ø)
src/GroupedColumnHeaderRow.tsx 100.00% <100.00%> (ø)
src/HeaderCell.tsx 95.94% <100.00%> (ø)
src/HeaderRow.tsx 100.00% <ø> (ø)
src/Row.tsx 100.00% <100.00%> (ø)
src/ScrollToCell.tsx 100.00% <100.00%> (ø)
src/SummaryCell.tsx 100.00% <100.00%> (ø)
... and 9 more
🚀 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.

};

for (const column of colSpanColumns) {
function* iterateOverRowsForColSpanArgs(): Generator<ColSpanArgs<R, SR>> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Refactored to use a generator -> single loop -> dedupe lots of stuff and can return/break in that loop (nested loops didn't break the outer loop)

break;
}
for (const column of colSpanColumns) {
if (column.frozen) continue;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added this check, since all frozen columns are displayed then we don't need to check whether they affect startIdx

const viewportColumns: CalculatedColumn<R, SR>[] = [];
for (let colIdx = 0; colIdx <= colOverscanEndIdx; colIdx++) {
const column = columns[colIdx];
const iterateOverViewportColumns = useCallback<IterateOverViewportColumns<R, SR>>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Reusable generator to iterate over viewport columns.
Unlike the previous implementation, we do not iterate over columns between lastFrozenColumnIndex and startIdx, which may be good if we have A LOT of columns, though unlikely.

It also yields the active column, as necessary, without doing
[...columns, activeColumn],
or columns.toSpliced(index, 0, activeColumn),
or [...columns.slice(...), activeColumns, ...columns.slice(...)],
which always left a bad taste in my mouth.

[startIdx, colOverscanEndIdx, columns, lastFrozenColumnIndex]
);

const iterateOverViewportColumnsForRow = useCallback<IterateOverViewportColumnsForRow<R, SR>>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Rows now use this, instead of passing an array.
It helps deduplicate colSpan handling,
the col span args is created only once per row now, instead of per column.

As a nice bonus don't pass lastFrozenColumnIndex to rows anymore.

The only "negative" is that now each row will iterate through iterateOverViewportColumns, instead of a straight for...of columnsArray but its implementation is so simple it should be fine.

[iterateOverViewportColumns, lastFrozenColumnIndex]
);

const iterateOverViewportColumnsForRowOutsideOfViewport = useCallback<
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If the row is outside the viewport, we use this iterator instead.
If a column is active, we render this column only, just like before.
If no columns are active, i.e. the row is active, then we render no columns at all, instead of rendering all the columns like in the previous implementation!


const [selectedPosition, setSelectedPosition] = useState(
(): SelectCellState | EditCellState<R> => ({ idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' })
const [activePosition, setActivePosition] = useState<ActiveCellState | EditCellState<R>>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What a huge PR just for this change 🙃

* Viewport: any valid position in the grid outside of header rows and summary rows
* Row selection is only allowed in TreeDataGrid
*/
function validatePosition({ idx, rowIdx }: Position) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've replaced all the individual position check utils with validatePosition.
It takes care to handle active/viewport bounds, and allows row selection only in TreeDataGrid.
Hopefully this is less confusing than trying to figure out which util to use, and if we need to use multiple utils to validate both row and column coordinates.
It's all boolean checks so it'll be fast.

onActivePositionChange({
rowIdx: position.rowIdx,
row: isRowIdxWithinViewportBounds(position.rowIdx) ? rows[position.rowIdx] : undefined,
row: rows[position.rowIdx],
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added the undefined type here, no need for the check

selectedColumn,
...viewportColumns.slice(lastFrozenColumnIndex + 1)
];
function* iterateOverViewportRowIdx() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added this generator to elegantly iterate over viewport rows.
We don't fudge around with row indexes anymore, less maths, more booleans, much simpler IMO.

);
}

function getRowViewportColumns(rowIdx: number) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This has been replaced by iterateOverViewportColumnsForRow & iterateOverViewportColumnsForRowOutsideOfViewport

@nstepien nstepien marked this pull request as ready for review February 24, 2026 18:24
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.

2 participants