@mui/x-data-grid 7.24.1 → 7.25.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 (118) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/DataGrid/DataGrid.js +1 -7
  3. package/components/GridRow.d.ts +0 -1
  4. package/components/GridRow.js +25 -19
  5. package/components/cell/GridCell.d.ts +9 -6
  6. package/components/cell/GridCell.js +29 -52
  7. package/components/containers/GridRootStyles.js +135 -37
  8. package/components/toolbar/GridToolbarColumnsButton.js +5 -4
  9. package/components/toolbar/GridToolbarDensitySelector.js +2 -2
  10. package/components/toolbar/GridToolbarExportContainer.js +2 -2
  11. package/components/toolbar/GridToolbarFilterButton.js +4 -3
  12. package/components/virtualization/GridMainContainer.d.ts +10 -0
  13. package/components/virtualization/GridMainContainer.js +10 -2
  14. package/components/virtualization/GridVirtualScrollbar.d.ts +4 -0
  15. package/components/virtualization/GridVirtualScrollbar.js +5 -5
  16. package/components/virtualization/GridVirtualScroller.js +18 -5
  17. package/components/virtualization/GridVirtualScrollerContent.js +11 -2
  18. package/hooks/features/columnResize/useGridColumnResize.js +2 -1
  19. package/hooks/features/columns/useGridColumns.js +7 -3
  20. package/hooks/features/dimensions/useGridDimensions.js +6 -8
  21. package/hooks/features/editing/gridEditingSelectors.d.ts +6 -0
  22. package/hooks/features/editing/gridEditingSelectors.js +7 -1
  23. package/hooks/features/editing/index.d.ts +1 -1
  24. package/hooks/features/editing/index.js +1 -1
  25. package/hooks/features/editing/useGridCellEditing.js +3 -3
  26. package/hooks/features/editing/useGridRowEditing.js +5 -6
  27. package/hooks/features/focus/useGridFocus.js +3 -2
  28. package/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +24 -16
  29. package/hooks/features/keyboardNavigation/utils.d.ts +1 -2
  30. package/hooks/features/keyboardNavigation/utils.js +0 -5
  31. package/hooks/features/listView/useGridListView.js +2 -1
  32. package/hooks/features/rowSelection/useGridRowSelection.js +3 -3
  33. package/hooks/features/rows/useGridParamsApi.d.ts +1 -1
  34. package/hooks/features/rows/useGridParamsApi.js +33 -14
  35. package/hooks/features/rows/useGridRowSpanning.js +94 -91
  36. package/hooks/features/rows/useGridRows.js +7 -8
  37. package/hooks/features/rows/useGridRowsMeta.js +3 -2
  38. package/hooks/features/virtualization/gridFocusedVirtualCellSelector.js +2 -2
  39. package/hooks/features/virtualization/useGridVirtualScroller.d.ts +9 -1
  40. package/hooks/features/virtualization/useGridVirtualScroller.js +31 -45
  41. package/index.js +1 -1
  42. package/models/api/gridApiCommon.d.ts +2 -2
  43. package/models/api/gridParamsApi.d.ts +29 -2
  44. package/models/api/index.d.ts +1 -1
  45. package/models/api/index.js +0 -1
  46. package/modern/DataGrid/DataGrid.js +1 -7
  47. package/modern/components/GridRow.js +25 -19
  48. package/modern/components/cell/GridCell.js +29 -52
  49. package/modern/components/containers/GridRootStyles.js +135 -37
  50. package/modern/components/toolbar/GridToolbarColumnsButton.js +5 -4
  51. package/modern/components/toolbar/GridToolbarDensitySelector.js +2 -2
  52. package/modern/components/toolbar/GridToolbarExportContainer.js +2 -2
  53. package/modern/components/toolbar/GridToolbarFilterButton.js +4 -3
  54. package/modern/components/virtualization/GridMainContainer.js +10 -2
  55. package/modern/components/virtualization/GridVirtualScrollbar.js +5 -5
  56. package/modern/components/virtualization/GridVirtualScroller.js +18 -5
  57. package/modern/components/virtualization/GridVirtualScrollerContent.js +11 -2
  58. package/modern/hooks/features/columnResize/useGridColumnResize.js +2 -1
  59. package/modern/hooks/features/columns/useGridColumns.js +7 -3
  60. package/modern/hooks/features/dimensions/useGridDimensions.js +6 -8
  61. package/modern/hooks/features/editing/gridEditingSelectors.js +7 -1
  62. package/modern/hooks/features/editing/index.js +1 -1
  63. package/modern/hooks/features/editing/useGridCellEditing.js +3 -3
  64. package/modern/hooks/features/editing/useGridRowEditing.js +5 -6
  65. package/modern/hooks/features/focus/useGridFocus.js +3 -2
  66. package/modern/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +24 -16
  67. package/modern/hooks/features/keyboardNavigation/utils.js +0 -5
  68. package/modern/hooks/features/listView/useGridListView.js +2 -1
  69. package/modern/hooks/features/rowSelection/useGridRowSelection.js +3 -3
  70. package/modern/hooks/features/rows/useGridParamsApi.js +33 -14
  71. package/modern/hooks/features/rows/useGridRowSpanning.js +94 -91
  72. package/modern/hooks/features/rows/useGridRows.js +7 -8
  73. package/modern/hooks/features/rows/useGridRowsMeta.js +3 -2
  74. package/modern/hooks/features/virtualization/gridFocusedVirtualCellSelector.js +2 -2
  75. package/modern/hooks/features/virtualization/useGridVirtualScroller.js +31 -45
  76. package/modern/index.js +1 -1
  77. package/modern/models/api/index.js +0 -1
  78. package/modern/utils/roundToDecimalPlaces.js +3 -0
  79. package/modern/utils/utils.js +6 -1
  80. package/node/DataGrid/DataGrid.js +1 -7
  81. package/node/components/GridRow.js +22 -16
  82. package/node/components/cell/GridCell.js +27 -50
  83. package/node/components/containers/GridRootStyles.js +135 -37
  84. package/node/components/toolbar/GridToolbarColumnsButton.js +5 -4
  85. package/node/components/toolbar/GridToolbarDensitySelector.js +2 -2
  86. package/node/components/toolbar/GridToolbarExportContainer.js +2 -2
  87. package/node/components/toolbar/GridToolbarFilterButton.js +4 -3
  88. package/node/components/virtualization/GridMainContainer.js +10 -2
  89. package/node/components/virtualization/GridVirtualScrollbar.js +5 -5
  90. package/node/components/virtualization/GridVirtualScroller.js +18 -5
  91. package/node/components/virtualization/GridVirtualScrollerContent.js +11 -2
  92. package/node/hooks/features/columnResize/useGridColumnResize.js +2 -1
  93. package/node/hooks/features/columns/useGridColumns.js +7 -3
  94. package/node/hooks/features/dimensions/useGridDimensions.js +6 -8
  95. package/node/hooks/features/editing/gridEditingSelectors.js +8 -2
  96. package/node/hooks/features/editing/index.js +7 -11
  97. package/node/hooks/features/editing/useGridCellEditing.js +2 -2
  98. package/node/hooks/features/editing/useGridRowEditing.js +3 -4
  99. package/node/hooks/features/focus/useGridFocus.js +3 -2
  100. package/node/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +24 -15
  101. package/node/hooks/features/keyboardNavigation/utils.js +0 -6
  102. package/node/hooks/features/listView/useGridListView.js +2 -1
  103. package/node/hooks/features/rowSelection/useGridRowSelection.js +2 -2
  104. package/node/hooks/features/rows/useGridParamsApi.js +33 -14
  105. package/node/hooks/features/rows/useGridRowSpanning.js +92 -89
  106. package/node/hooks/features/rows/useGridRows.js +7 -8
  107. package/node/hooks/features/rows/useGridRowsMeta.js +5 -4
  108. package/node/hooks/features/virtualization/gridFocusedVirtualCellSelector.js +2 -2
  109. package/node/hooks/features/virtualization/useGridVirtualScroller.js +31 -45
  110. package/node/index.js +1 -1
  111. package/node/models/api/index.js +0 -11
  112. package/node/utils/roundToDecimalPlaces.js +9 -0
  113. package/node/utils/utils.js +8 -1
  114. package/package.json +2 -2
  115. package/utils/roundToDecimalPlaces.d.ts +1 -0
  116. package/utils/roundToDecimalPlaces.js +3 -0
  117. package/utils/utils.d.ts +1 -0
  118. package/utils/utils.js +6 -1
