@mui/x-data-grid 9.10.0 → 9.11.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +224 -0
  2. package/README.md +1 -11
  3. package/components/cell/GridEditInputCell.js +1 -1
  4. package/components/cell/GridEditInputCell.mjs +1 -1
  5. package/components/cell/GridEditLongTextCell.js +1 -1
  6. package/components/cell/GridEditLongTextCell.mjs +1 -1
  7. package/components/toolbarV8/Toolbar.js +2 -125
  8. package/components/toolbarV8/Toolbar.mjs +2 -125
  9. package/components/toolbarV8/ToolbarButton.js +11 -50
  10. package/components/toolbarV8/ToolbarButton.mjs +11 -50
  11. package/hooks/features/columnResize/useGridColumnResize.js +3 -0
  12. package/hooks/features/columnResize/useGridColumnResize.mjs +3 -0
  13. package/hooks/features/columns/useGridColumns.js +5 -1
  14. package/hooks/features/columns/useGridColumns.mjs +5 -1
  15. package/hooks/features/dataSource/useGridDataSourceBase.js +33 -4
  16. package/hooks/features/dataSource/useGridDataSourceBase.mjs +33 -4
  17. package/hooks/features/editing/useGridCellEditing.js +9 -3
  18. package/hooks/features/editing/useGridCellEditing.mjs +9 -3
  19. package/hooks/features/editing/useGridRowEditing.js +7 -5
  20. package/hooks/features/editing/useGridRowEditing.mjs +7 -5
  21. package/hooks/features/filter/useGridFilter.js +1 -1
  22. package/hooks/features/filter/useGridFilter.mjs +1 -1
  23. package/hooks/features/rows/gridRowsUtils.js +17 -1
  24. package/hooks/features/rows/gridRowsUtils.mjs +17 -1
  25. package/hooks/features/rows/useGridParamsApi.d.mts +1 -1
  26. package/hooks/features/rows/useGridParamsApi.d.ts +1 -1
  27. package/hooks/features/rows/useGridParamsApi.js +1 -0
  28. package/hooks/features/rows/useGridParamsApi.mjs +1 -0
  29. package/hooks/features/sorting/useGridSorting.js +3 -0
  30. package/hooks/features/sorting/useGridSorting.mjs +3 -0
  31. package/index.js +1 -1
  32. package/index.mjs +1 -1
  33. package/models/api/gridColumnApi.d.mts +2 -2
  34. package/models/api/gridColumnApi.d.ts +2 -2
  35. package/package.json +4 -4
  36. package/components/toolbarV8/ToolbarContext.d.mts +0 -11
  37. package/components/toolbarV8/ToolbarContext.d.ts +0 -11
  38. package/components/toolbarV8/ToolbarContext.js +0 -21
  39. package/components/toolbarV8/ToolbarContext.mjs +0 -13
  40. package/components/toolbarV8/utils.d.mts +0 -5
  41. package/components/toolbarV8/utils.d.ts +0 -5
  42. package/components/toolbarV8/utils.js +0 -23
  43. package/components/toolbarV8/utils.mjs +0 -17
@@ -2,15 +2,15 @@
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
5
- const _excluded = ["render", "onKeyDown", "onFocus", "disabled", "aria-disabled"];
5
+ const _excluded = ["render", "onKeyDown", "onFocus", "onBlur", "disabled", "aria-disabled"],
6
+ _excluded2 = ["tabIndex"];
6
7
  import * as React from 'react';
7
8
  import PropTypes from 'prop-types';
8
9
  import useForkRef from '@mui/utils/useForkRef';
9
- import useId from '@mui/utils/useId';
10
10
  import { forwardRef } from '@mui/x-internals/forwardRef';
11
11
  import { useComponentRenderer } from '@mui/x-internals/useComponentRenderer';
12
+ import { useRegisterToolbarButton } from '@mui/x-internals/ToolbarContext';
12
13
  import { useGridRootProps } from "../../hooks/utils/useGridRootProps.mjs";
13
- import { useToolbarContext } from "./ToolbarContext.mjs";
14
14
  import { jsx as _jsx } from "react/jsx-runtime";
15
15
  /**
16
16
  * A button for performing actions from the toolbar.
@@ -26,59 +26,20 @@ import { jsx as _jsx } from "react/jsx-runtime";
26
26
  */
