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] Fix checkboxSelectionVisibleOnly reset the selection on filtering #14677

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid-premium.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
},
"keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" },
"keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" },
"keepUnfilteredRowsSelected": { "type": { "name": "bool" }, "default": "false" },
"loading": { "type": { "name": "bool" }, "default": "false" },
"localeText": { "type": { "name": "object" } },
"logger": {
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
},
"keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" },
"keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" },
"keepUnfilteredRowsSelected": { "type": { "name": "bool" }, "default": "false" },
"loading": { "type": { "name": "bool" }, "default": "false" },
"localeText": { "type": { "name": "object" } },
"logger": {
Expand Down
1 change: 1 addition & 0 deletions docs/pages/x/api/data-grid/data-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
}
},
"keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" },
"keepUnfilteredRowsSelected": { "type": { "name": "bool" }, "default": "false" },
"loading": { "type": { "name": "bool" }, "default": "false" },
"localeText": { "type": { "name": "object" } },
"logger": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@
"keepNonExistentRowsSelected": {
"description": "If <code>true</code>, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages."
},
"keepUnfilteredRowsSelected": {
"description": "If <code>true</code>, the selection model will retain the previously selected rows not part of the current filtered rows. Useful when the user wants to filter subsets of rows and keep the previously selected rows."
},
"loading": { "description": "If <code>true</code>, a loading overlay is displayed." },
"localeText": {
"description": "Set the locale text of the Data Grid. You can find all the translation keys supported in <a href=\"https://github.com/mui/mui-x/blob/HEAD/packages/x-data-grid/src/constants/localeTextConstants.ts\">the source</a> in the GitHub repository."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@
"keepNonExistentRowsSelected": {
"description": "If <code>true</code>, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages."
},
"keepUnfilteredRowsSelected": {
"description": "If <code>true</code>, the selection model will retain the previously selected rows not part of the current filtered rows. Useful when the user wants to filter subsets of rows and keep the previously selected rows."
},
"loading": { "description": "If <code>true</code>, a loading overlay is displayed." },
"localeText": {
"description": "Set the locale text of the Data Grid. You can find all the translation keys supported in <a href=\"https://github.com/mui/mui-x/blob/HEAD/packages/x-data-grid/src/constants/localeTextConstants.ts\">the source</a> in the GitHub repository."
Expand Down
3 changes: 3 additions & 0 deletions docs/translations/api-docs/data-grid/data-grid/data-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
"keepNonExistentRowsSelected": {
"description": "If <code>true</code>, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages."
},
"keepUnfilteredRowsSelected": {
"description": "If <code>true</code>, the selection model will retain the previously selected rows not part of the current filtered rows. Useful when the user wants to filter subsets of rows and keep the previously selected rows."
},
"loading": { "description": "If <code>true</code>, a loading overlay is displayed." },
"localeText": {
"description": "Set the locale text of the Data Grid. You can find all the translation keys supported in <a href=\"https://github.com/mui/mui-x/blob/HEAD/packages/x-data-grid/src/constants/localeTextConstants.ts\">the source</a> in the GitHub repository."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,12 @@ DataGridPremiumRaw.propTypes = {
* @default false
*/
keepNonExistentRowsSelected: PropTypes.bool,
/**
* If `true`, the selection model will retain the previously selected rows not part of the current filtered rows.
* Useful when the user wants to filter subsets of rows and keep the previously selected rows.
* @default false
*/
keepUnfilteredRowsSelected: PropTypes.bool,
/**
* If `true`, a loading overlay is displayed.
* @default false
Expand Down
6 changes: 6 additions & 0 deletions packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ DataGridProRaw.propTypes = {
* @default false
*/
keepNonExistentRowsSelected: PropTypes.bool,
/**
* If `true`, the selection model will retain the previously selected rows not part of the current filtered rows.
* Useful when the user wants to filter subsets of rows and keep the previously selected rows.
* @default false
*/
keepUnfilteredRowsSelected: PropTypes.bool,
/**
* If `true`, a loading overlay is displayed.
* @default false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,43 @@ describe('<DataGridPro /> - Row selection', () => {
fireEvent.click(selectAllCheckbox);
expect(apiRef.current.getSelectedRows()).to.have.length(2);
});

// https://github.com/mui/mui-x/issues/14074
it('should select all the rows of the current page keeping the previously selected rows when a filter is applied', () => {
render(
<TestDataGridSelection
rowLength={50}
checkboxSelection
checkboxSelectionVisibleOnly
initialState={{
pagination: { paginationModel: { pageSize: 2 } },
filter: {
filterModel: {
items: [
{
field: 'currencyPair',
value: 'usd',
operator: 'contains',
},
],
},
},
}}
pagination
pageSizeOptions={[2]}
/>,
);

fireEvent.click(getCell(0, 0).querySelector('input')!);
expect(apiRef.current.getSelectedRows()).to.have.keys([0]);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', {
name: /select all rows/i,
});
fireEvent.click(selectAllCheckbox);
expect(apiRef.current.getSelectedRows()).to.have.keys([0, 3, 4]);
expect(selectAllCheckbox.checked).to.equal(true);
});
});

describe('apiRef: getSelectedRows', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/x-data-grid/src/DataGrid/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,12 @@ DataGridRaw.propTypes = {
* @default false
*/
keepNonExistentRowsSelected: PropTypes.bool,
/**
* If `true`, the selection model will retain the previously selected rows not part of the current filtered rows.
* Useful when the user wants to filter subsets of rows and keep the previously selected rows.
* @default false
*/
keepUnfilteredRowsSelected: PropTypes.bool,
/**
* If `true`, a loading overlay is displayed.
* @default false
Expand Down
1 change: 1 addition & 0 deletions packages/x-data-grid/src/DataGrid/useDataGridProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const DATA_GRID_PROPS_DEFAULT_VALUES: DataGridPropsWithDefaultValues = {
indeterminateCheckboxAction: 'deselect',
keepColumnPositionIfDraggedOutside: false,
keepNonExistentRowsSelected: false,
keepUnfilteredRowsSelected: false,
loading: false,
logger: console,
logLevel: process.env.NODE_ENV === 'production' ? ('error' as const) : ('warn' as const),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ export const gridFilteredSortedRowIdsSelector = createSelectorMemoized(
(filteredSortedRowEntries) => filteredSortedRowEntries.map((row) => row.id),
);

/**
* Get a `Set` containing the ids of the rows accessible after the filtering process.
* Contains the collapsed children.
* @category Filtering
* @ignore - Do not document.
*/
export const gridFilteredSortedRowIdsSetSelector = createSelectorMemoized(
gridFilteredSortedRowEntriesSelector,
(filteredSortedRowEntries) => {
const filteredSortedRowIdsSetSelector = new Set<GridRowId>();
filteredSortedRowEntries.map((row) => filteredSortedRowIdsSetSelector.add(row.id));
return filteredSortedRowIdsSetSelector;
},
);

/**
* Get the ids to position in the current tree level lookup of the rows accessible after the filtering process.
* Does not contain the collapsed children.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { gridPaginatedVisibleSortedGridRowIdsSelector } from '../pagination';
import { gridFocusCellSelector } from '../focus/gridFocusStateSelector';
import {
gridExpandedSortedRowIdsSelector,
gridFilterModelSelector,
gridFilteredSortedRowIdsSetSelector,
} from '../filter/gridFilterSelector';
import { GRID_CHECKBOX_SELECTION_COL_DEF, GRID_ACTIONS_COLUMN_TYPE } from '../../../colDef';
import { GridCellModes } from '../../../models/gridEditRowModel';
Expand Down Expand Up @@ -82,6 +82,7 @@ export const useGridRowSelection = (
| 'paginationMode'
| 'classes'
| 'keepNonExistentRowsSelected'
| 'keepUnfilteredRowsSelected'
| 'rowSelection'
| 'signature'
>,
Expand Down Expand Up @@ -326,19 +327,32 @@ export const useGridRowSelection = (
/**
* EVENTS
*/
const isFirstRender = React.useRef(true);
const removeOutdatedSelection = React.useCallback(() => {
if (props.keepNonExistentRowsSelected) {
let firstRender = false;
if (isFirstRender.current) {
// `filteredRows` is not available before filtering process is done
firstRender = true;
isFirstRender.current = false;
}
if (props.keepNonExistentRowsSelected && (props.keepUnfilteredRowsSelected || firstRender)) {
return;
}
const currentSelection = gridRowSelectionStateSelector(apiRef.current.state);
const rowsLookup = gridRowsLookupSelector(apiRef);
const filteredRows = gridFilteredSortedRowIdsSetSelector(apiRef);

// We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
const selectionLookup = { ...selectedIdsLookupSelector(apiRef) };

let hasChanged = false;
currentSelection.forEach((id: GridRowId) => {
if (!rowsLookup[id]) {
if (!props.keepNonExistentRowsSelected && !rowsLookup[id]) {
delete selectionLookup[id];
hasChanged = true;
return;
}
if (!props.keepUnfilteredRowsSelected && !filteredRows.has(id)) {
delete selectionLookup[id];
hasChanged = true;
}
Expand All @@ -347,7 +361,7 @@ export const useGridRowSelection = (
if (hasChanged) {
apiRef.current.setRowSelectionModel(Object.values(selectionLookup));
}
}, [apiRef, props.keepNonExistentRowsSelected]);
}, [apiRef, props.keepNonExistentRowsSelected, props.keepUnfilteredRowsSelected, isFirstRender]);

const handleSingleRowSelection = React.useCallback(
(id: GridRowId, event: React.MouseEvent | React.KeyboardEvent) => {
Expand Down Expand Up @@ -452,8 +466,7 @@ export const useGridRowSelection = (
? gridPaginatedVisibleSortedGridRowIdsSelector(apiRef)
: gridExpandedSortedRowIdsSelector(apiRef);

const filterModel = gridFilterModelSelector(apiRef);
apiRef.current.selectRows(rowsToBeSelected, params.value, filterModel?.items.length > 0);
apiRef.current.selectRows(rowsToBeSelected, params.value);
},
[apiRef, props.checkboxSelectionVisibleOnly, props.pagination, props.paginationMode],
);
Expand Down Expand Up @@ -539,6 +552,11 @@ export const useGridRowSelection = (
'sortedRowsSet',
runIfRowSelectionIsEnabled(removeOutdatedSelection),
);
useGridApiEventHandler(
apiRef,
'filteredRowsSet',
runIfRowSelectionIsEnabled(removeOutdatedSelection),
);
useGridApiEventHandler(apiRef, 'rowClick', runIfRowSelectionIsEnabled(handleRowClick));
useGridApiEventHandler(
apiRef,
Expand Down
6 changes: 6 additions & 0 deletions packages/x-data-grid/src/models/props/DataGridProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ export interface DataGridPropsWithDefaultValues<R extends GridValidRowModel = an
* @default false
*/
keepNonExistentRowsSelected: boolean;
/**
* If `true`, the selection model will retain the previously selected rows not part of the current filtered rows.
* Useful when the user wants to filter subsets of rows and keep the previously selected rows.
* @default false
*/
keepUnfilteredRowsSelected: boolean;
/**
* Pass a custom logger in the components that implements the [[Logger]] interface.
* @default console
Expand Down
Loading
Loading