@mui/x-data-grid 9.12.0 → 9.14.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 (92) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/DataGrid/DataGrid.js +1 -1
  3. package/DataGrid/DataGrid.mjs +1 -1
  4. package/components/GridPagination.js +1 -1
  5. package/components/GridPagination.mjs +1 -1
  6. package/components/GridRow.d.mts +1 -0
  7. package/components/GridRow.d.ts +1 -0
  8. package/components/GridRow.js +41 -6
  9. package/components/GridRow.mjs +41 -6
  10. package/components/GridScrollArea.js +20 -7
  11. package/components/GridScrollArea.mjs +21 -8
  12. package/components/base/GridOverlays.js +6 -5
  13. package/components/base/GridOverlays.mjs +6 -5
  14. package/components/cell/GridActionsCell.js +53 -50
  15. package/components/cell/GridActionsCell.mjs +53 -50
  16. package/components/cell/GridActionsCellItem.js +0 -1
  17. package/components/cell/GridActionsCellItem.mjs +0 -1
  18. package/components/cell/GridCell.js +15 -4
  19. package/components/cell/GridCell.mjs +15 -4
  20. package/components/cell/GridEditBooleanCell.d.mts +1 -1
  21. package/components/cell/GridEditBooleanCell.d.ts +1 -1
  22. package/components/cell/GridEditBooleanCell.js +4 -4
  23. package/components/cell/GridEditBooleanCell.mjs +4 -4
  24. package/components/cell/GridEditDateCell.d.mts +1 -1
  25. package/components/cell/GridEditDateCell.d.ts +1 -1
  26. package/components/cell/GridEditDateCell.js +5 -5
  27. package/components/cell/GridEditDateCell.mjs +5 -5
  28. package/components/cell/GridEditInputCell.d.mts +1 -1
  29. package/components/cell/GridEditInputCell.d.ts +1 -1
  30. package/components/cell/GridEditInputCell.js +1 -1
  31. package/components/cell/GridEditInputCell.mjs +1 -1
  32. package/components/cell/GridEditLongTextCell.d.mts +1 -1
  33. package/components/cell/GridEditLongTextCell.d.ts +1 -1
  34. package/components/cell/GridEditLongTextCell.js +2 -2
  35. package/components/cell/GridEditLongTextCell.mjs +2 -2
  36. package/components/cell/GridEditSingleSelectCell.d.mts +1 -1
  37. package/components/cell/GridEditSingleSelectCell.d.ts +1 -1
  38. package/components/cell/GridEditSingleSelectCell.js +4 -4
  39. package/components/cell/GridEditSingleSelectCell.mjs +4 -4
  40. package/components/quickFilter/QuickFilter.js +7 -2
  41. package/components/quickFilter/QuickFilter.mjs +7 -2
  42. package/components/toolbarV8/Toolbar.d.mts +5 -1
  43. package/components/toolbarV8/Toolbar.d.ts +5 -1
  44. package/components/toolbarV8/Toolbar.js +34 -12
  45. package/components/toolbarV8/Toolbar.mjs +33 -11
  46. package/components/virtualization/GridVirtualScroller.js +3 -0
  47. package/components/virtualization/GridVirtualScroller.mjs +3 -0
  48. package/hooks/core/useGridVirtualizer.js +14 -1
  49. package/hooks/core/useGridVirtualizer.mjs +14 -1
  50. package/hooks/features/columnResize/useGridColumnResize.js +4 -1
  51. package/hooks/features/columnResize/useGridColumnResize.mjs +4 -1
  52. package/hooks/features/dataSource/models.d.mts +2 -2
  53. package/hooks/features/dataSource/models.d.ts +2 -2
  54. package/hooks/features/dataSource/useGridDataSourceBase.js +12 -8
  55. package/hooks/features/dataSource/useGridDataSourceBase.mjs +13 -9
  56. package/hooks/features/pivoting/gridPivotingInterfaces.d.mts +2 -2
  57. package/hooks/features/pivoting/gridPivotingInterfaces.d.ts +2 -2
  58. package/hooks/features/rowSelection/useGridRowSelection.d.mts +1 -1
  59. package/hooks/features/rowSelection/useGridRowSelection.d.ts +1 -1
  60. package/hooks/features/rowSelection/useGridRowSelection.js +51 -10
  61. package/hooks/features/rowSelection/useGridRowSelection.mjs +53 -12
  62. package/hooks/features/rowSelection/utils.js +5 -0
  63. package/hooks/features/rowSelection/utils.mjs +5 -0
  64. package/hooks/features/rows/gridRowsUtils.d.mts +37 -3
  65. package/hooks/features/rows/gridRowsUtils.d.ts +37 -3
  66. package/hooks/features/rows/gridRowsUtils.js +120 -23
  67. package/hooks/features/rows/gridRowsUtils.mjs +115 -22
  68. package/hooks/features/rows/useGridRows.js +10 -1
  69. package/hooks/features/rows/useGridRows.mjs +10 -1
  70. package/index.js +1 -1
  71. package/index.mjs +1 -1
  72. package/internals/index.d.mts +2 -2
  73. package/internals/index.d.ts +2 -2
  74. package/internals/index.js +28 -0
  75. package/internals/index.mjs +2 -2
  76. package/material/variables.d.mts +6 -1
  77. package/material/variables.d.ts +6 -1
  78. package/material/variables.js +17 -3
  79. package/material/variables.mjs +14 -4
  80. package/models/api/gridRowApi.d.mts +7 -7
  81. package/models/api/gridRowApi.d.ts +7 -7
  82. package/models/gridDataSource.d.mts +3 -0
  83. package/models/gridDataSource.d.ts +3 -0
  84. package/models/gridRows.d.mts +28 -1
  85. package/models/gridRows.d.ts +28 -1
  86. package/models/props/DataGridProps.d.mts +3 -3
  87. package/models/props/DataGridProps.d.ts +3 -3
  88. package/package.json +6 -6
  89. package/utils/keyboardUtils.d.mts +1 -0
  90. package/utils/keyboardUtils.d.ts +1 -0
  91. package/utils/keyboardUtils.js +6 -0
  92. package/utils/keyboardUtils.mjs +5 -0
