@mui/x-data-grid 8.18.0 → 8.20.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 (162) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/DataGrid/useDataGridComponent.js +4 -3
  3. package/components/GridRow.js +5 -2
  4. package/components/GridRowDragAndDropOverlay.d.ts +7 -0
  5. package/components/GridRowDragAndDropOverlay.js +73 -0
  6. package/components/cell/GridActionsCell.d.ts +9 -0
  7. package/components/cell/GridActionsCell.js +54 -34
  8. package/components/cell/GridBooleanCell.js +0 -10
  9. package/components/cell/GridCell.js +4 -10
  10. package/components/columnHeaders/GridColumnHeaderItem.js +2 -2
  11. package/components/columnSelection/GridCellCheckboxRenderer.js +37 -22
  12. package/components/containers/GridRootStyles.js +17 -40
  13. package/components/toolbarV8/Toolbar.js +1 -1
  14. package/components/virtualization/GridVirtualScrollbar.d.ts +1 -0
  15. package/components/virtualization/GridVirtualScrollbar.js +13 -8
  16. package/components/virtualization/GridVirtualScroller.js +2 -1
  17. package/components/virtualization/GridVirtualScrollerRenderZone.js +1 -1
  18. package/constants/dataGridPropsDefaultValues.js +2 -1
  19. package/constants/gridClasses.d.ts +0 -8
  20. package/constants/gridClasses.js +1 -1
  21. package/esm/DataGrid/useDataGridComponent.js +5 -4
  22. package/esm/components/GridRow.js +5 -2
  23. package/esm/components/GridRowDragAndDropOverlay.d.ts +7 -0
  24. package/esm/components/GridRowDragAndDropOverlay.js +66 -0
  25. package/esm/components/cell/GridActionsCell.d.ts +9 -0
  26. package/esm/components/cell/GridActionsCell.js +55 -34
  27. package/esm/components/cell/GridBooleanCell.js +0 -10
  28. package/esm/components/cell/GridCell.js +4 -10
  29. package/esm/components/columnHeaders/GridColumnHeaderItem.js +2 -2
  30. package/esm/components/columnSelection/GridCellCheckboxRenderer.js +37 -22
  31. package/esm/components/containers/GridRootStyles.js +17 -40
  32. package/esm/components/toolbarV8/Toolbar.js +1 -1
  33. package/esm/components/virtualization/GridVirtualScrollbar.d.ts +1 -0
  34. package/esm/components/virtualization/GridVirtualScrollbar.js +12 -7
  35. package/esm/components/virtualization/GridVirtualScroller.js +2 -1
  36. package/esm/components/virtualization/GridVirtualScrollerRenderZone.js +1 -1
  37. package/esm/constants/dataGridPropsDefaultValues.js +2 -1
  38. package/esm/constants/gridClasses.d.ts +0 -8
  39. package/esm/constants/gridClasses.js +1 -1
  40. package/esm/hooks/core/gridPropsSelectors.d.ts +2 -1
  41. package/esm/hooks/core/gridPropsSelectors.js +3 -0
  42. package/esm/hooks/core/pipeProcessing/gridPipeProcessingApi.d.ts +4 -3
  43. package/esm/hooks/core/useGridProps.js +8 -2
  44. package/esm/hooks/core/useGridVirtualizer.d.ts +80 -6
  45. package/esm/hooks/core/useGridVirtualizer.js +27 -12
  46. package/esm/hooks/features/columnGrouping/useGridColumnGrouping.js +6 -1
  47. package/esm/hooks/features/columnMenu/useGridColumnMenu.js +14 -4
  48. package/esm/hooks/features/columns/useGridColumnSpanning.js +9 -4
  49. package/esm/hooks/features/dataSource/useGridDataSourceBase.js +2 -2
  50. package/esm/hooks/features/dimensions/useGridDimensions.js +12 -6
  51. package/esm/hooks/features/editing/useGridCellEditing.js +1 -1
  52. package/esm/hooks/features/editing/useGridRowEditing.js +1 -1
  53. package/esm/hooks/features/export/serializers/csvSerializer.js +2 -4
  54. package/esm/hooks/features/export/useGridPrintExport.js +18 -18
  55. package/esm/hooks/features/filter/gridFilterUtils.js +5 -11
  56. package/esm/hooks/features/filter/index.d.ts +1 -1
  57. package/esm/hooks/features/filter/index.js +1 -1
  58. package/esm/hooks/features/filter/useGridFilter.d.ts +1 -1
  59. package/esm/hooks/features/filter/useGridFilter.js +3 -1
  60. package/esm/hooks/features/focus/useGridFocus.js +0 -1
  61. package/esm/hooks/features/keyboardNavigation/useGridKeyboardNavigation.d.ts +1 -1
  62. package/esm/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +189 -25
  63. package/esm/hooks/features/pagination/useGridPaginationMeta.js +3 -3
  64. package/esm/hooks/features/pagination/useGridPaginationModel.js +7 -4
  65. package/esm/hooks/features/pagination/useGridRowCount.js +31 -15
  66. package/esm/hooks/features/rowReorder/gridRowReorderInterfaces.d.ts +19 -0
  67. package/esm/hooks/features/rowReorder/gridRowReorderSelector.d.ts +20 -1
  68. package/esm/hooks/features/rowReorder/gridRowReorderSelector.js +19 -1
  69. package/esm/hooks/features/rowSelection/useGridRowSelection.js +17 -8
  70. package/esm/hooks/features/rowSelection/utils.d.ts +1 -0
  71. package/esm/hooks/features/rowSelection/utils.js +17 -4
  72. package/esm/hooks/features/rows/useGridRowSpanning.js +23 -60
  73. package/esm/hooks/features/rows/useGridRows.js +3 -1
  74. package/esm/hooks/features/rows/useGridRowsOverridableMethods.d.ts +1 -0
  75. package/esm/hooks/features/rows/useGridRowsOverridableMethods.js +57 -7
  76. package/esm/hooks/features/scroll/useGridScroll.js +2 -3
  77. package/esm/hooks/features/sorting/gridSortingUtils.js +1 -3
  78. package/esm/hooks/features/sorting/useGridSorting.d.ts +1 -1
  79. package/esm/hooks/features/sorting/useGridSorting.js +3 -1
  80. package/esm/hooks/features/virtualization/useGridVirtualization.js +24 -5
  81. package/esm/hooks/utils/useGridEvent.js +6 -2
  82. package/esm/hooks/utils/useGridSelector.js +2 -4
  83. package/esm/hooks/utils/useRunOncePerLoop.d.ts +4 -1
  84. package/esm/hooks/utils/useRunOncePerLoop.js +28 -18
  85. package/esm/index.js +1 -1
  86. package/esm/internals/index.d.ts +5 -4
  87. package/esm/internals/index.js +3 -3
  88. package/esm/material/index.js +1 -4
  89. package/esm/models/api/gridRowApi.d.ts +14 -1
  90. package/esm/models/api/index.d.ts +1 -1
  91. package/esm/models/api/index.js +0 -1
  92. package/esm/models/colDef/gridColDef.d.ts +14 -0
  93. package/esm/models/configuration/gridConfiguration.d.ts +2 -2
  94. package/esm/models/configuration/gridRowConfiguration.d.ts +6 -5
  95. package/esm/models/events/gridEventLookup.d.ts +5 -0
  96. package/esm/models/gridStateCommunity.d.ts +1 -1
  97. package/esm/models/params/gridCellParams.d.ts +0 -10
  98. package/esm/models/props/DataGridProps.d.ts +13 -6
  99. package/esm/utils/keyboardUtils.d.ts +1 -8
  100. package/esm/utils/keyboardUtils.js +0 -7
  101. package/hooks/core/gridPropsSelectors.d.ts +2 -1
  102. package/hooks/core/gridPropsSelectors.js +4 -1
  103. package/hooks/core/pipeProcessing/gridPipeProcessingApi.d.ts +4 -3
  104. package/hooks/core/useGridProps.js +8 -2
  105. package/hooks/core/useGridVirtualizer.d.ts +80 -6
  106. package/hooks/core/useGridVirtualizer.js +26 -11
  107. package/hooks/features/columnGrouping/useGridColumnGrouping.js +6 -1
  108. package/hooks/features/columnMenu/useGridColumnMenu.js +14 -4
  109. package/hooks/features/columns/useGridColumnSpanning.js +9 -4
  110. package/hooks/features/dataSource/useGridDataSourceBase.js +2 -2
  111. package/hooks/features/dimensions/useGridDimensions.js +12 -6
  112. package/hooks/features/editing/useGridCellEditing.js +1 -1
  113. package/hooks/features/editing/useGridRowEditing.js +1 -1
  114. package/hooks/features/export/serializers/csvSerializer.js +2 -4
  115. package/hooks/features/export/useGridPrintExport.js +18 -18
  116. package/hooks/features/filter/gridFilterUtils.js +5 -11
  117. package/hooks/features/filter/index.d.ts +1 -1
  118. package/hooks/features/filter/index.js +6 -0
  119. package/hooks/features/filter/useGridFilter.d.ts +1 -1
  120. package/hooks/features/filter/useGridFilter.js +3 -1
  121. package/hooks/features/focus/useGridFocus.js +0 -1
  122. package/hooks/features/keyboardNavigation/useGridKeyboardNavigation.d.ts +1 -1
  123. package/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +189 -25
  124. package/hooks/features/pagination/useGridPaginationMeta.js +2 -2
  125. package/hooks/features/pagination/useGridPaginationModel.js +7 -4
  126. package/hooks/features/pagination/useGridRowCount.js +30 -14
  127. package/hooks/features/rowReorder/gridRowReorderInterfaces.d.ts +19 -0
  128. package/hooks/features/rowReorder/gridRowReorderSelector.d.ts +20 -1
  129. package/hooks/features/rowReorder/gridRowReorderSelector.js +20 -2
  130. package/hooks/features/rowSelection/useGridRowSelection.js +17 -8
  131. package/hooks/features/rowSelection/utils.d.ts +1 -0
  132. package/hooks/features/rowSelection/utils.js +16 -3
  133. package/hooks/features/rows/useGridRowSpanning.js +23 -60
  134. package/hooks/features/rows/useGridRows.js +3 -1
  135. package/hooks/features/rows/useGridRowsOverridableMethods.d.ts +1 -0
  136. package/hooks/features/rows/useGridRowsOverridableMethods.js +57 -7
  137. package/hooks/features/scroll/useGridScroll.js +2 -3
  138. package/hooks/features/sorting/gridSortingUtils.js +1 -3
  139. package/hooks/features/sorting/useGridSorting.d.ts +1 -1
  140. package/hooks/features/sorting/useGridSorting.js +3 -1
  141. package/hooks/features/virtualization/useGridVirtualization.js +24 -5
  142. package/hooks/utils/useGridEvent.js +6 -2
  143. package/hooks/utils/useGridSelector.js +2 -4
  144. package/hooks/utils/useRunOncePerLoop.d.ts +4 -1
  145. package/hooks/utils/useRunOncePerLoop.js +27 -18
  146. package/index.js +1 -1
  147. package/internals/index.d.ts +5 -4
  148. package/internals/index.js +16 -9
  149. package/material/index.js +1 -4
  150. package/models/api/gridRowApi.d.ts +14 -1
  151. package/models/api/index.d.ts +1 -1
  152. package/models/api/index.js +0 -11
  153. package/models/colDef/gridColDef.d.ts +14 -0
  154. package/models/configuration/gridConfiguration.d.ts +2 -2
  155. package/models/configuration/gridRowConfiguration.d.ts +6 -5
  156. package/models/events/gridEventLookup.d.ts +5 -0
  157. package/models/gridStateCommunity.d.ts +1 -1
  158. package/models/params/gridCellParams.d.ts +0 -10
  159. package/models/props/DataGridProps.d.ts +13 -6
  160. package/package.json +3 -3
  161. package/utils/keyboardUtils.d.ts +1 -8
  162. package/utils/keyboardUtils.js +1 -13