@@ -9,10 +9,10 @@ import { warnOnce } from '@mui/x-internals/warning';
9
9
  import { useGridApiEventHandler, useGridApiOptionHandler } from "../../utils/useGridApiEventHandler.js";
10
10
  import { GridEditModes, GridRowModes } from "../../../models/gridEditRowModel.js";
11
11
  import { useGridApiMethod } from "../../utils/useGridApiMethod.js";
12
- import { gridEditRowsStateSelector } from "./gridEditingSelectors.js";
12
+ import { gridEditRowsStateSelector, gridRowIsEditingSelector } from "./gridEditingSelectors.js";
13
13
  import { isPrintableKey, isPasteShortcut } from "../../../utils/keyboardUtils.js";
14
14
  import { gridColumnFieldsSelector, gridVisibleColumnFieldsSelector } from "../columns/gridColumnsSelector.js";
15
- import { gridRowsDataRowIdToIdLookupSelector } from "../rows/gridRowsSelector.js";
15
+ import { gridRowsLookupSelector } from "../rows/gridRowsSelector.js";
16
16
  import { deepClone } from "../../../utils/utils.js";
17
17
  import { GridRowEditStopReasons, GridRowEditStartReasons } from "../../../models/params/gridRowParams.js";
18
18
  import { GRID_ACTIONS_COLUMN_TYPE } from "../../../colDef/index.js";
@@ -235,8 +235,7 @@ export const useGridRowEditing = (apiRef, props) => {
235
235
  if (props.editMode === GridEditModes.Cell) {
236
236
  return GridRowModes.View;
237
237
  }
238
- const editingState = gridEditRowsStateSelector(apiRef.current.state);
239
- const isEditing = editingState[id] && Object.keys(editingState[id]).length > 0;
238
+ const isEditing = gridRowIsEditingSelector(apiRef, id);
240
239
  return isEditing ? GridRowModes.Edit : GridRowModes.View;
241
240
  }, [apiRef, props.editMode]);
