Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Reshape row selection model #15651

Open
wants to merge 49 commits into
base: master
Choose a base branch
from

Conversation

cherniavskii
Copy link
Member

@cherniavskii cherniavskii commented Nov 28, 2024

Closes #15481
Fixes #15962

@cherniavskii cherniavskii added breaking change component: data grid This is the name of the generic UI component, not the React module! feature: Selection Related to the data grid Selection feature v8.x labels Nov 28, 2024
@mui-bot
Copy link

mui-bot commented Nov 28, 2024

Deploy preview: https://deploy-preview-15651--material-ui-x.netlify.app/

Generated by 🚫 dangerJS against 4017821

Comment on lines 642 to 644
if (!props.isRowSelectable && !props.checkboxSelectionVisibleOnly && applyAutoSelection) {
apiRef.current.selectRows({ type: 'exclude', ids: new Set() }, params.value, true);
} else {
Copy link
Member Author

Choose a reason for hiding this comment

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

The more corner cases I fix, the fewer opportunities for the exclude selection model 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

AFAIU it would only be for "select all", but for large datasets it would improve that interaction substantially.

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, but there is a number of conditions for the exclude model to be used:

if (
  !props.isRowSelectable &&
  !props.checkboxSelectionVisibleOnly &&
  applyAutoSelection &&
  !hasFilters
) {
  apiRef.current.selectRows({ type: 'exclude', ids: new Set() }, params.value, true);
} else {
  const rowsToBeSelected = getRowsToBeSelected();
  apiRef.current.selectRows(
    { type: 'include', ids: new Set(rowsToBeSelected) },
    params.value,
  );
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I would have thought that both models would be equivalent, assuming access to the full row id list (though that implies modifying the row list requires adjusting the selection model).

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, so { type: 'exclude', ids: new Set() } could be interpreted differently:

  1. All rows are selected (current approach)
  2. All selectable and visible rows are selected (this is what you meant, right?)

Do you see benefits in (2) compared to (1)?

Copy link
Member Author

Choose a reason for hiding this comment

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

On another thought, there's a difference between (1) and (2) when interpreting this model by the developers to perform operations on selected rows.
In (1), an empty exclude model means literally all rows.
In (2), an empty exclude model could mean different things depending on the data grid state. So developers would need to convert empty exclude into include (?) model to be able to perform actions on the dataset.

With the above, I think we should proceed with (1).
What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think (1) is the way to go. Both shapes of the model should be equivalent, which isn't the case for (2).

Copy link
Contributor

@romgrk romgrk Dec 5, 2024

Choose a reason for hiding this comment

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

The more corner cases I fix, the fewer opportunities for the exclude selection model

It's not that it's not usable for the conditions that you've guarded for, it's that some of them might require building a non-empty exclude model. You'd need an equivalent getRowsToBeSelectedAsExclude() function. And there are some cases in which it could make sense to do that work. For example, if there are filters applied that filter a small fraction (or none) of the total rows, using an exclude model would be much faster than building an include one.

Copy link
Member

Choose a reason for hiding this comment

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

In (1), an empty exclude model means literally all rows.

Does this mean that even if some rows are restricted to be selected by let's say isRowSelectable prop, they'd be selected but not de-selectable from the UI, similar to this?

(Based on the assumption that although we are discouraging the usage of exclude mode with isRowSelectable, we aren't forcing it.)

Copy link
Member Author

Choose a reason for hiding this comment

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

@MBilalShafi Yes, this is the current behavior:

Screen.Recording.2025-01-06.at.23.09.35.mov

@cherniavskii cherniavskii requested a review from a team December 2, 2024 12:39
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Dec 3, 2024
Copy link

github-actions bot commented Dec 3, 2024

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@cherniavskii
Copy link
Member Author

performance wise this still doesn't improve anything on automatic child selection, right?

It does, in 2 ways:

  1. When the exclude model is used, if the conditions for it are met:
    if (
      !props.isRowSelectable &&
      !props.checkboxSelectionVisibleOnly &&
      applyAutoSelection &&
      !hasFilters
    ) {
      apiRef.current.setRowSelectionModel({
        type: value ? 'exclude' : 'include',
        ids: new Set(),
      });
    }
  2. When the include model is used, we now use Set instead of an array. So the search operation is now O(1) instead of O(n):

@cherniavskii cherniavskii marked this pull request as ready for review December 5, 2024 17:59
@cherniavskii cherniavskii requested review from a team, romgrk and arminmeh December 5, 2024 17:59
packages/x-data-grid/src/models/gridRowSelectionModel.ts Outdated Show resolved Hide resolved

export type GridRowSelectionModel = readonly GridRowId[];
interface SelectionManager {
Copy link
Member

Choose a reason for hiding this comment

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

How about being more specific for the naming since we have cell selection too, and we could have column selection in future too.

Suggested change
interface SelectionManager {
interface RowSelectionManager {

Copy link
Member

Choose a reason for hiding this comment

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

Side note independent to this PR: Would it make sense to also implement an include/exclude model for the cell selection feature?
I assume a developer learning the interface of one would expect the other to be similar.

It could also make the cell selection faster particularly if we plan to implement this practice of the WAI-ARIA keyboard interaction guidelines:

Control + A: Selects all cells.

Another quick upside would be to make a somewhat expensive function getSelectedCellsAsArray performant.

// Current interface
Record<GridRowId, Record<GridColDef['field'], boolean>>

// With exclude/include: Could help with "Select all" / "Deselect all"
{ coords: Map<GridRowId, Set<GridColDef['field']>>, type: 'include' | 'exclude' }

// Just convert to Map/Set
Map<GridRowId, Set<GridColDef['field']>>

Wdyt?

Comment on lines 642 to 644
if (!props.isRowSelectable && !props.checkboxSelectionVisibleOnly && applyAutoSelection) {
apiRef.current.selectRows({ type: 'exclude', ids: new Set() }, params.value, true);
} else {
Copy link
Member

Choose a reason for hiding this comment

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

In (1), an empty exclude model means literally all rows.

Does this mean that even if some rows are restricted to be selected by let's say isRowSelectable prop, they'd be selected but not de-selectable from the UI, similar to this?

(Based on the assumption that although we are discouraging the usage of exclude mode with isRowSelectable, we aren't forcing it.)

@@ -191,3 +191,7 @@ export const getSelectInput = (combobox: Element) => {
export function getSelectByName(name: string) {
return getSelectInput(screen.getByRole('combobox', { name }))!;
}

export const includeRowSelection = (ids: GridRowId[]) => {
Copy link
Member

@MBilalShafi MBilalShafi Dec 17, 2024

Choose a reason for hiding this comment

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

Could this be useful as a public export too?
For example, it could be used as a suggestion in migration guide diff (and also in the codemod probably).

-const [rowSelectionModel, setRSM] = React.useState([1, 2, 3]);
+const [rowSelectionModel, setRSM] = React.useState(includeRowSelection([1, 2, 3]));

Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Dec 30, 2024
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jan 3, 2025
expect(onRowSelectionModelChange.callCount).to.equal(2); // Dev mode calls twice
expect(onRowSelectionModelChange.callCount).to.equal(reactMajor < 19 ? 2 : 1); // Dev mode calls twice on React 18
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably disable dev mode (aka strict mode) in testing, as it's not present in production.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change component: data grid This is the name of the generic UI component, not the React module! feature: Selection Related to the data grid Selection feature v8.x
Projects
None yet
5 participants