@@ -106,15 +106,32 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
106
106
  // There is one exception for the checkBoxHeader
107
107
  return;
108
108
  }
109
+ if (!isNavigationKey(event.key) && event.key !== 'Tab') {
110
+ return;
111
+ }
112
+
113
+ // Tab key is only allowed if tabbing is enabled for header/all, or if we are tabbing to the content area while `tabNavigation` is enabled for content only
114
+ if (event.key === 'Tab' && (props.tabNavigation === 'none' || props.tabNavigation === 'content' && event.shiftKey)) {
115
+ return;
116
+ }
109
117
  const currentPageRows = getCurrentPageRows();
110
118
  const viewportPageSize = apiRef.current.getViewportPageSize();
111
119
  const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0;
112
120
  const firstRowIndexInPage = currentPageRows.length > 0 ? 0 : null;
113
- const lastRowIndexInPage = currentPageRows.length - 1;
121
+ const lastRowIndexInPage = currentPageRows.length > 0 ? currentPageRows.length - 1 : null;
114
122
  const firstColIndex = 0;
115
- const lastColIndex = gridVisibleColumnDefinitionsSelector(apiRef).length - 1;
123
+ const lastColIndex = Math.max(0, gridVisibleColumnDefinitionsSelector(apiRef).length - 1);
116
124
  const columnGroupMaxDepth = gridColumnGroupsHeaderMaxDepthSelector(apiRef);