242
241
  const updateRowModesModel = useEventCallback(newModel => {
@@ -571,7 +570,7 @@ export const useGridRowEditing = (apiRef, props) => {
571
570
 
572
571
  // Run this effect synchronously so that the keyboard event can impact the yet-to-be-rendered input.
573
572
  useEnhancedEffect(() => {
574
- const idToIdLookup = gridRowsDataRowIdToIdLookupSelector(apiRef);
573
+ const rowsLookup = gridRowsLookupSelector(apiRef);
575
574
 
576
575
  // Update the ref here because updateStateToStopRowEditMode may change it later
577
576
  const copyOfPrevRowModesModel = prevRowModesModel.current;
@@ -583,7 +582,7 @@ export const useGridRowEditing = (apiRef, props) => {
583
582
  mode: GridRowModes.View
584
583
  };
585
584
  const prevMode = copyOfPrevRowModesModel[id]?.mode || GridRowModes.View;
586
- const originalId = idToIdLookup[id] ?? id;
585
+ const originalId = rowsLookup[id] ? apiRef.current.getRowId(rowsLookup[id]) : id;
587
586
  if (params.mode === GridRowModes.Edit && prevMode === GridRowModes.View) {
588
587
  updateStateToStartRowEditMode(_extends({
589
588
  id: originalId
@@ -326,10 +326,11 @@ export const useGridFocus = (apiRef, props) => {
326
326
  // If the focused cell is in a row which does not exist anymore,
327
327
  // focus previous row or remove the focus
328
328
  if (cell && !apiRef.current.getRow(cell.id)) {
329
- const lastFocusedRowId = gridFocusCellSelector(apiRef)?.id;
329
+ const lastFocusedRowId = cell.id;
330
330
  let nextRowId = null;
331
331
  if (typeof lastFocusedRowId !== 'undefined') {
332
- const lastFocusedRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(lastFocusedRowId);
332
+ const rowEl = apiRef.current.getRowElement(lastFocusedRowId);
333
+ const lastFocusedRowIndex = rowEl?.dataset.rowindex ? Number(rowEl?.dataset.rowindex) : 0;
333
334
  const currentPage = getVisibleRows(apiRef, {
334
335
  pagination: props.pagination,
335
336
  paginationMode: props.paginationMode
@@ -6,7 +6,6 @@ import { gridVisibleColumnDefinitionsSelector, gridVisibleColumnFieldsSelector }
6
6
  import { useGridLogger } from "../../utils/useGridLogger.js";
7
7
  import { useGridApiEventHandler } from "../../utils/useGridApiEventHandler.js";
8
8
  import { gridExpandedSortedRowEntriesSelector } from "../filter/gridFilterSelector.js";
9
- import { useGridVisibleRows } from "../../utils/useGridVisibleRows.js";
10
9
  import { GRID_CHECKBOX_SELECTION_COL_DEF } from "../../../colDef/gridCheckboxSelectionColDef.js";
11
10
  import { gridClasses } from "../../../constants/gridClasses.js";
12
11
  import { GridCellModes } from "../../../models/gridEditRowModel.js";
@@ -16,8 +15,14 @@ import { gridColumnGroupsHeaderMaxDepthSelector } from "../columnGrouping/gridCo
16
15
  import { gridHeaderFilteringEditFieldSelector, gridHeaderFilteringMenuSelector } from "../headerFiltering/gridHeaderFilteringSelectors.js";
17
16
  import { useGridRegisterPipeProcessor } from "../../core/pipeProcessing/index.js";
18
17
  import { isEventTargetInPortal } from "../../../utils/domUtils.js";
19
- import { enrichPageRowsWithPinnedRows, getLeftColumnIndex, getRightColumnIndex, findNonRowSpannedCell } from "./utils.js";
18
+ import { getLeftColumnIndex, getRightColumnIndex, findNonRowSpannedCell } from "./utils.js";
20
19
  import { gridListColumnSelector } from "../listView/gridListViewSelectors.js";
20
+ import { createSelectorMemoized } from "../../../utils/createSelector.js";
21
+ import { gridVisibleRowsSelector } from "../pagination/index.js";
22
+ import { gridPinnedRowsSelector } from "../rows/gridRowsSelector.js";
23
+ const gridVisibleRowsWithPinnedRowsSelector = createSelectorMemoized(gridVisibleRowsSelector, gridPinnedRowsSelector, (visibleRows, pinnedRows) => {
24
+ return (pinnedRows.top || []).concat(visibleRows.rows, pinnedRows.bottom || []);
25
+ });
21
26
 
22
27
  /**
23
28
  * @requires useGridSorting (method) - can be after
@@ -30,10 +35,11 @@ import { gridListColumnSelector } from "../listView/gridListViewSelectors.js";
30
35
  */
31
36
  export const useGridKeyboardNavigation = (apiRef, props) => {
32
37
  const logger = useGridLogger(apiRef, 'useGridKeyboardNavigation');
33
- const initialCurrentPageRows = useGridVisibleRows(apiRef, props).rows;
34
38
  const isRtl = useRtl();
35
39
  const listView = props.unstable_listView;
36
- const currentPageRows = React.useMemo(() => enrichPageRowsWithPinnedRows(apiRef, initialCurrentPageRows), [apiRef, initialCurrentPageRows]);
40
+ const getCurrentPageRows = React.useCallback(() => {
41
+ return gridVisibleRowsWithPinnedRowsSelector(apiRef);
42
+ }, [apiRef]);
37
43
  const headerFilteringEnabled = props.signature !== 'DataGrid' && props.headerFilters;
38
44
 
39
45
  /**
@@ -92,8 +98,8 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
92
98
  apiRef.current.setColumnGroupHeaderFocus(field, depth, event);
93
99
  }, [apiRef, logger]);
94
100
  const getRowIdFromIndex = React.useCallback(rowIndex => {
95
- return currentPageRows[rowIndex]?.id;
96
- }, [currentPageRows]);
101
+ return getCurrentPageRows()[rowIndex]?.id;
102
+ }, [getCurrentPageRows]);
97
103
  const handleColumnHeaderKeyDown = React.useCallback((params, event) => {
98
104
  const headerTitleNode = event.currentTarget.querySelector(`.${gridClasses.columnHeaderTitleContainerContent}`);
99
105
  const isFromInsideContent = !!headerTitleNode && headerTitleNode.contains(event.target);
@@ -102,6 +108,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
102
108
  // There is one exception for the checkBoxHeader
103
109
  return;
104
110
  }
111
+ const currentPageRows = getCurrentPageRows();
105
112
  const viewportPageSize = apiRef.current.getViewportPageSize();
106
113
  const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0;
107
114
  const firstRowIndexInPage = currentPageRows.length > 0 ? 0 : null;
@@ -113,12 +120,10 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
113
120
  switch (event.key) {
114
121
  case 'ArrowDown':
115
122
  {
116
- if (firstRowIndexInPage !== null) {
117
- if (headerFilteringEnabled) {
118
- goToHeaderFilter(colIndexBefore, event);
119
- } else {
120
- goToCell(colIndexBefore, getRowIdFromIndex(firstRowIndexInPage));
121
- }
123
+ if (headerFilteringEnabled) {
124
+ goToHeaderFilter(colIndexBefore, event);
125
+ } else if (firstRowIndexInPage !== null) {
126
+ goToCell(colIndexBefore, getRowIdFromIndex(firstRowIndexInPage));
122
127
  }
123
128
  break;
124
129
  }
@@ -192,13 +197,14 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
192
197
  if (shouldPreventDefault) {
193
198
  event.preventDefault();
194
199
  }
195
- }, [apiRef, currentPageRows.length, headerFilteringEnabled, goToHeaderFilter, goToCell, getRowIdFromIndex, isRtl, goToHeader, goToGroupHeader]);
200
+ }, [apiRef, getCurrentPageRows, headerFilteringEnabled, goToHeaderFilter, goToCell, getRowIdFromIndex, isRtl, goToHeader, goToGroupHeader]);
196
201
  const handleHeaderFilterKeyDown = React.useCallback((params, event) => {
197
202
  const isEditing = gridHeaderFilteringEditFieldSelector(apiRef) === params.field;
198
203
  const isHeaderMenuOpen = gridHeaderFilteringMenuSelector(apiRef) === params.field;
199
204
  if (isEditing || isHeaderMenuOpen || !isNavigationKey(event.key)) {
200
205
  return;
201
206
  }
207
+ const currentPageRows = getCurrentPageRows();
202
208
  const viewportPageSize = apiRef.current.getViewportPageSize();
203
209
  const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0;
204
210
  const firstRowIndexInPage = 0;
@@ -278,7 +284,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
278
284
  if (shouldPreventDefault) {
279
285
  event.preventDefault();
280
286
  }
281
- }, [apiRef, currentPageRows.length, goToHeaderFilter, isRtl, goToHeader, goToCell, getRowIdFromIndex]);
287
+ }, [apiRef, getCurrentPageRows, goToHeaderFilter, isRtl, goToHeader, goToCell, getRowIdFromIndex]);
282
288
  const handleColumnGroupHeaderKeyDown = React.useCallback((params, event) => {
283
289
  const focusedColumnGroup = gridFocusColumnGroupHeaderSelector(apiRef);
284
290
  if (focusedColumnGroup === null) {
@@ -293,6 +299,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
293
299
  depth,
294
300
  maxDepth
295
301
  } = params;
302
+ const currentPageRows = getCurrentPageRows();
296
303
  const viewportPageSize = apiRef.current.getViewportPageSize();
297
304
  const currentColIndex = apiRef.current.getColumnIndex(currentField);
298
305
  const colIndexBefore = currentField ? apiRef.current.getColumnIndex(currentField) : 0;
@@ -364,7 +371,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
364
371
  if (shouldPreventDefault) {
365
372
  event.preventDefault();
366
373
  }
367
- }, [apiRef, currentPageRows.length, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex]);
374
+ }, [apiRef, getCurrentPageRows, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex]);
368
375
  const handleCellKeyDown = React.useCallback((params, event) => {
369
376
  // Ignore portal
370
377
  if (isEventTargetInPortal(event)) {
@@ -383,6 +390,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
383
390
  if (!canUpdateFocus) {
384
391
  return;
385
392
  }
393
+ const currentPageRows = getCurrentPageRows();
386
394
  if (currentPageRows.length === 0) {
387
395
  return;
388
396
  }
@@ -511,7 +519,7 @@ export const useGridKeyboardNavigation = (apiRef, props) => {
511
519
  if (shouldPreventDefault) {
512
520
  event.preventDefault();
513
521
  }
514
- }, [apiRef, currentPageRows, isRtl, goToCell, getRowIdFromIndex, headerFilteringEnabled, goToHeaderFilter, goToHeader, listView]);
522
+ }, [apiRef, getCurrentPageRows, isRtl, goToCell, getRowIdFromIndex, headerFilteringEnabled, goToHeaderFilter, goToHeader, listView]);
515
523
  const checkIfCanStartEditing = React.useCallback((initialValue, {
516
524
  event
517
525
  }) => {
@@ -1,10 +1,5 @@
1
1
  import { gridFilteredSortedRowIdsSelector } from "../filter/gridFilterSelector.js";
2
2
  import { gridRowSpanningHiddenCellsSelector } from "../rows/gridRowSpanningSelectors.js";
3
- import { gridPinnedRowsSelector } from "../rows/gridRowsSelector.js";
4
- export function enrichPageRowsWithPinnedRows(apiRef, rows) {
5
- const pinnedRows = gridPinnedRowsSelector(apiRef) || {};
6
- return [...(pinnedRows.top || []), ...rows, ...(pinnedRows.bottom || [])];
7
- }
8
3
  export const getLeftColumnIndex = ({
9
4
  currentColIndex,
10
5
  firstColIndex,
@@ -1,5 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
+ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
3
4
  import { warnOnce } from '@mui/x-internals/warning';
4
5
  import { gridDimensionsSelector } from "../dimensions/index.js";
5
6
  import { useGridApiEventHandler } from "../../utils/useGridApiEventHandler.js";
@@ -37,7 +38,7 @@ export function useGridListView(apiRef, props) {
37
38
  /*
38
39
  * EFFECTS
39
40
  */
40
- React.useEffect(() => {
41
+ useEnhancedEffect(() => {
41
42
  const listColumn = props.unstable_listColumn;
42
43
  if (listColumn) {
43
44
  apiRef.current.setState(state => {
@@ -12,7 +12,7 @@ import { gridExpandedSortedRowIdsSelector, gridFilteredRowsLookupSelector } from
12
12
  import { GRID_CHECKBOX_SELECTION_COL_DEF, GRID_ACTIONS_COLUMN_TYPE } from "../../../colDef/index.js";
13
13
  import { GridCellModes } from "../../../models/gridEditRowModel.js";
14
14
  import { isKeyboardEvent, isNavigationKey } from "../../../utils/keyboardUtils.js";
15
- import { useGridVisibleRows } from "../../utils/useGridVisibleRows.js";
15
+ import { getVisibleRows } from "../../utils/useGridVisibleRows.js";
16
16
  import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from "../../../internals/constants.js";
17
17
  import { gridClasses } from "../../../constants/gridClasses.js";
18
18
  import { isEventTargetInPortal } from "../../../utils/domUtils.js";
@@ -64,7 +64,6 @@ export const useGridRowSelection = (apiRef, props) => {
64
64
  isRowSelectable: propIsRowSelectable
65
65
  } = props;
66
66
  const canHaveMultipleSelection = isMultipleRowSelectionEnabled(props);
67
- const visibleRows = useGridVisibleRows(apiRef, props);
68
67
  const tree = useGridSelector(apiRef, gridRowTreeSelector);
69
68
  const isNestedData = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector) > 1;
70
69
  const expandMouseRowRangeSelection = React.useCallback(id => {
@@ -412,6 +411,7 @@ export const useGridRowSelection = (apiRef, props) => {
412
411
  end = previousRowIndex;
413
412
  }
414
413
  }
414
+ const visibleRows = getVisibleRows(apiRef);
415
415
  const rowsBetweenStartAndEnd = visibleRows.rows.slice(start, end + 1).map(row => row.id);
416
416
  apiRef.current.selectRows(rowsBetweenStartAndEnd, !isNextRowSelected);
417
417
  return;
@@ -426,7 +426,7 @@ export const useGridRowSelection = (apiRef, props) => {
426
426
  event.preventDefault();
427
427
  selectRows(apiRef.current.getAllRowIds(), true);
428
428
  }
429
- }, [apiRef, handleSingleRowSelection, selectRows, visibleRows.rows, canHaveMultipleSelection]);
429
+ }, [apiRef, handleSingleRowSelection, selectRows, canHaveMultipleSelection]);
430
430
  useGridApiEventHandler(apiRef, 'sortedRowsSet', runIfRowSelectionIsEnabled(() => removeOutdatedSelection(true)));
431
431
  useGridApiEventHandler(apiRef, 'filteredRowsSet', runIfRowSelectionIsEnabled(removeOutdatedSelection));
432
432
  useGridApiEventHandler(apiRef, 'rowClick', runIfRowSelectionIsEnabled(handleRowClick));
@@ -30,37 +30,52 @@ export function useGridParamsApi(apiRef, props) {
30
30
  };
31
31
  return params;
32
32
  }, [apiRef]);
33
- const getCellParams = React.useCallback((id, field) => {
34
- const colDef = props.unstable_listView ? gridListColumnSelector(apiRef.current.state) : apiRef.current.getColumn(field);
35
- const row = apiRef.current.getRow(id);
36
- const rowNode = apiRef.current.getRowNode(id);
37
- if (!row || !rowNode) {
38
- throw new MissingRowIdError(`No row with id #${id} found`);
39
- }
33
+ const getCellParamsForRow = React.useCallback((id, field, row, {
34
+ cellMode,
35
+ colDef,
36
+ hasFocus,
37
+ rowNode,
38
+ tabIndex
39
+ }) => {
40
40
  const rawValue = row[field];
41
41
  const value = colDef?.valueGetter ? colDef.valueGetter(rawValue, row, colDef, apiRef) : rawValue;
42
- const cellFocus = gridFocusCellSelector(apiRef);
43
- const cellTabIndex = gridTabIndexCellSelector(apiRef);
44
42
  const params = {
45
43
  id,
46
44
  field,
47
45
  row,
48
46
  rowNode,
49
47
  colDef,
50
- cellMode: apiRef.current.getCellMode(id, field),
51
- hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id,
52
- tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1,
48
+ cellMode,
49
+ hasFocus,
50
+ tabIndex,
53
51
  value,
54
52
  formattedValue: value,
55
53
  isEditable: false,
56
- api: {}
54
+ api: null
57
55
  };
58
56
  if (colDef && colDef.valueFormatter) {
59
57
  params.formattedValue = colDef.valueFormatter(value, row, colDef, apiRef);
60
58
  }
61
59
  params.isEditable = colDef && apiRef.current.isCellEditable(params);
62
60
  return params;
63
- }, [apiRef, props.unstable_listView]);
61
+ }, [apiRef]);
62
+ const getCellParams = React.useCallback((id, field) => {
63
+ const row = apiRef.current.getRow(id);
64
+ const rowNode = apiRef.current.getRowNode(id);
65
+ if (!row || !rowNode) {
66
+ throw new MissingRowIdError(`No row with id #${id} found`);
67
+ }
68
+ const cellFocus = gridFocusCellSelector(apiRef);
69
+ const cellTabIndex = gridTabIndexCellSelector(apiRef);
70
+ const cellMode = apiRef.current.getCellMode(id, field);
71
+ return apiRef.current.getCellParamsForRow(id, field, row, {
72
+ colDef: props.unstable_listView && props.unstable_listColumn?.field === field ? gridListColumnSelector(apiRef.current.state) : apiRef.current.getColumn(field),
73
+ rowNode,
74
+ hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id,
75
+ tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1,
76
+ cellMode
77
+ });
78
+ }, [apiRef, props.unstable_listView, props.unstable_listColumn?.field]);
64
79
  const getCellValue = React.useCallback((id, field) => {
65
80
  const colDef = apiRef.current.getColumn(field);
66
81
  const row = apiRef.current.getRow(id);
@@ -119,5 +134,9 @@ export function useGridParamsApi(apiRef, props) {
119
134
  getColumnHeaderParams,
120
135
  getColumnHeaderElement
121
136
  };
137
+ const paramsPrivateApi = {
138
+ getCellParamsForRow
139
+ };
122
140
  useGridApiMethod(apiRef, paramsApi, 'public');
141
+ useGridApiMethod(apiRef, paramsPrivateApi, 'private');
123
142
  }
@@ -3,12 +3,14 @@ import * as React from 'react';
3
3
  import useLazyRef from '@mui/utils/useLazyRef';
4
4
  import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from "../../../internals/constants.js";
5
5
  import { gridVisibleColumnDefinitionsSelector } from "../columns/gridColumnsSelector.js";
6
- import { useGridVisibleRows } from "../../utils/useGridVisibleRows.js";
6
+ import { getVisibleRows } from "../../utils/useGridVisibleRows.js";
7
7
  import { gridRenderContextSelector } from "../virtualization/gridVirtualizationSelectors.js";
8
- import { useGridSelector } from "../../utils/useGridSelector.js";
9
- import { gridRowTreeSelector } from "./gridRowsSelector.js";
10
- import { getUnprocessedRange, isRowRangeUpdated, isRowContextInitialized, getCellValue } from "./gridRowSpanningUtils.js";
8
+ import { getUnprocessedRange, isRowContextInitialized, getCellValue } from "./gridRowSpanningUtils.js";
11
9
  import { GRID_CHECKBOX_SELECTION_FIELD } from "../../../colDef/gridCheckboxSelectionColDef.js";
10
+ import { useGridApiEventHandler } from "../../utils/useGridApiEventHandler.js";
11
+ import { runIf } from "../../../utils/utils.js";
12
+ import { gridPageSizeSelector } from "../pagination/index.js";
13
+ import { gridDataRowIdsSelector } from "./gridRowsSelector.js";
12
14
  const EMPTY_STATE = {
13
15
  spannedCells: {},
14
16
  hiddenCells: {},
@@ -53,8 +55,8 @@ const computeRowSpanningState = (apiRef, colDefs, visibleRows, range, rangeToPro
53
55
  const backwardsHiddenCells = [];
54
56
  if (index === rangeToProcess.firstRowIndex) {
55
57
  let prevIndex = index - 1;
56
- const prevRowEntry = visibleRows[prevIndex];
57
- while (prevIndex >= range.firstRowIndex && getCellValue(prevRowEntry.model, colDef, apiRef) === cellValue) {
58
+ let prevRowEntry = visibleRows[prevIndex];
59
+ while (prevIndex >= range.firstRowIndex && prevRowEntry && getCellValue(prevRowEntry.model, colDef, apiRef) === cellValue) {
58
60
  const currentRow = visibleRows[prevIndex + 1];
59
61
  if (hiddenCells[currentRow.id]) {
60
62
  hiddenCells[currentRow.id][colDef.field] = true;
@@ -68,6 +70,7 @@ const computeRowSpanningState = (apiRef, colDefs, visibleRows, range, rangeToPro
68
70
  spannedRowId = prevRowEntry.id;
69
71
  spannedRowIndex = prevIndex;
70
72
  prevIndex -= 1;
73
+ prevRowEntry = visibleRows[prevIndex];
71
74
  }
72
75
  }
73
76
  backwardsHiddenCells.forEach(hiddenCellIndex => {
@@ -123,6 +126,24 @@ const computeRowSpanningState = (apiRef, colDefs, visibleRows, range, rangeToPro
123
126
  processedRange
124
127
  };
125
128
  };
129
+ const getInitialRangeToProcess = (props, apiRef) => {
130
+ const rowCount = gridDataRowIdsSelector(apiRef).length;
131
+ if (props.pagination) {
132
+ const pageSize = gridPageSizeSelector(apiRef);
133
+ let paginationLastRowIndex = DEFAULT_ROWS_TO_PROCESS;
134
+ if (pageSize > 0) {
135
+ paginationLastRowIndex = pageSize - 1;
136
+ }
137
+ return {
138
+ firstRowIndex: 0,
139
+ lastRowIndex: Math.min(paginationLastRowIndex, rowCount)
140
+ };
141
+ }
142
+ return {
143
+ firstRowIndex: 0,
144
+ lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, rowCount)
145
+ };
146
+ };
126
147
 
127
148
  /**
128
149
  * @requires columnsStateInitializer (method) - should be initialized before
@@ -130,74 +151,52 @@ const computeRowSpanningState = (apiRef, colDefs, visibleRows, range, rangeToPro
130
151
  * @requires filterStateInitializer (method) - should be initialized before
131
152
  */
132
153
  export const rowSpanningStateInitializer = (state, props, apiRef) => {
133
- if (props.unstable_rowSpanning) {
134
- const rowIds = state.rows.dataRowIds || [];
135
- const orderedFields = state.columns.orderedFields || [];
136
- const dataRowIdToModelLookup = state.rows.dataRowIdToModelLookup;
137
- const columnsLookup = state.columns.lookup;
138
- const isFilteringPending = Boolean(state.filter.filterModel.items.length) || Boolean(state.filter.filterModel.quickFilterValues?.length);
139
- if (!rowIds.length || !orderedFields.length || !dataRowIdToModelLookup || !columnsLookup || isFilteringPending) {
140
- return _extends({}, state, {
141
- rowSpanning: EMPTY_STATE
142
- });
143
- }
144
- const rangeToProcess = {
145
- firstRowIndex: 0,
146
- lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(rowIds.length, 0))
147
- };
148
- const rows = rowIds.map(id => ({
149
- id,
150
- model: dataRowIdToModelLookup[id]
151
- }));
152
- const colDefs = orderedFields.map(field => columnsLookup[field]);
153
- const {
154
- spannedCells,
155
- hiddenCells,
156
- hiddenCellOriginMap
157
- } = computeRowSpanningState(apiRef, colDefs, rows, rangeToProcess, rangeToProcess, true, EMPTY_RANGE);
154
+ if (!props.unstable_rowSpanning) {
158
155
  return _extends({}, state, {
159
- rowSpanning: {
160
- spannedCells,
161
- hiddenCells,
162
- hiddenCellOriginMap
163
- }
156
+ rowSpanning: EMPTY_STATE
157
+ });
158
+ }
159
+ const rowIds = state.rows.dataRowIds || [];
160
+ const orderedFields = state.columns.orderedFields || [];
161
+ const dataRowIdToModelLookup = state.rows.dataRowIdToModelLookup;
162
+ const columnsLookup = state.columns.lookup;
163
+ const isFilteringPending = Boolean(state.filter.filterModel.items.length) || Boolean(state.filter.filterModel.quickFilterValues?.length);
164
+ if (!rowIds.length || !orderedFields.length || !dataRowIdToModelLookup || !columnsLookup || isFilteringPending) {
165
+ return _extends({}, state, {
166
+ rowSpanning: EMPTY_STATE
164
167
  });
165
168
  }
169
+ const rangeToProcess = getInitialRangeToProcess(props, apiRef);
170
+ const rows = rowIds.map(id => ({
171
+ id,
172
+ model: dataRowIdToModelLookup[id]
173
+ }));
174
+ const colDefs = orderedFields.map(field => columnsLookup[field]);
175
+ const {
176
+ spannedCells,
177
+ hiddenCells,
178
+ hiddenCellOriginMap
179
+ } = computeRowSpanningState(apiRef, colDefs, rows, rangeToProcess, rangeToProcess, true, EMPTY_RANGE);
166
180
  return _extends({}, state, {
167
- rowSpanning: EMPTY_STATE
181
+ rowSpanning: {
182
+ spannedCells,
183
+ hiddenCells,
184
+ hiddenCellOriginMap
185
+ }
168
186
  });
169
187
  };
170
188
  export const useGridRowSpanning = (apiRef, props) => {
171
- const {
172
- range,
173
- rows: visibleRows
174
- } = useGridVisibleRows(apiRef, props);
175
- const renderContext = useGridSelector(apiRef, gridRenderContextSelector);
176
- const colDefs = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector);
177
- const tree = useGridSelector(apiRef, gridRowTreeSelector);
178
189
  const processedRange = useLazyRef(() => {
179
- return Object.keys(apiRef.current.state.rowSpanning.spannedCells).length > 0 ? {
180
- firstRowIndex: 0,
181
- lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(apiRef.current.state.rows.dataRowIds.length, 0))
182
- } : EMPTY_RANGE;
190
+ return apiRef.current.state.rowSpanning !== EMPTY_STATE ? getInitialRangeToProcess(props, apiRef) : EMPTY_RANGE;
183
191
  });
184
- const lastRange = React.useRef(EMPTY_RANGE);
185
- const updateRowSpanningState = React.useCallback(
186
- // A reset needs to occur when:
187
- // - The `unstable_rowSpanning` prop is updated (feature flag)
188
- // - The filtering is applied
189
- // - The sorting is applied
190
- // - The `paginationModel` is updated
191
- // - The rows are updated
192
- (resetState = true) => {
193
- if (!props.unstable_rowSpanning) {
194
- if (apiRef.current.state.rowSpanning !== EMPTY_STATE) {
195
- apiRef.current.setState(state => _extends({}, state, {
196
- rowSpanning: EMPTY_STATE
197
- }));
198
- }
199
- return;
200
- }
192
+ const updateRowSpanningState = React.useCallback((renderContext, resetState = false) => {
193
+ const {
194
+ range,
195
+ rows: visibleRows
196
+ } = getVisibleRows(apiRef, {
197
+ pagination: props.pagination,
198
+ paginationMode: props.paginationMode
199
+ });
201
200
  if (range === null || !isRowContextInitialized(renderContext)) {
202
201
  return;
203
202
  }
@@ -211,6 +210,7 @@ export const useGridRowSpanning = (apiRef, props) => {
211
210
  if (rangeToProcess === null) {
212
211
  return;
213
212
  }
213
+ const colDefs = gridVisibleColumnDefinitionsSelector(apiRef);
214
214
  const {
215
215
  spannedCells,
216
216
  hiddenCells,
@@ -223,7 +223,8 @@ export const useGridRowSpanning = (apiRef, props) => {
223
223
  const currentSpannedCellsCount = Object.keys(apiRef.current.state.rowSpanning.spannedCells).length;
224
224
  const currentHiddenCellsCount = Object.keys(apiRef.current.state.rowSpanning.hiddenCells).length;
225
225
  const shouldUpdateState = resetState || newSpannedCellsCount !== currentSpannedCellsCount || newHiddenCellsCount !== currentHiddenCellsCount;
226
- if (!shouldUpdateState) {
226
+ const hasNoSpannedCells = newSpannedCellsCount === 0 && currentSpannedCellsCount === 0;
227
+ if (!shouldUpdateState || hasNoSpannedCells) {
227
228
  return;
228
229
  }
229
230
  apiRef.current.setState(state => {
@@ -235,33 +236,35 @@ export const useGridRowSpanning = (apiRef, props) => {
235
236
  }
236
237
  });
237
238
  });
238
- }, [apiRef, props.unstable_rowSpanning, range, renderContext, visibleRows, colDefs, processedRange]);
239
- const prevRenderContext = React.useRef(renderContext);
240
- const isFirstRender = React.useRef(true);
241
- const shouldResetState = React.useRef(false);
242
- const previousTree = React.useRef(tree);
243
- React.useEffect(() => {
244
- const firstRender = isFirstRender.current;
245
- if (isFirstRender.current) {
246
- isFirstRender.current = false;
247
- }
248
- if (tree !== previousTree.current) {
249
- previousTree.current = tree;
250
- updateRowSpanningState(true);
239
+ }, [apiRef, processedRange, props.pagination, props.paginationMode]);
240
+
241
+ // Reset events trigger a full re-computation of the row spanning state:
242
+ // - The `unstable_rowSpanning` prop is updated (feature flag)
243
+ // - The filtering is applied
244
+ // - The sorting is applied
245
+ // - The `paginationModel` is updated
246
+ // - The rows are updated
247
+ const resetRowSpanningState = React.useCallback(() => {
248
+ const renderContext = gridRenderContextSelector(apiRef);
249
+ if (!isRowContextInitialized(renderContext)) {
251
250
  return;
252
251
  }
253
- if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) {
254
- lastRange.current = range;
255
- shouldResetState.current = true;
256
- }
257
- if (!firstRender && prevRenderContext.current !== renderContext) {
258
- if (isRowRangeUpdated(prevRenderContext.current, renderContext)) {
259
- updateRowSpanningState(shouldResetState.current);
260
- shouldResetState.current = false;
252
+ updateRowSpanningState(renderContext, true);
253
+ }, [apiRef, updateRowSpanningState]);
254
+ useGridApiEventHandler(apiRef, 'renderedRowsIntervalChange', runIf(props.unstable_rowSpanning, updateRowSpanningState));
255
+ useGridApiEventHandler(apiRef, 'sortedRowsSet', runIf(props.unstable_rowSpanning, resetRowSpanningState));
256
+ useGridApiEventHandler(apiRef, 'paginationModelChange', runIf(props.unstable_rowSpanning, resetRowSpanningState));
257
+ useGridApiEventHandler(apiRef, 'filteredRowsSet', runIf(props.unstable_rowSpanning, resetRowSpanningState));
258
+ useGridApiEventHandler(apiRef, 'columnsChange', runIf(props.unstable_rowSpanning, resetRowSpanningState));
259
+ React.useEffect(() => {
260
+ if (!props.unstable_rowSpanning) {
261
+ if (apiRef.current.state.rowSpanning !== EMPTY_STATE) {
262
+ apiRef.current.setState(state => _extends({}, state, {
263
+ rowSpanning: EMPTY_STATE
264
+ }));
261
265
  }
262
- prevRenderContext.current = renderContext;
263
- return;
266
+ } else if (apiRef.current.state.rowSpanning === EMPTY_STATE) {
267
+ resetRowSpanningState();
264
268
  }
265
- updateRowSpanningState();
266
- }, [updateRowSpanningState, renderContext, range, lastRange, tree]);
269
+ }, [apiRef, resetRowSpanningState, props.unstable_rowSpanning]);
267
270
  };
@@ -39,7 +39,6 @@ export const useGridRows = (apiRef, props) => {
39
39
  }
40
40
  }
41
41
  const logger = useGridLogger(apiRef, 'useGridRows');
42
- const currentPage = getVisibleRows(apiRef);
43
42
  const lastUpdateMs = React.useRef(Date.now());
44
43
  const lastRowCount = React.useRef(props.rowCount);
45
44
  const timeout = useTimeout();
@@ -66,12 +65,6 @@ export const useGridRows = (apiRef, props) => {
66
65
  }
67
66
  return row.id;
68
67
  }, [getRowIdProp]);
69
- const lookup = React.useMemo(() => currentPage.rows.reduce((acc, {
70
- id
71
- }, index) => {
72
- acc[id] = index;
73
- return acc;
74
- }, {}), [currentPage.rows]);
75
68
  const throttledRowsChange = React.useCallback(({
76
69
  cache,
77
70
  throttle
@@ -170,7 +163,13 @@ export const useGridRows = (apiRef, props) => {
170
163
  }, [apiRef]);
171
164
  const getRowsCount = React.useCallback(() => gridRowCountSelector(apiRef), [apiRef]);
172
165
  const getAllRowIds = React.useCallback(() => gridDataRowIdsSelector(apiRef), [apiRef]);
173
- const getRowIndexRelativeToVisibleRows = React.useCallback(id => lookup[id], [lookup]);
166
+ const getRowIndexRelativeToVisibleRows = React.useCallback(id => {
167
+ const row = apiRef.current.getRow(id);
168
+ const {
169
+ rowToIndexMap
170
+ } = getVisibleRows(apiRef);
171
+ return rowToIndexMap.get(row);
172
+ }, [apiRef]);
174
173
  const setRowChildrenExpansion = React.useCallback((id, isExpanded) => {
175
174
  const currentNode = apiRef.current.getRowNode(id);
176
175
  if (!currentNode) {
@@ -1,6 +1,7 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import useLazyRef from '@mui/utils/useLazyRef';
4
+ import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
4
5
  import { ResizeObserver } from "../../../utils/ResizeObserver.js";
5
6
  import { useGridVisibleRows } from "../../utils/useGridVisibleRows.js";
6
7
  import { eslintUseValue } from "../../../utils/utils.js";
@@ -110,7 +111,7 @@ export const useGridRowsMeta = (apiRef, props) => {
110
111
  }
111
112
  apiRef.current.unstable_applyPipeProcessors('rowHeight', entry, row);
112
113
  return entry;
113
- }, [apiRef, currentPage.rows.length, getRowHeightProp, getEstimatedRowHeight, rowHeight, getRowSpacing, densityFactor]);
114
+ }, [apiRef, currentPage.rows, getRowHeightProp, getEstimatedRowHeight, rowHeight, getRowSpacing, densityFactor]);
114
115
  const hydrateRowsMeta = React.useCallback(() => {
115
116
  hasRowWithAutoHeight.current = false;
116
117
  pinnedRows.top.forEach(processHeightEntry);
@@ -181,7 +182,7 @@ export const useGridRowsMeta = (apiRef, props) => {
181
182
 
182
183
  // The effect is used to build the rows meta data - currentPageTotalHeight and positions.
183
184
  // Because of variable row height this is needed for the virtualization
184
- React.useEffect(() => {
185
+ useEnhancedEffect(() => {
185
186
  hydrateRowsMeta();
186
187
  }, [filterModel, paginationState, sortModel, hydrateRowsMeta]);
187
188
  const rowsMetaApi = {