@mui/x-data-grid-pro 8.18.0 → 8.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/DataGridPro/DataGridPro.js +42 -8
  3. package/DataGridPro/useDataGridProComponent.js +3 -2
  4. package/components/GridDetailPanelToggleCell.js +0 -10
  5. package/components/GridRowReorderCell.js +15 -13
  6. package/components/GridTreeDataGroupingCell.js +0 -10
  7. package/components/headerFiltering/GridHeaderFilterCell.js +2 -3
  8. package/esm/DataGridPro/DataGridPro.js +42 -8
  9. package/esm/DataGridPro/useDataGridProComponent.js +4 -3
  10. package/esm/components/GridDetailPanelToggleCell.js +0 -10
  11. package/esm/components/GridRowReorderCell.js +15 -13
  12. package/esm/components/GridTreeDataGroupingCell.js +0 -10
  13. package/esm/components/headerFiltering/GridHeaderFilterCell.js +2 -3
  14. package/esm/hooks/features/columnPinning/useGridColumnPinningPreProcessors.js +4 -0
  15. package/esm/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
  16. package/esm/hooks/features/detailPanel/useGridDetailPanel.js +6 -5
  17. package/esm/hooks/features/infiniteLoader/useGridInfiniteLoader.js +4 -4
  18. package/esm/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
  19. package/esm/hooks/features/rowReorder/commonReorderConditions.js +78 -0
  20. package/esm/hooks/features/rowReorder/index.d.ts +2 -1
  21. package/esm/hooks/features/rowReorder/index.js +2 -1
  22. package/esm/hooks/features/rowReorder/models.d.ts +17 -0
  23. package/esm/hooks/features/rowReorder/models.js +1 -0
  24. package/esm/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
  25. package/esm/hooks/features/rowReorder/reorderExecutor.js +29 -0
  26. package/esm/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
  27. package/esm/hooks/features/rowReorder/reorderValidator.js +14 -0
  28. package/esm/hooks/features/rowReorder/types.d.ts +25 -0
  29. package/esm/hooks/features/rowReorder/types.js +1 -0
  30. package/esm/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
  31. package/esm/hooks/features/rowReorder/useGridRowReorder.js +171 -82
  32. package/esm/hooks/features/rowReorder/utils.d.ts +82 -0
  33. package/esm/hooks/features/rowReorder/utils.js +259 -0
  34. package/esm/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
  35. package/esm/hooks/features/rows/useGridRowsOverridableMethods.js +59 -0
  36. package/esm/hooks/features/serverSideLazyLoader/useGridInfiniteLoadingIntersection.js +3 -3
  37. package/esm/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
  38. package/esm/hooks/features/treeData/treeDataReorderExecutor.js +534 -0
  39. package/esm/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
  40. package/esm/hooks/features/treeData/treeDataReorderValidator.js +35 -0
  41. package/esm/hooks/features/treeData/useGridTreeData.d.ts +3 -3
  42. package/esm/hooks/features/treeData/useGridTreeData.js +49 -4
  43. package/esm/hooks/features/treeData/utils.d.ts +8 -0
  44. package/esm/hooks/features/treeData/utils.js +96 -0
  45. package/esm/index.js +1 -1
  46. package/esm/internals/index.d.ts +8 -0
  47. package/esm/internals/index.js +6 -0
  48. package/esm/models/dataGridProProps.d.ts +32 -4
  49. package/esm/models/gridRowOrderChangeParams.d.ts +29 -5
  50. package/hooks/features/columnPinning/useGridColumnPinningPreProcessors.js +4 -0
  51. package/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
  52. package/hooks/features/detailPanel/useGridDetailPanel.js +5 -4
  53. package/hooks/features/infiniteLoader/useGridInfiniteLoader.js +2 -2
  54. package/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
  55. package/hooks/features/rowReorder/commonReorderConditions.js +84 -0
  56. package/hooks/features/rowReorder/index.d.ts +2 -1
  57. package/hooks/features/rowReorder/models.d.ts +17 -0
  58. package/hooks/features/rowReorder/models.js +5 -0
  59. package/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
  60. package/hooks/features/rowReorder/reorderExecutor.js +37 -0
  61. package/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
  62. package/hooks/features/rowReorder/reorderValidator.js +21 -0
  63. package/hooks/features/rowReorder/types.d.ts +25 -0
  64. package/hooks/features/rowReorder/types.js +5 -0
  65. package/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
  66. package/hooks/features/rowReorder/useGridRowReorder.js +169 -80
  67. package/hooks/features/rowReorder/utils.d.ts +82 -0
  68. package/hooks/features/rowReorder/utils.js +286 -0
  69. package/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
  70. package/hooks/features/rows/useGridRowsOverridableMethods.js +67 -0
  71. package/hooks/features/serverSideLazyLoader/useGridInfiniteLoadingIntersection.js +3 -3
  72. package/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
  73. package/hooks/features/treeData/treeDataReorderExecutor.js +541 -0
  74. package/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
  75. package/hooks/features/treeData/treeDataReorderValidator.js +41 -0
  76. package/hooks/features/treeData/useGridTreeData.d.ts +3 -3
  77. package/hooks/features/treeData/useGridTreeData.js +48 -3
  78. package/hooks/features/treeData/utils.d.ts +8 -0
  79. package/hooks/features/treeData/utils.js +109 -0
  80. package/index.js +1 -1
  81. package/internals/index.d.ts +8 -0
  82. package/internals/index.js +53 -1
  83. package/models/dataGridProProps.d.ts +32 -4
  84. package/models/gridRowOrderChangeParams.d.ts +29 -5
  85. package/package.json +4 -4