@@ -21,19 +21,21 @@ var _jsxRuntime = require("react/jsx-runtime");
21
21
  const GridOverlayWrapperRoot = (0, _styles.styled)('div', {
22
22
  name: 'MuiDataGrid',
23
23
  slot: 'OverlayWrapper',
24
- shouldForwardProp: prop => prop !== 'overlayType' && prop !== 'loadingOverlayVariant' && prop !== 'right'
24
+ shouldForwardProp: prop => prop !== 'overlayType' && prop !== 'loadingOverlayVariant'
25
25
  })(({
26
26
  overlayType,
27
- loadingOverlayVariant,
28
- right
27
+ loadingOverlayVariant
29
28
  }) =>
30
29
  // Skeleton overlay should flow with the scroll container and not be sticky
31
30
  loadingOverlayVariant !== 'skeleton' ? {
32
31
  position: 'sticky',
33
32
  // To stay in place while scrolling
34
33
  top: 'var(--DataGrid-topContainerHeight)',
34
+ // Both 0-insets keep the overlay in the viewport in LTR and RTL alike. Sticky insets
35
+ // are independent constraints, and a width: 0 box satisfies both at once, so it
36
+ // clamps at whichever edge the scroll pushes it past.
35
37
  left: 0,
36
- right: `${right}px`,
38
+ right: 0,
37
39
  width: 0,
38
40
  // To stay above the content instead of shifting it down
39
41
  height: 0,
@@ -70,7 +72,6 @@ function GridOverlayWrapper(props) {
70
72
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(GridOverlayWrapperRoot, (0, _extends2.default)({
71
73
  className: classes.root
72
74
  }, props, {
73
- right: dimensions.columnsTotalWidth - dimensions.viewportOuterSize.width,
74
75
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(GridOverlayWrapperInner, (0, _extends2.default)({
75
76
  className: classes.inner,
76
77
  style: {
@@ -13,19 +13,21 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  const GridOverlayWrapperRoot = styled('div', {
14
14
  name: 'MuiDataGrid',
15
15
  slot: 'OverlayWrapper',
16
- shouldForwardProp: prop => prop !== 'overlayType' && prop !== 'loadingOverlayVariant' && prop !== 'right'
16
+ shouldForwardProp: prop => prop !== 'overlayType' && prop !== 'loadingOverlayVariant'
17
17
  })(({
18
18
  overlayType,
19
- loadingOverlayVariant,
20
- right
19
+ loadingOverlayVariant
21
20
  }) =>
22
21
  // Skeleton overlay should flow with the scroll container and not be sticky
23
22
  loadingOverlayVariant !== 'skeleton' ? {
24
23
  position: 'sticky',
25
24
  // To stay in place while scrolling
26
25
  top: 'var(--DataGrid-topContainerHeight)',
26
+ // Both 0-insets keep the overlay in the viewport in LTR and RTL alike. Sticky insets
27
+ // are independent constraints, and a width: 0 box satisfies both at once, so it
28
+ // clamps at whichever edge the scroll pushes it past.
27
29
  left: 0,
28
- right: `${right}px`,
30
+ right: 0,
29
31
  width: 0,
30
32
  // To stay above the content instead of shifting it down
31
33
  height: 0,
@@ -62,7 +64,6 @@ export function GridOverlayWrapper(props) {
62
64
  return /*#__PURE__*/_jsx(GridOverlayWrapperRoot, _extends({
63
65
  className: classes.root
64
66
  }, props, {
65
- right: dimensions.columnsTotalWidth - dimensions.viewportOuterSize.width,
66
67
  children: /*#__PURE__*/_jsx(GridOverlayWrapperInner, _extends({
67
68
  className: classes.inner,
68
69
  style: {
@@ -20,6 +20,7 @@ var _gridClasses = require("../../constants/gridClasses");
20
20
  var _GridMenu = require("../menu/GridMenu");
21
21
  var _useGridRootProps = require("../../hooks/utils/useGridRootProps");
22
22
  var _useGridApiContext = require("../../hooks/utils/useGridApiContext");
23
+ var _domUtils = require("../../utils/domUtils");
23
24
  var _GridActionsCellItem = require("./GridActionsCellItem");
24
25
  var _jsxRuntime = require("react/jsx-runtime");
25
26
  const _excluded = ["api", "colDef", "id", "hasFocus", "isEditable", "field", "value", "formattedValue", "row", "rowNode", "cellMode", "tabIndex", "position", "onMenuOpen", "onMenuClose", "children", "suppressChildrenValidation"];
@@ -140,7 +141,9 @@ If this is intentional, you can suppress this warning by passing the \`suppressC
140
141
  }
141
142
  };
142
143
  const handleRootKeyDown = event => {
143
- if (numberOfButtons <= 1) {
144
+ // The menu renders in a portal, but its key events still bubble through the React tree.
145
+ // They must not move the roving focus between the buttons behind the open menu.
146
+ if (numberOfButtons <= 1 || (0, _domUtils.isEventTargetInPortal)(event)) {
144
147
  return;
145
148
  }
146
149
  const getNewIndex = (index, direction) => {
@@ -170,55 +173,55 @@ If this is intentional, you can suppress this warning by passing the \`suppressC
170
173
  setFocusedButtonIndex(newIndex);
171
174
  }
172
175
  };
173
-
174
- // role="menu" requires at least one child element
175
- const attributes = numberOfButtons > 0 ? {
176
- role: 'menu',
177
- onKeyDown: handleRootKeyDown
178
- } : undefined;
179
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", (0, _extends2.default)({
180
- ref: rootRef,
181
- tabIndex: -1,
182
- className: _gridClasses.gridClasses.actionsCell
183
- }, attributes, other, {
184
- children: [iconButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
185
- key: index,
186
- touchRippleRef: handleTouchRippleRef(index),
187
- onClick: handleButtonClick(index, button.props.onClick),
188
- tabIndex: focusedButtonIndex === index ? tabIndex : -1
189
- })), menuButtons.length > 0 && buttonId && /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.baseIconButton, (0, _extends2.default)({
190
- ref: buttonRef,
191
- id: buttonId,
192
- "aria-label": apiRef.current.getLocaleText('actionsCellMore'),
193
- "aria-haspopup": "menu",
194
- "aria-expanded": open,
195
- "aria-controls": open ? menuId : undefined,
196
- role: "menuitem",
197
- size: "small",
198
- onClick: toggleMenu,
199
- touchRippleRef: handleTouchRippleRef(buttonId),
200
- tabIndex: focusedButtonIndex === iconButtons.length ? tabIndex : -1
201
- }, rootProps.slotProps?.baseIconButton, {
202
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.moreActionsIcon, {
203
- fontSize: "small"
204
- })
205
- })), menuButtons.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_GridMenu.GridMenu, {
206
- open: open,
207
- target: buttonRef.current,
208
- position: position,
209
- onClose: hideMenu,
210
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.baseMenuList, {
211
- id: menuId,
212
- className: _gridClasses.gridClasses.menuList,
213
- "aria-labelledby": buttonId,
214
- autoFocusItem: true,
215
- children: menuButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
216
- key: index,
217
- closeMenu: hideMenu
218
- }))
219
- })
220
- })]
221
- }));
176
+ return (
177
+ /*#__PURE__*/
178
+ // The wrapper carries no role on purpose: the buttons are plain buttons inside the
179
+ // `gridcell`. It only relays the key events bubbling from them.
180
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
181
+ (0, _jsxRuntime.jsxs)("div", (0, _extends2.default)({
182
+ ref: rootRef,
183
+ tabIndex: -1,
184
+ className: _gridClasses.gridClasses.actionsCell,
185
+ onKeyDown: handleRootKeyDown
186
+ }, other, {
187
+ children: [iconButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
188
+ key: index,
189
+ touchRippleRef: handleTouchRippleRef(index),
190
+ onClick: handleButtonClick(index, button.props.onClick),
191
+ tabIndex: focusedButtonIndex === index ? tabIndex : -1
192
+ })), menuButtons.length > 0 && buttonId && /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.baseIconButton, (0, _extends2.default)({
193
+ ref: buttonRef,
194
+ id: buttonId,
195
+ "aria-label": apiRef.current.getLocaleText('actionsCellMore'),
196
+ "aria-haspopup": "menu",
197
+ "aria-expanded": open,
198
+ "aria-controls": open ? menuId : undefined,
199
+ size: "small",
200
+ onClick: toggleMenu,
201
+ touchRippleRef: handleTouchRippleRef(buttonId),
202
+ tabIndex: focusedButtonIndex === iconButtons.length ? tabIndex : -1
203
+ }, rootProps.slotProps?.baseIconButton, {
204
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.moreActionsIcon, {
205
+ fontSize: "small"
206
+ })
207
+ })), menuButtons.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_GridMenu.GridMenu, {
208
+ open: open,
209
+ target: buttonRef.current,
210
+ position: position,
211
+ onClose: hideMenu,
212
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.baseMenuList, {
213
+ id: menuId,
214
+ className: _gridClasses.gridClasses.menuList,
215
+ "aria-labelledby": buttonId,
216
+ autoFocusItem: true,
217
+ children: menuButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
218
+ key: index,
219
+ closeMenu: hideMenu
220
+ }))
221
+ })
222
+ })]
223
+ }))
224
+ );
222
225
  }
223
226
  process.env.NODE_ENV !== "production" ? GridActionsCell.propTypes /* remove-proptypes */ = {
224
227
  // ----------------------------- Warning --------------------------------
@@ -13,6 +13,7 @@ import { gridClasses } from "../../constants/gridClasses.mjs";
13
13
  import { GridMenu } from "../menu/GridMenu.mjs";
14
14
  import { useGridRootProps } from "../../hooks/utils/useGridRootProps.mjs";
15
15
  import { useGridApiContext } from "../../hooks/utils/useGridApiContext.mjs";
16
+ import { isEventTargetInPortal } from "../../utils/domUtils.mjs";
16
17
  import { GridActionsCellItem } from "./GridActionsCellItem.mjs";
17
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
19
  const hasActions = colDef => typeof colDef.getActions === 'function';
@@ -132,7 +133,9 @@ If this is intentional, you can suppress this warning by passing the \`suppressC
132
133
  }
133
134
  };
134
135
  const handleRootKeyDown = event => {
135
- if (numberOfButtons <= 1) {
136
+ // The menu renders in a portal, but its key events still bubble through the React tree.
137
+ // They must not move the roving focus between the buttons behind the open menu.
138
+ if (numberOfButtons <= 1 || isEventTargetInPortal(event)) {
136
139
  return;
137
140
  }
138
141
  const getNewIndex = (index, direction) => {
@@ -162,55 +165,55 @@ If this is intentional, you can suppress this warning by passing the \`suppressC
162
165
  setFocusedButtonIndex(newIndex);
163
166
  }
164
167
  };
165
-
166
- // role="menu" requires at least one child element
167
- const attributes = numberOfButtons > 0 ? {
168
- role: 'menu',
169
- onKeyDown: handleRootKeyDown
170
- } : undefined;
171
- return /*#__PURE__*/_jsxs("div", _extends({
172
- ref: rootRef,
173
- tabIndex: -1,
174
- className: gridClasses.actionsCell
175
- }, attributes, other, {
176
- children: [iconButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
177
- key: index,
178
- touchRippleRef: handleTouchRippleRef(index),
179
- onClick: handleButtonClick(index, button.props.onClick),
180
- tabIndex: focusedButtonIndex === index ? tabIndex : -1
181
- })), menuButtons.length > 0 && buttonId && /*#__PURE__*/_jsx(rootProps.slots.baseIconButton, _extends({
182
- ref: buttonRef,
183
- id: buttonId,
184
- "aria-label": apiRef.current.getLocaleText('actionsCellMore'),
185
- "aria-haspopup": "menu",
186
- "aria-expanded": open,
187
- "aria-controls": open ? menuId : undefined,
188
- role: "menuitem",
189
- size: "small",
190
- onClick: toggleMenu,
191
- touchRippleRef: handleTouchRippleRef(buttonId),
192
- tabIndex: focusedButtonIndex === iconButtons.length ? tabIndex : -1
193
- }, rootProps.slotProps?.baseIconButton, {
194
- children: /*#__PURE__*/_jsx(rootProps.slots.moreActionsIcon, {
195
- fontSize: "small"
196
- })
197
- })), menuButtons.length > 0 && /*#__PURE__*/_jsx(GridMenu, {
198
- open: open,
199
- target: buttonRef.current,
200
- position: position,
201
- onClose: hideMenu,
202
- children: /*#__PURE__*/_jsx(rootProps.slots.baseMenuList, {
203
- id: menuId,
204
- className: gridClasses.menuList,
205
- "aria-labelledby": buttonId,
206
- autoFocusItem: true,
207
- children: menuButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
208
- key: index,
209
- closeMenu: hideMenu
210
- }))
211
- })
212
- })]
213
- }));
168
+ return (
169
+ /*#__PURE__*/
170
+ // The wrapper carries no role on purpose: the buttons are plain buttons inside the
171
+ // `gridcell`. It only relays the key events bubbling from them.
172
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
173
+ _jsxs("div", _extends({
174
+ ref: rootRef,
175
+ tabIndex: -1,
176
+ className: gridClasses.actionsCell,
177
+ onKeyDown: handleRootKeyDown
178
+ }, other, {
179
+ children: [iconButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
180
+ key: index,
181
+ touchRippleRef: handleTouchRippleRef(index),
182
+ onClick: handleButtonClick(index, button.props.onClick),
183
+ tabIndex: focusedButtonIndex === index ? tabIndex : -1
184
+ })), menuButtons.length > 0 && buttonId && /*#__PURE__*/_jsx(rootProps.slots.baseIconButton, _extends({
185
+ ref: buttonRef,
186
+ id: buttonId,
187
+ "aria-label": apiRef.current.getLocaleText('actionsCellMore'),
188
+ "aria-haspopup": "menu",
189
+ "aria-expanded": open,
190
+ "aria-controls": open ? menuId : undefined,
191
+ size: "small",
192
+ onClick: toggleMenu,
193
+ touchRippleRef: handleTouchRippleRef(buttonId),
194
+ tabIndex: focusedButtonIndex === iconButtons.length ? tabIndex : -1
195
+ }, rootProps.slotProps?.baseIconButton, {
196
+ children: /*#__PURE__*/_jsx(rootProps.slots.moreActionsIcon, {
197
+ fontSize: "small"
198
+ })
199
+ })), menuButtons.length > 0 && /*#__PURE__*/_jsx(GridMenu, {
200
+ open: open,
201
+ target: buttonRef.current,
202
+ position: position,
203
+ onClose: hideMenu,
204
+ children: /*#__PURE__*/_jsx(rootProps.slots.baseMenuList, {
205
+ id: menuId,
206
+ className: gridClasses.menuList,
207
+ "aria-labelledby": buttonId,
208
+ autoFocusItem: true,
209
+ children: menuButtons.map((button, index) => /*#__PURE__*/React.cloneElement(button, {
210
+ key: index,
211
+ closeMenu: hideMenu
212
+ }))
213
+ })
214
+ })]
215
+ }))
216
+ );
214
217
  }
215
218
  process.env.NODE_ENV !== "production" ? GridActionsCell.propTypes /* remove-proptypes */ = {
216
219
  // ----------------------------- Warning --------------------------------
@@ -30,7 +30,6 @@ const GridActionsCellItem = exports.GridActionsCellItem = (0, _forwardRef.forwar
30
30
  };
31
31
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.baseIconButton, (0, _extends2.default)({
32
32
  size: "small",
33
- role: "menuitem",
34
33
  "aria-label": label
35
34
  }, other, {
36
35
  onClick: handleClick
@@ -23,7 +23,6 @@ const GridActionsCellItem = forwardRef((props, ref) => {
23
23
  };
24
24
  return /*#__PURE__*/_jsx(rootProps.slots.baseIconButton, _extends({
25
25
  size: "small",
26
- role: "menuitem",
27
26
  "aria-label": label
28
27
  }, other, {
29
28
  onClick: handleClick
@@ -101,14 +101,17 @@ const GridCell = (0, _forwardRef.forwardRef)(function GridCell(props, ref) {
101
101
  value: forcedValue,
102
102
  formattedValue: forcedFormattedValue
103
103
  } = cellAggregationResult || {};
104
+ const stateTabIndex = (0, _useGridSelector.useGridSelector)(apiRef, () => {
105
+ const cellTabIndex = (0, _gridFocusStateSelector.gridTabIndexCellSelector)(apiRef);
106
+ return cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === rowId ? 0 : -1;
107
+ });
104
108
  const cellParams = apiRef.current.getCellParamsForRow(rowId, field, row, {
105
109
  colDef: column,
106
110
  cellMode,
107
111
  rowNode: rowNode,
108
- tabIndex: (0, _useGridSelector.useGridSelector)(apiRef, () => {
109
- const cellTabIndex = (0, _gridFocusStateSelector.gridTabIndexCellSelector)(apiRef);
110
- return cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === rowId ? 0 : -1;
111
- }),
112
+ // A cell kept mounted outside the render context is collapsed to zero size, so it must not
113
+ // become the grid's tab stop: tabbing in would move focus to something the user can't see.
114
+ tabIndex: isNotVisible ? -1 : stateTabIndex,
112
115
  hasFocus: (0, _useGridSelector.useGridSelector)(apiRef, () => {
113
116
  const focus = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef);
114
117
  return focus?.id === rowId && focus.field === field;
@@ -174,6 +177,10 @@ const GridCell = (0, _forwardRef.forwardRef)(function GridCell(props, ref) {
174
177
  };
175
178
  const classes = useUtilityClasses(ownerState);
176
179
  const publishMouseUp = React.useCallback(eventName => event => {
180
+ // The row might have been deleted during the click
181
+ if (!apiRef.current.getRow(rowId)) {
182
+ return;
183
+ }
177
184
  const params = apiRef.current.getCellParams(rowId, field || '');
178
185
  apiRef.current.publishEvent(eventName, params, event);
179
186
  if (onMouseUp) {
@@ -181,6 +188,10 @@ const GridCell = (0, _forwardRef.forwardRef)(function GridCell(props, ref) {
181
188
  }
182
189
  }, [apiRef, field, onMouseUp, rowId]);
183
190
  const publishMouseDown = React.useCallback(eventName => event => {
191
+ // The row might have been deleted during the click
192
+ if (!apiRef.current.getRow(rowId)) {
193
+ return;
194
+ }
184
195
  const params = apiRef.current.getCellParams(rowId, field || '');
185
196
  apiRef.current.publishEvent(eventName, params, event);
186
197
  if (onMouseDown) {
@@ -94,14 +94,17 @@ const GridCell = forwardRef(function GridCell(props, ref) {
94
94
  value: forcedValue,
95
95
  formattedValue: forcedFormattedValue
96
96
  } = cellAggregationResult || {};
97
+ const stateTabIndex = useGridSelector(apiRef, () => {
98
+ const cellTabIndex = gridTabIndexCellSelector(apiRef);
99
+ return cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === rowId ? 0 : -1;
100
+ });
97
101
  const cellParams = apiRef.current.getCellParamsForRow(rowId, field, row, {
98
102
  colDef: column,
99
103
  cellMode,
100
104
  rowNode: rowNode,
101
- tabIndex: useGridSelector(apiRef, () => {
102
- const cellTabIndex = gridTabIndexCellSelector(apiRef);
103
- return cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === rowId ? 0 : -1;
104
- }),
105
+ // A cell kept mounted outside the render context is collapsed to zero size, so it must not
106
+ // become the grid's tab stop: tabbing in would move focus to something the user can't see.
107
+ tabIndex: isNotVisible ? -1 : stateTabIndex,
105
108
  hasFocus: useGridSelector(apiRef, () => {
106
109
  const focus = gridFocusCellSelector(apiRef);
107
110
  return focus?.id === rowId && focus.field === field;
@@ -167,6 +170,10 @@ const GridCell = forwardRef(function GridCell(props, ref) {
167
170
  };
168
171
  const classes = useUtilityClasses(ownerState);
169
172
  const publishMouseUp = React.useCallback(eventName => event => {
173
+ // The row might have been deleted during the click
174
+ if (!apiRef.current.getRow(rowId)) {
175
+ return;
176
+ }
170
177
  const params = apiRef.current.getCellParams(rowId, field || '');
171
178
  apiRef.current.publishEvent(eventName, params, event);
172
179
  if (onMouseUp) {
@@ -174,6 +181,10 @@ const GridCell = forwardRef(function GridCell(props, ref) {
174
181
  }
175
182
  }, [apiRef, field, onMouseUp, rowId]);
176
183
  const publishMouseDown = React.useCallback(eventName => event => {
184
+ // The row might have been deleted during the click
185
+ if (!apiRef.current.getRow(rowId)) {
186
+ return;
187
+ }
177
188
  const params = apiRef.current.getCellParams(rowId, field || '');
178
189
  apiRef.current.publishEvent(eventName, params, event);
179
190
  if (onMouseDown) {
@@ -5,7 +5,7 @@ export interface GridEditBooleanCellProps extends GridRenderEditCellParams, Omit
5
5
  * Callback called when the value is changed by the user.
6
6
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
7
7
  * @param {boolean} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
8
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
8
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
9
9
  */
10
10
  onValueChange?: (event: React.ChangeEvent<HTMLInputElement>, newValue: boolean) => Promise<void> | void;
11
11
  }
@@ -5,7 +5,7 @@ export interface GridEditBooleanCellProps extends GridRenderEditCellParams, Omit
5
5
  * Callback called when the value is changed by the user.
6
6
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
7
7
  * @param {boolean} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
8
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
8
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
9
9
  */
10
10
  onValueChange?: (event: React.ChangeEvent<HTMLInputElement>, newValue: boolean) => Promise<void> | void;
11
11
  }
@@ -51,15 +51,15 @@ function GridEditBooleanCell(props) {
51
51
  const classes = useUtilityClasses(ownerState);
52
52
  const handleChange = React.useCallback(async event => {
53
53
  const newValue = event.target.checked;
54
- if (onValueChange) {
55
- await onValueChange(event, newValue);
56
- }
57
54
  setValueState(newValue);
58
55
  await apiRef.current.setEditCellValue({
59
56
  id: idProp,
60
57
  field,
61
58
  value: newValue
62
59
  }, event);
60
+ if (onValueChange) {
61
+ await onValueChange(event, newValue);
62
+ }
63
63
  }, [apiRef, field, idProp, onValueChange]);
64
64
  React.useEffect(() => {
65
65
  setValueState(value);
@@ -126,7 +126,7 @@ process.env.NODE_ENV !== "production" ? GridEditBooleanCell.propTypes /* remove-
126
126
  * Callback called when the value is changed by the user.
127
127
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
128
128
  * @param {boolean} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
129
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
129
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
130
130
  */
131
131
  onValueChange: _propTypes.default.func,
132
132
  /**
@@ -43,15 +43,15 @@ function GridEditBooleanCell(props) {
43
43
  const classes = useUtilityClasses(ownerState);
44
44
  const handleChange = React.useCallback(async event => {
45
45
  const newValue = event.target.checked;
46
- if (onValueChange) {
47
- await onValueChange(event, newValue);
48
- }
49
46
  setValueState(newValue);
50
47
  await apiRef.current.setEditCellValue({
51
48
  id: idProp,
52
49
  field,
53
50
  value: newValue
54
51
  }, event);
52
+ if (onValueChange) {
53
+ await onValueChange(event, newValue);
54
+ }
55
55
  }, [apiRef, field, idProp, onValueChange]);
56
56
  React.useEffect(() => {
57
57
  setValueState(value);
@@ -118,7 +118,7 @@ process.env.NODE_ENV !== "production" ? GridEditBooleanCell.propTypes /* remove-
118
118
  * Callback called when the value is changed by the user.
119
119
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
120
120
  * @param {boolean} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
121
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
121
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
122
122
  */
123
123
  onValueChange: PropTypes.func,
124
124
  /**
@@ -6,7 +6,7 @@ export interface GridEditDateCellProps extends GridRenderEditCellParams {
6
6
  * Callback called when the value is changed by the user.
7
7
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
8
8
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
9
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
9
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
10
10
  */
11
11
  onValueChange?: (event: React.ChangeEvent<HTMLInputElement>, newValue: Date | null) => Promise<void> | void;
12
12
  slotProps?: {
@@ -6,7 +6,7 @@ export interface GridEditDateCellProps extends GridRenderEditCellParams {
6
6
  * Callback called when the value is changed by the user.
7
7
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
8
8
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
9
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
9
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
10
10
  */
11
11
  onValueChange?: (event: React.ChangeEvent<HTMLInputElement>, newValue: Date | null) => Promise<void> | void;
12
12
  slotProps?: {
@@ -94,18 +94,18 @@ function GridEditDateCell(props) {
94
94
  const handleChange = React.useCallback(async event => {
95
95
  const newFormattedDate = event.target.value;
96
96
  const newParsedDate = parseValueToDate(newFormattedDate);
97
- if (onValueChange) {
98
- await onValueChange(event, newParsedDate);
99
- }
100
97
  setValueState({
101
98
  parsed: newParsedDate,
102
99
  formatted: newFormattedDate
103
100
  });
104
- apiRef.current.setEditCellValue({
101
+ await apiRef.current.setEditCellValue({
105
102
  id,
106
103
  field,
107
104
  value: newParsedDate
108
105
  }, event);
106
+ if (onValueChange) {
107
+ await onValueChange(event, newParsedDate);
108
+ }
109
109
  }, [apiRef, field, id, onValueChange, parseValueToDate]);
110
110
  React.useEffect(() => {
111
111
  setValueState(state => {
@@ -180,7 +180,7 @@ process.env.NODE_ENV !== "production" ? GridEditDateCell.propTypes /* remove-pro
180
180
  * Callback called when the value is changed by the user.
181
181
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
182
182
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
183
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
183
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
184
184
  */
185
185
  onValueChange: _propTypes.default.func,
186
186
  /**
@@ -86,18 +86,18 @@ function GridEditDateCell(props) {
86
86
  const handleChange = React.useCallback(async event => {
87
87
  const newFormattedDate = event.target.value;
88
88
  const newParsedDate = parseValueToDate(newFormattedDate);
89
- if (onValueChange) {
90
- await onValueChange(event, newParsedDate);
91
- }
92
89
  setValueState({
93
90
  parsed: newParsedDate,
94
91
  formatted: newFormattedDate
95
92
  });
96
- apiRef.current.setEditCellValue({
93
+ await apiRef.current.setEditCellValue({
97
94
  id,
98
95
  field,
99
96
  value: newParsedDate
100
97
  }, event);
98
+ if (onValueChange) {
99
+ await onValueChange(event, newParsedDate);
100
+ }
101
101
  }, [apiRef, field, id, onValueChange, parseValueToDate]);
102
102
  React.useEffect(() => {
103
103
  setValueState(state => {
@@ -172,7 +172,7 @@ process.env.NODE_ENV !== "production" ? GridEditDateCell.propTypes /* remove-pro
172
172
  * Callback called when the value is changed by the user.
173
173
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
174
174
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
175
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
175
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
176
176
  */
177
177
  onValueChange: PropTypes.func,
178
178
  /**
@@ -7,7 +7,7 @@ export interface GridEditInputCellProps extends GridRenderEditCellParams {
7
7
  * Callback called when the value is changed by the user.
8
8
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
9
9
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
10
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
10
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
11
11
  */
12
12
  onValueChange?: (event: React.ChangeEvent<HTMLInputElement>, newValue: string) => Promise<void> | void;
13
13
  slotProps?: {
@@ -7,7 +7,7 @@ export interface GridEditInputCellProps extends GridRenderEditCellParams {
7
7
  * Callback called when the value is changed by the user.
8
8
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
9
9
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
10
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
10
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
11
11
  */
12
12
  onValueChange?: (event: React.ChangeEvent<HTMLInputElement>, newValue: string) => Promise<void> | void;
13
13
  slotProps?: {
@@ -153,7 +153,7 @@ process.env.NODE_ENV !== "production" ? GridEditInputCell.propTypes /* remove-pr
153
153
  * Callback called when the value is changed by the user.
154
154
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
155
155
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
156
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
156
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
157
157
  */
158
158
  onValueChange: _propTypes.default.func,
159
159
  /**
@@ -146,7 +146,7 @@ process.env.NODE_ENV !== "production" ? GridEditInputCell.propTypes /* remove-pr
146
146
  * Callback called when the value is changed by the user.
147
147
  * @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
148
148
  * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`.
149
- * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue`
149
+ * @returns {Promise<void> | void} A promise to be awaited after `apiRef.current.setEditCellValue` is called
150
150
  */
151
151
  onValueChange: PropTypes.func,
152
152
  /**