27
27
  const ToolbarButton = forwardRef(function ToolbarButton(props, ref) {
28
28
  const {
29
- render,
30
- onKeyDown,
31
- onFocus,
32
- disabled,
33
- 'aria-disabled': ariaDisabled
29
+ render
34
30
  } = props,
35
31
  other = _objectWithoutPropertiesLoose(props, _excluded);
36
- const id = useId();
37
32
  const rootProps = useGridRootProps();
38
33
  const buttonRef = React.useRef(null);
39
34
  const handleRef = useForkRef(buttonRef, ref);
40
- const {
41
- focusableItemId,
42
- registerItem,
43
- unregisterItem,
44
- onItemKeyDown,
45
- onItemFocus,
46
- onItemDisabled
47
- } = useToolbarContext();
48
- const handleKeyDown = event => {
49
- onItemKeyDown(event);
50
- onKeyDown?.(event);
51
- };
52
- const handleFocus = event => {
53
- onItemFocus(id);
54
- onFocus?.(event);
55
- };
56
- React.useEffect(() => {
57
- registerItem(id, buttonRef);
58
- return () => unregisterItem(id);
59
- // eslint-disable-next-line react-hooks/exhaustive-deps
60
- }, []);
61
- const previousDisabled = React.useRef(disabled);
62
- React.useEffect(() => {
63
- if (previousDisabled.current !== disabled && disabled === true) {
64
- onItemDisabled(id, disabled);
65
- }
66
- previousDisabled.current = disabled;
67
- }, [disabled, id, onItemDisabled]);
68
- const previousAriaDisabled = React.useRef(ariaDisabled);
69
- React.useEffect(() => {
70
- if (previousAriaDisabled.current !== ariaDisabled && ariaDisabled === true) {
71
- onItemDisabled(id, true);
72
- }
73
- previousAriaDisabled.current = ariaDisabled;
74
- }, [ariaDisabled, id, onItemDisabled]);
35
+ const _useRegisterToolbarBu = useRegisterToolbarButton(props, buttonRef),
36
+ {
37
+ tabIndex
38
+ } = _useRegisterToolbarBu,
39
+ toolbarButtonProps = _objectWithoutPropertiesLoose(_useRegisterToolbarBu, _excluded2);
75
40
  const element = useComponentRenderer(rootProps.slots.baseIconButton, render, _extends({}, rootProps.slotProps?.baseIconButton, {
76
- tabIndex: focusableItemId === id ? 0 : -1
77
- }, other, {
78
- disabled,
79
- 'aria-disabled': ariaDisabled,
80
- onKeyDown: handleKeyDown,
81
- onFocus: handleFocus,
41
+ tabIndex
42
+ }, other, toolbarButtonProps, {
82
43
  ref: handleRef
83
44
  }));
84
45
  return /*#__PURE__*/_jsx(React.Fragment, {
@@ -450,6 +450,9 @@ const useGridColumnResize = (apiRef, props) => {
450
450
  const columnHeaderElement = (0, _domUtils.findParentElementFromClassName)(event.target, _gridClasses.gridClasses.columnHeader);
451
451
  const field = (0, _domUtils.getFieldFromHeaderElem)(columnHeaderElement);
452
452
  const colDef = apiRef.current.getColumn(field);
453
+ if (!colDef) {
454
+ return;
455
+ }
453
456
  logger.debug(`Start Resize on col ${colDef.field}`);
454
457
  apiRef.current.publishEvent('columnResizeStart', {
455
458
  field
@@ -442,6 +442,9 @@ export const useGridColumnResize = (apiRef, props) => {
442
442
  const columnHeaderElement = findParentElementFromClassName(event.target, gridClasses.columnHeader);
443
443
  const field = getFieldFromHeaderElem(columnHeaderElement);
444
444
  const colDef = apiRef.current.getColumn(field);
445
+ if (!colDef) {
446
+ return;
447
+ }
445
448
  logger.debug(`Start Resize on col ${colDef.field}`);
446
449
  apiRef.current.publishEvent('columnResizeStart', {
447
450
  field
@@ -137,6 +137,10 @@ function useGridColumns(apiRef, props) {
137
137
  return allColumns.findIndex(col => col === field);
138
138
  }, [apiRef]);
139
139
  const setColumnIndex = React.useCallback((field, targetIndexPosition) => {
140
+ const column = apiRef.current.getColumn(field);
141
+ if (!column) {
142
+ return;
143
+ }
140
144
  const allColumns = (0, _gridColumnsSelector.gridColumnFieldsSelector)(apiRef);
141
145
  const oldIndexPosition = getColumnIndexRelativeToVisibleColumns(field);
142
146
  if (oldIndexPosition === targetIndexPosition) {
@@ -150,7 +154,7 @@ function useGridColumns(apiRef, props) {
150
154
  orderedFields: updatedColumns
151
155
  }));
152
156
  const params = {
153
- column: apiRef.current.getColumn(field),
157
+ column,
154
158
  targetIndex: apiRef.current.getColumnIndexRelativeToVisibleColumns(field),
155
159
  oldIndex: oldIndexPosition
156
160
  };
@@ -128,6 +128,10 @@ export function useGridColumns(apiRef, props) {
128
128
  return allColumns.findIndex(col => col === field);
129
129
  }, [apiRef]);
130
130
  const setColumnIndex = React.useCallback((field, targetIndexPosition) => {
131
+ const column = apiRef.current.getColumn(field);
132
+ if (!column) {
133
+ return;
134
+ }
131
135
  const allColumns = gridColumnFieldsSelector(apiRef);
132
136
  const oldIndexPosition = getColumnIndexRelativeToVisibleColumns(field);
133
137
  if (oldIndexPosition === targetIndexPosition) {
@@ -141,7 +145,7 @@ export function useGridColumns(apiRef, props) {
141
145
  orderedFields: updatedColumns
142
146
  }));
143
147
  const params = {
144
- column: apiRef.current.getColumn(field),
148
+ column,
145
149
  targetIndex: apiRef.current.getColumnIndexRelativeToVisibleColumns(field),
146
150
  oldIndex: oldIndexPosition
147
151
  };
@@ -47,6 +47,11 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
47
47
  }, [currentStrategy]);
48
48
  const paginationModel = (0, _useGridSelector.useGridSelector)(apiRef, _gridPaginationSelector.gridPaginationModelSelector);
49
49
  const lastRequestId = React.useRef(0);
50
+ // `false` while a request is in flight, and for any request that errors or is discarded as
51
+ // stale. Lets the mount effect tell "rows are already displayed" from "the fetch never landed".
52
+ const rowsAreUpToDate = React.useRef(false);
53
+ // Requests that are still running and will apply their response when they settle.
54
+ const pendingRequestCount = React.useRef(0);
50
55
  const pollingIntervalRef = React.useRef(null);
51
56
  const onDataSourceErrorProp = props.onDataSourceError;
52
57
  const revalidateMs = props.dataSourceRevalidateMs;
@@ -91,6 +96,7 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
91
96
  keepChildrenExpanded
92
97
  }
93
98
  });
99
+ rowsAreUpToDate.current = true;
94
100
  if (standardRowsUpdateStrategyActive) {
95
101
  apiRef.current.setLoading(false);
96
102
  }
@@ -103,6 +109,8 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
103
109
  }
104
110
  const requestId = lastRequestId.current + 1;
105
111
  lastRequestId.current = requestId;
112
+ rowsAreUpToDate.current = false;
113
+ pendingRequestCount.current += 1;
106
114
  try {
107
115
  const getRowsResponse = await getRows(fetchParams);
108
116
  const cacheResponses = cacheChunkManager.splitResponse(fetchParams, getRowsResponse);
@@ -116,6 +124,7 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
116
124
  keepChildrenExpanded
117
125
  }
118
126
  });
127
+ rowsAreUpToDate.current = true;
119
128
  }
120
129
  } catch (originalError) {
121
130
  if (lastRequestId.current === requestId) {
@@ -138,6 +147,7 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
138
147
  }
139
148
  }
