@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.
- package/CHANGELOG.md +175 -0
- package/DataGridPro/DataGridPro.js +42 -8
- package/DataGridPro/useDataGridProComponent.js +3 -2
- package/components/GridDetailPanelToggleCell.js +0 -10
- package/components/GridRowReorderCell.js +15 -13
- package/components/GridTreeDataGroupingCell.js +0 -10
- package/components/headerFiltering/GridHeaderFilterCell.js +2 -3
- package/esm/DataGridPro/DataGridPro.js +42 -8
- package/esm/DataGridPro/useDataGridProComponent.js +4 -3
- package/esm/components/GridDetailPanelToggleCell.js +0 -10
- package/esm/components/GridRowReorderCell.js +15 -13
- package/esm/components/GridTreeDataGroupingCell.js +0 -10
- package/esm/components/headerFiltering/GridHeaderFilterCell.js +2 -3
- package/esm/hooks/features/columnPinning/useGridColumnPinningPreProcessors.js +4 -0
- package/esm/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
- package/esm/hooks/features/detailPanel/useGridDetailPanel.js +6 -5
- package/esm/hooks/features/infiniteLoader/useGridInfiniteLoader.js +4 -4
- package/esm/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
- package/esm/hooks/features/rowReorder/commonReorderConditions.js +78 -0
- package/esm/hooks/features/rowReorder/index.d.ts +2 -1
- package/esm/hooks/features/rowReorder/index.js +2 -1
- package/esm/hooks/features/rowReorder/models.d.ts +17 -0
- package/esm/hooks/features/rowReorder/models.js +1 -0
- package/esm/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
- package/esm/hooks/features/rowReorder/reorderExecutor.js +29 -0
- package/esm/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
- package/esm/hooks/features/rowReorder/reorderValidator.js +14 -0
- package/esm/hooks/features/rowReorder/types.d.ts +25 -0
- package/esm/hooks/features/rowReorder/types.js +1 -0
- package/esm/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
- package/esm/hooks/features/rowReorder/useGridRowReorder.js +171 -82
- package/esm/hooks/features/rowReorder/utils.d.ts +82 -0
- package/esm/hooks/features/rowReorder/utils.js +259 -0
- package/esm/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
- package/esm/hooks/features/rows/useGridRowsOverridableMethods.js +59 -0
- package/esm/hooks/features/serverSideLazyLoader/useGridInfiniteLoadingIntersection.js +3 -3
- package/esm/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
- package/esm/hooks/features/treeData/treeDataReorderExecutor.js +534 -0
- package/esm/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
- package/esm/hooks/features/treeData/treeDataReorderValidator.js +35 -0
- package/esm/hooks/features/treeData/useGridTreeData.d.ts +3 -3
- package/esm/hooks/features/treeData/useGridTreeData.js +49 -4
- package/esm/hooks/features/treeData/utils.d.ts +8 -0
- package/esm/hooks/features/treeData/utils.js +96 -0
- package/esm/index.js +1 -1
- package/esm/internals/index.d.ts +8 -0
- package/esm/internals/index.js +6 -0
- package/esm/models/dataGridProProps.d.ts +32 -4
- package/esm/models/gridRowOrderChangeParams.d.ts +29 -5
- package/hooks/features/columnPinning/useGridColumnPinningPreProcessors.js +4 -0
- package/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
- package/hooks/features/detailPanel/useGridDetailPanel.js +5 -4
- package/hooks/features/infiniteLoader/useGridInfiniteLoader.js +2 -2
- package/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
- package/hooks/features/rowReorder/commonReorderConditions.js +84 -0
- package/hooks/features/rowReorder/index.d.ts +2 -1
- package/hooks/features/rowReorder/models.d.ts +17 -0
- package/hooks/features/rowReorder/models.js +5 -0
- package/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
- package/hooks/features/rowReorder/reorderExecutor.js +37 -0
- package/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
- package/hooks/features/rowReorder/reorderValidator.js +21 -0
- package/hooks/features/rowReorder/types.d.ts +25 -0
- package/hooks/features/rowReorder/types.js +5 -0
- package/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
- package/hooks/features/rowReorder/useGridRowReorder.js +169 -80
- package/hooks/features/rowReorder/utils.d.ts +82 -0
- package/hooks/features/rowReorder/utils.js +286 -0
- package/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
- package/hooks/features/rows/useGridRowsOverridableMethods.js +67 -0
- package/hooks/features/serverSideLazyLoader/useGridInfiniteLoadingIntersection.js +3 -3
- package/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
- package/hooks/features/treeData/treeDataReorderExecutor.js +541 -0
- package/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
- package/hooks/features/treeData/treeDataReorderValidator.js +41 -0
- package/hooks/features/treeData/useGridTreeData.d.ts +3 -3
- package/hooks/features/treeData/useGridTreeData.js +48 -3
- package/hooks/features/treeData/utils.d.ts +8 -0
- package/hooks/features/treeData/utils.js +109 -0
- package/index.js +1 -1
- package/internals/index.d.ts +8 -0
- package/internals/index.js +53 -1
- package/models/dataGridProProps.d.ts +32 -4
- package/models/gridRowOrderChangeParams.d.ts +29 -5
- 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,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 {
|
|
3
|
-
import { DataGridProProcessedProps } from "../../../models/dataGridProProps.js";
|
|
4
|
-
export declare const useGridTreeData: (apiRef: RefObject<
|
|
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;
|