@mui/x-data-grid 7.17.0 → 7.18.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 (85) hide show
  1. package/CHANGELOG.md +98 -5
  2. package/DataGrid/DataGrid.js +11 -1
  3. package/DataGrid/useDataGridComponent.js +3 -0
  4. package/DataGrid/useDataGridProps.js +2 -1
  5. package/components/GridRow.js +1 -0
  6. package/components/cell/GridCell.js +30 -5
  7. package/hooks/features/columnHeaders/useGridColumnHeaders.d.ts +0 -1
  8. package/hooks/features/columnHeaders/useGridColumnHeaders.js +11 -11
  9. package/hooks/features/columnResize/useGridColumnResize.js +6 -6
  10. package/hooks/features/dimensions/gridDimensionsApi.d.ts +4 -0
  11. package/hooks/features/dimensions/useGridDimensions.d.ts +1 -1
  12. package/hooks/features/dimensions/useGridDimensions.js +4 -1
  13. package/hooks/features/editing/useGridCellEditing.js +2 -18
  14. package/hooks/features/editing/useGridRowEditing.js +6 -1
  15. package/hooks/features/editing/utils.d.ts +2 -0
  16. package/hooks/features/editing/utils.js +15 -0
  17. package/hooks/features/export/useGridPrintExport.js +2 -1
  18. package/hooks/features/focus/useGridFocus.js +2 -1
  19. package/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +10 -46
  20. package/hooks/features/keyboardNavigation/utils.d.ts +17 -0
  21. package/hooks/features/keyboardNavigation/utils.js +58 -0
  22. package/hooks/features/rows/gridRowSpanningSelectors.d.ts +4 -0
  23. package/hooks/features/rows/gridRowSpanningSelectors.js +5 -0
  24. package/hooks/features/rows/gridRowSpanningUtils.d.ts +10 -0
  25. package/hooks/features/rows/gridRowSpanningUtils.js +42 -0
  26. package/hooks/features/rows/useGridRowSpanning.d.ts +27 -0
  27. package/hooks/features/rows/useGridRowSpanning.js +257 -0
  28. package/hooks/features/virtualization/useGridVirtualScroller.d.ts +1 -1
  29. package/hooks/features/virtualization/useGridVirtualScroller.js +17 -7
  30. package/hooks/utils/useGridApiEventHandler.js +0 -1
  31. package/index.js +1 -1
  32. package/internals/index.d.ts +1 -0
  33. package/internals/index.js +1 -0
  34. package/models/colDef/gridColDef.d.ts +4 -0
  35. package/models/gridStateCommunity.d.ts +2 -0
  36. package/models/props/DataGridProps.d.ts +10 -0
  37. package/modern/DataGrid/DataGrid.js +11 -1
  38. package/modern/DataGrid/useDataGridComponent.js +3 -0
  39. package/modern/DataGrid/useDataGridProps.js +2 -1
  40. package/modern/components/GridRow.js +1 -0
  41. package/modern/components/cell/GridCell.js +30 -5
  42. package/modern/hooks/features/columnHeaders/useGridColumnHeaders.js +11 -11
  43. package/modern/hooks/features/columnResize/useGridColumnResize.js +6 -6
  44. package/modern/hooks/features/dimensions/useGridDimensions.js +4 -1
  45. package/modern/hooks/features/editing/useGridCellEditing.js +2 -18
  46. package/modern/hooks/features/editing/useGridRowEditing.js +6 -1
  47. package/modern/hooks/features/editing/utils.js +15 -0
  48. package/modern/hooks/features/export/useGridPrintExport.js +2 -1
  49. package/modern/hooks/features/focus/useGridFocus.js +2 -1
  50. package/modern/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +10 -46
  51. package/modern/hooks/features/keyboardNavigation/utils.js +58 -0
  52. package/modern/hooks/features/rows/gridRowSpanningSelectors.js +5 -0
  53. package/modern/hooks/features/rows/gridRowSpanningUtils.js +42 -0
  54. package/modern/hooks/features/rows/useGridRowSpanning.js +257 -0
  55. package/modern/hooks/features/virtualization/useGridVirtualScroller.js +17 -7
  56. package/modern/hooks/utils/useGridApiEventHandler.js +0 -1
  57. package/modern/index.js +1 -1
  58. package/modern/internals/index.js +1 -0
  59. package/modern/utils/domUtils.js +12 -12
  60. package/node/DataGrid/DataGrid.js +11 -1
  61. package/node/DataGrid/useDataGridComponent.js +3 -0
  62. package/node/DataGrid/useDataGridProps.js +2 -1
  63. package/node/components/GridRow.js +1 -0
  64. package/node/components/cell/GridCell.js +30 -5
  65. package/node/hooks/features/columnHeaders/useGridColumnHeaders.js +11 -11
  66. package/node/hooks/features/columnResize/useGridColumnResize.js +6 -6
  67. package/node/hooks/features/dimensions/useGridDimensions.js +4 -1
  68. package/node/hooks/features/editing/useGridCellEditing.js +2 -18
  69. package/node/hooks/features/editing/useGridRowEditing.js +6 -1
  70. package/node/hooks/features/editing/utils.js +22 -0
  71. package/node/hooks/features/export/useGridPrintExport.js +2 -1
  72. package/node/hooks/features/focus/useGridFocus.js +2 -1
  73. package/node/hooks/features/keyboardNavigation/useGridKeyboardNavigation.js +16 -53
  74. package/node/hooks/features/keyboardNavigation/utils.js +68 -0
  75. package/node/hooks/features/rows/gridRowSpanningSelectors.js +11 -0
  76. package/node/hooks/features/rows/gridRowSpanningUtils.js +52 -0
  77. package/node/hooks/features/rows/useGridRowSpanning.js +267 -0
  78. package/node/hooks/features/virtualization/useGridVirtualScroller.js +17 -7
  79. package/node/hooks/utils/useGridApiEventHandler.js +0 -1
  80. package/node/index.js +1 -1
  81. package/node/internals/index.js +15 -0
  82. package/node/utils/domUtils.js +12 -12
  83. package/package.json +3 -3
  84. package/utils/domUtils.d.ts +4 -4
  85. package/utils/domUtils.js +12 -12
