@mui/x-data-grid 8.18.0 → 8.19.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 (106) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/DataGrid/useDataGridComponent.js +4 -3
  3. package/components/GridRow.js +1 -1
  4. package/components/cell/GridActionsCell.d.ts +9 -0
  5. package/components/cell/GridActionsCell.js +54 -34
  6. package/components/cell/GridBooleanCell.js +0 -10
  7. package/components/cell/GridCell.js +4 -10
  8. package/components/columnHeaders/GridColumnHeaderItem.js +2 -2
  9. package/components/columnSelection/GridCellCheckboxRenderer.js +37 -22
  10. package/components/containers/GridRootStyles.js +1 -1
  11. package/components/toolbarV8/Toolbar.js +1 -1
  12. package/components/virtualization/GridVirtualScrollbar.d.ts +1 -0
  13. package/components/virtualization/GridVirtualScrollbar.js +13 -8
  14. package/components/virtualization/GridVirtualScroller.js +2 -1
  15. package/components/virtualization/GridVirtualScrollerRenderZone.js +1 -1
  16. package/constants/dataGridPropsDefaultValues.js +2 -1
  17. package/esm/DataGrid/useDataGridComponent.js +5 -4
  18. package/esm/components/GridRow.js +1 -1
  19. package/esm/components/cell/GridActionsCell.d.ts +9 -0
  20. package/esm/components/cell/GridActionsCell.js +55 -34
  21. package/esm/components/cell/GridBooleanCell.js +0 -10
  22. package/esm/components/cell/GridCell.js +4 -10
  23. package/esm/components/columnHeaders/GridColumnHeaderItem.js +2 -2
  24. package/esm/components/columnSelection/GridCellCheckboxRenderer.js +37 -22
  25. package/esm/components/containers/GridRootStyles.js +1 -1
  26. package/esm/components/toolbarV8/Toolbar.js +1 -1
  27. package/esm/components/virtualization/GridVirtualScrollbar.d.ts +1 -0
  28. package/esm/components/virtualization/GridVirtualScrollbar.js +12 -7
  29. package/esm/components/virtualization/GridVirtualScroller.js +2 -1
  30. package/esm/components/virtualization/GridVirtualScrollerRenderZone.js +1 -1
  31. package/esm/constants/dataGridPropsDefaultValues.js +2 -1
  32. package/esm/hooks/core/gridPropsSelectors.d.ts +2 -1
  33. package/esm/hooks/core/gridPropsSelectors.js +3 -0
  34. package/esm/hooks/core/useGridProps.js +8 -2
  35. package/esm/hooks/core/useGridVirtualizer.d.ts +80 -6
  36. package/esm/hooks/core/useGridVirtualizer.js +27 -12
  37. package/esm/hooks/features/columnGrouping/useGridColumnGrouping.js +6 -1
  38. package/esm/hooks/features/columnMenu/useGridColumnMenu.js +14 -4
  39. package/esm/hooks/features/columns/useGridColumnSpanning.js +9 -4
  40. package/esm/hooks/features/dimensions/useGridDimensions.js +12 -6
  41. package/esm/hooks/features/export/useGridPrintExport.js +18 -18
  42. package/esm/hooks/features/filter/useGridFilter.d.ts +1 -1
  43. package/esm/hooks/features/filter/useGridFilter.js +3 -1
  44. package/esm/hooks/features/focus/useGridFocus.js +0 -1
  45. package/esm/hooks/features/keyboardNavigation/useGridKeyboardNavigation.d.ts +1 -1
  46. package/esm/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +189 -25
  47. package/esm/hooks/features/pagination/useGridPaginationMeta.js +3 -3
  48. package/esm/hooks/features/pagination/useGridPaginationModel.js +7 -4
  49. package/esm/hooks/features/pagination/useGridRowCount.js +31 -15
  50. package/esm/hooks/features/rowSelection/useGridRowSelection.js +8 -7
  51. package/esm/hooks/features/rowSelection/utils.d.ts +1 -0
  52. package/esm/hooks/features/rowSelection/utils.js +17 -4
  53. package/esm/hooks/features/rows/useGridRowSpanning.js +23 -60
  54. package/esm/hooks/features/scroll/useGridScroll.js +2 -3
  55. package/esm/hooks/features/sorting/useGridSorting.d.ts +1 -1
  56. package/esm/hooks/features/sorting/useGridSorting.js +3 -1
  57. package/esm/hooks/features/virtualization/useGridVirtualization.js +24 -5
  58. package/esm/hooks/utils/useGridEvent.js +6 -2
  59. package/esm/hooks/utils/useRunOncePerLoop.d.ts +4 -1
  60. package/esm/hooks/utils/useRunOncePerLoop.js +28 -18
  61. package/esm/index.js +1 -1
  62. package/esm/models/colDef/gridColDef.d.ts +14 -0
  63. package/esm/models/events/gridEventLookup.d.ts +5 -0
  64. package/esm/models/gridStateCommunity.d.ts +1 -1
  65. package/esm/models/params/gridCellParams.d.ts +0 -10
  66. package/esm/models/props/DataGridProps.d.ts +13 -6
  67. package/esm/utils/keyboardUtils.d.ts +1 -8
  68. package/esm/utils/keyboardUtils.js +0 -7
  69. package/hooks/core/gridPropsSelectors.d.ts +2 -1
  70. package/hooks/core/gridPropsSelectors.js +4 -1
  71. package/hooks/core/useGridProps.js +8 -2
  72. package/hooks/core/useGridVirtualizer.d.ts +80 -6
  73. package/hooks/core/useGridVirtualizer.js +26 -11
  74. package/hooks/features/columnGrouping/useGridColumnGrouping.js +6 -1
  75. package/hooks/features/columnMenu/useGridColumnMenu.js +14 -4
  76. package/hooks/features/columns/useGridColumnSpanning.js +9 -4
  77. package/hooks/features/dimensions/useGridDimensions.js +12 -6
  78. package/hooks/features/export/useGridPrintExport.js +18 -18
  79. package/hooks/features/filter/useGridFilter.d.ts +1 -1
  80. package/hooks/features/filter/useGridFilter.js +3 -1
  81. package/hooks/features/focus/useGridFocus.js +0 -1
  82. package/hooks/features/keyboardNavigation/useGridKeyboardNavigation.d.ts +1 -1
  83. package/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +189 -25
  84. package/hooks/features/pagination/useGridPaginationMeta.js +2 -2
  85. package/hooks/features/pagination/useGridPaginationModel.js +7 -4
  86. package/hooks/features/pagination/useGridRowCount.js +30 -14
  87. package/hooks/features/rowSelection/useGridRowSelection.js +8 -7
  88. package/hooks/features/rowSelection/utils.d.ts +1 -0
  89. package/hooks/features/rowSelection/utils.js +16 -3
  90. package/hooks/features/rows/useGridRowSpanning.js +23 -60
  91. package/hooks/features/scroll/useGridScroll.js +2 -3
  92. package/hooks/features/sorting/useGridSorting.d.ts +1 -1
  93. package/hooks/features/sorting/useGridSorting.js +3 -1
  94. package/hooks/features/virtualization/useGridVirtualization.js +24 -5
  95. package/hooks/utils/useGridEvent.js +6 -2
  96. package/hooks/utils/useRunOncePerLoop.d.ts +4 -1
  97. package/hooks/utils/useRunOncePerLoop.js +27 -18
  98. package/index.js +1 -1
  99. package/models/colDef/gridColDef.d.ts +14 -0
  100. package/models/events/gridEventLookup.d.ts +5 -0
  101. package/models/gridStateCommunity.d.ts +1 -1
  102. package/models/params/gridCellParams.d.ts +0 -10
  103. package/models/props/DataGridProps.d.ts +13 -6
  104. package/package.json +3 -3
  105. package/utils/keyboardUtils.d.ts +1 -8
  106. 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
  };