117
125
  let shouldPreventDefault = true;
126
+
127
+ // If we are tabbing inside the header area while only content `tabNavigation` is enabled, go to the first cell
128
+ if (event.key === 'Tab' && props.tabNavigation === 'content' && !event.shiftKey) {
129
+ if (firstRowIndexInPage !== null) {
130
+ goToCell(firstColIndex, getRowIdFromIndex(firstRowIndexInPage));
131
+ event.preventDefault();
132
+ }
133
+ return;
134
+ }
118
135
  switch (event.key) {
119
136
  case 'ArrowDown':
120
137
  {
@@ -182,6 +199,36 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
182
199
  }
183
200
  break;
184
201
  }
202
+ case 'Tab':
203
+ {
204
+ if (event.shiftKey) {
205
+ // if the key is pressed on the first column header, Shift+Tab should go to the group header if it is there or allow default behavior
206
+ if (colIndexBefore === firstColIndex) {
207
+ if (columnGroupMaxDepth > 0) {
208
+ goToGroupHeader(lastColIndex, columnGroupMaxDepth - 1, event);
209
+ } else {
210
+ shouldPreventDefault = false;
211
+ }
212
+ } else {
213
+ goToHeader(colIndexBefore - 1, event);
214
+ }
215
+ } else if (colIndexBefore === lastColIndex) {
216
+ // the key is pressed at the last column header. go to the first header filter or the first cell
217
+ if (headerFilteringEnabled) {
218
+ goToHeaderFilter(firstColIndex, event);
219
+ }
220
+ // this point can be reached if tabbing is enabled for all content or just header.
221
+ // if it is not enabled for all content or there is no data, then do not focus on the first cell
222
+ else if (props.tabNavigation === 'all' && firstRowIndexInPage !== null) {
223
+ goToCell(firstColIndex, getRowIdFromIndex(firstRowIndexInPage));
224
+ } else {
225
+ shouldPreventDefault = false;
226
+ }
227
+ } else {
228
+ goToHeader(colIndexBefore + 1, event);
229
+ }
230
+ break;
231
+ }
185
232
  case ' ':