@@ -14,19 +14,22 @@ var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"
14
14
  var _xDataGrid = require("@mui/x-data-grid");
15
15
  var _internals = require("@mui/x-data-grid/internals");
16
16
  var _gridRowReorderColDef = require("./gridRowReorderColDef");
17
+ var _utils = require("./utils");
17
18
  const EMPTY_REORDER_STATE = {
18
19
  previousTargetId: null,
19
20
  dragDirection: null,
20
21
  previousDropPosition: null
21
22
  };
23
+ const TIMEOUT_CLEAR_BUFFER_PX = 5;
24
+ const EMPTY_TIMEOUT_INFO = {
25
+ rowId: null
26
+ };
22
27
  const useUtilityClasses = ownerState => {
23
28
  const {
24
29
  classes
25
30
  } = ownerState;
26
31
  const slots = {
27
32
  rowDragging: ['row--dragging'],
28
- rowDropAbove: ['row--dropAbove'],
29
- rowDropBelow: ['row--dropBelow'],
30
33
  rowBeingDragged: ['row--beingDragged']
31
34
  };
32
35
  return (0, _composeClasses.default)(slots, _xDataGrid.getDataGridUtilityClass, classes);
@@ -48,14 +51,12 @@ const useGridRowReorder = (apiRef, props) => {
48
51
  const dragRowNode = React.useRef(null);
49
52
  const originRowIndex = React.useRef(null);
50
53
  const removeDnDStylesTimeout = React.useRef(undefined);
51
- const previousDropIndicatorRef = React.useRef(null);
52
54
  const ownerState = {
53
55
  classes: props.classes
54
56
  };
55
57
  const classes = useUtilityClasses(ownerState);
56
58
  const [dragRowId, setDragRowId] = React.useState('');
57
- const sortedRowIndexLookup = (0, _xDataGrid.useGridSelector)(apiRef, _internals.gridExpandedSortedRowIndexLookupSelector);
58
- const timeoutRowId = React.useRef('');
59
+ const timeoutInfoRef = React.useRef(EMPTY_TIMEOUT_INFO);
59
60
  const timeout = (0, _useTimeout.default)();
60
61
  const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
61
62
  const dropTarget = React.useRef({
@@ -70,26 +71,30 @@ const useGridRowReorder = (apiRef, props) => {
70
71
  }, []);
71
72
 
72
73
  // TODO: remove sortModel check once row reorder is sorting compatible
73
- // remove treeData check once row reorder is treeData compatible
74
74
  const isRowReorderDisabled = React.useMemo(() => {
75
- return !props.rowReordering || !!sortModel.length || props.treeData;
76
- }, [props.rowReordering, sortModel, props.treeData]);
77
- const applyDropIndicator = React.useCallback((targetRowId, position) => {
78
- // Remove existing drop indicator from previous target
79
- if (previousDropIndicatorRef.current) {
80
- previousDropIndicatorRef.current.classList.remove(classes.rowDropAbove, classes.rowDropBelow);
81
- previousDropIndicatorRef.current = null;
82
- }
83
-
84
- // Apply new drop indicator
85
- if (targetRowId !== undefined && position !== null) {
86
- const targetRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${targetRowId}"]`);
87
- if (targetRow) {
88
- targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
89
- previousDropIndicatorRef.current = targetRow;
75
+ return !props.rowReordering || !!sortModel.length;
76
+ }, [props.rowReordering, sortModel]);
77
+ const calculateDropPosition = React.useCallback(event => {
78
+ // For tree data, we need to find the cell element to avoid flickerings on top 20% selection
79
+ const targetElement = props.treeData ? (0, _utils.findCellElement)(event.target) : event.target;
80
+ const targetRect = targetElement.getBoundingClientRect();
81
+ const relativeY = Math.floor(event.clientY - targetRect.top);
82
+ if (props.treeData) {
83
+ // For tree data: top 20% = above, middle 60% = over, bottom 20% = below
84
+ const topThreshold = targetRect.height * 0.2;
85
+ const bottomThreshold = targetRect.height * 0.8;
86
+ if (relativeY < topThreshold) {
87
+ return 'above';
90
88
  }
89
+ if (relativeY > bottomThreshold) {
90
+ return 'below';
91
+ }
92
+ return 'inside';
91
93
  }
92
- }, [apiRef, classes]);
94
+ // For flat data and row grouping: split at midpoint
95
+ const midPoint = targetRect.height / 2;
96
+ return relativeY < midPoint ? 'above' : 'below';
97
+ }, [props.treeData]);
93
98
  const applyDraggedState = React.useCallback((rowId, isDragged) => {
94
99
  if (rowId) {
95
100
  const draggedRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${rowId}"]`);
@@ -102,7 +107,7 @@ const useGridRowReorder = (apiRef, props) => {
102
107
  }
103
108
  }
104
109
  }, [apiRef, classes.rowBeingDragged]);
105
- const applyRowAnimation = React.useCallback(callback => {
110
+ const applyRowAnimation = React.useCallback(async callback => {
106
111
  const rootElement = apiRef.current.rootElementRef?.current;
107
112
  if (!rootElement) {
108
113
  return;
@@ -119,7 +124,7 @@ const useGridRowReorder = (apiRef, props) => {
119
124
  initialPositions.set(rowId, row.getBoundingClientRect());
120
125
  }
121
126
  });
122
- callback();
127
+ await callback();
123
128
 
124
129
  // Use `requestAnimationFrame` to ensure DOM has updated
125
130
  requestAnimationFrame(() => {
@@ -161,15 +166,20 @@ const useGridRowReorder = (apiRef, props) => {
161
166
  if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
162
167
  return;
163
168
  }
164
- if (timeoutRowId.current) {
169
+ if (timeoutInfoRef.current) {
165
170
  timeout.clear();
166
- timeoutRowId.current = '';
171
+ timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
167
172
  }
168
173
  logger.debug(`Start dragging row ${params.id}`);
169
174
  // Prevent drag events propagation.
170
175
  // For more information check here https://github.com/mui/mui-x/issues/2680.
171
176
  event.stopPropagation();
172
- apiRef.current.setRowDragActive(true);
177
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
178
+ rowReorder: (0, _extends2.default)({}, state.rowReorder, {
179
+ isActive: true,
180
+ draggedRowId: params.id
181
+ })
182
+ }));
173
183
  dragRowNode.current = event.currentTarget;
174
184
  // Apply cell-level dragging class to the drag handle
175
185
  dragRowNode.current.classList.add(classes.rowDragging);
@@ -180,9 +190,10 @@ const useGridRowReorder = (apiRef, props) => {
180
190
  removeDnDStylesTimeout.current = setTimeout(() => {
181
191
  dragRowNode.current.classList.remove(classes.rowDragging);
182
192
  });
193
+ const sortedRowIndexLookup = (0, _xDataGrid.gridExpandedSortedRowIndexLookupSelector)(apiRef);
183
194
  originRowIndex.current = sortedRowIndexLookup[params.id];
184
195
  apiRef.current.setCellFocus(params.id, _gridRowReorderColDef.GRID_REORDER_COL_DEF.field);
185
- }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, applyDraggedState, sortedRowIndexLookup, timeout]);
196
+ }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, applyDraggedState, timeout]);
186
197
  const handleDragOver = React.useCallback((params, event) => {
187
198
  if (dragRowId === '') {
188
199
  return;
@@ -192,34 +203,36 @@ const useGridRowReorder = (apiRef, props) => {
192
203
  if (!sourceNode || !targetNode || targetNode.type === 'footer' || targetNode.type === 'pinnedRow' || !event.target) {
193
204
  return;
194
205
  }
195
-
196
- // Find the relative 'y' mouse position based on the event.target
197
- const targetRect = event.target.getBoundingClientRect();
198
- const relativeY = Math.floor(event.clientY - targetRect.top);
199
- const midPoint = Math.floor(targetRect.height / 2);
200
206
  logger.debug(`Dragging over row ${params.id}`);
201
207
  event.preventDefault();
202
208
  // Prevent drag events propagation.
203
209
  // For more information check here https://github.com/mui/mui-x/issues/2680.
204
210
  event.stopPropagation();
205
- if (timeoutRowId.current && timeoutRowId.current !== params.id) {
211
+ if (timeoutInfoRef.current && (timeoutInfoRef.current.rowId !== params.id ||
212
+ // Avoid accidental opening of node when the user is moving over a row
213
+ event.clientY > timeoutInfoRef.current.clientY + TIMEOUT_CLEAR_BUFFER_PX || event.clientY < timeoutInfoRef.current.clientY - TIMEOUT_CLEAR_BUFFER_PX || event.clientX > timeoutInfoRef.current.clientX + TIMEOUT_CLEAR_BUFFER_PX || event.clientX < timeoutInfoRef.current.clientX - TIMEOUT_CLEAR_BUFFER_PX)) {
206
214
  timeout.clear();
207
- timeoutRowId.current = '';
215
+ timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
208
216
  }
209
- if (targetNode.type === 'group' && targetNode.depth < sourceNode.depth && !targetNode.childrenExpanded && !timeoutRowId.current) {
217
+
218
+ // Calculate drop position using new logic
219
+ const dropPosition = calculateDropPosition(event);
220
+ if (targetNode.type === 'group' && !targetNode.childrenExpanded && !timeoutInfoRef.current.rowId && targetNode.id !== sourceNode.id && (dropPosition === 'inside' || targetNode.depth < sourceNode.depth)) {
210
221
  timeout.start(500, () => {
211
222
  const rowNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, params.id);
212
223
  // TODO: Handle `dataSource` case with https://github.com/mui/mui-x/issues/18947
213
224
  apiRef.current.setRowChildrenExpansion(params.id, !rowNode.childrenExpanded);
214
225
  });
215
- timeoutRowId.current = params.id;
226
+ timeoutInfoRef.current = {
227
+ rowId: params.id,
228
+ clientY: event.clientY,
229
+ clientX: event.clientX
230
+ };
216
231
  return;
217
232
  }
233
+ const sortedRowIndexLookup = (0, _xDataGrid.gridExpandedSortedRowIndexLookupSelector)(apiRef);
218
234
  const targetRowIndex = sortedRowIndexLookup[params.id];
219
235
  const sourceRowIndex = sortedRowIndexLookup[dragRowId];
220
-
221
- // Determine drop position based on relativeY position within the row
222
- const dropPosition = relativeY < midPoint ? 'above' : 'below';
223
236
  const currentReorderState = {
224
237
  dragDirection: targetRowIndex < sourceRowIndex ? 'up' : 'down',
225
238
  previousTargetId: params.id,
@@ -232,7 +245,7 @@ const useGridRowReorder = (apiRef, props) => {
232
245
 
233
246
  // Check if this is an adjacent position
234
247
  const isAdjacentPosition = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1;
235
- const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', -1, {
248
+ const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
236
249
  sourceRowId: dragRowId,
237
250
  targetRowId: params.id,
238
251
  dropPosition,
@@ -240,13 +253,21 @@ const useGridRowReorder = (apiRef, props) => {
240
253
  });
241
254
 
242
255
  // Show drop indicator for valid drops OR adjacent positions OR same node
243
- if (validatedIndex !== -1 || isAdjacentPosition || isSameNode) {
256
+ if (isRowReorderValid || isAdjacentPosition || isSameNode) {
244
257
  dropTarget.current = {
245
258
  targetRowId: params.id,
246
259
  targetRowIndex,
247
260
  dropPosition
248
261
  };
249
- applyDropIndicator(params.id, dropPosition);
262
+ // Update state with drop target
263
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
264
+ rowReorder: (0, _extends2.default)({}, state.rowReorder, {
265
+ dropTarget: {
266
+ rowId: params.id,
267
+ position: dropPosition
268
+ }
269
+ })
270
+ }));
250
271
  } else {
251
272
  // Clear indicators for invalid drops
252
273
  dropTarget.current = {
@@ -254,7 +275,12 @@ const useGridRowReorder = (apiRef, props) => {
254
275
  targetRowIndex: null,
255
276
  dropPosition: null
256
277
  };
257
- applyDropIndicator(null, null);
278
+ // Clear state drop target
279
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
280
+ rowReorder: (0, _extends2.default)({}, state.rowReorder, {
281
+ dropTarget: undefined
282
+ })
283
+ }));
258
284
  }
259
285
  previousReorderState.current = currentReorderState;
260
286
  }
@@ -265,16 +291,16 @@ const useGridRowReorder = (apiRef, props) => {
265
291
  } else {
266
292
  event.dataTransfer.dropEffect = 'copy';
267
293
  }
268
- }, [dragRowId, apiRef, logger, timeout, sortedRowIndexLookup, applyDropIndicator]);
269
- const handleDragEnd = React.useCallback((_, event) => {
294
+ }, [dragRowId, apiRef, logger, timeout, calculateDropPosition]);
295
+ const handleDragEnd = React.useCallback(async (_, event) => {
270
296
  // Call the gridEditRowsStateSelector directly to avoid infnite loop
271
297
  const editRowsState = (0, _internals.gridEditRowsStateSelector)(apiRef);
272
298
  if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
273
299
  return;
274
300
  }
275
- if (timeoutRowId.current) {
301
+ if (timeoutInfoRef.current) {
276
302
  timeout.clear();
277
- timeoutRowId.current = '';
303
+ timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
278
304
  }
279
305
  logger.debug('End dragging row');
280
306
  event.preventDefault();
@@ -286,11 +312,6 @@ const useGridRowReorder = (apiRef, props) => {
286
312
  const dragDirection = previousReorderState.current.dragDirection;
287
313
  previousReorderState.current = EMPTY_REORDER_STATE;
288
314
 
289
- // Clear visual indicators and dragged state
290
- applyDropIndicator(null, null);
291
- applyDraggedState(dragRowId, false);
292
- apiRef.current.setRowDragActive(false);
293
-
294
315
  // Check if the row was dropped outside the grid.
295
316
  if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
296
317
  // Reset drop target state
@@ -301,29 +322,80 @@ const useGridRowReorder = (apiRef, props) => {
301
322
  };
302
323
  originRowIndex.current = null;
303
324
  setDragRowId('');
325
+ // Clear visual indicators and dragged state
326
+ applyDraggedState(dragRowId, false);
327
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
328
+ rowReorder: {
329
+ isActive: false,
330
+ draggedRowId: null
331
+ }
332
+ }));
304
333
  return;