140
149
  } finally {
150
+ pendingRequestCount.current -= 1;
141
151
  if (standardRowsUpdateStrategyActive && lastRequestId.current === requestId) {
142
152
  apiRef.current.setLoading(false);
143
153
  }
@@ -313,12 +323,34 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
313
323
  }
314
324
  }, [revalidateMs, stopPolling]);
315
325
  React.useEffect(() => stopPolling, [stopPolling]);
326
+ const lastApiRef = React.useRef(apiRef);
327
+ const lastStrategy = React.useRef(currentStrategy);
328
+ const lastDataSource = React.useRef(props.dataSource);
316
329
  React.useEffect(() => {
330
+ // `<Activity mode="hidden">` leaves the root element in the document, a real unmount does not.
331
+ // Only discard the in-flight response in the latter case, so hiding lets the request land.
332
+ const rootElement = apiRef.current?.rootElementRef?.current ?? null;
333
+ const ignoreInFlightRequest = () => {
334
+ if (!rootElement?.isConnected) {
335
+ lastRequestId.current += 1;
336
+ }
337
+ };
338
+
317
339
  // Return early if the proper strategy isn't set yet
318
340
  // Context: https://github.com/mui/mui-x/issues/19650
319
341
  if (currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.Default && currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.LazyLoading && currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.GroupedData && currentStrategy !== _utils2.DataSourceRowsUpdateStrategy.LazyLoadedGroupedData) {
320
342
  return undefined;
321
343
  }
344
+ const dependenciesChanged = lastApiRef.current !== apiRef || lastStrategy.current !== currentStrategy || lastDataSource.current !== props.dataSource;
345
+ lastApiRef.current = apiRef;
346
+ lastStrategy.current = currentStrategy;
347
+ lastDataSource.current = props.dataSource;
348
+
349
+ // Re-mounting the effect (`<Activity />` becoming visible again, for instance) must not
350
+ // re-fetch data that is already displayed.
351
+ if (!dependenciesChanged && (rowsAreUpToDate.current || pendingRequestCount.current > 0)) {
352
+ return ignoreInFlightRequest;
353
+ }
322
354
  if (props.dataSource) {
323
355
  stopPolling();
324
356
  // `dataSourceKeepPreviousData` only applies to the flat `Default` strategy (mirroring
@@ -332,10 +364,7 @@ const useGridDataSourceBase = (apiRef, props, options = {}) => {
332
364
  apiRef.current.dataSource.cache.clear();
333
365
  apiRef.current.dataSource.fetchRows();
334
366
  }
335
- return () => {
336
- // ignore the current request on unmount
337
- lastRequestId.current += 1;
338
- };
367
+ return ignoreInFlightRequest;
339
368
  }, [apiRef, props.dataSource, props.dataSourceKeepPreviousData, currentStrategy, stopPolling]);
340
369
  React.useEffect(() => {
341
370
  // `dataSourceKeepPreviousData` is a no-op for tree data and row grouping: those
@@ -40,6 +40,11 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
40
40
  }, [currentStrategy]);
41
41
  const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector);