@@ -0,0 +1,42 @@
1
+ export function getUnprocessedRange(testRange, processedRange) {
2
+ if (testRange.firstRowIndex >= processedRange.firstRowIndex && testRange.lastRowIndex <= processedRange.lastRowIndex) {
3
+ return null;
4
+ }
5
+ // Overflowing at the end
6
+ // Example: testRange={ firstRowIndex: 10, lastRowIndex: 20 }, processedRange={ firstRowIndex: 0, lastRowIndex: 15 }
7
+ // Unprocessed Range={ firstRowIndex: 16, lastRowIndex: 20 }
8
+ if (testRange.firstRowIndex >= processedRange.firstRowIndex && testRange.lastRowIndex > processedRange.lastRowIndex) {
9
+ return {
10
+ firstRowIndex: processedRange.lastRowIndex,
11
+ lastRowIndex: testRange.lastRowIndex
12
+ };
13
+ }
14
+ // Overflowing at the beginning
15
+ // Example: testRange={ firstRowIndex: 0, lastRowIndex: 20 }, processedRange={ firstRowIndex: 16, lastRowIndex: 30 }
16
+ // Unprocessed Range={ firstRowIndex: 0, lastRowIndex: 15 }
17
+ if (testRange.firstRowIndex < processedRange.firstRowIndex && testRange.lastRowIndex <= processedRange.lastRowIndex) {
18
+ return {
19
+ firstRowIndex: testRange.firstRowIndex,
20
+ lastRowIndex: processedRange.firstRowIndex - 1
21
+ };
22
+ }
23
+ // TODO: Should return two ranges handle overflowing at both ends ?
24
+ return testRange;
25
+ }
26
+ export function isRowContextInitialized(renderContext) {
27
+ return renderContext.firstRowIndex !== 0 || renderContext.lastRowIndex !== 0;
28
+ }
29
+ export function isRowRangeUpdated(range1, range2) {
30
+ return range1.firstRowIndex !== range2.firstRowIndex || range1.lastRowIndex !== range2.lastRowIndex;
31
+ }
32
+ export const getCellValue = (row, colDef, apiRef) => {
33
+ if (!row) {
34
+ return null;
35
+ }
36
+ let cellValue = row[colDef.field];
37
+ const valueGetter = colDef.rowSpanValueGetter ?? colDef.valueGetter;
38
+ if (valueGetter) {
39
+ cellValue = valueGetter(cellValue, row, colDef, apiRef);
40
+ }
41
+ return cellValue;
42
+ };
@@ -0,0 +1,257 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import * as React from 'react';
3
+ import useLazyRef from '@mui/utils/useLazyRef';
4
+ import { gridVisibleColumnDefinitionsSelector } from "../columns/gridColumnsSelector.js";
5
+ import { useGridVisibleRows } from "../../utils/useGridVisibleRows.js";
6
+ import { gridRenderContextSelector } from "../virtualization/gridVirtualizationSelectors.js";
7
+ import { useGridSelector } from "../../utils/useGridSelector.js";
8
+ import { getUnprocessedRange, isRowRangeUpdated, isRowContextInitialized, getCellValue } from "./gridRowSpanningUtils.js";
9
+ const EMPTY_STATE = {
10
+ spannedCells: {},
11
+ hiddenCells: {},
12
+ hiddenCellOriginMap: {}
13
+ };
14
+ const EMPTY_RANGE = {
15
+ firstRowIndex: 0,
16
+ lastRowIndex: 0
17
+ };
18
+ const skippedFields = new Set(['__check__', '__reorder__', '__detail_panel_toggle__']);
19
+ /**
20
+ * Default number of rows to process during state initialization to avoid flickering.
21
+ * Number `20` is arbitrarily chosen to be large enough to cover most of the cases without
22
+ * compromising performance.
23
+ */
24
+ const DEFAULT_ROWS_TO_PROCESS = 20;
25
+ const computeRowSpanningState = (apiRef, colDefs, visibleRows, range, rangeToProcess, resetState, processedRange) => {
26
+ const spannedCells = resetState ? {} : _extends({}, apiRef.current.state.rowSpanning.spannedCells);
27
+ const hiddenCells = resetState ? {} : _extends({}, apiRef.current.state.rowSpanning.hiddenCells);
28
+ const hiddenCellOriginMap = resetState ? {} : _extends({}, apiRef.current.state.rowSpanning.hiddenCellOriginMap);
29
+ if (resetState) {
30
+ processedRange = EMPTY_RANGE;
31
+ }
32
+ colDefs.forEach(colDef => {
33
+ if (skippedFields.has(colDef.field)) {
34
+ return;
35
+ }
36
+ for (let index = rangeToProcess.firstRowIndex; index <= rangeToProcess.lastRowIndex; index += 1) {
37
+ const row = visibleRows[index];
38
+ if (hiddenCells[row.id]?.[colDef.field]) {
39
+ continue;
40
+ }
41
+ const cellValue = getCellValue(row.model, colDef, apiRef);
42
+ if (cellValue == null) {
43
+ continue;
44
+ }
45
+ let spannedRowId = row.id;
46
+ let spannedRowIndex = index;
47
+ let rowSpan = 0;
48
+
49
+ // For first index, also scan in the previous rows to handle the reset state case e.g by sorting
50
+ const backwardsHiddenCells = [];
51
+ if (index === rangeToProcess.firstRowIndex) {
52
+ let prevIndex = index - 1;
53
+ const prevRowEntry = visibleRows[prevIndex];
54
+ while (prevIndex >= range.firstRowIndex && getCellValue(prevRowEntry.model, colDef, apiRef) === cellValue) {
55
+ const currentRow = visibleRows[prevIndex + 1];
56
+ if (hiddenCells[currentRow.id]) {
57
+ hiddenCells[currentRow.id][colDef.field] = true;
58
+ } else {
59
+ hiddenCells[currentRow.id] = {
60
+ [colDef.field]: true
61
+ };
62
+ }
63
+ backwardsHiddenCells.push(index);
64
+ rowSpan += 1;
65
+ spannedRowId = prevRowEntry.id;
66
+ spannedRowIndex = prevIndex;
67
+ prevIndex -= 1;
68
+ }
69
+ }
70
+ backwardsHiddenCells.forEach(hiddenCellIndex => {
71
+ if (hiddenCellOriginMap[hiddenCellIndex]) {
72
+ hiddenCellOriginMap[hiddenCellIndex][colDef.field] = spannedRowIndex;
73
+ } else {
74
+ hiddenCellOriginMap[hiddenCellIndex] = {
75
+ [colDef.field]: spannedRowIndex
76
+ };
77
+ }
78
+ });
79
+
80
+ // Scan the next rows
81
+ let relativeIndex = index + 1;
82
+ while (relativeIndex <= range.lastRowIndex && visibleRows[relativeIndex] && getCellValue(visibleRows[relativeIndex].model, colDef, apiRef) === cellValue) {
83
+ const currentRow = visibleRows[relativeIndex];
84
+ if (hiddenCells[currentRow.id]) {
85
+ hiddenCells[currentRow.id][colDef.field] = true;
86
+ } else {
87
+ hiddenCells[currentRow.id] = {
88
+ [colDef.field]: true
89
+ };
90
+ }
91
+ if (hiddenCellOriginMap[relativeIndex]) {
92
+ hiddenCellOriginMap[relativeIndex][colDef.field] = spannedRowIndex;
93
+ } else {
94
+ hiddenCellOriginMap[relativeIndex] = {
95
+ [colDef.field]: spannedRowIndex
96
+ };
97
+ }
98
+ relativeIndex += 1;
99
+ rowSpan += 1;
100
+ }
101
+ if (rowSpan > 0) {
102
+ if (spannedCells[spannedRowId]) {
103
+ spannedCells[spannedRowId][colDef.field] = rowSpan + 1;
104
+ } else {
105
+ spannedCells[spannedRowId] = {
106
+ [colDef.field]: rowSpan + 1
107
+ };
108
+ }
109
+ }
110
+ }
111
+ processedRange = {
112
+ firstRowIndex: Math.min(processedRange.firstRowIndex, rangeToProcess.firstRowIndex),
113
+ lastRowIndex: Math.max(processedRange.lastRowIndex, rangeToProcess.lastRowIndex)
114
+ };
115
+ });
116
+ return {
117
+ spannedCells,
118
+ hiddenCells,
119
+ hiddenCellOriginMap,
120
+ processedRange
121
+ };
122
+ };
123
+
124
+ /**
125
+ * @requires columnsStateInitializer (method) - should be initialized before
126
+ * @requires rowsStateInitializer (method) - should be initialized before
127
+ * @requires filterStateInitializer (method) - should be initialized before
128
+ */
129
+ export const rowSpanningStateInitializer = (state, props, apiRef) => {
130
+ if (props.unstable_rowSpanning) {
131
+ const rowIds = state.rows.dataRowIds || [];
132
+ const orderedFields = state.columns.orderedFields || [];
133
+ const dataRowIdToModelLookup = state.rows.dataRowIdToModelLookup;
134
+ const columnsLookup = state.columns.lookup;
135
+ const isFilteringPending = Boolean(state.filter.filterModel.items.length) || Boolean(state.filter.filterModel.quickFilterValues?.length);
136
+ if (!rowIds.length || !orderedFields.length || !dataRowIdToModelLookup || !columnsLookup || isFilteringPending) {
137
+ return _extends({}, state, {
138
+ rowSpanning: EMPTY_STATE
139
+ });
140
+ }
141
+ const rangeToProcess = {
142
+ firstRowIndex: 0,
143
+ lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS - 1, Math.max(rowIds.length - 1, 0))
144
+ };
145
+ const rows = rowIds.map(id => ({
146
+ id,
147
+ model: dataRowIdToModelLookup[id]
148
+ }));
149
+ const colDefs = orderedFields.map(field => columnsLookup[field]);
150
+ const {
151
+ spannedCells,
152
+ hiddenCells,
153
+ hiddenCellOriginMap
154
+ } = computeRowSpanningState(apiRef, colDefs, rows, rangeToProcess, rangeToProcess, true, EMPTY_RANGE);
155
+ return _extends({}, state, {
156
+ rowSpanning: {
157
+ spannedCells,
158
+ hiddenCells,
159
+ hiddenCellOriginMap
160
+ }
161
+ });
162
+ }
163
+ return _extends({}, state, {
164
+ rowSpanning: EMPTY_STATE
165
+ });
166
+ };
167
+ export const useGridRowSpanning = (apiRef, props) => {
168
+ const {
169
+ range,
170
+ rows: visibleRows
171
+ } = useGridVisibleRows(apiRef, props);
172
+ const renderContext = useGridSelector(apiRef, gridRenderContextSelector);
173
+ const colDefs = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector);
174
+ const processedRange = useLazyRef(() => {
175
+ return Object.keys(apiRef.current.state.rowSpanning.spannedCells).length > 0 ? {
176
+ firstRowIndex: 0,
177
+ lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS - 1, Math.max(apiRef.current.state.rows.dataRowIds.length - 1, 0))
178
+ } : EMPTY_RANGE;
179
+ });
180
+ const lastRange = React.useRef(EMPTY_RANGE);
181
+ const updateRowSpanningState = React.useCallback(
182
+ // A reset needs to occur when:
183
+ // - The `unstable_rowSpanning` prop is updated (feature flag)
184
+ // - The filtering is applied
185
+ // - The sorting is applied
186
+ // - The `paginationModel` is updated
187
+ // - The rows are updated
188
+ (resetState = true) => {
189
+ if (!props.unstable_rowSpanning) {
190
+ if (apiRef.current.state.rowSpanning !== EMPTY_STATE) {
191
+ apiRef.current.setState(state => _extends({}, state, {
192
+ rowSpanning: EMPTY_STATE
193
+ }));
194
+ }
195
+ return;
196
+ }
197
+ if (range === null || !isRowContextInitialized(renderContext)) {
198
+ return;
199
+ }
200
+ if (resetState) {
201
+ processedRange.current = EMPTY_RANGE;
202
+ }
203
+ const rangeToProcess = getUnprocessedRange({
204
+ firstRowIndex: renderContext.firstRowIndex,
205
+ lastRowIndex: renderContext.lastRowIndex - 1
206
+ }, processedRange.current);
207
+ if (rangeToProcess === null) {
208
+ return;
209
+ }
210
+ const {
211
+ spannedCells,
212
+ hiddenCells,
213
+ hiddenCellOriginMap,
214
+ processedRange: newProcessedRange
215
+ } = computeRowSpanningState(apiRef, colDefs, visibleRows, range, rangeToProcess, resetState, processedRange.current);
216
+ processedRange.current = newProcessedRange;
217
+ const newSpannedCellsCount = Object.keys(spannedCells).length;
218
+ const newHiddenCellsCount = Object.keys(hiddenCells).length;
219
+ const currentSpannedCellsCount = Object.keys(apiRef.current.state.rowSpanning.spannedCells).length;
220
+ const currentHiddenCellsCount = Object.keys(apiRef.current.state.rowSpanning.hiddenCells).length;
221
+ const shouldUpdateState = resetState || newSpannedCellsCount !== currentSpannedCellsCount || newHiddenCellsCount !== currentHiddenCellsCount;
222
+ if (!shouldUpdateState) {
223
+ return;
224
+ }
225
+ apiRef.current.setState(state => {
226
+ return _extends({}, state, {
227
+ rowSpanning: {
228
+ spannedCells,
229
+ hiddenCells,
230
+ hiddenCellOriginMap
231
+ }
232
+ });
233
+ });
234
+ }, [apiRef, props.unstable_rowSpanning, range, renderContext, visibleRows, colDefs, processedRange]);
235
+ const prevRenderContext = React.useRef(renderContext);
236
+ const isFirstRender = React.useRef(true);
237
+ const shouldResetState = React.useRef(false);
238
+ React.useEffect(() => {
239
+ const firstRender = isFirstRender.current;
240
+ if (isFirstRender.current) {
241
+ isFirstRender.current = false;
242
+ }
243
+ if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) {
244
+ lastRange.current = range;
245
+ shouldResetState.current = true;
246
+ }
247
+ if (!firstRender && prevRenderContext.current !== renderContext) {
248
+ if (isRowRangeUpdated(prevRenderContext.current, renderContext)) {
249
+ updateRowSpanningState(shouldResetState.current);
250
+ shouldResetState.current = false;
251
+ }
252
+ prevRenderContext.current = renderContext;
253
+ return;
254
+ }
255
+ updateRowSpanningState();
256
+ }, [updateRowSpanningState, renderContext, range, lastRange]);
257
+ };
@@ -24,6 +24,7 @@ import { getFirstNonSpannedColumnToRender } from "../columns/gridColumnsUtils.js
24
24
  import { getMinimalContentHeight } from "../rows/gridRowsUtils.js";
