@mui/x-data-grid-pro 8.19.0 → 8.21.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 (71) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/DataGridPro/DataGridPro.js +29 -2
  3. package/components/GridRowReorderCell.js +15 -3
  4. package/components/headerFiltering/GridHeaderFilterCell.js +2 -3
  5. package/esm/DataGridPro/DataGridPro.js +29 -2
  6. package/esm/components/GridRowReorderCell.js +15 -3
  7. package/esm/components/headerFiltering/GridHeaderFilterCell.js +2 -3
  8. package/esm/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
  9. package/esm/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
  10. package/esm/hooks/features/rowReorder/commonReorderConditions.js +78 -0
  11. package/esm/hooks/features/rowReorder/index.d.ts +2 -1
  12. package/esm/hooks/features/rowReorder/index.js +2 -1
  13. package/esm/hooks/features/rowReorder/models.d.ts +17 -0
  14. package/esm/hooks/features/rowReorder/models.js +1 -0
  15. package/esm/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
  16. package/esm/hooks/features/rowReorder/reorderExecutor.js +29 -0
  17. package/esm/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
  18. package/esm/hooks/features/rowReorder/reorderValidator.js +14 -0
  19. package/esm/hooks/features/rowReorder/types.d.ts +25 -0
  20. package/esm/hooks/features/rowReorder/types.js +1 -0
  21. package/esm/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
  22. package/esm/hooks/features/rowReorder/useGridRowReorder.js +167 -80
  23. package/esm/hooks/features/rowReorder/utils.d.ts +82 -0
  24. package/esm/hooks/features/rowReorder/utils.js +259 -0
  25. package/esm/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
  26. package/esm/hooks/features/rows/useGridRowsOverridableMethods.js +59 -0
  27. package/esm/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
  28. package/esm/hooks/features/treeData/treeDataReorderExecutor.js +534 -0
  29. package/esm/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
  30. package/esm/hooks/features/treeData/treeDataReorderValidator.js +35 -0
  31. package/esm/hooks/features/treeData/useGridTreeData.d.ts +3 -3
  32. package/esm/hooks/features/treeData/useGridTreeData.js +49 -4
  33. package/esm/hooks/features/treeData/utils.d.ts +8 -0
  34. package/esm/hooks/features/treeData/utils.js +96 -0
  35. package/esm/index.js +1 -1
  36. package/esm/internals/index.d.ts +8 -0
  37. package/esm/internals/index.js +6 -0
  38. package/esm/models/dataGridProProps.d.ts +32 -4
  39. package/esm/models/gridRowOrderChangeParams.d.ts +29 -5
  40. package/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
  41. package/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
  42. package/hooks/features/rowReorder/commonReorderConditions.js +84 -0
  43. package/hooks/features/rowReorder/index.d.ts +2 -1
  44. package/hooks/features/rowReorder/models.d.ts +17 -0
  45. package/hooks/features/rowReorder/models.js +5 -0
  46. package/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
  47. package/hooks/features/rowReorder/reorderExecutor.js +37 -0
  48. package/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
  49. package/hooks/features/rowReorder/reorderValidator.js +21 -0
  50. package/hooks/features/rowReorder/types.d.ts +25 -0
  51. package/hooks/features/rowReorder/types.js +5 -0
  52. package/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
  53. package/hooks/features/rowReorder/useGridRowReorder.js +168 -81
  54. package/hooks/features/rowReorder/utils.d.ts +82 -0
  55. package/hooks/features/rowReorder/utils.js +286 -0
  56. package/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
  57. package/hooks/features/rows/useGridRowsOverridableMethods.js +67 -0
  58. package/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
  59. package/hooks/features/treeData/treeDataReorderExecutor.js +541 -0
  60. package/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
  61. package/hooks/features/treeData/treeDataReorderValidator.js +41 -0
  62. package/hooks/features/treeData/useGridTreeData.d.ts +3 -3
  63. package/hooks/features/treeData/useGridTreeData.js +48 -3
  64. package/hooks/features/treeData/utils.d.ts +8 -0
  65. package/hooks/features/treeData/utils.js +109 -0
  66. package/index.js +1 -1
  67. package/internals/index.d.ts +8 -0
  68. package/internals/index.js +53 -1
  69. package/models/dataGridProProps.d.ts +32 -4
  70. package/models/gridRowOrderChangeParams.d.ts +29 -5
  71. 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,13 +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 timeoutRowId = React.useRef('');