42
42
  const lastRequestId = React.useRef(0);
43
+ // `false` while a request is in flight, and for any request that errors or is discarded as
44
+ // stale. Lets the mount effect tell "rows are already displayed" from "the fetch never landed".
45
+ const rowsAreUpToDate = React.useRef(false);
46
+ // Requests that are still running and will apply their response when they settle.
47
+ const pendingRequestCount = React.useRef(0);
43
48
  const pollingIntervalRef = React.useRef(null);
44
49
  const onDataSourceErrorProp = props.onDataSourceError;
45
50
  const revalidateMs = props.dataSourceRevalidateMs;
@@ -84,6 +89,7 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
84
89
  keepChildrenExpanded
85
90
  }
86
91
  });
92
+ rowsAreUpToDate.current = true;
87
93
  if (standardRowsUpdateStrategyActive) {
88
94
  apiRef.current.setLoading(false);
89
95
  }
@@ -96,6 +102,8 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
96
102
  }
97
103
  const requestId = lastRequestId.current + 1;
98
104
  lastRequestId.current = requestId;
105
+ rowsAreUpToDate.current = false;
106
+ pendingRequestCount.current += 1;
99
107
  try {
100
108
  const getRowsResponse = await getRows(fetchParams);
101
109
  const cacheResponses = cacheChunkManager.splitResponse(fetchParams, getRowsResponse);
@@ -109,6 +117,7 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
109
117
  keepChildrenExpanded
110
118
  }
111
119
  });
120
+ rowsAreUpToDate.current = true;
112
121
  }
113
122
  } catch (originalError) {
114
123
  if (lastRequestId.current === requestId) {
@@ -131,6 +140,7 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
131
140
  }
132
141
  }
133
142
  } finally {
143
+ pendingRequestCount.current -= 1;
134
144
  if (standardRowsUpdateStrategyActive && lastRequestId.current === requestId) {
135
145
  apiRef.current.setLoading(false);
136
146
  }
@@ -306,12 +316,34 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
306
316
  }
307
317
  }, [revalidateMs, stopPolling]);
308
318
  React.useEffect(() => stopPolling, [stopPolling]);