25
25
  import { gridRenderContextSelector, gridVirtualizationRowEnabledSelector, gridVirtualizationColumnEnabledSelector } from "./gridVirtualizationSelectors.js";
26
26
  import { EMPTY_RENDER_CONTEXT } from "./useGridVirtualization.js";
27
+ import { gridRowSpanningHiddenCellsOriginMapSelector } from "../rows/gridRowSpanningSelectors.js";
27
28
  import { jsx as _jsx } from "react/jsx-runtime";
28
29
  const MINIMUM_COLUMN_WIDTH = 50;
29
30
  var ScrollDirection = /*#__PURE__*/function (ScrollDirection) {
@@ -325,7 +326,7 @@ export const useGridVirtualScroller = () => {
325
326
  if (!isPinnedSection && frozenContext.current && rowIndexInPage >= frozenContext.current.firstRowIndex && rowIndexInPage < frozenContext.current.lastRowIndex) {
326
327
  currentRenderContext = frozenContext.current;
327
328
  }
328
- const offsetLeft = computeOffsetLeft(columnPositions, currentRenderContext, isRtl, pinnedColumns.left.length);
329
+ const offsetLeft = computeOffsetLeft(columnPositions, currentRenderContext, pinnedColumns.left.length);
329
330
  const showBottomBorder = isLastVisibleInSection && params.position === 'top';
330
331
  rows.push(/*#__PURE__*/_jsx(rootProps.slots.row, _extends({
331
332
  row: model,
@@ -353,7 +354,7 @@ export const useGridVirtualScroller = () => {
353
354
  if (panel) {
354
355
  rows.push(panel);
355
356
  }
356
- if (isLastVisible) {
357
+ if (params.position === undefined && isLastVisibleInSection) {
357
358
  rows.push(apiRef.current.getInfiniteLoadingTriggerElement?.({
358
359
  lastRowId: id
359
360
  }));
@@ -447,6 +448,7 @@ function inputsSelector(apiRef, rootProps, enabledForRows, enabledForColumns) {
447
448
  const dimensions = gridDimensionsSelector(apiRef.current.state);
448
449
  const currentPage = getVisibleRows(apiRef, rootProps);
449
450
  const visibleColumns = gridVisibleColumnDefinitionsSelector(apiRef);
451
+ const hiddenCellsOriginMap = gridRowSpanningHiddenCellsOriginMapSelector(apiRef);
450
452
  const lastRowId = apiRef.current.state.rows.dataRowIds.at(-1);
451
453
  const lastColumn = visibleColumns.at(-1);
452
454
  return {
@@ -467,7 +469,8 @@ function inputsSelector(apiRef, rootProps, enabledForRows, enabledForColumns) {
467
469
  rows: currentPage.rows,
468
470
  range: currentPage.range,
469
471
  pinnedColumns: gridVisiblePinnedColumnDefinitionsSelector(apiRef),
470
- visibleColumns
472
+ visibleColumns,
473
+ hiddenCellsOriginMap
471
474
  };
472
475
  }
473
476
  function computeRenderContext(inputs, scrollPosition, scrollCache) {
@@ -485,10 +488,18 @@ function computeRenderContext(inputs, scrollPosition, scrollCache) {
485
488
  if (inputs.enabledForRows) {
486
489
  // Clamp the value because the search may return an index out of bounds.
487
490
  // In the last index, this is not needed because Array.slice doesn't include it.
488
- const firstRowIndex = Math.min(getNearestIndexToRender(inputs, top, {
491
+ let firstRowIndex = Math.min(getNearestIndexToRender(inputs, top, {
489
492
  atStart: true,
490
493
  lastPosition: inputs.rowsMeta.positions[inputs.rowsMeta.positions.length - 1] + inputs.lastRowHeight
491
494
  }), inputs.rowsMeta.positions.length - 1);
495
+
496
+ // If any of the cells in the `firstRowIndex` is hidden due to an extended row span,
497
+ // Make sure the row from where the rowSpan is originated is visible.
498
+ const rowSpanHiddenCellOrigin = inputs.hiddenCellsOriginMap[firstRowIndex];
499
+ if (rowSpanHiddenCellOrigin) {
500
+ const minSpannedRowIndex = Math.min(...Object.values(rowSpanHiddenCellOrigin));
501
+ firstRowIndex = Math.min(firstRowIndex, minSpannedRowIndex);
502
+ }
492
503
  const lastRowIndex = inputs.autoHeight ? firstRowIndex + inputs.rows.length : getNearestIndexToRender(inputs, top + inputs.viewportInnerHeight);
493
504
  renderContext.firstRowIndex = firstRowIndex;
494
505
  renderContext.lastRowIndex = lastRowIndex;
@@ -642,9 +653,8 @@ export function areRenderContextsEqual(context1, context2) {
642
653
  }
643
654
  return context1.firstRowIndex === context2.firstRowIndex && context1.lastRowIndex === context2.lastRowIndex && context1.firstColumnIndex === context2.firstColumnIndex && context1.lastColumnIndex === context2.lastColumnIndex;
644
655
  }
645
- export function computeOffsetLeft(columnPositions, renderContext, isRtl, pinnedLeftLength) {
646
- const factor = isRtl ? -1 : 1;
647
- const left = factor * (columnPositions[renderContext.firstColumnIndex] ?? 0) - (columnPositions[pinnedLeftLength] ?? 0);
656
+ export function computeOffsetLeft(columnPositions, renderContext, pinnedLeftLength) {
657
+ const left = (columnPositions[renderContext.firstColumnIndex] ?? 0) - (columnPositions[pinnedLeftLength] ?? 0);
648
658
  return Math.abs(left);
649
659
  }
650
660
  function directionForDelta(dx, dy) {
@@ -90,7 +90,6 @@ const optionsSubscriberOptions = {
90
90
  isFirst: true
91
91
  };
92
92
  export function useGridApiOptionHandler(apiRef, eventName, handler) {
93
- // Validate that only one per event name?
94
93
  useGridApiEventHandler(apiRef, eventName, handler, optionsSubscriberOptions);
95
94
  }
96
95
  export { GridSignature };
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid v7.17.0
2
+ * @mui/x-data-grid v7.18.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -32,6 +32,7 @@ export { useGridPreferencesPanel, preferencePanelStateInitializer } from "../hoo
32
32
  export { useGridEditing, editingStateInitializer } from "../hooks/features/editing/useGridEditing.js";
33
33
  export { gridEditRowsStateSelector } from "../hooks/features/editing/gridEditingSelectors.js";
34
34
  export { useGridRows, rowsStateInitializer } from "../hooks/features/rows/useGridRows.js";
35
+ export { useGridRowSpanning, rowSpanningStateInitializer } from "../hooks/features/rows/useGridRowSpanning.js";
35
36
  export { useGridAriaAttributes } from "../hooks/utils/useGridAriaAttributes.js";
36
37
  export { useGridRowAriaAttributes } from "../hooks/features/rows/useGridRowAriaAttributes.js";
37
38
  export { useGridRowsPreProcessors } from "../hooks/features/rows/useGridRowsPreProcessors.js";
@@ -120,22 +120,22 @@ const findPinnedCells = ({
120
120
  });
121
121
  return cells;
122
122
  };
123
- export function findLeftPinnedCellsAfterCol(api, col) {
123
+ export function findLeftPinnedCellsAfterCol(api, col, isRtl) {
124
124
  const colIndex = parseCellColIndex(col);
125
125
  return findPinnedCells({
126
126
  api,
127
127
  colIndex,
128
- position: 'left',
129
- filterFn: index => index > colIndex
128
+ position: isRtl ? 'right' : 'left',
129
+ filterFn: index => isRtl ? index < colIndex : index > colIndex
130
130
  });
131
131
  }
132
- export function findRightPinnedCellsBeforeCol(api, col) {
132
+ export function findRightPinnedCellsBeforeCol(api, col, isRtl) {
133
133
  const colIndex = parseCellColIndex(col);
134
134
  return findPinnedCells({
135
135
  api,
136
136
  colIndex,
137
- position: 'right',
138
- filterFn: index => index < colIndex
137
+ position: isRtl ? 'left' : 'right',
138
+ filterFn: index => isRtl ? index > colIndex : index < colIndex
139
139
  });
140
140
  }
141
141
  const findPinnedHeaders = ({
@@ -159,22 +159,22 @@ const findPinnedHeaders = ({
159
159
  });
160
160
  return elements;
161
161
  };
162
- export function findLeftPinnedHeadersAfterCol(api, col) {
162
+ export function findLeftPinnedHeadersAfterCol(api, col, isRtl) {
163
163
  const colIndex = parseCellColIndex(col);
164
164
  return findPinnedHeaders({
165
165
  api,
166
- position: 'left',
166
+ position: isRtl ? 'right' : 'left',
167
167
  colIndex,
168
- filterFn: index => index > colIndex
168
+ filterFn: index => isRtl ? index < colIndex : index > colIndex
169
169
  });
170
170
  }
171
- export function findRightPinnedHeadersBeforeCol(api, col) {
171
+ export function findRightPinnedHeadersBeforeCol(api, col, isRtl) {
172
172
  const colIndex = parseCellColIndex(col);
173
173
  return findPinnedHeaders({
174
174
  api,
175
- position: 'right',
175
+ position: isRtl ? 'left' : 'right',
176
176
  colIndex,
177
- filterFn: index => index < colIndex
177
+ filterFn: index => isRtl ? index > colIndex : index < colIndex
178
178
  });
179
179
  }
180
180
  export function findGridHeader(api, field) {
@@ -125,6 +125,11 @@ DataGridRaw.propTypes = {
125
125
  * @default 150
126
126
  */
127
127
  columnBufferPx: _propTypes.default.number,
128
+ /**
129
+ * Sets the height in pixels of the column group headers in the Data Grid.
130
+ * Inherits the `columnHeaderHeight` value if not set.
131
+ */
132
+ columnGroupHeaderHeight: _propTypes.default.number,
128
133
  columnGroupingModel: _propTypes.default.arrayOf(_propTypes.default.object),
129
134
  /**
130
135
  * Sets the height in pixel of the column headers in the Data Grid.
@@ -750,5 +755,10 @@ DataGridRaw.propTypes = {
750
755
  /**
751
756
  * The system prop that allows defining system overrides as well as additional CSS styles.
752
757
  */
753
- sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object])
758
+ sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]),
759
+ /**
760
+ * If `true`, the Data Grid will auto span the cells over the rows having the same value.
761
+ * @default false
762
+ */
763
+ unstable_rowSpanning: _propTypes.default.bool
754
764
  };
@@ -33,6 +33,7 @@ var _useGridColumnSpanning = require("../hooks/features/columns/useGridColumnSpa
33
33
  var _useGridColumnGrouping = require("../hooks/features/columnGrouping/useGridColumnGrouping");
34
34
  var _virtualization = require("../hooks/features/virtualization");
35
35
  var _useGridColumnResize = require("../hooks/features/columnResize/useGridColumnResize");
36
+ var _useGridRowSpanning = require("../hooks/features/rows/useGridRowSpanning");
36
37
  const useDataGridComponent = (inputApiRef, props) => {
37
38
  const apiRef = (0, _useGridInitialization.useGridInitialization)(inputApiRef, props);
38
39
 
@@ -54,6 +55,7 @@ const useDataGridComponent = (inputApiRef, props) => {
54
55
  (0, _useGridInitializeState.useGridInitializeState)(_useGridSorting.sortingStateInitializer, apiRef, props);
55
56
  (0, _useGridInitializeState.useGridInitializeState)(_useGridPreferencesPanel.preferencePanelStateInitializer, apiRef, props);
56
57
  (0, _useGridInitializeState.useGridInitializeState)(_useGridFilter.filterStateInitializer, apiRef, props);
58
+ (0, _useGridInitializeState.useGridInitializeState)(_useGridRowSpanning.rowSpanningStateInitializer, apiRef, props);
57
59
  (0, _useGridInitializeState.useGridInitializeState)(_useGridDensity.densityStateInitializer, apiRef, props);
58
60
  (0, _useGridInitializeState.useGridInitializeState)(_useGridColumnResize.columnResizeStateInitializer, apiRef, props);
59
61
  (0, _useGridInitializeState.useGridInitializeState)(_useGridPagination.paginationStateInitializer, apiRef, props);
@@ -65,6 +67,7 @@ const useDataGridComponent = (inputApiRef, props) => {
65
67
  (0, _useGridRowSelection.useGridRowSelection)(apiRef, props);
66
68
  (0, _useGridColumns.useGridColumns)(apiRef, props);
67
69
  (0, _useGridRows.useGridRows)(apiRef, props);
70
+ (0, _useGridRowSpanning.useGridRowSpanning)(apiRef, props);
68
71
  (0, _useGridParamsApi.useGridParamsApi)(apiRef);
69
72
  (0, _useGridColumnSpanning.useGridColumnSpanning)(apiRef);
70
73
  (0, _useGridColumnGrouping.useGridColumnGrouping)(apiRef, props);
@@ -81,7 +81,8 @@ const DATA_GRID_PROPS_DEFAULT_VALUES = exports.DATA_GRID_PROPS_DEFAULT_VALUES =
81
81
  showColumnVerticalBorder: false,
82
82
  sortingMode: 'client',
83
83
  sortingOrder: ['asc', 'desc', null],
84
- throttleRowsMs: 0
84
+ throttleRowsMs: 0,
85
+ unstable_rowSpanning: false
85
86
  };
86
87
  const defaultSlots = _defaultGridSlotsComponents.DATA_GRID_DEFAULT_SLOTS_COMPONENTS;
87
88
  const useDataGridProps = inProps => {
@@ -346,6 +346,7 @@ process.env.NODE_ENV !== "production" ? GridRow.propTypes = {
346
346
  height: _propTypes.default.number.isRequired,
347
347
  width: _propTypes.default.number.isRequired
348
348
  }).isRequired,
349
+ groupHeaderHeight: _propTypes.default.number.isRequired,
349
350
  hasScrollX: _propTypes.default.bool.isRequired,
350
351
  hasScrollY: _propTypes.default.bool.isRequired,
351
352
  headerFilterHeight: _propTypes.default.number.isRequired,
@@ -13,6 +13,7 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
13
13
  var _clsx = _interopRequireDefault(require("clsx"));
14
14
  var _utils = require("@mui/utils");
15
15
  var _fastMemo = require("@mui/x-internals/fastMemo");
16
+ var _RtlProvider = require("@mui/system/RtlProvider");
16
17
  var _doesSupportPreventScroll = require("../../utils/doesSupportPreventScroll");
17
18
  var _gridClasses = require("../../constants/gridClasses");
18
19
  var _models = require("../../models");
@@ -23,6 +24,7 @@ var _gridFocusStateSelector = require("../../hooks/features/focus/gridFocusState
23
24
  var _useGridParamsApi = require("../../hooks/features/rows/useGridParamsApi");
24
25
  var _cellBorderUtils = require("../../utils/cellBorderUtils");
25
26
  var _gridColumnsInterfaces = require("../../hooks/features/columns/gridColumnsInterfaces");
27
+ var _gridRowSpanningSelectors = require("../../hooks/features/rows/gridRowSpanningSelectors");
26
28
  var _jsxRuntime = require("react/jsx-runtime");
27
29
  const _excluded = ["column", "rowId", "editCellState", "align", "children", "colIndex", "width", "className", "style", "gridHasScrollX", "colSpan", "disableDragEvents", "isNotVisible", "pinnedOffset", "pinnedPosition", "sectionIndex", "sectionLength", "gridHasFiller", "onClick", "onDoubleClick", "onMouseDown", "onMouseUp", "onMouseOver", "onKeyDown", "onKeyUp", "onDragEnter", "onDragOver"],
28
30
  _excluded2 = ["changeReason", "unstable_updateValueOnRender"];
@@ -114,6 +116,7 @@ const GridCell = /*#__PURE__*/React.forwardRef(function GridCell(props, ref) {
114
116
  other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded);
115
117
  const apiRef = (0, _useGridApiContext.useGridApiContext)();
116
118
  const rootProps = (0, _useGridRootProps.useGridRootProps)();
119
+ const isRtl = (0, _RtlProvider.useRtl)();
117
120
  const field = column.field;
118
121
  const cellParams = (0, _useGridSelector.useGridSelector)(apiRef, () => {
119
122
  // This is required because `.getCellParams` tries to get the `state.rows.tree` entry
@@ -134,6 +137,8 @@ const GridCell = /*#__PURE__*/React.forwardRef(function GridCell(props, ref) {
134
137
  id: rowId,
135
138
  field
136
139
  }));
140
+ const hiddenCells = (0, _useGridSelector.useGridSelector)(apiRef, _gridRowSpanningSelectors.gridRowSpanningHiddenCellsSelector);
141
+ const spannedCells = (0, _useGridSelector.useGridSelector)(apiRef, _gridRowSpanningSelectors.gridRowSpanningSpannedCellsSelector);
137
142
  const {
138
143
  cellMode,
139
144
  hasFocus,
@@ -206,6 +211,8 @@ const GridCell = /*#__PURE__*/React.forwardRef(function GridCell(props, ref) {
206
211
  propHandler(event);
207
212
  }
208
213
  }, [apiRef, field, rowId]);
214
+ const isCellRowSpanned = hiddenCells[rowId]?.[field] ?? false;
215
+ const rowSpan = spannedCells[rowId]?.[field] ?? 1;
209
216
  const style = React.useMemo(() => {
210
217
  if (isNotVisible) {
211
218
  return {
@@ -218,14 +225,21 @@ const GridCell = /*#__PURE__*/React.forwardRef(function GridCell(props, ref) {
218
225
  const cellStyle = (0, _extends2.default)({
219
226
  '--width': `${width}px`
220
227
  }, styleProp);
221
- if (pinnedPosition === PinnedPosition.LEFT) {
222
- cellStyle.left = pinnedOffset;
228
+ const isLeftPinned = pinnedPosition === PinnedPosition.LEFT;
229
+ const isRightPinned = pinnedPosition === PinnedPosition.RIGHT;
230
+ if (isLeftPinned || isRightPinned) {
231
+ let side = isLeftPinned ? 'left' : 'right';
232
+ if (isRtl) {
233
+ side = isLeftPinned ? 'right' : 'left';
234
+ }
235
+ cellStyle[side] = pinnedOffset;
223
236
  }
224
- if (pinnedPosition === PinnedPosition.RIGHT) {
225
- cellStyle.right = pinnedOffset;
237
+ if (rowSpan > 1) {
238
+ cellStyle.height = `calc(var(--height) * ${rowSpan})`;
239
+ cellStyle.zIndex = 5;
226
240
  }
227
241
  return cellStyle;
228
- }, [width, isNotVisible, styleProp, pinnedOffset, pinnedPosition]);
242
+ }, [width, isNotVisible, styleProp, pinnedOffset, pinnedPosition, isRtl, rowSpan]);
229
243
  React.useEffect(() => {
230
244
  if (!hasFocus || cellMode === _models.GridCellModes.Edit) {
231
245
  return;
@@ -245,6 +259,16 @@ const GridCell = /*#__PURE__*/React.forwardRef(function GridCell(props, ref) {
245
259
  }
246
260
  }
247
261
  }, [hasFocus, cellMode, apiRef]);
262
+ if (isCellRowSpanned) {
263
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
264
+ "data-colindex": colIndex,
265
+ role: "presentation",
266
+ style: (0, _extends2.default)({}, style, {
267
+ minWidth: 'var(--width)',
268
+ maxWidth: 'var(--width)'
269
+ })
270
+ });
271
+ }
248
272
  if (cellParams === EMPTY_CELL_PARAMS) {
249
273
  return null;
250
274
  }
@@ -305,6 +329,7 @@ const GridCell = /*#__PURE__*/React.forwardRef(function GridCell(props, ref) {
305
329
  "data-colindex": colIndex,
306
330
  "aria-colindex": colIndex + 1,
307
331
  "aria-colspan": colSpan,
332
+ "aria-rowspan": rowSpan,
308
333
  style: style,
309
334
  title: title,
310
335
  tabIndex: tabIndex,