59
+ const timeoutInfoRef = React.useRef(EMPTY_TIMEOUT_INFO);
58
60
  const timeout = (0, _useTimeout.default)();
59
61
  const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
60
62
  const dropTarget = React.useRef({
@@ -69,26 +71,30 @@ const useGridRowReorder = (apiRef, props) => {
69
71
  }, []);
70
72
 
71
73
  // TODO: remove sortModel check once row reorder is sorting compatible
72
- // remove treeData check once row reorder is treeData compatible
73
74
  const isRowReorderDisabled = React.useMemo(() => {
74
- return !props.rowReordering || !!sortModel.length || props.treeData;
75
- }, [props.rowReordering, sortModel, props.treeData]);
76
- const applyDropIndicator = React.useCallback((targetRowId, position) => {
77
- // Remove existing drop indicator from previous target
78
- if (previousDropIndicatorRef.current) {
79
- previousDropIndicatorRef.current.classList.remove(classes.rowDropAbove, classes.rowDropBelow);
80
- previousDropIndicatorRef.current = null;
81
- }
82
-
83
- // Apply new drop indicator
84
- if (targetRowId !== undefined && position !== null) {
85
- const targetRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${targetRowId}"]`);
86
- if (targetRow) {
87
- targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
88
- 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';
89
88
  }
89
+ if (relativeY > bottomThreshold) {
90
+ return 'below';
91
+ }
92
+ return 'inside';
90
93
  }
91
- }, [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]);
92
98
  const applyDraggedState = React.useCallback((rowId, isDragged) => {
93
99
  if (rowId) {
94
100
  const draggedRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${rowId}"]`);
@@ -101,7 +107,7 @@ const useGridRowReorder = (apiRef, props) => {
101
107
  }
102
108
  }
103
109
  }, [apiRef, classes.rowBeingDragged]);
104
- const applyRowAnimation = React.useCallback(callback => {
110
+ const applyRowAnimation = React.useCallback(async callback => {
105
111
  const rootElement = apiRef.current.rootElementRef?.current;
106
112
  if (!rootElement) {
107
113
  return;
@@ -118,7 +124,7 @@ const useGridRowReorder = (apiRef, props) => {
118
124
  initialPositions.set(rowId, row.getBoundingClientRect());
119
125
  }
120
126
  });
121
- callback();
127
+ await callback();
122
128
 
123
129
  // Use `requestAnimationFrame` to ensure DOM has updated
124
130
  requestAnimationFrame(() => {
@@ -160,15 +166,20 @@ const useGridRowReorder = (apiRef, props) => {
160
166
  if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
161
167
  return;
162
168
  }
163
- if (timeoutRowId.current) {
169
+ if (timeoutInfoRef.current) {
164
170
  timeout.clear();
165
- timeoutRowId.current = '';
171
+ timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
166
172
  }
167
173
  logger.debug(`Start dragging row ${params.id}`);
168
174
  // Prevent drag events propagation.
169
175
  // For more information check here https://github.com/mui/mui-x/issues/2680.
170
176
  event.stopPropagation();
171
- 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
+ }));
172
183
  dragRowNode.current = event.currentTarget;
173
184
  // Apply cell-level dragging class to the drag handle
174
185
  dragRowNode.current.classList.add(classes.rowDragging);
@@ -179,7 +190,7 @@ const useGridRowReorder = (apiRef, props) => {
179
190
  removeDnDStylesTimeout.current = setTimeout(() => {
180
191
  dragRowNode.current.classList.remove(classes.rowDragging);
181
192
  });
182
- const sortedRowIndexLookup = (0, _internals.gridExpandedSortedRowIndexLookupSelector)(apiRef);
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
196
  }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, applyDraggedState, timeout]);
@@ -192,35 +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
  }
218
- const sortedRowIndexLookup = (0, _internals.gridExpandedSortedRowIndexLookupSelector)(apiRef);
233
+ const sortedRowIndexLookup = (0, _xDataGrid.gridExpandedSortedRowIndexLookupSelector)(apiRef);
219
234
  const targetRowIndex = sortedRowIndexLookup[params.id];
220
235
  const sourceRowIndex = sortedRowIndexLookup[dragRowId];
221
-
222
- // Determine drop position based on relativeY position within the row
223
- const dropPosition = relativeY < midPoint ? 'above' : 'below';
224
236
  const currentReorderState = {
225
237
  dragDirection: targetRowIndex < sourceRowIndex ? 'up' : 'down',
226
238
  previousTargetId: params.id,
@@ -233,7 +245,7 @@ const useGridRowReorder = (apiRef, props) => {
233
245
 
234
246
  // Check if this is an adjacent position
235
247
  const isAdjacentPosition = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1;
236
- const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', -1, {
248
+ const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
237
249
  sourceRowId: dragRowId,
238
250
  targetRowId: params.id,
239
251
  dropPosition,
@@ -241,13 +253,21 @@ const useGridRowReorder = (apiRef, props) => {
241
253
  });
242
254
 
243
255
  // Show drop indicator for valid drops OR adjacent positions OR same node
244
- if (validatedIndex !== -1 || isAdjacentPosition || isSameNode) {
256
+ if (isRowReorderValid || isAdjacentPosition || isSameNode) {
245
257
  dropTarget.current = {
246
258
  targetRowId: params.id,
247
259
  targetRowIndex,
248
260
  dropPosition
249
261
  };
250
- 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
+ }));
251
271
  } else {
252
272
  // Clear indicators for invalid drops
253
273
  dropTarget.current = {
@@ -255,7 +275,12 @@ const useGridRowReorder = (apiRef, props) => {
255
275
  targetRowIndex: null,
256
276
  dropPosition: null
257
277
  };
258
- 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
+ }));
259
284
  }