186
233
  {
187
234
  // prevent Space event from scrolling
@@ -195,27 +242,43 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
195
242
  if (shouldPreventDefault) {
196
243
  event.preventDefault();
197
244
  }
198
- }, [apiRef, getCurrentPageRows, headerFilteringEnabled, goToHeaderFilter, goToCell, getRowIdFromIndex, isRtl, goToHeader, goToGroupHeader]);
245
+ }, [apiRef, props.tabNavigation, getCurrentPageRows, headerFilteringEnabled, goToHeaderFilter, goToCell, getRowIdFromIndex, isRtl, goToHeader, goToGroupHeader]);
199
246
  const handleHeaderFilterKeyDown = React.useCallback((params, event) => {
200
247
  const isEditing = gridHeaderFilteringEditFieldSelector(apiRef) === params.field;
201
248
  const isHeaderMenuOpen = gridHeaderFilteringMenuSelector(apiRef) === params.field;
202
- if (isEditing || isHeaderMenuOpen || !isNavigationKey(event.key)) {
249
+ if (isHeaderMenuOpen || isEditing && event.key !== 'Tab') {
250
+ return;
251
+ }
252
+ if (!isNavigationKey(event.key) && event.key !== 'Tab') {
253
+ return;
254
+ }
255
+
256
+ // Tab key is only allowed if tabbing is enabled for header/all, or if we are tabbing to the content area while `tabNavigation` is enabled for content only
257
+ if (event.key === 'Tab' && (props.tabNavigation === 'none' || props.tabNavigation === 'content' && event.shiftKey)) {
203
258
  return;
204
259
  }
205
260
  const currentPageRows = getCurrentPageRows();
206
261
  const viewportPageSize = apiRef.current.getViewportPageSize();
207
262
  const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0;
208
- const firstRowIndexInPage = 0;
209
- const lastRowIndexInPage = currentPageRows.length - 1;
263
+ const firstRowIndexInPage = currentPageRows.length > 0 ? 0 : null;
264
+ const lastRowIndexInPage = currentPageRows.length > 0 ? currentPageRows.length - 1 : null;
210
265
  const firstColIndex = 0;
211
- const lastColIndex = gridVisibleColumnDefinitionsSelector(apiRef).length - 1;
266
+ const lastColIndex = Math.max(0, gridVisibleColumnDefinitionsSelector(apiRef).length - 1);
212
267
  let shouldPreventDefault = true;
268
+
269
+ // If we are tabbing inside the header area while only content `tabNavigation` is enabled, go to the first cell
270
+ if (event.key === 'Tab' && props.tabNavigation === 'content' && !event.shiftKey) {
271
+ if (firstRowIndexInPage !== null) {
272
+ goToCell(firstColIndex, getRowIdFromIndex(firstRowIndexInPage));
273
+ event.preventDefault();
274
+ }
275
+ return;
276
+ }
213
277
  switch (event.key) {
214
278
  case 'ArrowDown':
215
279
  {
216
- const rowId = getRowIdFromIndex(firstRowIndexInPage);
217
- if (firstRowIndexInPage !== null && rowId != null) {
218
- goToCell(colIndexBefore, rowId);
280
+ if (firstRowIndexInPage !== null) {
281
+ goToCell(colIndexBefore, getRowIdFromIndex(firstRowIndexInPage));
219
282
  }
220
283
  break;
221
284
  }
@@ -269,6 +332,29 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
269
332
  goToHeaderFilter(lastColIndex, event);
270
333
  break;
271
334
  }
335
+ case 'Tab':
336
+ {
337
+ if (event.shiftKey) {
338
+ // if the key is pressed on the first header filter, Shift+Tab should go to the column header
339
+ if (colIndexBefore === firstColIndex) {
340
+ goToHeader(lastColIndex, event);
341
+ } else {
342
+ goToHeaderFilter(colIndexBefore - 1, event);
343
+ }
344
+ } else if (colIndexBefore === lastColIndex) {
345
+ // the key is pressed at the last header filter.
346
+ // this point can be reached if `tabNavigation` is enabled for all content or just header.
347
+ // if it is not enabled for all content or there is no data, then do not focus on the first cell
348
+ if (props.tabNavigation === 'all' && firstRowIndexInPage !== null) {
349
+ goToCell(firstColIndex, getRowIdFromIndex(firstRowIndexInPage));
350
+ } else {
351
+ shouldPreventDefault = false;
352
+ }
353
+ } else {
354
+ goToHeaderFilter(colIndexBefore + 1, event);
355
+ }
356
+ break;
357
+ }
272
358
  case ' ':
273
359
  {
274
360
  // prevent Space event from scrolling
@@ -282,7 +368,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
282
368
  if (shouldPreventDefault) {
283
369
  event.preventDefault();
284
370
  }
285
- }, [apiRef, getCurrentPageRows, goToHeaderFilter, isRtl, goToHeader, goToCell, getRowIdFromIndex]);
371
+ }, [apiRef, props.tabNavigation, getCurrentPageRows, goToHeaderFilter, isRtl, goToHeader, goToCell, getRowIdFromIndex]);
286
372
  const handleColumnGroupHeaderKeyDown = React.useCallback((params, event) => {
287
373
  const focusedColumnGroup = gridFocusColumnGroupHeaderSelector(apiRef);
288
374
  if (focusedColumnGroup === null) {
@@ -297,15 +383,32 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
297
383
  depth,
298
384
  maxDepth
299
385
  } = params;
386
+ if (!isNavigationKey(event.key) && event.key !== 'Tab') {
387
+ return;
388
+ }
389
+
390
+ // Tab key is only allowed if tabbing is enabled for header/all, or if we are tabbing to the content area while `tabNavigation` is enabled for content only
391
+ if (event.key === 'Tab' && (props.tabNavigation === 'none' || props.tabNavigation === 'content' && event.shiftKey)) {
392
+ return;
393
+ }
300
394
  const currentPageRows = getCurrentPageRows();
301
395
  const viewportPageSize = apiRef.current.getViewportPageSize();
302
396
  const currentColIndex = apiRef.current.getColumnIndex(currentField);
303
397
  const colIndexBefore = currentField ? apiRef.current.getColumnIndex(currentField) : 0;
304
- const firstRowIndexInPage = 0;
305
- const lastRowIndexInPage = currentPageRows.length - 1;
398
+ const firstRowIndexInPage = currentPageRows.length > 0 ? 0 : null;
399
+ const lastRowIndexInPage = currentPageRows.length > 0 ? currentPageRows.length - 1 : null;
306
400
  const firstColIndex = 0;
307
- const lastColIndex = gridVisibleColumnDefinitionsSelector(apiRef).length - 1;
401
+ const lastColIndex = Math.max(0, gridVisibleColumnDefinitionsSelector(apiRef).length - 1);
308
402
  let shouldPreventDefault = true;
403
+
404
+ // If we are tabbing inside the header area while only content `tabNavigation` is enabled, go to the first cell
405
+ if (event.key === 'Tab' && props.tabNavigation === 'content' && !event.shiftKey) {
406
+ if (firstRowIndexInPage !== null) {
407
+ goToCell(firstColIndex, getRowIdFromIndex(firstRowIndexInPage));
408
+ event.preventDefault();
409
+ }
410
+ return;
411
+ }
309
412
  switch (event.key) {
310
413
  case 'ArrowDown':
311
414
  {
@@ -356,6 +459,37 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
356
459
  goToGroupHeader(lastColIndex, currentDepth, event);
357
460
  break;
358
461
  }
462
+ case 'Tab':
463
+ {
464
+ if (event.shiftKey) {
465
+ const remainingLeftColumns = fields.indexOf(currentField);
466
+ const targetColIndex = currentColIndex - remainingLeftColumns - 1;
467
+ if (targetColIndex < firstColIndex) {
468
+ if (depth === 0) {
469
+ // if the key is pressed on the first column group header at the top level, Shift+Tab should allow default behavior
470
+ shouldPreventDefault = false;
471
+ } else {
472
+ // Navigate to last column group header at the previous depth
473
+ goToGroupHeader(lastColIndex, currentDepth - 1, event);
474
+ }
475
+ } else {
476
+ goToGroupHeader(targetColIndex, currentDepth, event);
477
+ }
478
+ } else {
479
+ const remainingRightColumns = fields.length - fields.indexOf(currentField) - 1;
480
+ const targetColIndex = currentColIndex + remainingRightColumns + 1;
481
+ if (targetColIndex > lastColIndex && depth === maxDepth - 1) {
482
+ // the key is pressed at the last column group header at the deepest level. go to the first column header
483
+ goToHeader(firstColIndex, event);
484
+ } else if (targetColIndex > lastColIndex) {
485
+ // go down a depth level
486
+ goToGroupHeader(firstColIndex, currentDepth + 1, event);
487
+ } else {
488
+ goToGroupHeader(targetColIndex, currentDepth, event);
489
+ }
490
+ }
491
+ break;
492
+ }
359
493
  case ' ':
360
494
  {
361
495
  // prevent Space event from scrolling
@@ -369,7 +503,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
369
503
  if (shouldPreventDefault) {
370
504
  event.preventDefault();
371
505
  }
372
- }, [apiRef, getCurrentPageRows, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex]);
506
+ }, [apiRef, props.tabNavigation, getCurrentPageRows, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex]);
373
507
  const handleCellKeyDown = React.useCallback((params, event) => {
374
508
  // Ignore portal
375
509
  if (isEventTargetInPortal(event)) {
@@ -378,7 +512,12 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
378
512
 
379
513
  // Get the most recent params because the cell mode may have changed by another listener
380
514
  const cellParams = apiRef.current.getCellParams(params.id, params.field);
381
- if (cellParams.cellMode === GridCellModes.Edit || !isNavigationKey(event.key)) {
515
+ if (cellParams.cellMode === GridCellModes.Edit || !isNavigationKey(event.key) && event.key !== 'Tab') {
516
+ return;
517
+ }
518
+
519
+ // Tab key is only allowed if tabbing is enabled or if we are tabbing to the header area while `tabNavigation` is enabled for header only (with Shift+Tab)
520
+ if (event.key === 'Tab' && (props.tabNavigation === 'none' || props.tabNavigation === 'header' && !event.shiftKey)) {
382
521
  return;
383
522
  }
384
523
  const canUpdateFocus = apiRef.current.unstable_applyPipeProcessors('canUpdateFocus', true, {
@@ -392,14 +531,22 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
392
531
  if (currentPageRows.length === 0) {
393
532
  return;
394
533
  }
534
+ const previousAreaNavigationFn = headerFilteringEnabled ? goToHeaderFilter : goToHeader;
395
535
  const viewportPageSize = apiRef.current.getViewportPageSize();
396
536
  const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0;
397
537
  const rowIndexBefore = currentPageRows.findIndex(row => row.id === params.id);
398
538
  const firstRowIndexInPage = 0;
399
539
  const lastRowIndexInPage = currentPageRows.length - 1;
400
540
  const firstColIndex = 0;
401
- const lastColIndex = gridVisibleColumnDefinitionsSelector(apiRef).length - 1;
541
+ const lastColIndex = Math.max(0, gridVisibleColumnDefinitionsSelector(apiRef).length - 1);
402
542
  let shouldPreventDefault = true;
543
+
544
+ // If we are tabbing inside the content area while only header `tabNavigation` is enabled, go to the last header filter or column header
545
+ if (event.key === 'Tab' && props.tabNavigation === 'header' && event.shiftKey) {
546
+ previousAreaNavigationFn(lastColIndex, event);
547
+ event.preventDefault();
548
+ return;
549
+ }
403
550
  switch (event.key) {
404
551
  case 'ArrowDown':
405
552
  {
@@ -413,10 +560,8 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
413
560
  {
414
561
  if (rowIndexBefore > firstRowIndexInPage) {
415
562
  goToCell(colIndexBefore, getRowIdFromIndex(rowIndexBefore - 1));
416
- } else if (headerFilteringEnabled) {
417
- goToHeaderFilter(colIndexBefore, event);
418
563
  } else {
419
- goToHeader(colIndexBefore, event);
564
+ previousAreaNavigationFn(colIndexBefore, event);
420
565
  }
421
566
  break;
422
567
  }
@@ -448,10 +593,29 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
448
593
  }
449
594
  case 'Tab':
450
595
  {
451
- // "Tab" is only triggered by the row / cell editing feature
452
- if (event.shiftKey && colIndexBefore > firstColIndex) {
453
- goToCell(colIndexBefore - 1, getRowIdFromIndex(rowIndexBefore), 'left');
454
- } else if (!event.shiftKey && colIndexBefore < lastColIndex) {
596
+ if (event.shiftKey) {
597
+ // if the key is pressed on the first cell, Shift+Tab should go to the last column header
598
+ // same is if `tabNavigation` is enabled for header only
599
+ if (colIndexBefore === firstColIndex && rowIndexBefore === firstRowIndexInPage) {
600
+ if (props.tabNavigation === 'all') {
601
+ previousAreaNavigationFn(lastColIndex, event);
602
+ } else {
603
+ shouldPreventDefault = false;
604
+ }
605
+ } else if (colIndexBefore === firstColIndex) {
606
+ goToCell(lastColIndex, getRowIdFromIndex(rowIndexBefore - 1));
607
+ } else {
608
+ goToCell(colIndexBefore - 1, getRowIdFromIndex(rowIndexBefore), 'left');
609
+ }
610
+ } else if (colIndexBefore === lastColIndex) {
611
+ // the key is pressed at the last column. if it is also the last row, do not to anything to allow the default behavior
612
+ // otherwise, go to the first column of the next row
613
+ if (rowIndexBefore !== lastRowIndexInPage) {
614
+ goToCell(firstColIndex, getRowIdFromIndex(rowIndexBefore + 1));
615
+ } else {
616
+ shouldPreventDefault = false;
617
+ }
618
+ } else {
455
619
  goToCell(colIndexBefore + 1, getRowIdFromIndex(rowIndexBefore), 'right');
456
620
  }
457
621
  break;
@@ -515,7 +679,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
515
679
  if (shouldPreventDefault) {
516
680
  event.preventDefault();
517
681
  }
518
- }, [apiRef, getCurrentPageRows, isRtl, goToCell, getRowIdFromIndex, headerFilteringEnabled, goToHeaderFilter, goToHeader]);
682
+ }, [apiRef, props.tabNavigation, getCurrentPageRows, isRtl, goToCell, getRowIdFromIndex, headerFilteringEnabled, goToHeaderFilter, goToHeader]);
519
683
  const checkIfCanStartEditing = React.useCallback((initialValue, {
520
684
  event
521
685
  }) => {
@@ -2,12 +2,11 @@
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import * as React from 'react';
5
- import { useGridLogger, useGridSelector, useGridApiMethod } from "../../utils/index.js";
5
+ import { useGridLogger, useGridApiMethod } from "../../utils/index.js";
6
6
  import { useGridRegisterPipeProcessor } from "../../core/pipeProcessing/index.js";
7
7
  import { gridPaginationMetaSelector } from "./gridPaginationSelector.js";
8
8
  export const useGridPaginationMeta = (apiRef, props) => {
9
9
  const logger = useGridLogger(apiRef, 'useGridPaginationMeta');
10
- const paginationMeta = useGridSelector(apiRef, gridPaginationMetaSelector);
11
10
  apiRef.current.registerControlState({
12
11
  stateId: 'paginationMeta',
13
12
  propModel: props.paginationMeta,
@@ -20,6 +19,7 @@ export const useGridPaginationMeta = (apiRef, props) => {
20
19
  * API METHODS
21
20
  */
22
21
  const setPaginationMeta = React.useCallback(newPaginationMeta => {
22
+ const paginationMeta = gridPaginationMetaSelector(apiRef);
23
23
  if (paginationMeta === newPaginationMeta) {
24
24
  return;
25
25
  }
@@ -29,7 +29,7 @@ export const useGridPaginationMeta = (apiRef, props) => {
29
29
  meta: newPaginationMeta
30
30
  })
31
31
  }));
32
- }, [apiRef, logger, paginationMeta]);
32
+ }, [apiRef, logger]);
33
33
  const paginationMetaApi = {
34
34
  setPaginationMeta
35
35
  };
@@ -60,6 +60,7 @@ export const useGridPaginationModel = (apiRef, props) => {
60
60
  pageSize: currentModel.pageSize
61
61
  });
62
62
  }, [apiRef, logger]);
63
+ const debouncedSetPage = React.useMemo(() => debounce(setPage, 0), [setPage]);
63
64
  const setPageSize = React.useCallback(pageSize => {
64
65
  const currentModel = gridPaginationModelSelector(apiRef);
65
66
  if (pageSize === currentModel.pageSize) {
@@ -154,9 +155,11 @@ export const useGridPaginationModel = (apiRef, props) => {
154
155
  }
155
156
  const pageCount = gridPageCountSelector(apiRef);
156
157
  if (paginationModel.page > pageCount - 1) {
157
- apiRef.current.setPage(Math.max(0, pageCount - 1));
158
+ queueMicrotask(() => {
159
+ debouncedSetPage(Math.max(0, pageCount - 1));
160
+ });
158
161
  }
159
- }, [apiRef]);
162
+ }, [apiRef, debouncedSetPage]);
160
163
 
161
164
  /**
162
165
  * Goes to the first row of the grid
@@ -164,7 +167,7 @@ export const useGridPaginationModel = (apiRef, props) => {
164
167
  const navigateToStart = React.useCallback(() => {
165
168
  const paginationModel = gridPaginationModelSelector(apiRef);
166
169
  if (paginationModel.page !== 0) {
167
- apiRef.current.setPage(0);
170
+ debouncedSetPage(0);
168
171
  }
169
172
 
170
173
  // If the page was not changed it might be needed to scroll to the top
@@ -174,7 +177,7 @@ export const useGridPaginationModel = (apiRef, props) => {
174
177
  top: 0
175
178
  });
176
179
  }
177
- }, [apiRef]);
180
+ }, [apiRef, debouncedSetPage]);
178
181
  const debouncedNavigateToStart = React.useMemo(() => debounce(navigateToStart, 0), [navigateToStart]);
179
182
 
180
183
  /**
@@ -3,16 +3,13 @@
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import * as React from 'react';
5
5
  import useLazyRef from '@mui/utils/useLazyRef';
6
+ import { useStoreEffect } from '@mui/x-internals/store';
6
7
  import { gridFilteredTopLevelRowCountSelector } from "../filter/index.js";
7
- import { useGridLogger, useGridSelector, useGridApiMethod, useGridEvent } from "../../utils/index.js";
8
+ import { useGridLogger, useGridApiMethod, useGridEvent } from "../../utils/index.js";
8
9
  import { useGridRegisterPipeProcessor } from "../../core/pipeProcessing/index.js";
9
10
  import { gridPaginationRowCountSelector, gridPaginationMetaSelector, gridPaginationModelSelector } from "./gridPaginationSelector.js";
10
11
  export const useGridRowCount = (apiRef, props) => {
11
12
  const logger = useGridLogger(apiRef, 'useGridRowCount');
12
- const visibleTopLevelRowCount = useGridSelector(apiRef, gridFilteredTopLevelRowCountSelector);
13
- const rowCountState = useGridSelector(apiRef, gridPaginationRowCountSelector);
14
- const paginationMeta = useGridSelector(apiRef, gridPaginationMetaSelector);
15
- const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector);
16
13
  const previousPageSize = useLazyRef(() => gridPaginationModelSelector(apiRef).pageSize);
17
14
  apiRef.current.registerControlState({
18
15
  stateId: 'paginationRowCount',
@@ -26,6 +23,7 @@ export const useGridRowCount = (apiRef, props) => {
26
23
  * API METHODS
27
24
  */
28
25
  const setRowCount = React.useCallback(newRowCount => {
26
+ const rowCountState = gridPaginationRowCountSelector(apiRef);
29
27
  if (rowCountState === newRowCount) {
30
28
  return;
31
29
  }
@@ -35,7 +33,7 @@ export const useGridRowCount = (apiRef, props) => {
35
33
  rowCount: newRowCount
36
34
  })
37
35
  }));
