@mui/x-data-grid 7.22.1 → 7.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,70 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 7.22.2
7
+
8
+ _Nov 8, 2024_
9
+
10
+ We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨:
11
+
12
+ - 👨🏽‍💻 API enhancements
13
+ - 🐞 Bugfixes
14
+
15
+ Special thanks go out to the community contributors who have helped make this release possible:
16
+ @clins1994, @GuillaumeMeheut, @k-rajat19.
17
+ Following are all team members who have contributed to this release:
18
+ @LukasTy, @MBilalShafi, @KenanYusuf, @arminmeh.
19
+
20
+ ### Upcoming alpha
21
+
22
+ Keep an eye out for the MUI⠀X `v8.0.0-aplha.0` release soon. It will follow a weekly release schedule as always until it is stable.
23
+
24
+ <!--/ HIGHLIGHT_ABOVE_SEPARATOR /-->
25
+
26
+ ### Data Grid
27
+
28
+ #### `@mui/x-data-grid@7.22.2`
29
+
30
+ - [DataGrid] Fix `null` reference error in `GridVirtualScrollbar` (#15289) @MBilalShafi
31
+ - [DataGrid] Fix filtering with `boolean` column type (#15257) @k-rajat19
32
+ - [DataGrid] Improve row selection propagation trigger (#15274) @MBilalShafi
33
+ - [DataGrid] Preprocess edit cell props on backspace/delete (#15223) @KenanYusuf
34
+ - [DataGrid] Add a recipe to persist column width and order (#15309) @MBilalShafi
35
+
36
+ #### `@mui/x-data-grid-pro@7.22.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
37
+
38
+ Same changes as in `@mui/x-data-grid@7.22.2`, plus:
39
+
40
+ - [DataGridPro] Apply default properties if they are not passed in a reorder column (#15320) @k-rajat19
41
+ - [DataGridPro] Toggle row expansion with `Enter` key in Tree data (#15313) @k-rajat19
42
+
43
+ #### `@mui/x-data-grid-premium@7.22.2` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
44
+
45
+ Same changes as in `@mui/x-data-grid-pro@7.22.2`, plus:
46
+
47
+ - [DataGridPremium] Fix incorrect rows selection count when selection propagation is enabled with row grouping (#15222) @arminmeh
48
+
49
+ ### Date and Time Pickers
50
+
51
+ #### `@mui/x-date-pickers@7.22.2`
52
+
53
+ - [pickers] Add support for `moment-hijri@3.0.0` (#15248) @LukasTy
54
+
55
+ #### `@mui/x-date-pickers-pro@7.22.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
56
+
57
+ Same changes as in `@mui/x-date-pickers@7.22.2`.
58
+
59
+ ### Charts
60
+
61
+ #### `@mui/x-charts@7.22.2`
62
+
63
+ - [charts] Allow `SeriesValueFormatter` to return `null` value (#15295) @clins1994
64
+ - [charts] Allow configuring the `domainLimit` for each axis. (#15325) @GuillaumeMeheut
65
+
66
+ #### `@mui/x-charts-pro@7.0.0-beta.7` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
67
+
68
+ Same changes as in `@mui/x-charts@7.22.2`.
69
+
6
70
  ## 7.22.1
7
71
 
8
72
  _Nov 1, 2024_
@@ -1,14 +1,12 @@
1
- import { GridFilterInputBoolean } from "../components/panel/filterPanel/GridFilterInputBoolean.js";
1
+ import { GridFilterInputBoolean, sanitizeFilterItemValue } from "../components/panel/filterPanel/GridFilterInputBoolean.js";
2
2
  export const getGridBooleanOperators = () => [{
3
3
  value: 'is',
4
4
  getApplyFilterFn: filterItem => {
5
- if (!filterItem.value) {
5
+ const sanitizedValue = sanitizeFilterItemValue(filterItem.value);
6
+ if (sanitizedValue === undefined) {
6
7
  return null;
7
8
  }
8
- const valueAsBoolean = String(filterItem.value) === 'true';
9
- return value => {
10
- return Boolean(value) === valueAsBoolean;
11
- };
9
+ return value => Boolean(value) === sanitizedValue;
12
10
  },
13
11
  InputComponent: GridFilterInputBoolean
14
12
  }];
@@ -9,6 +9,7 @@ export type GridFilterInputBooleanProps = GridFilterInputValueProps & TextFieldP
9
9
  */
10
10
  isFilterActive?: boolean;
11
11
  };
12
+ export declare const sanitizeFilterItemValue: (value: any) => boolean | undefined;
12
13
  declare function GridFilterInputBoolean(props: GridFilterInputBooleanProps): React.JSX.Element;
13
14
  declare namespace GridFilterInputBoolean {
14
15
  var propTypes: any;
@@ -7,6 +7,15 @@ import { refType, unstable_useId as useId } from '@mui/utils';
7
7
  import { styled } from '@mui/material/styles';
8
8
  import { useGridRootProps } from "../../../hooks/utils/useGridRootProps.js";
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ export const sanitizeFilterItemValue = value => {
11
+ if (String(value).toLowerCase() === 'true') {
12
+ return true;
13
+ }
14
+ if (String(value).toLowerCase() === 'false') {
15
+ return false;
16
+ }
17
+ return undefined;
18
+ };
10
19
  const BooleanOperatorContainer = styled('div')({
11
20
  display: 'flex',
12
21
  alignItems: 'center',
@@ -27,7 +36,7 @@ function GridFilterInputBoolean(props) {
27
36
  variant = 'standard'
28
37
  } = props,
29
38
  others = _objectWithoutPropertiesLoose(props, _excluded);
30
- const [filterValueState, setFilterValueState] = React.useState(item.value || '');
39
+ const [filterValueState, setFilterValueState] = React.useState(sanitizeFilterItemValue(item.value));
31
40
  const rootProps = useGridRootProps();
32
41
  const labelId = useId();
33
42
  const selectId = useId();
@@ -35,14 +44,14 @@ function GridFilterInputBoolean(props) {
35
44
  const isSelectNative = baseSelectProps.native ?? false;
36
45
  const baseSelectOptionProps = rootProps.slotProps?.baseSelectOption || {};
37
46
  const onFilterChange = React.useCallback(event => {
38
- const value = event.target.value;
47
+ const value = sanitizeFilterItemValue(event.target.value);
39
48
  setFilterValueState(value);
40
49
  applyValue(_extends({}, item, {
41
- value: Boolean(value)
50
+ value
42
51
  }));
43
52
  }, [applyValue, item]);
44
53
  React.useEffect(() => {
45
- setFilterValueState(item.value || '');
54
+ setFilterValueState(sanitizeFilterItemValue(item.value));
46
55
  }, [item.value]);
47
56
  const label = labelProp ?? apiRef.current.getLocaleText('filterPanelInputLabel');
48
57
  return /*#__PURE__*/_jsxs(BooleanOperatorContainer, {
@@ -57,7 +66,7 @@ function GridFilterInputBoolean(props) {
57
66
  labelId: labelId,
58
67
  id: selectId,
59
68
  label: label,
60
- value: filterValueState,
69
+ value: filterValueState === undefined ? '' : String(filterValueState),
61
70
  onChange: onFilterChange,
62
71
  variant: variant,
63
72
  notched: variant === 'outlined' ? true : undefined,
@@ -70,6 +70,9 @@ const GridVirtualScrollbar = /*#__PURE__*/React.forwardRef(function GridVirtualS
70
70
  const onScrollerScroll = useEventCallback(() => {
71
71
  const scroller = apiRef.current.virtualScrollerRef.current;
72
72
  const scrollbar = scrollbarRef.current;
73
+ if (!scrollbar) {
74
+ return;
75
+ }
73
76
  if (scroller[propertyScroll] === lastPosition.current) {
74
77
  return;
75
78
  }
@@ -85,6 +88,9 @@ const GridVirtualScrollbar = /*#__PURE__*/React.forwardRef(function GridVirtualS
85
88
  const onScrollbarScroll = useEventCallback(() => {
86
89
  const scroller = apiRef.current.virtualScrollerRef.current;
87
90
  const scrollbar = scrollbarRef.current;
91
+ if (!scrollbar) {
92
+ return;
93
+ }
88
94
  if (isLocked.current) {
89
95
  isLocked.current = false;
90
96
  return;
@@ -241,26 +241,45 @@ export const useGridCellEditing = (apiRef, props) => {
241
241
  mode: GridCellModes.Edit
242
242
  }, other));
243
243
  }, [throwIfNotEditable, throwIfNotInMode, updateFieldInCellModesModel]);
244
- const updateStateToStartCellEditMode = useEventCallback(params => {
244
+ const updateStateToStartCellEditMode = useEventCallback(async params => {
245
245
  const {
246
246
  id,
247
247
  field,
248
248
  deleteValue,
249
249
  initialValue
250
250
  } = params;
251
- let newValue = apiRef.current.getCellValue(id, field);
251
+ const value = apiRef.current.getCellValue(id, field);
252
+ let newValue = value;
252
253
  if (deleteValue) {
253
254
  newValue = getDefaultCellValue(apiRef.current.getColumn(field));
254
255
  } else if (initialValue) {
255
256
  newValue = initialValue;
256
257
  }
257
- const newProps = {
258
+ const column = apiRef.current.getColumn(field);
259
+ const shouldProcessEditCellProps = !!column.preProcessEditCellProps && deleteValue;
260
+ let newProps = {
258
261
  value: newValue,
259
262
  error: false,
260
- isProcessingProps: false
263
+ isProcessingProps: shouldProcessEditCellProps
261
264
  };
262
265
  updateOrDeleteFieldState(id, field, newProps);
263
266
  apiRef.current.setCellFocus(id, field);
267
+ if (shouldProcessEditCellProps) {
268
+ newProps = await Promise.resolve(column.preProcessEditCellProps({
269
+ id,
270
+ row: apiRef.current.getRow(id),
271
+ props: newProps,
272
+ hasChanged: newValue !== value
273
+ }));
274
+ // Check if still in edit mode before updating
275
+ if (apiRef.current.getCellMode(id, field) === GridCellModes.Edit) {
276
+ const editingState = gridEditRowsStateSelector(apiRef.current.state);
277
+ updateOrDeleteFieldState(id, field, _extends({}, newProps, {
278
+ value: editingState[id][field].value,
279
+ isProcessingProps: false
280
+ }));
281
+ }
282
+ }
264
283
  });
265
284
  const stopCellEditMode = React.useCallback(params => {
266
285
  const {
@@ -318,10 +318,11 @@ export const useGridRowEditing = (apiRef, props) => {
318
318
  if (!cellParams.isEditable) {
319
319
  return acc;
320
320
  }
321
+ const column = apiRef.current.getColumn(field);
321
322
  let newValue = apiRef.current.getCellValue(id, field);
322
323
  if (fieldToFocus === field && (deleteValue || initialValue)) {
323
324
  if (deleteValue) {
324
- newValue = getDefaultCellValue(apiRef.current.getColumn(field));
325
+ newValue = getDefaultCellValue(column);
325
326
  } else if (initialValue) {
326
327
  newValue = initialValue;
327
328
  }
@@ -329,7 +330,7 @@ export const useGridRowEditing = (apiRef, props) => {
329
330
  acc[field] = {
330
331
  value: newValue,
331
332
  error: false,
332
- isProcessingProps: false
333
+ isProcessingProps: !!column.preProcessEditCellProps && deleteValue
333
334
  };
334
335
  return acc;
335
336
  }, {});
@@ -337,6 +338,26 @@ export const useGridRowEditing = (apiRef, props) => {
337
338
  if (fieldToFocus) {
338
339
  apiRef.current.setCellFocus(id, fieldToFocus);
339
340
  }
341
+ columnFields.filter(field => !!apiRef.current.getColumn(field).preProcessEditCellProps && deleteValue).forEach(field => {
342
+ const column = apiRef.current.getColumn(field);
343
+ const value = apiRef.current.getCellValue(id, field);
344
+ const newValue = deleteValue ? getDefaultCellValue(column) : initialValue ?? value;
345
+ Promise.resolve(column.preProcessEditCellProps({
346
+ id,
347
+ row: apiRef.current.getRow(id),
348
+ props: newProps[field],
349
+ hasChanged: newValue !== value
350
+ })).then(processedProps => {
351
+ // Check if still in edit mode before updating
352
+ if (apiRef.current.getRowMode(id) === GridRowModes.Edit) {
353
+ const editingState = gridEditRowsStateSelector(apiRef.current.state);
354
+ updateOrDeleteFieldState(id, field, _extends({}, processedProps, {
355
+ value: editingState[id][field].value,
356
+ isProcessingProps: false
357
+ }));
358
+ }
359
+ });
360
+ });
340
361
  });
341
362
  const stopRowEditMode = React.useCallback(params => {
342
363
  const {
@@ -9,4 +9,4 @@ export declare const rowSelectionStateInitializer: GridStateInitializer<Pick<Dat
9
9
  * @requires useGridFocus (state) - can be after
10
10
  * @requires useGridKeyboardNavigation (`cellKeyDown` event must first be consumed by it)
11
11
  */
12
- export declare const useGridRowSelection: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, "checkboxSelection" | "rowSelectionModel" | "onRowSelectionModelChange" | "disableMultipleRowSelection" | "disableRowSelectionOnClick" | "isRowSelectable" | "checkboxSelectionVisibleOnly" | "pagination" | "paginationMode" | "classes" | "keepNonExistentRowsSelected" | "rowSelection" | "rowSelectionPropagation" | "signature">) => void;
12
+ export declare const useGridRowSelection: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, "checkboxSelection" | "rowSelectionModel" | "onRowSelectionModelChange" | "disableMultipleRowSelection" | "disableRowSelectionOnClick" | "isRowSelectable" | "checkboxSelectionVisibleOnly" | "pagination" | "paginationMode" | "filterMode" | "classes" | "keepNonExistentRowsSelected" | "rowSelection" | "rowSelectionPropagation" | "signature">) => void;
@@ -4,7 +4,7 @@ import { GridSignature, useGridApiEventHandler } from "../../utils/useGridApiEve
4
4
  import { useGridApiMethod } from "../../utils/useGridApiMethod.js";
5
5
  import { useGridLogger } from "../../utils/useGridLogger.js";
6
6
  import { useGridSelector } from "../../utils/useGridSelector.js";
7
- import { gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.js";
7
+ import { gridRowsLookupSelector, gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.js";
8
8
  import { gridRowSelectionStateSelector, selectedGridRowsSelector, selectedIdsLookupSelector } from "./gridRowSelectionSelector.js";
9
9
  import { gridPaginatedVisibleSortedGridRowIdsSelector } from "../pagination/index.js";
10
10
  import { gridFocusCellSelector } from "../focus/gridFocusStateSelector.js";
@@ -171,30 +171,33 @@ export const useGridRowSelection = (apiRef, props) => {
171
171
  let newSelection;
172
172
  if (resetSelection) {
173
173
  if (isSelected) {
174
- newSelection = selectableIds;
174
+ newSelection = new Set(selectableIds);
175
175
  if (applyAutoSelection) {
176
176
  const addRow = rowId => {
177
- newSelection.push(rowId);
177
+ newSelection.add(rowId);
178
178
  };
179
179
  selectableIds.forEach(id => {
180
180
  findRowsToSelect(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow);
181
181
  });
182
182
  }
183
183
  } else {
184
- newSelection = [];
184
+ newSelection = new Set();
185
+ }
186
+ const currentLookup = selectedIdsLookupSelector(apiRef);
187
+ if (newSelection.size === Object.keys(currentLookup).length && Array.from(newSelection).every(id => currentLookup[id] === id)) {
188
+ return;
185
189
  }
186
190
  } else {
187
- // We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
188
- const selectionLookup = _extends({}, selectedIdsLookupSelector(apiRef));
191
+ newSelection = new Set(Object.values(selectedIdsLookupSelector(apiRef)));
189
192
  const addRow = rowId => {
190
- selectionLookup[rowId] = rowId;
193
+ newSelection.add(rowId);
191
194
  };
192
195
  const removeRow = rowId => {
193
- delete selectionLookup[rowId];
196
+ newSelection.delete(rowId);
194
197
  };
195
198
  selectableIds.forEach(id => {
196
199
  if (isSelected) {
197
- selectionLookup[id] = id;
200
+ newSelection.add(id);
198
201
  if (applyAutoSelection) {
199
202
  findRowsToSelect(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow);
200
203
  }
@@ -205,11 +208,10 @@ export const useGridRowSelection = (apiRef, props) => {
205
208
  }
206
209
  }
207
210
  });
208
- newSelection = Object.values(selectionLookup);
209
211
  }
210
- const isSelectionValid = newSelection.length < 2 || canHaveMultipleSelection;
212
+ const isSelectionValid = newSelection.size < 2 || canHaveMultipleSelection;
211
213
  if (isSelectionValid) {
212
- apiRef.current.setRowSelectionModel(newSelection);
214
+ apiRef.current.setRowSelectionModel(Array.from(newSelection));
213
215
  }
214
216
  }, [logger, applyAutoSelection, canHaveMultipleSelection, apiRef, tree, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents]);
215
217
  const selectRowRange = React.useCallback(({
@@ -252,13 +254,20 @@ export const useGridRowSelection = (apiRef, props) => {
252
254
  return;
253
255
  }
254
256
  const currentSelection = gridRowSelectionStateSelector(apiRef.current.state);
257
+ const rowsLookup = gridRowsLookupSelector(apiRef);
255
258
  const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
256
259
 
257
260
  // We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
258
261
  const selectionLookup = _extends({}, selectedIdsLookupSelector(apiRef));
262
+ const isNonExistent = id => {
263
+ if (props.filterMode === 'server') {
264
+ return !rowsLookup[id];
265
+ }
266
+ return filteredRowsLookup[id] !== true;
267
+ };
259
268
  let hasChanged = false;
260
269
  currentSelection.forEach(id => {
261
- if (filteredRowsLookup[id] !== true) {
270
+ if (isNonExistent(id)) {
262
271
  if (props.keepNonExistentRowsSelected) {
263
272
  return;
264
273
  }
@@ -284,15 +293,20 @@ export const useGridRowSelection = (apiRef, props) => {
284
293
  }
285
294
  }
286
295
  });
287
- if (hasChanged || isNestedData && !sortModelUpdated) {
296
+
297
+ // For nested data, on row tree updation (filtering, adding rows, etc.) when the selection is
298
+ // not empty, we need to re-run scanning of the tree to propagate the selection changes
299
+ // Example: A parent whose de-selected children are filtered out should now be selected
300
+ const shouldReapplyPropagation = isNestedData && props.rowSelectionPropagation?.parents && Object.keys(selectionLookup).length > 0;
301
+ if (hasChanged || shouldReapplyPropagation && !sortModelUpdated) {
288
302
  const newSelection = Object.values(selectionLookup);
289
- if (isNestedData) {
303
+ if (shouldReapplyPropagation) {
290
304
  apiRef.current.selectRows(newSelection, true, true);
291
305
  } else {
292
306
  apiRef.current.setRowSelectionModel(newSelection);
293
307
  }
294
308
  }
295
- }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, tree]);
309
+ }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, props.filterMode, tree]);
296
310
  const handleSingleRowSelection = React.useCallback((id, event) => {
297
311
  const hasCtrlKey = event.metaKey || event.ctrlKey;
298
312
 
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid v7.22.1
2
+ * @mui/x-data-grid v7.22.2
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -1,14 +1,12 @@
1
- import { GridFilterInputBoolean } from "../components/panel/filterPanel/GridFilterInputBoolean.js";
1
+ import { GridFilterInputBoolean, sanitizeFilterItemValue } from "../components/panel/filterPanel/GridFilterInputBoolean.js";
2
2
  export const getGridBooleanOperators = () => [{
3
3
  value: 'is',
4
4
  getApplyFilterFn: filterItem => {
5
- if (!filterItem.value) {
5
+ const sanitizedValue = sanitizeFilterItemValue(filterItem.value);
6
+ if (sanitizedValue === undefined) {
6
7
  return null;
7
8
  }
8
- const valueAsBoolean = String(filterItem.value) === 'true';
9
- return value => {
10
- return Boolean(value) === valueAsBoolean;
11
- };
9
+ return value => Boolean(value) === sanitizedValue;
12
10
  },
13
11
  InputComponent: GridFilterInputBoolean
14
12
  }];
@@ -7,6 +7,15 @@ import { refType, unstable_useId as useId } from '@mui/utils';
7
7
  import { styled } from '@mui/material/styles';
8
8
  import { useGridRootProps } from "../../../hooks/utils/useGridRootProps.js";
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ export const sanitizeFilterItemValue = value => {
11
+ if (String(value).toLowerCase() === 'true') {
12
+ return true;
13
+ }
14
+ if (String(value).toLowerCase() === 'false') {
15
+ return false;
16
+ }
17
+ return undefined;
18
+ };
10
19
  const BooleanOperatorContainer = styled('div')({
11
20
  display: 'flex',
12
21
  alignItems: 'center',
@@ -27,7 +36,7 @@ function GridFilterInputBoolean(props) {
27
36
  variant = 'standard'
28
37
  } = props,
29
38
  others = _objectWithoutPropertiesLoose(props, _excluded);
30
- const [filterValueState, setFilterValueState] = React.useState(item.value || '');
39
+ const [filterValueState, setFilterValueState] = React.useState(sanitizeFilterItemValue(item.value));
31
40
  const rootProps = useGridRootProps();
32
41
  const labelId = useId();
33
42
  const selectId = useId();
@@ -35,14 +44,14 @@ function GridFilterInputBoolean(props) {
35
44
  const isSelectNative = baseSelectProps.native ?? false;
36
45
  const baseSelectOptionProps = rootProps.slotProps?.baseSelectOption || {};
37
46
  const onFilterChange = React.useCallback(event => {
38
- const value = event.target.value;
47
+ const value = sanitizeFilterItemValue(event.target.value);
39
48
  setFilterValueState(value);
40
49
  applyValue(_extends({}, item, {
41
- value: Boolean(value)
50
+ value
42
51
  }));
43
52
  }, [applyValue, item]);
44
53
  React.useEffect(() => {
45
- setFilterValueState(item.value || '');
54
+ setFilterValueState(sanitizeFilterItemValue(item.value));
46
55
  }, [item.value]);
47
56
  const label = labelProp ?? apiRef.current.getLocaleText('filterPanelInputLabel');
48
57
  return /*#__PURE__*/_jsxs(BooleanOperatorContainer, {
@@ -57,7 +66,7 @@ function GridFilterInputBoolean(props) {
57
66
  labelId: labelId,
58
67
  id: selectId,
59
68
  label: label,
60
- value: filterValueState,
69
+ value: filterValueState === undefined ? '' : String(filterValueState),
61
70
  onChange: onFilterChange,
62
71
  variant: variant,
63
72
  notched: variant === 'outlined' ? true : undefined,
@@ -70,6 +70,9 @@ const GridVirtualScrollbar = /*#__PURE__*/React.forwardRef(function GridVirtualS
70
70
  const onScrollerScroll = useEventCallback(() => {
71
71
  const scroller = apiRef.current.virtualScrollerRef.current;
72
72
  const scrollbar = scrollbarRef.current;
73
+ if (!scrollbar) {
74
+ return;
75
+ }
73
76
  if (scroller[propertyScroll] === lastPosition.current) {
74
77
  return;
75
78
  }
@@ -85,6 +88,9 @@ const GridVirtualScrollbar = /*#__PURE__*/React.forwardRef(function GridVirtualS
85
88
  const onScrollbarScroll = useEventCallback(() => {
86
89
  const scroller = apiRef.current.virtualScrollerRef.current;
87
90
  const scrollbar = scrollbarRef.current;
91
+ if (!scrollbar) {
92
+ return;
93
+ }
88
94
  if (isLocked.current) {
89
95
  isLocked.current = false;
90
96
  return;
@@ -241,26 +241,45 @@ export const useGridCellEditing = (apiRef, props) => {
241
241
  mode: GridCellModes.Edit
242
242
  }, other));
243
243
  }, [throwIfNotEditable, throwIfNotInMode, updateFieldInCellModesModel]);
244
- const updateStateToStartCellEditMode = useEventCallback(params => {
244
+ const updateStateToStartCellEditMode = useEventCallback(async params => {
245
245
  const {
246
246
  id,
247
247
  field,
248
248
  deleteValue,
249
249
  initialValue
250
250
  } = params;
251
- let newValue = apiRef.current.getCellValue(id, field);
251
+ const value = apiRef.current.getCellValue(id, field);
252
+ let newValue = value;
252
253
  if (deleteValue) {
253
254
  newValue = getDefaultCellValue(apiRef.current.getColumn(field));
254
255
  } else if (initialValue) {
255
256
  newValue = initialValue;
256
257
  }
257
- const newProps = {
258
+ const column = apiRef.current.getColumn(field);
259
+ const shouldProcessEditCellProps = !!column.preProcessEditCellProps && deleteValue;
260
+ let newProps = {
258
261
  value: newValue,
259
262
  error: false,
260
- isProcessingProps: false
263
+ isProcessingProps: shouldProcessEditCellProps
261
264
  };
262
265
  updateOrDeleteFieldState(id, field, newProps);
263
266
  apiRef.current.setCellFocus(id, field);
267
+ if (shouldProcessEditCellProps) {
268
+ newProps = await Promise.resolve(column.preProcessEditCellProps({
269
+ id,
270
+ row: apiRef.current.getRow(id),
271
+ props: newProps,
272
+ hasChanged: newValue !== value
273
+ }));
274
+ // Check if still in edit mode before updating
275
+ if (apiRef.current.getCellMode(id, field) === GridCellModes.Edit) {
276
+ const editingState = gridEditRowsStateSelector(apiRef.current.state);
277
+ updateOrDeleteFieldState(id, field, _extends({}, newProps, {
278
+ value: editingState[id][field].value,
279
+ isProcessingProps: false
280
+ }));
281
+ }
282
+ }
264
283
  });
265
284
  const stopCellEditMode = React.useCallback(params => {
266
285
  const {
@@ -318,10 +318,11 @@ export const useGridRowEditing = (apiRef, props) => {
318
318
  if (!cellParams.isEditable) {
319
319
  return acc;
320
320
  }
321
+ const column = apiRef.current.getColumn(field);
321
322
  let newValue = apiRef.current.getCellValue(id, field);
322
323
  if (fieldToFocus === field && (deleteValue || initialValue)) {
323
324
  if (deleteValue) {
324
- newValue = getDefaultCellValue(apiRef.current.getColumn(field));
325
+ newValue = getDefaultCellValue(column);
325
326
  } else if (initialValue) {
326
327
  newValue = initialValue;
327
328
  }
@@ -329,7 +330,7 @@ export const useGridRowEditing = (apiRef, props) => {
329
330
  acc[field] = {
330
331
  value: newValue,
331
332
  error: false,
332
- isProcessingProps: false
333
+ isProcessingProps: !!column.preProcessEditCellProps && deleteValue
333
334
  };
334
335
  return acc;
335
336
  }, {});
@@ -337,6 +338,26 @@ export const useGridRowEditing = (apiRef, props) => {
337
338
  if (fieldToFocus) {
338
339
  apiRef.current.setCellFocus(id, fieldToFocus);
339
340
  }
341
+ columnFields.filter(field => !!apiRef.current.getColumn(field).preProcessEditCellProps && deleteValue).forEach(field => {
342
+ const column = apiRef.current.getColumn(field);
343
+ const value = apiRef.current.getCellValue(id, field);
344
+ const newValue = deleteValue ? getDefaultCellValue(column) : initialValue ?? value;
345
+ Promise.resolve(column.preProcessEditCellProps({
346
+ id,
347
+ row: apiRef.current.getRow(id),
348
+ props: newProps[field],
349
+ hasChanged: newValue !== value
350
+ })).then(processedProps => {
351
+ // Check if still in edit mode before updating
352
+ if (apiRef.current.getRowMode(id) === GridRowModes.Edit) {
353
+ const editingState = gridEditRowsStateSelector(apiRef.current.state);
354
+ updateOrDeleteFieldState(id, field, _extends({}, processedProps, {
355
+ value: editingState[id][field].value,
356
+ isProcessingProps: false
357
+ }));
358
+ }
359
+ });
360
+ });
340
361
  });
341
362
  const stopRowEditMode = React.useCallback(params => {
342
363
  const {
@@ -4,7 +4,7 @@ import { GridSignature, useGridApiEventHandler } from "../../utils/useGridApiEve
4
4
  import { useGridApiMethod } from "../../utils/useGridApiMethod.js";
5
5
  import { useGridLogger } from "../../utils/useGridLogger.js";
6
6
  import { useGridSelector } from "../../utils/useGridSelector.js";
7
- import { gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.js";
7
+ import { gridRowsLookupSelector, gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.js";
8
8
  import { gridRowSelectionStateSelector, selectedGridRowsSelector, selectedIdsLookupSelector } from "./gridRowSelectionSelector.js";
9
9
  import { gridPaginatedVisibleSortedGridRowIdsSelector } from "../pagination/index.js";
10
10
  import { gridFocusCellSelector } from "../focus/gridFocusStateSelector.js";
@@ -171,30 +171,33 @@ export const useGridRowSelection = (apiRef, props) => {
171
171
  let newSelection;
172
172
  if (resetSelection) {
173
173
  if (isSelected) {
174
- newSelection = selectableIds;
174
+ newSelection = new Set(selectableIds);
175
175
  if (applyAutoSelection) {
176
176
  const addRow = rowId => {
177
- newSelection.push(rowId);
177
+ newSelection.add(rowId);
178
178
  };
179
179
  selectableIds.forEach(id => {
180
180
  findRowsToSelect(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow);
181
181
  });
182
182
  }
183
183
  } else {
184
- newSelection = [];
184
+ newSelection = new Set();
185
+ }
186
+ const currentLookup = selectedIdsLookupSelector(apiRef);
187
+ if (newSelection.size === Object.keys(currentLookup).length && Array.from(newSelection).every(id => currentLookup[id] === id)) {
188
+ return;
185
189
  }
186
190
  } else {
187
- // We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
188
- const selectionLookup = _extends({}, selectedIdsLookupSelector(apiRef));
191
+ newSelection = new Set(Object.values(selectedIdsLookupSelector(apiRef)));
189
192
  const addRow = rowId => {
190
- selectionLookup[rowId] = rowId;
193
+ newSelection.add(rowId);
191
194
  };
192
195
  const removeRow = rowId => {
193
- delete selectionLookup[rowId];
196
+ newSelection.delete(rowId);
194
197
  };
195
198
  selectableIds.forEach(id => {
196
199
  if (isSelected) {
197
- selectionLookup[id] = id;
200
+ newSelection.add(id);
198
201
  if (applyAutoSelection) {
199
202
  findRowsToSelect(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow);
200
203
  }
@@ -205,11 +208,10 @@ export const useGridRowSelection = (apiRef, props) => {
205
208
  }
206
209
  }
207
210
  });
208
- newSelection = Object.values(selectionLookup);
209
211
  }
210
- const isSelectionValid = newSelection.length < 2 || canHaveMultipleSelection;
212
+ const isSelectionValid = newSelection.size < 2 || canHaveMultipleSelection;
211
213
  if (isSelectionValid) {
212
- apiRef.current.setRowSelectionModel(newSelection);
214
+ apiRef.current.setRowSelectionModel(Array.from(newSelection));
213
215
  }
214
216
  }, [logger, applyAutoSelection, canHaveMultipleSelection, apiRef, tree, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents]);
215
217
  const selectRowRange = React.useCallback(({
@@ -252,13 +254,20 @@ export const useGridRowSelection = (apiRef, props) => {
252
254
  return;
253
255
  }
254
256
  const currentSelection = gridRowSelectionStateSelector(apiRef.current.state);
257
+ const rowsLookup = gridRowsLookupSelector(apiRef);
255
258
  const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
256
259
 
257
260
  // We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
258
261
  const selectionLookup = _extends({}, selectedIdsLookupSelector(apiRef));
262
+ const isNonExistent = id => {
263
+ if (props.filterMode === 'server') {
264
+ return !rowsLookup[id];
265
+ }
266
+ return filteredRowsLookup[id] !== true;
267
+ };
259
268
  let hasChanged = false;
260
269
  currentSelection.forEach(id => {
261
- if (filteredRowsLookup[id] !== true) {
270
+ if (isNonExistent(id)) {
262
271
  if (props.keepNonExistentRowsSelected) {
263
272
  return;
264
273
  }
@@ -284,15 +293,20 @@ export const useGridRowSelection = (apiRef, props) => {
284
293
  }
285
294
  }
286
295
  });
287
- if (hasChanged || isNestedData && !sortModelUpdated) {
296
+
297
+ // For nested data, on row tree updation (filtering, adding rows, etc.) when the selection is
298
+ // not empty, we need to re-run scanning of the tree to propagate the selection changes
299
+ // Example: A parent whose de-selected children are filtered out should now be selected
300
+ const shouldReapplyPropagation = isNestedData && props.rowSelectionPropagation?.parents && Object.keys(selectionLookup).length > 0;
301
+ if (hasChanged || shouldReapplyPropagation && !sortModelUpdated) {
288
302
  const newSelection = Object.values(selectionLookup);
289
- if (isNestedData) {
303
+ if (shouldReapplyPropagation) {
290
304
  apiRef.current.selectRows(newSelection, true, true);
291
305
  } else {
292
306
  apiRef.current.setRowSelectionModel(newSelection);
293
307
  }
294
308
  }
295
- }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, tree]);
309
+ }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, props.filterMode, tree]);
296
310
  const handleSingleRowSelection = React.useCallback((id, event) => {
297
311
  const hasCtrlKey = event.metaKey || event.ctrlKey;
298
312
 
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid v7.22.1
2
+ * @mui/x-data-grid v7.22.2
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -8,13 +8,11 @@ var _GridFilterInputBoolean = require("../components/panel/filterPanel/GridFilte
8
8
  const getGridBooleanOperators = () => [{
9
9
  value: 'is',
10
10
  getApplyFilterFn: filterItem => {
11
- if (!filterItem.value) {
11
+ const sanitizedValue = (0, _GridFilterInputBoolean.sanitizeFilterItemValue)(filterItem.value);
12
+ if (sanitizedValue === undefined) {
12
13
  return null;
13
14
  }
14
- const valueAsBoolean = String(filterItem.value) === 'true';
15
- return value => {
16
- return Boolean(value) === valueAsBoolean;
17
- };
15
+ return value => Boolean(value) === sanitizedValue;
18
16
  },
19
17
  InputComponent: _GridFilterInputBoolean.GridFilterInputBoolean
20
18
  }];
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.GridFilterInputBoolean = GridFilterInputBoolean;
9
+ exports.sanitizeFilterItemValue = void 0;
9
10
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
10
11
  var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
11
12
  var React = _interopRequireWildcard(require("react"));
@@ -15,6 +16,16 @@ var _styles = require("@mui/material/styles");
15
16
  var _useGridRootProps = require("../../../hooks/utils/useGridRootProps");
16
17
  var _jsxRuntime = require("react/jsx-runtime");
17
18
  const _excluded = ["item", "applyValue", "apiRef", "focusElementRef", "isFilterActive", "clearButton", "tabIndex", "label", "variant", "InputLabelProps"];
19
+ const sanitizeFilterItemValue = value => {
20
+ if (String(value).toLowerCase() === 'true') {
21
+ return true;
22
+ }
23
+ if (String(value).toLowerCase() === 'false') {
24
+ return false;
25
+ }
26
+ return undefined;
27
+ };
28
+ exports.sanitizeFilterItemValue = sanitizeFilterItemValue;
18
29
  const BooleanOperatorContainer = (0, _styles.styled)('div')({
19
30
  display: 'flex',
20
31
  alignItems: 'center',
@@ -35,7 +46,7 @@ function GridFilterInputBoolean(props) {
35
46
  variant = 'standard'
36
47
  } = props,
37
48
  others = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded);
38
- const [filterValueState, setFilterValueState] = React.useState(item.value || '');
49
+ const [filterValueState, setFilterValueState] = React.useState(sanitizeFilterItemValue(item.value));
39
50
  const rootProps = (0, _useGridRootProps.useGridRootProps)();
40
51
  const labelId = (0, _utils.unstable_useId)();
41
52
  const selectId = (0, _utils.unstable_useId)();
@@ -43,14 +54,14 @@ function GridFilterInputBoolean(props) {
43
54
  const isSelectNative = baseSelectProps.native ?? false;
44
55
  const baseSelectOptionProps = rootProps.slotProps?.baseSelectOption || {};
45
56
  const onFilterChange = React.useCallback(event => {
46
- const value = event.target.value;
57
+ const value = sanitizeFilterItemValue(event.target.value);
47
58
  setFilterValueState(value);
48
59
  applyValue((0, _extends2.default)({}, item, {
49
- value: Boolean(value)
60
+ value
50
61
  }));
51
62
  }, [applyValue, item]);
52
63
  React.useEffect(() => {
53
- setFilterValueState(item.value || '');
64
+ setFilterValueState(sanitizeFilterItemValue(item.value));
54
65
  }, [item.value]);
55
66
  const label = labelProp ?? apiRef.current.getLocaleText('filterPanelInputLabel');
56
67
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(BooleanOperatorContainer, {
@@ -65,7 +76,7 @@ function GridFilterInputBoolean(props) {
65
76
  labelId: labelId,
66
77
  id: selectId,
67
78
  label: label,
68
- value: filterValueState,
79
+ value: filterValueState === undefined ? '' : String(filterValueState),
69
80
  onChange: onFilterChange,
70
81
  variant: variant,
71
82
  notched: variant === 'outlined' ? true : undefined,
@@ -77,6 +77,9 @@ const GridVirtualScrollbar = exports.GridVirtualScrollbar = /*#__PURE__*/React.f
77
77
  const onScrollerScroll = (0, _utils.unstable_useEventCallback)(() => {
78
78
  const scroller = apiRef.current.virtualScrollerRef.current;
79
79
  const scrollbar = scrollbarRef.current;
80
+ if (!scrollbar) {
81
+ return;
82
+ }
80
83
  if (scroller[propertyScroll] === lastPosition.current) {
81
84
  return;
82
85
  }
@@ -92,6 +95,9 @@ const GridVirtualScrollbar = exports.GridVirtualScrollbar = /*#__PURE__*/React.f
92
95
  const onScrollbarScroll = (0, _utils.unstable_useEventCallback)(() => {
93
96
  const scroller = apiRef.current.virtualScrollerRef.current;
94
97
  const scrollbar = scrollbarRef.current;
98
+ if (!scrollbar) {
99
+ return;
100
+ }
95
101
  if (isLocked.current) {
96
102
  isLocked.current = false;
97
103
  return;
@@ -249,26 +249,45 @@ const useGridCellEditing = (apiRef, props) => {
249
249
  mode: _gridEditRowModel.GridCellModes.Edit
250
250
  }, other));
251
251
  }, [throwIfNotEditable, throwIfNotInMode, updateFieldInCellModesModel]);
252
- const updateStateToStartCellEditMode = (0, _utils.unstable_useEventCallback)(params => {
252
+ const updateStateToStartCellEditMode = (0, _utils.unstable_useEventCallback)(async params => {
253
253
  const {
254
254
  id,
255
255
  field,
256
256
  deleteValue,
257
257
  initialValue
258
258
  } = params;
259
- let newValue = apiRef.current.getCellValue(id, field);
259
+ const value = apiRef.current.getCellValue(id, field);
260
+ let newValue = value;
260
261
  if (deleteValue) {
261
262
  newValue = (0, _utils3.getDefaultCellValue)(apiRef.current.getColumn(field));
262
263
  } else if (initialValue) {
263
264
  newValue = initialValue;
264
265
  }
265
- const newProps = {
266
+ const column = apiRef.current.getColumn(field);
267
+ const shouldProcessEditCellProps = !!column.preProcessEditCellProps && deleteValue;
268
+ let newProps = {
266
269
  value: newValue,
267
270
  error: false,
268
- isProcessingProps: false
271
+ isProcessingProps: shouldProcessEditCellProps
269
272
  };
270
273
  updateOrDeleteFieldState(id, field, newProps);
271
274
  apiRef.current.setCellFocus(id, field);
275
+ if (shouldProcessEditCellProps) {
276
+ newProps = await Promise.resolve(column.preProcessEditCellProps({
277
+ id,
278
+ row: apiRef.current.getRow(id),
279
+ props: newProps,
280
+ hasChanged: newValue !== value
281
+ }));
282
+ // Check if still in edit mode before updating
283
+ if (apiRef.current.getCellMode(id, field) === _gridEditRowModel.GridCellModes.Edit) {
284
+ const editingState = (0, _gridEditingSelectors.gridEditRowsStateSelector)(apiRef.current.state);
285
+ updateOrDeleteFieldState(id, field, (0, _extends2.default)({}, newProps, {
286
+ value: editingState[id][field].value,
287
+ isProcessingProps: false
288
+ }));
289
+ }
290
+ }
272
291
  });
273
292
  const stopCellEditMode = React.useCallback(params => {
274
293
  const {
@@ -326,10 +326,11 @@ const useGridRowEditing = (apiRef, props) => {
326
326
  if (!cellParams.isEditable) {
327
327
  return acc;
328
328
  }
329
+ const column = apiRef.current.getColumn(field);
329
330
  let newValue = apiRef.current.getCellValue(id, field);
330
331
  if (fieldToFocus === field && (deleteValue || initialValue)) {
331
332
  if (deleteValue) {
332
- newValue = (0, _utils3.getDefaultCellValue)(apiRef.current.getColumn(field));
333
+ newValue = (0, _utils3.getDefaultCellValue)(column);
333
334
  } else if (initialValue) {
334
335
  newValue = initialValue;
335
336
  }
@@ -337,7 +338,7 @@ const useGridRowEditing = (apiRef, props) => {
337
338
  acc[field] = {
338
339
  value: newValue,
339
340
  error: false,
340
- isProcessingProps: false
341
+ isProcessingProps: !!column.preProcessEditCellProps && deleteValue
341
342
  };
342
343
  return acc;
343
344
  }, {});
@@ -345,6 +346,26 @@ const useGridRowEditing = (apiRef, props) => {
345
346
  if (fieldToFocus) {
346
347
  apiRef.current.setCellFocus(id, fieldToFocus);
347
348
  }
349
+ columnFields.filter(field => !!apiRef.current.getColumn(field).preProcessEditCellProps && deleteValue).forEach(field => {
350
+ const column = apiRef.current.getColumn(field);
351
+ const value = apiRef.current.getCellValue(id, field);
352
+ const newValue = deleteValue ? (0, _utils3.getDefaultCellValue)(column) : initialValue ?? value;
353
+ Promise.resolve(column.preProcessEditCellProps({
354
+ id,
355
+ row: apiRef.current.getRow(id),
356
+ props: newProps[field],
357
+ hasChanged: newValue !== value
358
+ })).then(processedProps => {
359
+ // Check if still in edit mode before updating
360
+ if (apiRef.current.getRowMode(id) === _gridEditRowModel.GridRowModes.Edit) {
361
+ const editingState = (0, _gridEditingSelectors.gridEditRowsStateSelector)(apiRef.current.state);
362
+ updateOrDeleteFieldState(id, field, (0, _extends2.default)({}, processedProps, {
363
+ value: editingState[id][field].value,
364
+ isProcessingProps: false
365
+ }));
366
+ }
367
+ });
368
+ });
348
369
  });
349
370
  const stopRowEditMode = React.useCallback(params => {
350
371
  const {
@@ -180,30 +180,33 @@ const useGridRowSelection = (apiRef, props) => {
180
180
  let newSelection;
181
181
  if (resetSelection) {
182
182
  if (isSelected) {
183
- newSelection = selectableIds;
183
+ newSelection = new Set(selectableIds);
184
184
  if (applyAutoSelection) {
185
185
  const addRow = rowId => {
186
- newSelection.push(rowId);
186
+ newSelection.add(rowId);
187
187
  };
188
188
  selectableIds.forEach(id => {
189
189
  (0, _utils.findRowsToSelect)(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow);
190
190
  });
191
191
  }
192
192
  } else {
193
- newSelection = [];
193
+ newSelection = new Set();
194
+ }
195
+ const currentLookup = (0, _gridRowSelectionSelector.selectedIdsLookupSelector)(apiRef);
196
+ if (newSelection.size === Object.keys(currentLookup).length && Array.from(newSelection).every(id => currentLookup[id] === id)) {
197
+ return;
194
198
  }
195
199
  } else {
196
- // We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
197
- const selectionLookup = (0, _extends2.default)({}, (0, _gridRowSelectionSelector.selectedIdsLookupSelector)(apiRef));
200
+ newSelection = new Set(Object.values((0, _gridRowSelectionSelector.selectedIdsLookupSelector)(apiRef)));
198
201
  const addRow = rowId => {
199
- selectionLookup[rowId] = rowId;
202
+ newSelection.add(rowId);
200
203
  };
201
204
  const removeRow = rowId => {
202
- delete selectionLookup[rowId];
205
+ newSelection.delete(rowId);
203
206
  };
204
207
  selectableIds.forEach(id => {
205
208
  if (isSelected) {
206
- selectionLookup[id] = id;
209
+ newSelection.add(id);
207
210
  if (applyAutoSelection) {
208
211
  (0, _utils.findRowsToSelect)(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow);
209
212
  }
@@ -214,11 +217,10 @@ const useGridRowSelection = (apiRef, props) => {
214
217
  }
215
218
  }
216
219
  });
217
- newSelection = Object.values(selectionLookup);
218
220
  }
219
- const isSelectionValid = newSelection.length < 2 || canHaveMultipleSelection;
221
+ const isSelectionValid = newSelection.size < 2 || canHaveMultipleSelection;
220
222
  if (isSelectionValid) {
221
- apiRef.current.setRowSelectionModel(newSelection);
223
+ apiRef.current.setRowSelectionModel(Array.from(newSelection));
222
224
  }
223
225
  }, [logger, applyAutoSelection, canHaveMultipleSelection, apiRef, tree, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents]);
224
226
  const selectRowRange = React.useCallback(({
@@ -261,13 +263,20 @@ const useGridRowSelection = (apiRef, props) => {
261
263
  return;
262
264
  }
263
265
  const currentSelection = (0, _gridRowSelectionSelector.gridRowSelectionStateSelector)(apiRef.current.state);
266
+ const rowsLookup = (0, _gridRowsSelector.gridRowsLookupSelector)(apiRef);
264
267
  const filteredRowsLookup = (0, _gridFilterSelector.gridFilteredRowsLookupSelector)(apiRef);
265
268
 
266
269
  // We clone the existing object to avoid mutating the same object returned by the selector to others part of the project
267
270
  const selectionLookup = (0, _extends2.default)({}, (0, _gridRowSelectionSelector.selectedIdsLookupSelector)(apiRef));
271
+ const isNonExistent = id => {
272
+ if (props.filterMode === 'server') {
273
+ return !rowsLookup[id];
274
+ }
275
+ return filteredRowsLookup[id] !== true;
276
+ };
268
277
  let hasChanged = false;
269
278
  currentSelection.forEach(id => {
270
- if (filteredRowsLookup[id] !== true) {
279
+ if (isNonExistent(id)) {
271
280
  if (props.keepNonExistentRowsSelected) {
272
281
  return;
273
282
  }
@@ -293,15 +302,20 @@ const useGridRowSelection = (apiRef, props) => {
293
302
  }
294
303
  }
295
304
  });
296
- if (hasChanged || isNestedData && !sortModelUpdated) {
305
+
306
+ // For nested data, on row tree updation (filtering, adding rows, etc.) when the selection is
307
+ // not empty, we need to re-run scanning of the tree to propagate the selection changes
308
+ // Example: A parent whose de-selected children are filtered out should now be selected
309
+ const shouldReapplyPropagation = isNestedData && props.rowSelectionPropagation?.parents && Object.keys(selectionLookup).length > 0;
310
+ if (hasChanged || shouldReapplyPropagation && !sortModelUpdated) {
297
311
  const newSelection = Object.values(selectionLookup);
298
- if (isNestedData) {
312
+ if (shouldReapplyPropagation) {
299
313
  apiRef.current.selectRows(newSelection, true, true);
300
314
  } else {
301
315
  apiRef.current.setRowSelectionModel(newSelection);
302
316
  }
303
317
  }
304
- }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, tree]);
318
+ }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, props.filterMode, tree]);
305
319
  const handleSingleRowSelection = React.useCallback((id, event) => {
306
320
  const hasCtrlKey = event.metaKey || event.ctrlKey;
307
321
 
package/node/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid v7.22.1
2
+ * @mui/x-data-grid v7.22.2
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-data-grid",
3
- "version": "7.22.1",
3
+ "version": "7.22.2",
4
4
  "description": "The Community plan edition of the Data Grid components (MUI X).",
5
5
  "author": "MUI Team",
6
6
  "main": "./node/index.js",