305
334
  }
306
335
  if (dropTarget.current.targetRowIndex !== null && dropTarget.current.targetRowId !== null) {
307
- const sourceRowIndex = originRowIndex.current;
308
- const targetRowIndex = dropTarget.current.targetRowIndex;
309
- const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', targetRowIndex, {
336
+ const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
310
337
  sourceRowId: dragRowId,
311
338
  targetRowId: dropTarget.current.targetRowId,
312
339
  dropPosition: dropTarget.current.dropPosition,
313
340
  dragDirection: dragDirection
314
341
  });
315
- if (validatedIndex !== -1) {
316
- applyRowAnimation(() => {
317
- apiRef.current.setRowIndex(dragRowId, validatedIndex);
342
+ if (isRowReorderValid) {
343
+ try {
344
+ const rowTree = (0, _xDataGrid.gridRowTreeSelector)(apiRef);
345
+ const sourceNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, dragRowId);
346
+ if (!sourceNode) {
347
+ throw new Error(`MUI X: No row node found for id #${dragRowId}`);
348
+ }
318
349
 
319
- // Emit the rowOrderChange event only once when the reordering stops.
320
- const rowOrderChangeParams = {
321
- row: apiRef.current.getRow(dragRowId),
322
- targetIndex: validatedIndex,
323
- oldIndex: sourceRowIndex
324
- };
325
- apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
326
- });
350
+ // Calculate oldParent and oldIndex
351
+ const oldParent = sourceNode.parent;
352
+ const oldParentNode = rowTree[oldParent];
353
+ const oldIndexInParent = oldParentNode.children.indexOf(dragRowId) ?? originRowIndex.current;
354
+ await applyRowAnimation(async () => {
355
+ await apiRef.current.setRowPosition(dragRowId, dropTarget.current.targetRowId, dropTarget.current.dropPosition);
356
+ const updatedTree = (0, _xDataGrid.gridRowTreeSelector)(apiRef);
357
+ const updatedNode = updatedTree[dragRowId];
358
+ if (!updatedNode) {
359
+ throw new Error(`MUI X: Row node for id #${dragRowId} not found after move`);
360
+ }
361
+ const newParent = updatedNode.parent;
362
+ const newParentNode = updatedTree[newParent];
363
+ const newIndexInParent = newParentNode.children.indexOf(dragRowId);
364
+
365
+ // Only emit event and clear state after successful reorder
366
+ const rowOrderChangeParams = {
367
+ row: apiRef.current.getRow(dragRowId),
368
+ oldIndex: oldIndexInParent,
369
+ targetIndex: newIndexInParent,
370
+ oldParent: oldParent === _xDataGrid.GRID_ROOT_GROUP_ID ? null : oldParent,
371
+ newParent: newParent === _xDataGrid.GRID_ROOT_GROUP_ID ? null : newParent
372
+ };
373
+ applyDraggedState(dragRowId, false);
374
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
375
+ rowReorder: {
376
+ isActive: false,
377
+ draggedRowId: null
378
+ }
379
+ }));
380
+ apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
381
+ });
382
+ } catch (error) {
383
+ // Handle error: reset visual state but don't publish success event
384
+ applyDraggedState(dragRowId, false);
385
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
386
+ rowReorder: {
387
+ isActive: false,
388
+ draggedRowId: null
389
+ }
390
+ }));
391
+ }
392
+ } else {
393
+ applyDraggedState(dragRowId, false);
394
+ apiRef.current.setState(state => (0, _extends2.default)({}, state, {
395
+ rowReorder: (0, _extends2.default)({}, state.rowReorder, {
396
+ dropTarget: undefined
397
+ })
398
+ }));
327
399
  }