38
- }, [apiRef, logger, rowCountState]);
36
+ }, [apiRef, logger]);
39
37
  const paginationRowCountApi = {
40
38
  setRowCount
41
39
  };
@@ -83,28 +81,46 @@ export const useGridRowCount = (apiRef, props) => {
83
81
  }
84
82
  if (model.pageSize !== previousPageSize.current) {
85
83
  previousPageSize.current = model.pageSize;
84
+ const rowCountState = gridPaginationRowCountSelector(apiRef);
86
85
  if (rowCountState === -1) {
87
86
  // Row count unknown and page size changed, reset the page
88
87
  apiRef.current.setPage(0);
89
88
  }
90
89
  }
91
- }, [props.paginationMode, previousPageSize, rowCountState, apiRef]);
90
+ }, [props.paginationMode, previousPageSize, apiRef]);
92
91
  useGridEvent(apiRef, 'paginationModelChange', handlePaginationModelChange);
93
92
 
94
93
  /**
95
94
  * EFFECTS
96
95
  */
97
96
  React.useEffect(() => {
98
- if (props.paginationMode === 'client') {
99
- apiRef.current.setRowCount(visibleTopLevelRowCount);
100
- } else if (props.rowCount != null) {
97
+ if (props.paginationMode === 'server' && props.rowCount != null) {
101
98
  apiRef.current.setRowCount(props.rowCount);
102
99
  }
103
- }, [apiRef, props.paginationMode, visibleTopLevelRowCount, props.rowCount]);
104
- const isLastPage = paginationMeta.hasNextPage === false;
105
- React.useEffect(() => {
106
- if (isLastPage && rowCountState === -1) {
100
+ }, [apiRef, props.paginationMode, props.rowCount]);
101
+ useStoreEffect(
102
+ // typings not supported currently, but methods work
103
+ apiRef.current.store, () => {
104
+ const isLastPage = gridPaginationMetaSelector(apiRef).hasNextPage === false;
105
+ if (isLastPage) {
106
+ return true;
107
+ }
108
+ if (props.paginationMode === 'client') {
109
+ return gridFilteredTopLevelRowCountSelector(apiRef);
110
+ }
111
+ return undefined;
112
+ }, (_, isLastPageOrRowCount) => {
113
+ if (isLastPageOrRowCount === true && gridPaginationRowCountSelector(apiRef) !== -1) {
114
+ const visibleTopLevelRowCount = gridFilteredTopLevelRowCountSelector(apiRef);
115
+ const paginationModel = gridPaginationModelSelector(apiRef);
107
116
  apiRef.current.setRowCount(paginationModel.pageSize * paginationModel.page + visibleTopLevelRowCount);
117
+ } else if (typeof isLastPageOrRowCount === 'number') {
118
+ apiRef.current.setRowCount(isLastPageOrRowCount);
119
+ }
120
+ });
121
+ React.useEffect(() => {
122
+ if (props.paginationMode === 'client') {
123
+ apiRef.current.setRowCount(gridFilteredTopLevelRowCountSelector(apiRef));
108
124
  }
109
- }, [apiRef, visibleTopLevelRowCount, isLastPage, rowCountState, paginationModel]);
125
+ }, [apiRef, props.paginationMode]);
110
126
  };