319
+ const lastApiRef = React.useRef(apiRef);
320
+ const lastStrategy = React.useRef(currentStrategy);
321
+ const lastDataSource = React.useRef(props.dataSource);
309
322
  React.useEffect(() => {
323
+ // `<Activity mode="hidden">` leaves the root element in the document, a real unmount does not.
324
+ // Only discard the in-flight response in the latter case, so hiding lets the request land.
325
+ const rootElement = apiRef.current?.rootElementRef?.current ?? null;
326
+ const ignoreInFlightRequest = () => {
327
+ if (!rootElement?.isConnected) {
328
+ lastRequestId.current += 1;
329
+ }
330
+ };
331
+
310
332
  // Return early if the proper strategy isn't set yet
311
333
  // Context: https://github.com/mui/mui-x/issues/19650
312
334
  if (currentStrategy !== DataSourceRowsUpdateStrategy.Default && currentStrategy !== DataSourceRowsUpdateStrategy.LazyLoading && currentStrategy !== DataSourceRowsUpdateStrategy.GroupedData && currentStrategy !== DataSourceRowsUpdateStrategy.LazyLoadedGroupedData) {
313
335
  return undefined;
314
336
  }
337
+ const dependenciesChanged = lastApiRef.current !== apiRef || lastStrategy.current !== currentStrategy || lastDataSource.current !== props.dataSource;
338
+ lastApiRef.current = apiRef;
339
+ lastStrategy.current = currentStrategy;
340
+ lastDataSource.current = props.dataSource;
341
+
342
+ // Re-mounting the effect (`<Activity />` becoming visible again, for instance) must not
343
+ // re-fetch data that is already displayed.
344
+ if (!dependenciesChanged && (rowsAreUpToDate.current || pendingRequestCount.current > 0)) {
345
+ return ignoreInFlightRequest;
346
+ }
315
347
  if (props.dataSource) {
316
348
  stopPolling();
317
349
  // `dataSourceKeepPreviousData` only applies to the flat `Default` strategy (mirroring
@@ -325,10 +357,7 @@ export const useGridDataSourceBase = (apiRef, props, options = {}) => {
325
357
  apiRef.current.dataSource.cache.clear();
326
358
  apiRef.current.dataSource.fetchRows();
327
359
  }
328
- return () => {
329
- // ignore the current request on unmount
330
- lastRequestId.current += 1;
331
- };
360
+ return ignoreInFlightRequest;
332
361
  }, [apiRef, props.dataSource, props.dataSourceKeepPreviousData, currentStrategy, stopPolling]);
333
362
  React.useEffect(() => {
334
363
  // `dataSourceKeepPreviousData` is a no-op for tree data and row grouping: those
@@ -260,14 +260,17 @@ const useGridCellEditing = (apiRef, props) => {
260
260
  deleteValue,
261
261
  initialValue
262
262
  } = params;
263
+ const column = apiRef.current.getColumn(field);
264
+ if (!column) {
265
+ return;
266
+ }
263
267
  const value = apiRef.current.getCellValue(id, field);
264
268
  let newValue = value;
265
269
  if (deleteValue) {
266
- newValue = (0, _utils2.getDefaultCellValue)(apiRef.current.getColumn(field));
270
+ newValue = (0, _utils2.getDefaultCellValue)(column);
267
271
  } else if (initialValue) {
268
272
  newValue = initialValue;
269
273
  }
270
- const column = apiRef.current.getColumn(field);
271
274
  const shouldProcessEditCellProps = !!column.preProcessEditCellProps && deleteValue;
272
275
  let newProps = {
273
276
  value: newValue,
@@ -410,6 +413,9 @@ const useGridCellEditing = (apiRef, props) => {
410
413
  throwIfNotEditable(id, field);
411
414
  throwIfNotInMode(id, field, _gridEditRowModel.GridCellModes.Edit);
412
415
  const column = apiRef.current.getColumn(field);
416
+ if (!column) {
417
+ return false;
418
+ }
413
419
  const row = apiRef.current.getRow(id);
414
420
  let parsedValue = value;
415
421
  if (column.valueParser && !skipValueParser) {
@@ -461,7 +467,7 @@ const useGridCellEditing = (apiRef, props) => {
461
467
  const {
462
468
  value
463
469
  } = editingState[id][field];
464
- return column.valueSetter ? column.valueSetter(value, row, column, apiRef) : (0, _extends2.default)({}, row, {
470
+ return column?.valueSetter ? column.valueSetter(value, row, column, apiRef) : (0, _extends2.default)({}, row, {
465
471
  [field]: value
466
472
  });
467
473
  }, [apiRef]);
@@ -253,14 +253,17 @@ export const useGridCellEditing = (apiRef, props) => {
253
253
  deleteValue,
254
254
  initialValue
255
255
  } = params;
256
+ const column = apiRef.current.getColumn(field);
257
+ if (!column) {
258
+ return;
259
+ }
256
260
  const value = apiRef.current.getCellValue(id, field);
257
261
  let newValue = value;
258
262
  if (deleteValue) {
259
- newValue = getDefaultCellValue(apiRef.current.getColumn(field));
263
+ newValue = getDefaultCellValue(column);
260
264
  } else if (initialValue) {
261
265
  newValue = initialValue;
262
266
  }
263
- const column = apiRef.current.getColumn(field);
264
267
  const shouldProcessEditCellProps = !!column.preProcessEditCellProps && deleteValue;
265
268
  let newProps = {
266
269
  value: newValue,
@@ -403,6 +406,9 @@ export const useGridCellEditing = (apiRef, props) => {
403
406
  throwIfNotEditable(id, field);
404
407
  throwIfNotInMode(id, field, GridCellModes.Edit);
405
408
  const column = apiRef.current.getColumn(field);
409
+ if (!column) {
410
+ return false;
411
+ }
406
412
  const row = apiRef.current.getRow(id);
407
413
  let parsedValue = value;
408
414
  if (column.valueParser && !skipValueParser) {
@@ -454,7 +460,7 @@ export const useGridCellEditing = (apiRef, props) => {
454
460
  const {
455
461
  value
456
462
  } = editingState[id][field];
457
- return column.valueSetter ? column.valueSetter(value, row, column, apiRef) : _extends({}, row, {
463
+ return column?.valueSetter ? column.valueSetter(value, row, column, apiRef) : _extends({}, row, {
458
464
  [field]: value
459
465
  });
460
466
  }, [apiRef]);
@@ -135,7 +135,7 @@ const useGridRowEditing = (apiRef, props) => {
135
135
  } else if (event.key === 'Tab') {
136
136
  const columnFields = (0, _gridColumnsSelector.gridVisibleColumnFieldsSelector)(apiRef).filter(field => {
137
137
  const column = apiRef.current.getColumn(field);
138
- if (column.type === _colDef.GRID_ACTIONS_COLUMN_TYPE) {
138
+ if (column?.type === _colDef.GRID_ACTIONS_COLUMN_TYPE) {
139
139
  return true;
140
140
  }
141
141
  return apiRef.current.isCellEditable(apiRef.current.getCellParams(params.id, field));
@@ -340,11 +340,10 @@ const useGridRowEditing = (apiRef, props) => {
340
340
  if (!cellParams.isEditable) {
341
341
  return acc;
342
342
  }
343
- const column = apiRef.current.getColumn(field);
344
343
  let newValue = apiRef.current.getCellValue(id, field);
345
344
  if (fieldToFocus === field && (deleteValue || initialValue)) {
346
345
  if (deleteValue) {
347
- newValue = (0, _utils2.getDefaultCellValue)(column);
346
+ newValue = (0, _utils2.getDefaultCellValue)(col);
348
347
  } else if (initialValue) {
349
348
  newValue = initialValue;
350
349
  }
@@ -352,7 +351,7 @@ const useGridRowEditing = (apiRef, props) => {
352
351
  acc[field] = {
353
352
  value: newValue,
354
353
  error: false,
355
- isProcessingProps: column.editable && !!column.preProcessEditCellProps && deleteValue
354
+ isProcessingProps: col.editable && !!col.preProcessEditCellProps && deleteValue
356
355
  };
357
356
  return acc;
358
357
  }, {});
@@ -503,6 +502,9 @@ const useGridRowEditing = (apiRef, props) => {
503
502
  } = params;
504
503
  throwIfNotEditable(id, field);
505
504
  const column = apiRef.current.getColumn(field);
505
+ if (!column) {
506
+ return Promise.resolve(false);
507
+ }
506
508
  const row = apiRef.current.getRow(id);
507
509
  let parsedValue = value;
508
510
  if (column.valueParser && !skipValueParser) {
@@ -556,7 +558,7 @@ const useGridRowEditing = (apiRef, props) => {
556
558
  return;
557
559
  }
558
560
  const fieldColumn = apiRef.current.getColumn(thisField);
559
- if (!fieldColumn.preProcessEditCellProps) {
561
+ if (!fieldColumn?.preProcessEditCellProps) {
560
562
  return;
561
563
  }
562
564
  fieldProps = (0, _extends2.default)({}, fieldProps, {
@@ -128,7 +128,7 @@ export const useGridRowEditing = (apiRef, props) => {
128
128
  } else if (event.key === 'Tab') {
129
129
  const columnFields = gridVisibleColumnFieldsSelector(apiRef).filter(field => {
130
130
  const column = apiRef.current.getColumn(field);
131
- if (column.type === GRID_ACTIONS_COLUMN_TYPE) {
131
+ if (column?.type === GRID_ACTIONS_COLUMN_TYPE) {
132
132
  return true;
133
133
  }
134
134
  return apiRef.current.isCellEditable(apiRef.current.getCellParams(params.id, field));
@@ -333,11 +333,10 @@ export const useGridRowEditing = (apiRef, props) => {
333
333
  if (!cellParams.isEditable) {
334
334
  return acc;
335
335
  }
336
- const column = apiRef.current.getColumn(field);
337
336
  let newValue = apiRef.current.getCellValue(id, field);
338
337
  if (fieldToFocus === field && (deleteValue || initialValue)) {
339
338
  if (deleteValue) {
340
- newValue = getDefaultCellValue(column);
339
+ newValue = getDefaultCellValue(col);
341
340
  } else if (initialValue) {
342
341
  newValue = initialValue;
343
342
  }
@@ -345,7 +344,7 @@ export const useGridRowEditing = (apiRef, props) => {
345
344
  acc[field] = {
346
345
  value: newValue,
347
346
  error: false,
348
- isProcessingProps: column.editable && !!column.preProcessEditCellProps && deleteValue
347
+ isProcessingProps: col.editable && !!col.preProcessEditCellProps && deleteValue
349
348
  };
350
349
  return acc;
351
350
  }, {});
@@ -496,6 +495,9 @@ export const useGridRowEditing = (apiRef, props) => {
496
495
  } = params;
497
496
  throwIfNotEditable(id, field);
498
497
  const column = apiRef.current.getColumn(field);
498
+ if (!column) {
499
+ return Promise.resolve(false);
500
+ }
499
501
  const row = apiRef.current.getRow(id);
500
502
  let parsedValue = value;
501
503
  if (column.valueParser && !skipValueParser) {
@@ -549,7 +551,7 @@ export const useGridRowEditing = (apiRef, props) => {
549
551
  return;
550
552
  }
551
553
  const fieldColumn = apiRef.current.getColumn(thisField);
552
- if (!fieldColumn.preProcessEditCellProps) {
554
+ if (!fieldColumn?.preProcessEditCellProps) {
553
555
  return;
554
556
  }
555
557
  fieldProps = _extends({}, fieldProps, {
@@ -113,7 +113,7 @@ const useGridFilter = (apiRef, props, configuration) => {
113
113
  return true;
114
114
  }
115
115
  const column = apiRef.current.getColumn(item.field);
116
- const filterOperator = column.filterOperators?.find(operator => operator.value === item.operator);
116
+ const filterOperator = column?.filterOperators?.find(operator => operator.value === item.operator);
117
117
  const requiresFilterValue = typeof filterOperator?.requiresFilterValue === 'undefined' ? true : filterOperator?.requiresFilterValue;
118
118
 
119
119
  // Operators like `isEmpty` don't have and don't require `item.value`.
@@ -104,7 +104,7 @@ export const useGridFilter = (apiRef, props, configuration) => {
104
104
  return true;
105
105
  }
106
106
  const column = apiRef.current.getColumn(item.field);
107
- const filterOperator = column.filterOperators?.find(operator => operator.value === item.operator);
107
+ const filterOperator = column?.filterOperators?.find(operator => operator.value === item.operator);
108
108
  const requiresFilterValue = typeof filterOperator?.requiresFilterValue === 'undefined' ? true : filterOperator?.requiresFilterValue;
109
109
 
110
110
  // Operators like `isEmpty` don't have and don't require `item.value`.
@@ -261,7 +261,23 @@ const updateCacheWithNewRows = ({
261
261
  }
262
262
 
263
263
  // Update the data row lookups.
264
- dataRowIdToModelLookup[id] = (0, _extends2.default)({}, oldRow, partialRow);
264
+ // Prototype-preservation rules:
265
+ // 1. If partialRow is a class instance (non-plain object), use its prototype — the
266
+ // caller is passing in a fully-constructed instance and owns the prototype chain.
267
+ // 2. If partialRow is a plain object (Object.prototype), prefer oldRow's prototype so
268
+ // that a partial-update object does not silently discard the existing row's class.
269
+ // 3. If both are plain objects the result is also a plain object (no-op).
270
+ //
271
+ // Note: private class fields (#field syntax) cannot be preserved through a spread-style
272
+ // merge because the brand check is tied to the original instance. Rows that rely on
273
+ // private fields must supply a fully-constructed instance as partialRow (rule 1 above)
274
+ // rather than a plain-object partial update.
275
+ const partialRowProto = Object.getPrototypeOf(partialRow);
276
+ const isPartialRowPlain = partialRowProto === Object.prototype || partialRowProto === null;
277
+ const proto = isPartialRowPlain ? Object.getPrototypeOf(oldRow) : partialRowProto;
278
+ const merged = Object.create(proto);
279
+ Object.assign(merged, oldRow, partialRow);
280
+ dataRowIdToModelLookup[id] = merged;
265
281
  return;
266
282
  }
267
283
 
@@ -242,7 +242,23 @@ export const updateCacheWithNewRows = ({
242
242
  }
243
243
 
244
244
  // Update the data row lookups.
245
- dataRowIdToModelLookup[id] = _extends({}, oldRow, partialRow);
245
+ // Prototype-preservation rules:
246
+ // 1. If partialRow is a class instance (non-plain object), use its prototype — the
247
+ // caller is passing in a fully-constructed instance and owns the prototype chain.
248
+ // 2. If partialRow is a plain object (Object.prototype), prefer oldRow's prototype so
249
+ // that a partial-update object does not silently discard the existing row's class.
250
+ // 3. If both are plain objects the result is also a plain object (no-op).
251
+ //
252
+ // Note: private class fields (#field syntax) cannot be preserved through a spread-style
253
+ // merge because the brand check is tied to the original instance. Rows that rely on
254
+ // private fields must supply a fully-constructed instance as partialRow (rule 1 above)
255
+ // rather than a plain-object partial update.
256
+ const partialRowProto = Object.getPrototypeOf(partialRow);
257
+ const isPartialRowPlain = partialRowProto === Object.prototype || partialRowProto === null;
258
+ const proto = isPartialRowPlain ? Object.getPrototypeOf(oldRow) : partialRowProto;
259
+ const merged = Object.create(proto);
260
+ Object.assign(merged, oldRow, partialRow);
261
+ dataRowIdToModelLookup[id] = merged;
246
262
  return;
247
263
  }
248
264
 
@@ -1,7 +1,7 @@
1
1
  import type { RefObject } from '@mui/x-internals/types';
2
2
  import type { GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.mjs";
3
- import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.mjs";
4
3
  import type { GridConfiguration } from "../../../models/configuration/gridConfiguration.mjs";
4
+ import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.mjs";
5
5
  /**
6
6
  * @requires useGridColumns (method)
7
7
  * @requires useGridRows (method)
@@ -1,7 +1,7 @@
1
1
  import type { RefObject } from '@mui/x-internals/types';
2
2
  import type { GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.js";
3
- import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.js";
4
3
  import type { GridConfiguration } from "../../../models/configuration/gridConfiguration.js";
4
+ import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.js";
5
5
  /**
6
6
  * @requires useGridColumns (method)
7
7
  * @requires useGridRows (method)
@@ -76,6 +76,7 @@ function useGridParamsApi(apiRef, props, configuration) {
76
76
  const cellTabIndex = (0, _gridFocusStateSelector.gridTabIndexCellSelector)(apiRef);
77
77
  const cellMode = apiRef.current.getCellMode(id, field);
78
78
  return apiRef.current.getCellParamsForRow(id, field, row, {
79
+ // Params keep a non-nullable `colDef`, but it can be `undefined` at runtime for an unknown field.
79
80
  colDef: props.listView && props.listViewColumn?.field === field ? (0, _gridListViewSelectors.gridListColumnSelector)(apiRef) : apiRef.current.getColumn(field),
80
81
  rowNode,
81
82
  hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id,
@@ -69,6 +69,7 @@ export function useGridParamsApi(apiRef, props, configuration) {
69
69
  const cellTabIndex = gridTabIndexCellSelector(apiRef);
70
70
  const cellMode = apiRef.current.getCellMode(id, field);
71
71
  return apiRef.current.getCellParamsForRow(id, field, row, {
72
+ // Params keep a non-nullable `colDef`, but it can be `undefined` at runtime for an unknown field.
72
73
  colDef: props.listView && props.listViewColumn?.field === field ? gridListColumnSelector(apiRef) : apiRef.current.getColumn(field),
73
74
  rowNode,
74
75
  hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id,
@@ -120,6 +120,9 @@ const useGridSorting = (apiRef, props) => {
120
120
  }, [apiRef, logger, props.disableMultipleColumnsSorting]);
121
121
  const sortColumn = React.useCallback((field, direction, allowMultipleSorting) => {
122
122
  const column = apiRef.current.getColumn(field);
123
+ if (!column) {
124
+ return;
125
+ }
123
126
  const sortItem = createSortItem(column, direction);
124
127
  let sortModel;
125
128
  if (!allowMultipleSorting || props.disableMultipleColumnsSorting) {
@@ -111,6 +111,9 @@ export const useGridSorting = (apiRef, props) => {
111
111
  }, [apiRef, logger, props.disableMultipleColumnsSorting]);
112
112
  const sortColumn = React.useCallback((field, direction, allowMultipleSorting) => {
113
113
  const column = apiRef.current.getColumn(field);
114
+ if (!column) {
115
+ return;
116
+ }
114
117
  const sortItem = createSortItem(column, direction);
115
118
  let sortModel;
116
119
  if (!allowMultipleSorting || props.disableMultipleColumnsSorting) {
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid v9.10.0
2
+ * @mui/x-data-grid v9.11.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid v9.10.0
2
+ * @mui/x-data-grid v9.11.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the