@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
@@ -0,0 +1,534 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import { gridRowNodeSelector, gridRowTreeSelector } from '@mui/x-data-grid';
3
+ import { BaseReorderOperation, RowReorderExecutor } from "../rowReorder/reorderExecutor.js";
4
+ import { calculateTargetIndex, isDescendantOf } from "../rowReorder/utils.js";
5
+ import { displaySetTreeDataPathWarning, removeNodeFromSourceParent, updateLeafPath, updateGroupHierarchyPaths, updateNodeParentAndDepth, buildTreeDataPath } from "./utils.js";
6
+
7
+ /**
8
+ * Handles reordering of items within the same parent group.
9
+ */
10
+ export class SameParentSwapOperation extends BaseReorderOperation {
11
+ operationType = 'same-parent-swap';
12
+ detectOperation(ctx) {
13
+ if (ctx.dropPosition === 'inside') {
14
+ return null;
15
+ }
16
+ const {
17
+ sourceRowId,
18
+ placeholderIndex,
19
+ sortedFilteredRowIds,
20
+ sortedFilteredRowIndexLookup,
21
+ rowTree,
22
+ apiRef
23
+ } = ctx;
24
+ const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
25
+ if (!sourceNode || sourceNode.type === 'footer') {
26
+ return null;
27
+ }
28
+ let targetIndex = placeholderIndex;
29
+ const sourceIndex = sortedFilteredRowIndexLookup[sourceRowId];
30
+ if (targetIndex === sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
31
+ targetIndex -= 1;
32
+ }
33
+ let targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
34
+ if (placeholderIndex > sourceIndex && sourceNode.parent === targetNode.parent) {
35
+ targetIndex = placeholderIndex - 1;
36
+ targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
37
+ if (targetNode && targetNode.depth !== sourceNode.depth) {
38
+ while (targetNode.depth > sourceNode.depth && targetIndex >= 0) {
39
+ targetIndex -= 1;
40
+ targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
41
+ }
42
+ }
43
+ if (targetIndex === -1) {
44
+ return null;
45
+ }
46
+ }
47
+ let isLastChild = false;
48
+ if (!targetNode) {
49
+ if (placeholderIndex >= sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
50
+ targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[sortedFilteredRowIds.length - 1]);
51
+ isLastChild = true;
52
+ } else {
53
+ return null;
54
+ }
55
+ }
56
+ let adjustedTargetNode = targetNode;
57
+
58
+ // Case A and B adjustment
59
+ if (targetNode.type === 'group' && sourceNode.parent !== targetNode.parent && sourceNode.depth > targetNode.depth) {
60
+ let i = targetIndex - 1;
61
+ while (i >= 0) {
62
+ const node = gridRowNodeSelector(apiRef, sortedFilteredRowIds[i]);
63
+ if (node && node.depth < sourceNode.depth) {
64
+ return null;
65
+ }
66
+ if (node && node.depth === sourceNode.depth) {
67
+ targetIndex = i;
68
+ adjustedTargetNode = node;
69
+ break;
70
+ }
71
+ i -= 1;
72
+ }
73
+ }
74
+
75
+ // Check if below last node in the same group as source node
76
+ const isBelowPosition = ctx.dropPosition === 'below';
77
+ if (isBelowPosition && sourceNode.parent !== adjustedTargetNode.parent) {
78
+ const unAdjustedTargetIndex = placeholderIndex - 1;
79
+ const unAdjustedTargetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[unAdjustedTargetIndex]);
80
+ if (unAdjustedTargetNode && unAdjustedTargetNode.parent === sourceNode.parent) {
81
+ adjustedTargetNode = unAdjustedTargetNode;
82
+ isLastChild = true;
83
+ }
84
+ }
85
+ if (sourceNode.parent !== adjustedTargetNode.parent) {
86
+ return null;
87
+ }
88
+ const actualTargetIndex = calculateTargetIndex(sourceNode, adjustedTargetNode, isLastChild, rowTree);
89
+ targetNode = adjustedTargetNode;
90
+ return {
91
+ sourceNode,
92
+ targetNode,
93
+ actualTargetIndex,
94
+ isLastChild,
95
+ operationType: this.operationType
96
+ };
97
+ }
98
+ executeOperation(operation, ctx) {
99
+ const {
100
+ sourceNode,
101
+ actualTargetIndex
102
+ } = operation;
103
+ const {
104
+ apiRef,
105
+ sourceRowId
106
+ } = ctx;
107
+ apiRef.current.setState(state => {
108
+ const group = gridRowTreeSelector(apiRef)[sourceNode.parent];
109
+ const currentChildren = [...group.children];
110
+ const oldIndex = currentChildren.findIndex(row => row === sourceRowId);
111
+ if (oldIndex === -1 || actualTargetIndex === -1 || oldIndex === actualTargetIndex) {
112
+ return state;
113
+ }
114
+ currentChildren.splice(actualTargetIndex, 0, currentChildren.splice(oldIndex, 1)[0]);
115
+ return _extends({}, state, {
116
+ rows: _extends({}, state.rows, {
117
+ tree: _extends({}, state.rows.tree, {
118
+ [sourceNode.parent]: _extends({}, group, {
119
+ children: currentChildren
120
+ })
121
+ })
122
+ })
123
+ });
124
+ });
125
+ apiRef.current.publishEvent('rowsSet');
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Handles moving leaf nodes between different parents.
131
+ */
132
+ class CrossParentLeafOperation extends BaseReorderOperation {
133
+ operationType = 'cross-parent-leaf';
134
+ detectOperation(ctx) {
135
+ // Fail for "inside" position - let DropOnLeafOperation handle it
136
+ if (ctx.dropPosition === 'inside') {
137
+ return null;
138
+ }
139
+ const {
140
+ sourceRowId,
141
+ placeholderIndex,
142
+ sortedFilteredRowIds,
143
+ rowTree,
144
+ apiRef,
145
+ setTreeDataPath
146
+ } = ctx;
147
+ const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
148
+ if (!sourceNode || sourceNode.type !== 'leaf') {
149
+ return null;
150
+ }
151
+ if (!setTreeDataPath) {
152
+ displaySetTreeDataPathWarning('Cross-parent reordering');
153
+ }
154
+ let targetIndex = placeholderIndex;
155
+ if (targetIndex === sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
156
+ targetIndex = sortedFilteredRowIds.length - 1;
157
+ }
158
+ if (targetIndex < 0) {
159
+ return null;
160
+ }
161
+ const targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
162
+ if (!targetNode) {
163
+ return null;
164
+ }
165
+ if (sourceNode.parent === targetNode.parent) {
166
+ return null;
167
+ }
168
+ const actualTargetIndex = calculateTargetIndex(sourceNode, targetNode, placeholderIndex >= sortedFilteredRowIds.length, rowTree);
169
+ return {
170
+ sourceNode,
171
+ targetNode,
172
+ actualTargetIndex,
173
+ isLastChild: placeholderIndex >= sortedFilteredRowIds.length,
174
+ operationType: this.operationType
175
+ };
176
+ }
177
+ async executeOperation(operation, ctx) {
178
+ const {
179
+ sourceNode,
180
+ targetNode,
181
+ actualTargetIndex
182
+ } = operation;
183
+ const {
184
+ apiRef
185
+ } = ctx;
186
+ const rowTree = gridRowTreeSelector(apiRef);
187
+ const targetParentNode = rowTree[targetNode.parent];
188
+ const targetPath = buildTreeDataPath(targetParentNode, rowTree);
189
+ const updatedRow = await updateLeafPath(sourceNode, targetPath, ctx);
190
+ if (!updatedRow) {
191
+ return;
192
+ }
193
+
194
+ // Update tree structure
195
+ apiRef.current.setState(state => {
196
+ const updatedTree = _extends({}, state.rows.tree);
197
+ removeNodeFromSourceParent(updatedTree, sourceNode);
198
+ const targetParent = updatedTree[targetNode.parent];
199
+ const targetChildren = [...targetParent.children];
200
+ targetChildren.splice(actualTargetIndex, 0, sourceNode.id);
201
+ updatedTree[targetNode.parent] = _extends({}, targetParent, {
202
+ children: targetChildren
203
+ });
204
+ const parentNode = updatedTree[targetNode.parent];
205
+ updateNodeParentAndDepth(updatedTree, sourceNode, targetNode.parent, parentNode.depth + 1);
206
+ return _extends({}, state, {
207
+ rows: _extends({}, state.rows, {
208
+ tree: updatedTree
209
+ })
210
+ });
211
+ });
212
+ apiRef.current.updateRows([updatedRow]);
213
+ apiRef.current.publishEvent('rowsSet');
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Handles dropping any node (leaf or group) "inside" a leaf node.
219
+ * This converts the target leaf into a parent group and makes the dragged node its child.
220
+ */
221
+ class DropOnLeafOperation extends BaseReorderOperation {
222
+ operationType = 'drop-on-leaf';
223
+ detectOperation(ctx) {
224
+ const {
225
+ sourceRowId,
226
+ dropPosition,
227
+ placeholderIndex,
228
+ sortedFilteredRowIds,
229
+ apiRef,
230
+ setTreeDataPath
231
+ } = ctx;
232
+ if (dropPosition !== 'inside') {
233
+ return null;
234
+ }
235
+ const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
236
+ if (!sourceNode || sourceNode.type === 'footer') {
237
+ return null;
238
+ }
239
+ if (!setTreeDataPath) {
240
+ displaySetTreeDataPathWarning('Drop on leaf reordering');
241
+ }
242
+
243
+ // Find target node
244
+ let targetIndex = placeholderIndex;
245
+ if (targetIndex === sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
246
+ targetIndex = sortedFilteredRowIds.length - 1;
247
+ }
248
+ if (targetIndex < 0) {
249
+ return null;
250
+ }
251
+ const targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
252
+ if (!targetNode || targetNode.type !== 'leaf') {
253
+ return null;
254
+ }
255
+
256
+ // Target leaf will become a parent, so the actual target index is 0 (first child)
257
+ const actualTargetIndex = 0;
258
+ return {
259
+ sourceNode,
260
+ targetNode,
261
+ actualTargetIndex,
262
+ isLastChild: false,
263
+ operationType: this.operationType
264
+ };
265
+ }
266
+ async executeOperation(operation, ctx) {
267
+ const {
268
+ sourceNode,
269
+ targetNode
270
+ } = operation;
271
+ const {
272
+ apiRef
273
+ } = ctx;
274
+ const rowTree = gridRowTreeSelector(apiRef);
275
+
276
+ // Build target path for the new structure
277
+ const targetPath = buildTreeDataPath(targetNode, rowTree);
278
+ let rowsToUpdate = [];
279
+
280
+ // Handle source node path updates
281
+ if (sourceNode.type === 'leaf') {
282
+ // Simple leaf move
283
+ const updatedRow = await updateLeafPath(sourceNode, targetPath, ctx);
284
+ if (!updatedRow) {
285
+ return;
286
+ }
287
+ rowsToUpdate.push(updatedRow);
288
+ } else {
289
+ // Group move - update entire hierarchy
290
+ const sourceParentNode = rowTree[sourceNode.parent];
291
+ const sourceBasePath = buildTreeDataPath(sourceParentNode, rowTree);
292
+ rowsToUpdate = await updateGroupHierarchyPaths(sourceNode, sourceBasePath, targetPath, ctx);
293
+ if (rowsToUpdate.length === 0) {
294
+ return;
295
+ }
296
+ }
297
+ apiRef.current.setState(state => {
298
+ const updatedTree = _extends({}, state.rows.tree);
299
+ removeNodeFromSourceParent(updatedTree, sourceNode);
300
+ updatedTree[targetNode.id] = _extends({}, targetNode, {
301
+ type: 'group',
302
+ children: [sourceNode.id],
303
+ childrenFromPath: {},
304
+ groupingField: null,
305
+ isAutoGenerated: false,
306
+ childrenExpanded: true
307
+ });
308
+ updateNodeParentAndDepth(updatedTree, sourceNode, targetNode.id, targetNode.depth + 1);
309
+ return _extends({}, state, {
310
+ rows: _extends({}, state.rows, {
311
+ tree: updatedTree
312
+ })
313
+ });
314
+ });
315
+
316
+ // Update rows in the grid
317
+ apiRef.current.updateRows(rowsToUpdate);
318
+ apiRef.current.publishEvent('rowsSet');
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Handles dropping any node (leaf or group) "inside" a group node.
324
+ * This makes the dragged node the first child of the target group.
325
+ */
326
+ class DropOnGroupOperation extends BaseReorderOperation {
327
+ operationType = 'drop-on-group';
328
+ detectOperation(ctx) {
329
+ const {
330
+ sourceRowId,
331
+ dropPosition,
332
+ placeholderIndex,
333
+ sortedFilteredRowIds,
334
+ apiRef,
335
+ setTreeDataPath,
336
+ rowTree
337
+ } = ctx;
338
+
339
+ // Only applies to "inside" drop position
340
+ if (dropPosition !== 'inside') {
341
+ return null;
342
+ }
343
+ const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
344
+ if (!sourceNode || sourceNode.type === 'footer') {
345
+ return null;
346
+ }
347
+ if (!setTreeDataPath) {
348
+ displaySetTreeDataPathWarning('Drop on group reordering');
349
+ }
350
+ let targetIndex = placeholderIndex;
351
+ if (targetIndex === sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
352
+ targetIndex = sortedFilteredRowIds.length - 1;
353
+ }
354
+ if (targetIndex < 0) {
355
+ return null;
356
+ }
357
+ const targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
358
+ if (!targetNode || targetNode.type !== 'group') {
359
+ return null;
360
+ }
361
+ if (isDescendantOf(targetNode, sourceNode, rowTree)) {
362
+ return null;
363
+ }
364
+ const actualTargetIndex = 0;
365
+ return {
366
+ sourceNode,
367
+ targetNode,
368
+ actualTargetIndex,
369
+ isLastChild: false,
370
+ operationType: this.operationType
371
+ };
372
+ }
373
+ async executeOperation(operation, ctx) {
374
+ const {
375
+ sourceNode,
376
+ targetNode
377
+ } = operation;
378
+ const {
379
+ apiRef
380
+ } = ctx;
381
+ const rowTree = gridRowTreeSelector(apiRef);
382
+
383
+ // Build target path for the new structure
384
+ const targetPath = buildTreeDataPath(targetNode, rowTree);
385
+ let rowsToUpdate = [];
386
+
387
+ // Handle source node path updates
388
+ if (sourceNode.type === 'leaf') {
389
+ // Simple leaf move
390
+ const updatedRow = await updateLeafPath(sourceNode, targetPath, ctx);
391
+ if (!updatedRow) {
392
+ return;
393
+ }
394
+ rowsToUpdate.push(updatedRow);
395
+ } else {
396
+ // Group move - update entire hierarchy
397
+ const sourceParentNode = rowTree[sourceNode.parent];
398
+ const sourceBasePath = buildTreeDataPath(sourceParentNode, rowTree);
399
+ rowsToUpdate = await updateGroupHierarchyPaths(sourceNode, sourceBasePath, targetPath, ctx);
400
+ if (rowsToUpdate.length === 0) {
401
+ return;
402
+ }
403
+ }
404
+
405
+ // Update tree structure
406
+ apiRef.current.setState(state => {
407
+ const updatedTree = _extends({}, state.rows.tree);
408
+
409
+ // Remove source from its current parent
410
+ removeNodeFromSourceParent(updatedTree, sourceNode);
411
+
412
+ // Add source as first child of target group
413
+ const targetGroup = updatedTree[targetNode.id];
414
+ const targetChildren = [sourceNode.id, ...targetGroup.children];
415
+ updatedTree[targetNode.id] = _extends({}, targetGroup, {
416
+ children: targetChildren
417
+ });
418
+ updateNodeParentAndDepth(updatedTree, sourceNode, targetNode.id, targetNode.depth + 1);
419
+ return _extends({}, state, {
420
+ rows: _extends({}, state.rows, {
421
+ tree: updatedTree
422
+ })
423
+ });
424
+ });
425
+
426
+ // Update rows in the grid
427
+ apiRef.current.updateRows(rowsToUpdate);
428
+ apiRef.current.publishEvent('rowsSet');
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Handles moving group nodes (and all their descendants) between different parents.
434
+ */
435
+ class CrossParentGroupOperation extends BaseReorderOperation {
436
+ operationType = 'cross-parent-group';
437
+ detectOperation(ctx) {
438
+ if (ctx.dropPosition === 'inside') {
439
+ return null;
440
+ }
441
+ const {
442
+ sourceRowId,
443
+ placeholderIndex,
444
+ sortedFilteredRowIds,
445
+ rowTree,
446
+ apiRef,
447
+ setTreeDataPath
448
+ } = ctx;
449
+ const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
450
+ if (!sourceNode || sourceNode.type !== 'group') {
451
+ return null;
452
+ }
453
+ if (!setTreeDataPath) {
454
+ displaySetTreeDataPathWarning('Cross-parent reordering');
455
+ }
456
+
457
+ // Find target node
458
+ let targetIndex = placeholderIndex;
459
+ if (targetIndex === sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
460
+ targetIndex = sortedFilteredRowIds.length - 1;
461
+ }
462
+ if (targetIndex < 0) {
463
+ return null;
464
+ }
465
+ const targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[targetIndex]);
466
+ if (!targetNode) {
467
+ return null;
468
+ }
469
+ if (sourceNode.parent === targetNode.parent) {
470
+ return null;
471
+ }
472
+ if (isDescendantOf(targetNode, sourceNode, rowTree)) {
473
+ return null;
474
+ }
475
+ const actualTargetIndex = calculateTargetIndex(sourceNode, targetNode, placeholderIndex >= sortedFilteredRowIds.length, rowTree);
476
+ return {
477
+ sourceNode,
478
+ targetNode,
479
+ actualTargetIndex,
480
+ isLastChild: placeholderIndex >= sortedFilteredRowIds.length,
481
+ operationType: this.operationType
482
+ };
483
+ }
484
+ async executeOperation(operation, ctx) {
485
+ const {
486
+ sourceNode,
487
+ targetNode,
488
+ actualTargetIndex
489
+ } = operation;
490
+ const {
491
+ apiRef
492
+ } = ctx;
493
+ const rowTree = gridRowTreeSelector(apiRef);
494
+
495
+ // Calculate new base path for the moved group
496
+ const targetParentNode = rowTree[targetNode.parent];
497
+ const newBasePath = buildTreeDataPath(targetParentNode, rowTree);
498
+
499
+ // Calculate the original base path depth
500
+ const sourceParentNode = rowTree[sourceNode.parent];
501
+ const sourceBasePath = buildTreeDataPath(sourceParentNode, rowTree);
502
+
503
+ // Update group hierarchy paths
504
+ const updates = await updateGroupHierarchyPaths(sourceNode, sourceBasePath, newBasePath, ctx);
505
+ if (updates.length > 0) {
506
+ // Update tree structure (partial moves are allowed)
507
+ apiRef.current.setState(state => {
508
+ const updatedTree = _extends({}, state.rows.tree);
509
+
510
+ // Remove from source parent
511
+ removeNodeFromSourceParent(updatedTree, sourceNode);
512
+
513
+ // Add to target parent
514
+ const targetParent = updatedTree[targetNode.parent];
515
+ const targetChildren = [...targetParent.children];
516
+ targetChildren.splice(actualTargetIndex, 0, sourceNode.id);
517
+ updatedTree[targetNode.parent] = _extends({}, targetParent, {
518
+ children: targetChildren
519
+ });
520
+ const newParentNode = updatedTree[targetNode.parent];
521
+ const newGroupDepth = newParentNode.depth + 1;
522
+ updateNodeParentAndDepth(updatedTree, sourceNode, targetNode.parent, newGroupDepth);
523
+ return _extends({}, state, {
524
+ rows: _extends({}, state.rows, {
525
+ tree: updatedTree
526
+ })
527
+ });
528
+ });
529
+ apiRef.current.updateRows(updates);
530
+ apiRef.current.publishEvent('rowsSet');
531
+ }
532
+ }
533
+ }
534
+ export const treeDataReorderExecutor = new RowReorderExecutor([new SameParentSwapOperation(), new CrossParentLeafOperation(), new DropOnLeafOperation(), new DropOnGroupOperation(), new CrossParentGroupOperation()]);
@@ -0,0 +1,2 @@
1
+ import { RowReorderValidator } from "../rowReorder/reorderValidator.js";
2
+ export declare const treeDataReorderValidator: RowReorderValidator;
@@ -0,0 +1,35 @@
1
+ import { gridRowTreeSelector } from '@mui/x-data-grid';
2
+ import { RowReorderValidator } from "../rowReorder/reorderValidator.js";
3
+ import { commonReorderConditions as conditions } from "../rowReorder/commonReorderConditions.js";
4
+ const validationRules = [{
5
+ name: 'same-position',
6
+ applies: ctx => ctx.sourceNode.id === ctx.targetNode.id,
7
+ isInvalid: () => true,
8
+ message: 'Source and target are the same'
9
+ }, {
10
+ name: 'adjacent-position',
11
+ applies: ctx => conditions.isAdjacentPosition(ctx),
12
+ isInvalid: () => true,
13
+ message: 'Source and target are adjacent'
14
+ }, {
15
+ name: 'to-descendent',
16
+ applies: ctx => conditions.isGroupToLeaf(ctx) || conditions.isGroupToGroup(ctx),
17
+ isInvalid: ctx => {
18
+ let currentNode = ctx.targetNode;
19
+ const rowTree = gridRowTreeSelector(ctx.apiRef);
20
+ while (currentNode.parent) {
21
+ currentNode = rowTree[currentNode.parent];
22
+ if (currentNode.id === ctx.sourceNode.id) {
23
+ return true;
24
+ }
25
+ }
26
+ return false;
27
+ },
28
+ message: 'Cannot drop group on one of its descendents'
29
+ }, {
30
+ name: 'group-to-group-above-leaf-belongs-to-source',
31
+ applies: ctx => conditions.isGroupToGroup(ctx) && conditions.isDropAbove(ctx) && conditions.prevIsLeaf(ctx),
32
+ isInvalid: conditions.prevBelongsToSource,
33
+ message: 'Previous leaf belongs to source group or its descendants'
34
+ }];
35
+ export const treeDataReorderValidator = new RowReorderValidator(validationRules);
@@ -1,4 +1,4 @@
1
1
  import { RefObject } from '@mui/x-internals/types';
2
- import { GridApiPro } from "../../../models/gridApiPro.js";
3
- import { DataGridProProcessedProps } from "../../../models/dataGridProProps.js";
4
- export declare const useGridTreeData: (apiRef: RefObject<GridApiPro>, props: Pick<DataGridProProcessedProps, "dataSource">) => void;
2
+ import type { GridPrivateApiPro } from "../../../models/gridApiPro.js";
3
+ import type { DataGridProProcessedProps } from "../../../models/dataGridProProps.js";
4
+ export declare const useGridTreeData: (apiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "treeData" | "dataSource" | "isValidRowReorder">) => void;
@@ -1,10 +1,9 @@
1
1
  import * as React from 'react';
2
- import { useGridEvent } from '@mui/x-data-grid';
2
+ import { useGridEvent, gridRowMaximumTreeDepthSelector, gridExpandedSortedRowIdsSelector, gridRowTreeSelector, gridExpandedSortedRowIndexLookupSelector } from '@mui/x-data-grid';
3
+ import { useGridRegisterPipeProcessor } from '@mui/x-data-grid/internals';
3
4
  import { GRID_TREE_DATA_GROUPING_FIELD } from "./gridTreeDataGroupColDef.js";
5
+ import { treeDataReorderValidator } from "./treeDataReorderValidator.js";
4
6
  export const useGridTreeData = (apiRef, props) => {
5
- /**
6
- * EVENTS
7
- */
8
7
  const handleCellKeyDown = React.useCallback((params, event) => {
9
8
  const cellParams = apiRef.current.getCellParams(params.id, params.field);
10
9
  if (cellParams.colDef.field === GRID_TREE_DATA_GROUPING_FIELD && (event.key === ' ' || event.key === 'Enter') && !event.shiftKey) {
@@ -18,5 +17,51 @@ export const useGridTreeData = (apiRef, props) => {
18
17
  apiRef.current.setRowChildrenExpansion(params.id, !params.rowNode.childrenExpanded);
19
18
  }
20
19
  }, [apiRef, props.dataSource]);
20
+ const isValidRowReorderProp = props.isValidRowReorder;
21
+ const isRowReorderValid = React.useCallback((initialValue, {
22
+ sourceRowId,
23
+ targetRowId,
24
+ dropPosition,
25
+ dragDirection
26
+ }) => {
27
+ if (gridRowMaximumTreeDepthSelector(apiRef) === 1 || !props.treeData) {
28
+ return initialValue;
29
+ }
30
+ const expandedSortedRowIndexLookup = gridExpandedSortedRowIndexLookupSelector(apiRef);
31
+ const expandedSortedRowIds = gridExpandedSortedRowIdsSelector(apiRef);
32
+ const rowTree = gridRowTreeSelector(apiRef);
33
+ const targetRowIndex = expandedSortedRowIndexLookup[targetRowId];
34
+ const sourceNode = rowTree[sourceRowId];
35
+ const targetNode = rowTree[targetRowId];
36
+ const prevNode = targetRowIndex > 0 ? rowTree[expandedSortedRowIds[targetRowIndex - 1]] : null;
37
+ const nextNode = targetRowIndex < expandedSortedRowIds.length - 1 ? rowTree[expandedSortedRowIds[targetRowIndex + 1]] : null;
38
+
39
+ // Basic validity checks
40
+ if (!sourceNode || !targetNode) {
41
+ return false;
42
+ }
43
+
44
+ // Create context object
45
+ const context = {
46
+ apiRef,
47
+ sourceNode,
48
+ targetNode,
49
+ prevNode,
50
+ nextNode,
51
+ dropPosition,
52
+ dragDirection
53
+ };
54
+
55
+ // First apply internal validation
56
+ let isValid = treeDataReorderValidator.validate(context);
57
+
58
+ // If internal validation passes AND user provided additional validation
59
+ if (isValid && isValidRowReorderProp) {
60
+ // Apply additional user restrictions
61
+ isValid = isValidRowReorderProp(context);
62
+ }
63
+ return isValid;
64
+ }, [apiRef, props.treeData, isValidRowReorderProp]);
65
+ useGridRegisterPipeProcessor(apiRef, 'isRowReorderValid', isRowReorderValid);
21
66
  useGridEvent(apiRef, 'cellKeyDown', handleCellKeyDown);
22
67
  };
@@ -0,0 +1,8 @@
1
+ import { type GridRowId, type GridTreeNode, type GridGroupNode, type GridValidRowModel, type GridRowTreeConfig } from '@mui/x-data-grid';
2
+ import type { ReorderExecutionContext } from "../rowReorder/types.js";
3
+ export declare const buildTreeDataPath: (node: GridTreeNode, tree: GridRowTreeConfig) => string[];
4
+ export declare function displaySetTreeDataPathWarning(operationName: string): void;
5
+ export declare function removeNodeFromSourceParent(updatedTree: Record<string, GridTreeNode>, sourceNode: GridTreeNode): void;
6
+ export declare function updateLeafPath(sourceNode: GridTreeNode, targetPath: string[], ctx: ReorderExecutionContext): Promise<GridValidRowModel | null>;
7
+ export declare function updateGroupHierarchyPaths(sourceNode: GridGroupNode, sourceBasePath: string[], targetPath: string[], ctx: ReorderExecutionContext): Promise<GridValidRowModel[]>;
8
+ export declare function updateNodeParentAndDepth(updatedTree: Record<string, GridTreeNode>, node: GridTreeNode, newParentId: GridRowId, newDepth: number): void;