@@ -62,7 +62,6 @@ export const useGridRowSelection = (apiRef, props) => {
62
62
  isRowSelectable: propIsRowSelectable
63
63
  } = props;
64
64
  const canHaveMultipleSelection = isMultipleRowSelectionEnabled(props);
65
- const tree = useGridSelector(apiRef, gridRowTreeSelector);
66
65
  const expandMouseRowRangeSelection = React.useCallback(id => {
67
66
  let endId = id;
68
67
  const startId = lastRowToggled.current ?? id;
@@ -128,6 +127,7 @@ export const useGridRowSelection = (apiRef, props) => {
128
127
  if (!apiRef.current.isRowSelectable(id)) {
129
128
  return;
130
129
  }
130
+ const tree = gridRowTreeSelector(apiRef);
131
131
  lastRowToggled.current = id;
132
132
  if (resetSelection) {
133
133
  logger.debug(`Setting selection for row ${id}`);
@@ -173,12 +173,13 @@ export const useGridRowSelection = (apiRef, props) => {
173
173
  apiRef.current.setRowSelectionModel(newSelectionModel, 'singleRowSelection');
174
174
  }
175
175
  }
176
- }, [apiRef, logger, applyAutoSelection, tree, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents, canHaveMultipleSelection]);
176
+ }, [apiRef, logger, applyAutoSelection, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents, canHaveMultipleSelection]);
177
177
  const selectRows = React.useCallback((ids, isSelected = true, resetSelection = false) => {
178
178
  logger.debug(`Setting selection for several rows`);
179
179
  if (props.rowSelection === false) {
180
180
  return;
181
181
  }
182
+ const tree = gridRowTreeSelector(apiRef);
182
183
  const selectableIds = new Set();
183
184
  for (let i = 0; i < ids.length; i += 1) {
184
185
  const id = ids[i];
@@ -239,7 +240,7 @@ export const useGridRowSelection = (apiRef, props) => {
239
240
  if (isSelectionValid) {
240
241
  apiRef.current.setRowSelectionModel(newSelectionModel, 'multipleRowsSelection');
241
242
  }
242
- }, [logger, applyAutoSelection, canHaveMultipleSelection, apiRef, tree, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents, props.rowSelection]);
243
+ }, [logger, applyAutoSelection, canHaveMultipleSelection, apiRef, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents, props.rowSelection]);
243
244
  const getPropagatedRowSelectionModel = React.useCallback(inputSelectionModel => {
244
245
  if (!isNestedData || !applyAutoSelection || inputSelectionModel.type === 'exclude' || inputSelectionModel.ids.size === 0 && inputSelectionModel.type === 'include') {
245
246
  return inputSelectionModel;
@@ -248,6 +249,7 @@ export const useGridRowSelection = (apiRef, props) => {
248
249
  type: inputSelectionModel.type,
249
250
  ids: new Set(inputSelectionModel.ids)
250
251
  };
252
+ const tree = gridRowTreeSelector(apiRef);
251
253
  const selectionManager = createRowSelectionManager(propagatedSelectionModel);
252
254
  const addRow = rowId => {
253
255
  selectionManager.select(rowId);
@@ -256,7 +258,7 @@ export const useGridRowSelection = (apiRef, props) => {
256
258
  findRowsToSelect(apiRef, tree, id, props.rowSelectionPropagation?.descendants ?? false, props.rowSelectionPropagation?.parents ?? false, addRow, selectionManager);
257
259
  }
258
260
  return propagatedSelectionModel;
259
- }, [apiRef, tree, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents, isNestedData, applyAutoSelection]);
261
+ }, [apiRef, props.rowSelectionPropagation?.descendants, props.rowSelectionPropagation?.parents, isNestedData, applyAutoSelection]);
260
262
  const selectRowRange = React.useCallback(({
261
263
  startId,
262
264
  endId
@@ -325,7 +327,7 @@ export const useGridRowSelection = (apiRef, props) => {
325
327
  if (!props.rowSelectionPropagation?.parents) {
326
328
  continue;
327
329
  }
328
- const node = tree[id];
330
+ const node = rowTree[id];
329
331
  if (node?.type === 'group') {
330
332
  const isAutoGenerated = node.isAutoGenerated;
331
333
  if (isAutoGenerated) {
@@ -366,7 +368,7 @@ export const useGridRowSelection = (apiRef, props) => {
366
368
  apiRef.current.setRowSelectionModel(newSelectionModel, 'multipleRowsSelection');
367
369
  }
368
370
  }
369
- }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, props.filterMode, tree, getRowsToBeSelected]);
371
+ }, [apiRef, isNestedData, props.rowSelectionPropagation?.parents, props.keepNonExistentRowsSelected, props.filterMode, getRowsToBeSelected]);
370
372
  const handleSingleRowSelection = React.useCallback((id, event) => {
371
373
  const hasCtrlKey = event.metaKey || event.ctrlKey;
372
374
 
@@ -530,7 +532,6 @@ export const useGridRowSelection = (apiRef, props) => {
530
532
  }
531
533
  apiRef.current.setRowSelectionModel(propRowSelectionModel);
532
534
  });