260
285
  previousReorderState.current = currentReorderState;
261
286
  }
@@ -266,16 +291,16 @@ const useGridRowReorder = (apiRef, props) => {
266
291
  } else {
267
292
  event.dataTransfer.dropEffect = 'copy';
268
293
  }
269
- }, [dragRowId, apiRef, logger, timeout, applyDropIndicator]);
270
- const handleDragEnd = React.useCallback((_, event) => {
294
+ }, [dragRowId, apiRef, logger, timeout, calculateDropPosition]);
295
+ const handleDragEnd = React.useCallback(async (_, event) => {
271
296
  // Call the gridEditRowsStateSelector directly to avoid infnite loop
272
297
  const editRowsState = (0, _internals.gridEditRowsStateSelector)(apiRef);
273
298
  if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
274
299
  return;
275
300
  }
276
- if (timeoutRowId.current) {
301
+ if (timeoutInfoRef.current) {
277
302
  timeout.clear();
278
- timeoutRowId.current = '';
303
+ timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
279
304
  }
280
305
  logger.debug('End dragging row');
281
306
  event.preventDefault();
@@ -287,11 +312,6 @@ const useGridRowReorder = (apiRef, props) => {
287
312
  const dragDirection = previousReorderState.current.dragDirection;
288
313
  previousReorderState.current = EMPTY_REORDER_STATE;
289
314
 
290
- // Clear visual indicators and dragged state
291
- applyDropIndicator(null, null);
292
- applyDraggedState(dragRowId, false);
293
- apiRef.current.setRowDragActive(false);
294
-
295
315
  // Check if the row was dropped outside the grid.
296
316
  if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
297
317
  // Reset drop target state
@@ -302,29 +322,80 @@ const useGridRowReorder = (apiRef, props) => {
302
322
  };
303
323
  originRowIndex.current = null;
304
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
+ }));
305
333
  return;