@@ -1,3 +1,5 @@
1
+ import type { GridRowId } from "../../../models/gridRows.js";
2
+ import type { RowReorderDropPosition } from "../../../models/api/gridRowApi.js";
1
3
  /**
2
4
  * The row reorder state.
3
5
  */
@@ -6,4 +8,21 @@ export interface GridRowReorderState {
6
8
  * Whether a row drag operation is currently active.
7
9
  */
8
10
  isActive: boolean;
11
+ /**
12
+ * The row ID being dragged.
13
+ */
14
+ draggedRowId: GridRowId | null;
15
+ /**
16
+ * The current drop target information.
17
+ */
18
+ dropTarget?: {
19
+ /**
20
+ * The row ID where the drop indicator should be shown.
21
+ */
22
+ rowId: GridRowId;
23
+ /**
24
+ * The position of the drop indicator relative to the target row.
25
+ */
26
+ position: RowReorderDropPosition;
27
+ };
9
28
  }
@@ -1,5 +1,24 @@
1
1
  import { GridStateCommunity } from "../../../models/gridStateCommunity.js";
2
+ import type { GridRowId } from "../../../models/gridRows.js";
2
3
  export declare const gridRowReorderStateSelector: import("@mui/x-data-grid").OutputSelector<GridStateCommunity, unknown, import("./gridRowReorderInterfaces.js").GridRowReorderState>;