533
- useGridEvent(apiRef, 'sortedRowsSet', runIfRowSelectionIsEnabled(() => removeOutdatedSelection(true)));
534
535
  useGridEvent(apiRef, 'filteredRowsSet', runIfRowSelectionIsEnabled(() => removeOutdatedSelection()));
535
536
  useGridEvent(apiRef, 'rowClick', runIfRowSelectionIsEnabled(handleRowClick));
536
537
  useGridEvent(apiRef, 'rowSelectionCheckboxChange', runIfRowSelectionIsEnabled(handleRowSelectionCheckboxChange));
@@ -13,6 +13,7 @@ export declare const checkboxPropsSelector: (args_0: import("react").RefObject<{
13
13
  }) => {
14
14
  isIndeterminate: boolean;
15
15
  isChecked: boolean;
16
+ isSelectable: boolean;
16
17
  };
17
18
  export declare function isMultipleRowSelectionEnabled(props: Pick<DataGridProcessedProps, 'signature' | 'disableMultipleRowSelection' | 'checkboxSelection'>): boolean;
18
19
  export declare const findRowsToSelect: (apiRef: RefObject<GridPrivateApiCommunity>, tree: GridRowTreeConfig, selectedRow: GridRowId, autoSelectDescendants: boolean, autoSelectParents: boolean, addRow: (rowId: GridRowId) => void, rowSelectionManager?: RowSelectionManager) => void;