328
400
  }
329
401
 
@@ -334,8 +406,9 @@ const useGridRowReorder = (apiRef, props) => {
334
406
  dropPosition: null
335
407
  };
336
408
  setDragRowId('');
337
- }, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDropIndicator, applyDraggedState, timeout, applyRowAnimation]);
338
- const getRowReorderTargetIndex = React.useCallback((initialValue, {
409
+ }, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDraggedState, timeout, applyRowAnimation]);
410
+ const isValidRowReorderProp = props.isValidRowReorder;
411
+ const isRowReorderValid = React.useCallback((initialValue, {
339
412
  sourceRowId,
340
413
  targetRowId,
341
414
  dropPosition,
@@ -344,27 +417,43 @@ const useGridRowReorder = (apiRef, props) => {
344
417
  if ((0, _xDataGrid.gridRowMaximumTreeDepthSelector)(apiRef) > 1) {
345
418
  return initialValue;
346
419
  }
420
+ const sortedRowIndexLookup = (0, _xDataGrid.gridExpandedSortedRowIndexLookupSelector)(apiRef);
347
421
  const targetRowIndex = sortedRowIndexLookup[targetRowId];
348
422
  const sourceRowIndex = sortedRowIndexLookup[sourceRowId];
349
423
 
350
- // Check if this drop would result in no actual movement
424
+ // Apply internal validation: check if this drop would result in no actual movement
351
425
  const isAdjacentNode = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
352
426
  // dragging to immediately below (above next row)
353
427
  dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
354
428
 
355
429
  if (isAdjacentNode || sourceRowIndex === targetRowIndex) {
356
- // Return -1 to prevent actual movement (indicators handled separately)
357
- return -1;
430
+ return false;
358
431
  }
359
- let finalTargetIndex;
360
- if (dragDirection === 'up') {
361
- finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1;
362
- } else {
363
- finalTargetIndex = dropPosition === 'above' ? targetRowIndex - 1 : targetRowIndex;
432
+
433
+ // Internal validation passed, now apply additional user validation if provided
434
+ if (isValidRowReorderProp) {
435
+ const expandedSortedRowIds = (0, _xDataGrid.gridExpandedSortedRowIdsSelector)(apiRef);
436
+ const rowTree = (0, _xDataGrid.gridRowTreeSelector)(apiRef);
437
+ const sourceNode = rowTree[sourceRowId];
438
+ const targetNode = rowTree[targetRowId];
439
+ const prevNode = targetRowIndex > 0 ? rowTree[expandedSortedRowIds[targetRowIndex - 1]] : null;
440
+ const nextNode = targetRowIndex < expandedSortedRowIds.length - 1 ? rowTree[expandedSortedRowIds[targetRowIndex + 1]] : null;
441
+ const context = {
442
+ apiRef,
443
+ sourceNode,
444
+ targetNode,
445
+ prevNode,
446
+ nextNode,
447
+ dropPosition,
448
+ dragDirection
449
+ };
450
+ if (!isValidRowReorderProp(context)) {
451
+ return false;
452
+ }
364
453
  }
365
- return finalTargetIndex;
366
- }, [apiRef, sortedRowIndexLookup]);
367
- (0, _internals.useGridRegisterPipeProcessor)(apiRef, 'getRowReorderTargetIndex', getRowReorderTargetIndex);
454
+ return true;
455
+ }, [apiRef, isValidRowReorderProp]);
456
+ (0, _internals.useGridRegisterPipeProcessor)(apiRef, 'isRowReorderValid', isRowReorderValid);
368
457
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragStart', handleDragStart);
369
458
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragOver', handleDragOver);
370
459
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragEnd', handleDragEnd);
@@ -0,0 +1,82 @@
1
+ import type { RefObject } from '@mui/x-internals/types';
2
+ import { type GridRowId, type GridTreeNode, type GridGroupNode, type GridRowTreeConfig, type GridKeyValue, type GridValidRowModel } from '@mui/x-data-grid';
3
+ import { type ReorderOperationType } from "./types.js";
4
+ import type { GridPrivateApiPro } from "../../../models/gridApiPro.js";
5
+ import { DataGridProProcessedProps } from "../../../models/dataGridProProps.js";
6
+ export { getNodePathInTree } from "../../../utils/tree/utils.js";
7
+ /**
8
+ * Finds the closest cell element from the given event target.
9
+ * If the target itself is a cell, returns it.
10
+ * Otherwise, searches for the closest parent with 'cell' in its className.
11
+ * @param target - The event target to start searching from
12
+ * @returns The cell element or the original target if no cell is found
13
+ */
14
+ export declare function findCellElement(target: EventTarget | null): Element;
15
+ export declare function determineOperationType(sourceNode: GridTreeNode, targetNode: GridTreeNode): ReorderOperationType;
16
+ export declare function calculateTargetIndex(sourceNode: GridTreeNode, targetNode: GridTreeNode, isLastChild: boolean, rowTree: Record<GridRowId, GridTreeNode>): number;
17
+ export declare const collectAllLeafDescendants: (groupNode: GridGroupNode, tree: GridRowTreeConfig) => GridRowId[];
18
+ export declare const collectAllDescendants: (groupNode: GridGroupNode, tree: GridRowTreeConfig) => GridTreeNode[];
19
+ export declare const isDescendantOf: (possibleDescendant: GridTreeNode, ancestor: GridTreeNode, tree: GridRowTreeConfig) => boolean;
20
+ export declare const updateDescendantDepths: (group: GridGroupNode, tree: GridRowTreeConfig, depthDiff: number) => void;
21
+ /**
22
+ * Finds an existing group node with the same groupingKey and groupingField under a parent.
23
+ *
24
+ * @param parentNode - The parent group node to search in
25
+ * @param groupingKey - The grouping key to match
26
+ * @param groupingField - The grouping field to match
27
+ * @param tree - The row tree configuration
28
+ * @returns The existing group node if found, null otherwise
29
+ */
30
+ export declare function findExistingGroupWithSameKey(parentNode: GridGroupNode, groupingKey: GridKeyValue, groupingField: string, tree: GridRowTreeConfig): GridGroupNode | null;
31
+ /**
32
+ * Removes empty ancestor groups from the tree after a row move operation.
33
+ * Walks up the tree from the given group, removing any empty groups encountered.
34
+ *
35
+ * @param groupId - The ID of the group to start checking from
36
+ * @param tree - The row tree configuration
37
+ * @param removedGroups - Set to track which groups have been removed
38
+ * @returns The number of root-level groups that were removed
39
+ */
40
+ export declare function removeEmptyAncestors(groupId: GridRowId, tree: GridRowTreeConfig, removedGroups: Set<GridRowId>): number;
41
+ export declare function handleProcessRowUpdateError(error: any, onProcessRowUpdateError?: DataGridProProcessedProps['onProcessRowUpdateError']): void;
42
+ /**
43
+ * Handles batch row updates with partial failure tracking.
44
+ *
45
+ * This class is designed for operations that need to update multiple rows
46
+ * atomically (like moving entire groups), while gracefully handling cases
47
+ * where some updates succeed and others fail.
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * const updater = new BatchRowUpdater(apiRef, processRowUpdate, onError);
52
+ *
53
+ * // Queue multiple updates
54
+ * updater.queueUpdate('row1', originalRow1, newRow1);
55
+ * updater.queueUpdate('row2', originalRow2, newRow2);
56
+ *
57
+ * // Execute all updates
58
+ * const { successful, failed, updates } = await updater.executeAll();
59
+ *
60
+ * // Handle results
61
+ * if (successful.length > 0) {
62
+ * apiRef.current.updateRows(updates);
63
+ * }
64
+ * ```
65
+ */
66
+ export declare class BatchRowUpdater {
67
+ private apiRef;
68
+ private processRowUpdate;
69
+ private onProcessRowUpdateError;
70
+ private rowsToUpdate;
71
+ private originalRows;
72
+ private successfulRowIds;
73
+ private failedRowIds;
74
+ private pendingRowUpdates;
75
+ constructor(apiRef: RefObject<GridPrivateApiPro>, processRowUpdate: DataGridProProcessedProps['processRowUpdate'] | undefined, onProcessRowUpdateError: DataGridProProcessedProps['onProcessRowUpdateError'] | undefined);
76
+ queueUpdate(rowId: GridRowId, originalRow: GridValidRowModel, updatedRow: GridValidRowModel): void;
77
+ executeAll(): Promise<{
78
+ successful: GridRowId[];
79
+ failed: GridRowId[];
80
+ updates: GridValidRowModel[];
81
+ }>;
82
+ }