3
4
  export declare const gridIsRowDragActiveSelector: (args_0: import("react").RefObject<{
4
5
  state: GridStateCommunity;
5
- } | null>) => boolean;
6
+ } | null>) => boolean;
7
+ export declare const gridRowDropTargetSelector: (args_0: import("react").RefObject<{
8
+ state: GridStateCommunity;
9
+ } | null>) => {
10
+ rowId: GridRowId;
11
+ position: import("../../../internals/index.js").RowReorderDropPosition;
12
+ } | {
13
+ rowId: null;
14
+ position: null;
15
+ };
16
+ export declare const gridRowDropTargetRowIdSelector: (args_0: import("react").RefObject<{
17
+ state: GridStateCommunity;
18
+ } | null>) => GridRowId | null;
19
+ export declare const gridRowDropPositionSelector: (args_0: import("react").RefObject<{
20
+ state: GridStateCommunity;
21
+ } | null>, rowId: GridRowId) => import("../../../internals/index.js").RowReorderDropPosition | null;
22
+ export declare const gridDraggedRowIdSelector: (args_0: import("react").RefObject<{
23
+ state: GridStateCommunity;
24
+ } | null>) => GridRowId | null;
@@ -1,3 +1,21 @@
1
1
  import { createRootSelector, createSelector } from "../../../utils/createSelector.js";