@@ -3,8 +3,10 @@ import { GRID_ROOT_GROUP_ID } from "../rows/gridRowsUtils.js";
3
3
  import { gridFilteredRowsLookupSelector } from "../filter/gridFilterSelector.js";
4
4
  import { gridSortedRowIdsSelector } from "../sorting/gridSortingSelector.js";
5
5
  import { gridRowSelectionManagerSelector } from "./gridRowSelectionSelector.js";
6
- import { gridRowTreeSelector } from "../rows/gridRowsSelector.js";
6
+ import { gridRowsLookupSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.js";
7
7
  import { createSelector } from "../../../utils/createSelector.js";
8
+ import { gridColumnDefinitionsSelector } from "../columns/index.js";
9
+ import { gridRowSelectableSelector } from "../../core/gridPropsSelectors.js";
8
10
  export const ROW_SELECTION_PROPAGATION_DEFAULT = {
9
11
  parents: true,
10
12
  descendants: true
@@ -27,15 +29,25 @@ function getGridRowGroupSelectableDescendants(apiRef, groupId) {
27
29
  }
28
30
  return descendants;
29
31
  }
30
- export const checkboxPropsSelector = createSelector(gridRowTreeSelector, gridFilteredRowsLookupSelector, gridRowSelectionManagerSelector, (rowTree, filteredRowsLookup, rowSelectionManager, {
32
+ export const checkboxPropsSelector = createSelector(gridColumnDefinitionsSelector, gridRowTreeSelector, gridFilteredRowsLookupSelector, gridRowSelectionManagerSelector, gridRowsLookupSelector, gridRowSelectableSelector, (columns, rowTree, filteredRowsLookup, rowSelectionManager, rowsLookup, isRowSelectable, {
31
33
  groupId,
32
34
  autoSelectParents
33
35
  }) => {
34
36
  const groupNode = rowTree[groupId];
37
+ const rowParams = {
38
+ id: groupId,
39
+ row: rowsLookup[groupId],
40
+ columns
41
+ };
42
+ let isSelectable = true;
43
+ if (typeof isRowSelectable === 'function') {
44
+ isSelectable = isRowSelectable(rowParams);
45
+ }
35
46
  if (!groupNode || groupNode.type !== 'group' || rowSelectionManager.has(groupId)) {
36
47
  return {
37
48
  isIndeterminate: false,
38
- isChecked: rowSelectionManager.has(groupId)
49
+ isChecked: rowSelectionManager.has(groupId),
50
+ isSelectable
39
51
  };
40
52
  }
41
53
  let hasSelectedDescendant = false;
@@ -60,7 +72,8 @@ export const checkboxPropsSelector = createSelector(gridRowTreeSelector, gridFil
60
72
  traverseDescendants(groupId);
61
73
  return {
62
74
  isIndeterminate: hasSelectedDescendant && hasUnSelectedDescendant,
63
- isChecked: autoSelectParents ? hasSelectedDescendant && !hasUnSelectedDescendant : false
75
+ isChecked: autoSelectParents ? hasSelectedDescendant && !hasUnSelectedDescendant : false,
76
+ isSelectable
64
77
  };
65
78
  });
66
79
  export function isMultipleRowSelectionEnabled(props) {