306
334
  }
307
335
  if (dropTarget.current.targetRowIndex !== null && dropTarget.current.targetRowId !== null) {
308
- const sourceRowIndex = originRowIndex.current;
309
- const targetRowIndex = dropTarget.current.targetRowIndex;
310
- const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', targetRowIndex, {
336
+ const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
311
337
  sourceRowId: dragRowId,
312
338
  targetRowId: dropTarget.current.targetRowId,
313
339
  dropPosition: dropTarget.current.dropPosition,
314
340
  dragDirection: dragDirection
315
341
  });
316
- if (validatedIndex !== -1) {
317
- applyRowAnimation(() => {
318
- 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
+ }
349
+
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);
319
364
 
320
- // Emit the rowOrderChange event only once when the reordering stops.
321
- const rowOrderChangeParams = {
322
- row: apiRef.current.getRow(dragRowId),
323
- targetIndex: validatedIndex,
324
- oldIndex: sourceRowIndex
325
- };
326
- apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
327
- });
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
+ }));
328
399
  }
329
400
  }
330
401
 
@@ -335,8 +406,9 @@ const useGridRowReorder = (apiRef, props) => {
335
406
  dropPosition: null
336
407
  };
337
408
  setDragRowId('');
338
- }, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDropIndicator, applyDraggedState, timeout, applyRowAnimation]);
339
- const getRowReorderTargetIndex = React.useCallback((initialValue, {
409
+ }, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDraggedState, timeout, applyRowAnimation]);
410
+ const isValidRowReorderProp = props.isValidRowReorder;
411
+ const isRowReorderValid = React.useCallback((initialValue, {
340
412
  sourceRowId,
341
413
  targetRowId,
342
414
  dropPosition,
@@ -345,28 +417,43 @@ const useGridRowReorder = (apiRef, props) => {
345
417
  if ((0, _xDataGrid.gridRowMaximumTreeDepthSelector)(apiRef) > 1) {
346
418
  return initialValue;
347
419
  }
348
- const sortedRowIndexLookup = (0, _internals.gridExpandedSortedRowIndexLookupSelector)(apiRef);
420
+ const sortedRowIndexLookup = (0, _xDataGrid.gridExpandedSortedRowIndexLookupSelector)(apiRef);
349
421
  const targetRowIndex = sortedRowIndexLookup[targetRowId];
350
422
  const sourceRowIndex = sortedRowIndexLookup[sourceRowId];
351
423
 
352
- // Check if this drop would result in no actual movement
424
+ // Apply internal validation: check if this drop would result in no actual movement
353
425
  const isAdjacentNode = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
354
426
  // dragging to immediately below (above next row)
355
427
  dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
356
428
 
357
429
  if (isAdjacentNode || sourceRowIndex === targetRowIndex) {
358
- // Return -1 to prevent actual movement (indicators handled separately)
359
- return -1;
430
+ return false;
360
431
  }
361
- let finalTargetIndex;
362
- if (dragDirection === 'up') {
363
- finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1;
364
- } else {
365
- 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
+ }
366
453
  }
367
- return finalTargetIndex;
368
- }, [apiRef]);
369
- (0, _internals.useGridRegisterPipeProcessor)(apiRef, 'getRowReorderTargetIndex', getRowReorderTargetIndex);
454
+ return true;
455
+ }, [apiRef, isValidRowReorderProp]);
456
+ (0, _internals.useGridRegisterPipeProcessor)(apiRef, 'isRowReorderValid', isRowReorderValid);
370
457
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragStart', handleDragStart);
371
458
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragOver', handleDragOver);
372
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
+ }