2
2
  export const gridRowReorderStateSelector = createRootSelector(state => state.rowReorder);
3
- export const gridIsRowDragActiveSelector = createSelector(gridRowReorderStateSelector, rowReorder => rowReorder?.isActive ?? false);
3
+ export const gridIsRowDragActiveSelector = createSelector(gridRowReorderStateSelector, rowReorder => rowReorder?.isActive ?? false);
4
+
5
+ // Selector for the entire drop target state
6
+ export const gridRowDropTargetSelector = createSelector(gridRowReorderStateSelector, rowReorder => rowReorder?.dropTarget ?? {
7
+ rowId: null,
8
+ position: null
9
+ });
10
+ export const gridRowDropTargetRowIdSelector = createSelector(gridRowDropTargetSelector, dropTarget => dropTarget.rowId ?? null);
11
+
12
+ // Selector for a specific row's drop position
13
+ export const gridRowDropPositionSelector = createSelector(gridRowDropTargetSelector, (dropTarget, rowId) => {
14
+ if (dropTarget.rowId === rowId) {
15
+ return dropTarget.position;
16
+ }
17
+ return null;
18
+ });
19
+
20
+ // Selector for the dragged row ID
21
+ export const gridDraggedRowIdSelector = createSelector(gridRowReorderStateSelector, rowReorder => rowReorder?.draggedRowId ?? null);