@mui/x-data-grid-pro 8.19.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 +62 -0
- package/DataGridPro/DataGridPro.js +29 -2
- package/components/GridRowReorderCell.js +15 -3
- package/components/headerFiltering/GridHeaderFilterCell.js +2 -3
- package/esm/DataGridPro/DataGridPro.js +29 -2
- package/esm/components/GridRowReorderCell.js +15 -3
- package/esm/components/headerFiltering/GridHeaderFilterCell.js +2 -3
- package/esm/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
- 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 +167 -80
- 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/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/dataSource/useGridDataSourceBasePro.js +1 -1
- 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 +168 -81
- 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/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 +2 -2
|
@@ -4,22 +4,25 @@ import _extends from "@babel/runtime/helpers/esm/extends";
|
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import useTimeout from '@mui/utils/useTimeout';
|
|
6
6
|
import composeClasses from '@mui/utils/composeClasses';
|
|
7
|
-
import { useGridLogger, useGridEvent, getDataGridUtilityClass, useGridSelector, gridSortModelSelector, useGridEventPriority, gridRowNodeSelector, gridRowMaximumTreeDepthSelector, useGridApiMethod } from '@mui/x-data-grid';
|
|
8
|
-
import { gridEditRowsStateSelector, useGridRegisterPipeProcessor
|
|
7
|
+
import { useGridLogger, useGridEvent, getDataGridUtilityClass, useGridSelector, gridSortModelSelector, useGridEventPriority, gridRowNodeSelector, gridRowMaximumTreeDepthSelector, useGridApiMethod, gridExpandedSortedRowIdsSelector, gridRowTreeSelector, gridExpandedSortedRowIndexLookupSelector, GRID_ROOT_GROUP_ID } from '@mui/x-data-grid';
|
|
8
|
+
import { gridEditRowsStateSelector, useGridRegisterPipeProcessor } from '@mui/x-data-grid/internals';
|
|
9
9
|
import { GRID_REORDER_COL_DEF } from "./gridRowReorderColDef.js";
|
|
10
|
+
import { findCellElement } from "./utils.js";
|
|
10
11
|
const EMPTY_REORDER_STATE = {
|
|
11
12
|
previousTargetId: null,
|
|
12
13
|
dragDirection: null,
|
|
13
14
|
previousDropPosition: null
|
|
14
15
|
};
|
|
16
|
+
const TIMEOUT_CLEAR_BUFFER_PX = 5;
|
|
17
|
+
const EMPTY_TIMEOUT_INFO = {
|
|
18
|
+
rowId: null
|
|
19
|
+
};
|
|
15
20
|
const useUtilityClasses = ownerState => {
|
|
16
21
|
const {
|
|
17
22
|
classes
|
|
18
23
|
} = ownerState;
|
|
19
24
|
const slots = {
|
|
20
25
|
rowDragging: ['row--dragging'],
|
|
21
|
-
rowDropAbove: ['row--dropAbove'],
|
|
22
|
-
rowDropBelow: ['row--dropBelow'],
|
|
23
26
|
rowBeingDragged: ['row--beingDragged']
|
|
24
27
|
};
|
|
25
28
|
return composeClasses(slots, getDataGridUtilityClass, classes);
|
|
@@ -40,13 +43,12 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
40
43
|
const dragRowNode = React.useRef(null);
|
|
41
44
|
const originRowIndex = React.useRef(null);
|
|
42
45
|
const removeDnDStylesTimeout = React.useRef(undefined);
|
|
43
|
-
const previousDropIndicatorRef = React.useRef(null);
|
|
44
46
|
const ownerState = {
|
|
45
47
|
classes: props.classes
|
|
46
48
|
};
|
|
47
49
|
const classes = useUtilityClasses(ownerState);
|
|
48
50
|
const [dragRowId, setDragRowId] = React.useState('');
|
|
49
|
-
const
|
|
51
|
+
const timeoutInfoRef = React.useRef(EMPTY_TIMEOUT_INFO);
|
|
50
52
|
const timeout = useTimeout();
|
|
51
53
|
const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
|
|
52
54
|
const dropTarget = React.useRef({
|
|
@@ -61,26 +63,30 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
61
63
|
}, []);
|
|
62
64
|
|
|
63
65
|
// TODO: remove sortModel check once row reorder is sorting compatible
|
|
64
|
-
// remove treeData check once row reorder is treeData compatible
|
|
65
66
|
const isRowReorderDisabled = React.useMemo(() => {
|
|
66
|
-
return !props.rowReordering || !!sortModel.length
|
|
67
|
-
}, [props.rowReordering, sortModel
|
|
68
|
-
const
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
|
|
80
|
-
previousDropIndicatorRef.current = targetRow;
|
|
67
|
+
return !props.rowReordering || !!sortModel.length;
|
|
68
|
+
}, [props.rowReordering, sortModel]);
|
|
69
|
+
const calculateDropPosition = React.useCallback(event => {
|
|
70
|
+
// For tree data, we need to find the cell element to avoid flickerings on top 20% selection
|
|
71
|
+
const targetElement = props.treeData ? findCellElement(event.target) : event.target;
|
|
72
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
73
|
+
const relativeY = Math.floor(event.clientY - targetRect.top);
|
|
74
|
+
if (props.treeData) {
|
|
75
|
+
// For tree data: top 20% = above, middle 60% = over, bottom 20% = below
|
|
76
|
+
const topThreshold = targetRect.height * 0.2;
|
|
77
|
+
const bottomThreshold = targetRect.height * 0.8;
|
|
78
|
+
if (relativeY < topThreshold) {
|
|
79
|
+
return 'above';
|
|
81
80
|
}
|
|
81
|
+
if (relativeY > bottomThreshold) {
|
|
82
|
+
return 'below';
|
|
83
|
+
}
|
|
84
|
+
return 'inside';
|
|
82
85
|
}
|
|
83
|
-
|
|
86
|
+
// For flat data and row grouping: split at midpoint
|
|
87
|
+
const midPoint = targetRect.height / 2;
|
|
88
|
+
return relativeY < midPoint ? 'above' : 'below';
|
|
89
|
+
}, [props.treeData]);
|
|
84
90
|
const applyDraggedState = React.useCallback((rowId, isDragged) => {
|
|
85
91
|
if (rowId) {
|
|
86
92
|
const draggedRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${rowId}"]`);
|
|
@@ -93,7 +99,7 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
}, [apiRef, classes.rowBeingDragged]);
|
|
96
|
-
const applyRowAnimation = React.useCallback(callback => {
|
|
102
|
+
const applyRowAnimation = React.useCallback(async callback => {
|
|
97
103
|
const rootElement = apiRef.current.rootElementRef?.current;
|
|
98
104
|
if (!rootElement) {
|
|
99
105
|
return;
|
|
@@ -110,7 +116,7 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
110
116
|
initialPositions.set(rowId, row.getBoundingClientRect());
|
|
111
117
|
}
|
|
112
118
|
});
|
|
113
|
-
callback();
|
|
119
|
+
await callback();
|
|
114
120
|
|
|
115
121
|
// Use `requestAnimationFrame` to ensure DOM has updated
|
|
116
122
|
requestAnimationFrame(() => {
|
|
@@ -152,15 +158,20 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
152
158
|
if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
|
|
153
159
|
return;
|
|
154
160
|
}
|
|
155
|
-
if (
|
|
161
|
+
if (timeoutInfoRef.current) {
|
|
156
162
|
timeout.clear();
|
|
157
|
-
|
|
163
|
+
timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
|
|
158
164
|
}
|
|
159
165
|
logger.debug(`Start dragging row ${params.id}`);
|
|
160
166
|
// Prevent drag events propagation.
|
|
161
167
|
// For more information check here https://github.com/mui/mui-x/issues/2680.
|
|
162
168
|
event.stopPropagation();
|
|
163
|
-
apiRef.current.
|
|
169
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
170
|
+
rowReorder: _extends({}, state.rowReorder, {
|
|
171
|
+
isActive: true,
|
|
172
|
+
draggedRowId: params.id
|
|
173
|
+
})
|
|
174
|
+
}));
|
|
164
175
|
dragRowNode.current = event.currentTarget;
|
|
165
176
|
// Apply cell-level dragging class to the drag handle
|
|
166
177
|
dragRowNode.current.classList.add(classes.rowDragging);
|
|
@@ -184,35 +195,36 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
184
195
|
if (!sourceNode || !targetNode || targetNode.type === 'footer' || targetNode.type === 'pinnedRow' || !event.target) {
|
|
185
196
|
return;
|
|
186
197
|
}
|
|
187
|
-
|
|
188
|
-
// Find the relative 'y' mouse position based on the event.target
|
|
189
|
-
const targetRect = event.target.getBoundingClientRect();
|
|
190
|
-
const relativeY = Math.floor(event.clientY - targetRect.top);
|
|
191
|
-
const midPoint = Math.floor(targetRect.height / 2);
|
|
192
198
|
logger.debug(`Dragging over row ${params.id}`);
|
|
193
199
|
event.preventDefault();
|
|
194
200
|
// Prevent drag events propagation.
|
|
195
201
|
// For more information check here https://github.com/mui/mui-x/issues/2680.
|
|
196
202
|
event.stopPropagation();
|
|
197
|
-
if (
|
|
203
|
+
if (timeoutInfoRef.current && (timeoutInfoRef.current.rowId !== params.id ||
|
|
204
|
+
// Avoid accidental opening of node when the user is moving over a row
|
|
205
|
+
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)) {
|
|
198
206
|
timeout.clear();
|
|
199
|
-
|
|
207
|
+
timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
|
|
200
208
|
}
|
|
201
|
-
|
|
209
|
+
|
|
210
|
+
// Calculate drop position using new logic
|
|
211
|
+
const dropPosition = calculateDropPosition(event);
|
|
212
|
+
if (targetNode.type === 'group' && !targetNode.childrenExpanded && !timeoutInfoRef.current.rowId && targetNode.id !== sourceNode.id && (dropPosition === 'inside' || targetNode.depth < sourceNode.depth)) {
|
|
202
213
|
timeout.start(500, () => {
|
|
203
214
|
const rowNode = gridRowNodeSelector(apiRef, params.id);
|
|
204
215
|
// TODO: Handle `dataSource` case with https://github.com/mui/mui-x/issues/18947
|
|
205
216
|
apiRef.current.setRowChildrenExpansion(params.id, !rowNode.childrenExpanded);
|
|
206
217
|
});
|
|
207
|
-
|
|
218
|
+
timeoutInfoRef.current = {
|
|
219
|
+
rowId: params.id,
|
|
220
|
+
clientY: event.clientY,
|
|
221
|
+
clientX: event.clientX
|
|
222
|
+
};
|
|
208
223
|
return;
|
|
209
224
|
}
|
|
210
225
|
const sortedRowIndexLookup = gridExpandedSortedRowIndexLookupSelector(apiRef);
|
|
211
226
|
const targetRowIndex = sortedRowIndexLookup[params.id];
|
|
212
227
|
const sourceRowIndex = sortedRowIndexLookup[dragRowId];
|
|
213
|
-
|
|
214
|
-
// Determine drop position based on relativeY position within the row
|
|
215
|
-
const dropPosition = relativeY < midPoint ? 'above' : 'below';
|
|
216
228
|
const currentReorderState = {
|
|
217
229
|
dragDirection: targetRowIndex < sourceRowIndex ? 'up' : 'down',
|
|
218
230
|
previousTargetId: params.id,
|
|
@@ -225,7 +237,7 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
225
237
|
|
|
226
238
|
// Check if this is an adjacent position
|
|
227
239
|
const isAdjacentPosition = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1;
|
|
228
|
-
const
|
|
240
|
+
const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
|
|
229
241
|
sourceRowId: dragRowId,
|
|
230
242
|
targetRowId: params.id,
|
|
231
243
|
dropPosition,
|
|
@@ -233,13 +245,21 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
233
245
|
});
|
|
234
246
|
|
|
235
247
|
// Show drop indicator for valid drops OR adjacent positions OR same node
|
|
236
|
-
if (
|
|
248
|
+
if (isRowReorderValid || isAdjacentPosition || isSameNode) {
|
|
237
249
|
dropTarget.current = {
|
|
238
250
|
targetRowId: params.id,
|
|
239
251
|
targetRowIndex,
|
|
240
252
|
dropPosition
|
|
241
253
|
};
|
|
242
|
-
|
|
254
|
+
// Update state with drop target
|
|
255
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
256
|
+
rowReorder: _extends({}, state.rowReorder, {
|
|
257
|
+
dropTarget: {
|
|
258
|
+
rowId: params.id,
|
|
259
|
+
position: dropPosition
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
}));
|
|
243
263
|
} else {
|
|
244
264
|
// Clear indicators for invalid drops
|
|
245
265
|
dropTarget.current = {
|
|
@@ -247,7 +267,12 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
247
267
|
targetRowIndex: null,
|
|
248
268
|
dropPosition: null
|
|
249
269
|
};
|
|
250
|
-
|
|
270
|
+
// Clear state drop target
|
|
271
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
272
|
+
rowReorder: _extends({}, state.rowReorder, {
|
|
273
|
+
dropTarget: undefined
|
|
274
|
+
})
|
|
275
|
+
}));
|
|
251
276
|
}
|
|
252
277
|
previousReorderState.current = currentReorderState;
|
|
253
278
|
}
|
|
@@ -258,16 +283,16 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
258
283
|
} else {
|
|
259
284
|
event.dataTransfer.dropEffect = 'copy';
|
|
260
285
|
}
|
|
261
|
-
}, [dragRowId, apiRef, logger, timeout,
|
|
262
|
-
const handleDragEnd = React.useCallback((_, event) => {
|
|
286
|
+
}, [dragRowId, apiRef, logger, timeout, calculateDropPosition]);
|
|
287
|
+
const handleDragEnd = React.useCallback(async (_, event) => {
|
|
263
288
|
// Call the gridEditRowsStateSelector directly to avoid infnite loop
|
|
264
289
|
const editRowsState = gridEditRowsStateSelector(apiRef);
|
|
265
290
|
if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
|
|
266
291
|
return;
|
|
267
292
|
}
|
|
268
|
-
if (
|
|
293
|
+
if (timeoutInfoRef.current) {
|
|
269
294
|
timeout.clear();
|
|
270
|
-
|
|
295
|
+
timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
|
|
271
296
|
}
|
|
272
297
|
logger.debug('End dragging row');
|
|
273
298
|
event.preventDefault();
|
|
@@ -279,11 +304,6 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
279
304
|
const dragDirection = previousReorderState.current.dragDirection;
|
|
280
305
|
previousReorderState.current = EMPTY_REORDER_STATE;
|
|
281
306
|
|
|
282
|
-
// Clear visual indicators and dragged state
|
|
283
|
-
applyDropIndicator(null, null);
|
|
284
|
-
applyDraggedState(dragRowId, false);
|
|
285
|
-
apiRef.current.setRowDragActive(false);
|
|
286
|
-
|
|
287
307
|
// Check if the row was dropped outside the grid.
|
|
288
308
|
if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
|
|
289
309
|
// Reset drop target state
|
|
@@ -294,29 +314,80 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
294
314
|
};
|
|
295
315
|
originRowIndex.current = null;
|
|
296
316
|
setDragRowId('');
|
|
317
|
+
// Clear visual indicators and dragged state
|
|
318
|
+
applyDraggedState(dragRowId, false);
|
|
319
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
320
|
+
rowReorder: {
|
|
321
|
+
isActive: false,
|
|
322
|
+
draggedRowId: null
|
|
323
|
+
}
|
|
324
|
+
}));
|
|
297
325
|
return;
|
|
298
326
|
}
|
|
299
327
|
if (dropTarget.current.targetRowIndex !== null && dropTarget.current.targetRowId !== null) {
|
|
300
|
-
const
|
|
301
|
-
const targetRowIndex = dropTarget.current.targetRowIndex;
|
|
302
|
-
const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', targetRowIndex, {
|
|
328
|
+
const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
|
|
303
329
|
sourceRowId: dragRowId,
|
|
304
330
|
targetRowId: dropTarget.current.targetRowId,
|
|
305
331
|
dropPosition: dropTarget.current.dropPosition,
|
|
306
332
|
dragDirection: dragDirection
|
|
307
333
|
});
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
apiRef
|
|
334
|
+
if (isRowReorderValid) {
|
|
335
|
+
try {
|
|
336
|
+
const rowTree = gridRowTreeSelector(apiRef);
|
|
337
|
+
const sourceNode = gridRowNodeSelector(apiRef, dragRowId);
|
|
338
|
+
if (!sourceNode) {
|
|
339
|
+
throw new Error(`MUI X: No row node found for id #${dragRowId}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Calculate oldParent and oldIndex
|
|
343
|
+
const oldParent = sourceNode.parent;
|
|
344
|
+
const oldParentNode = rowTree[oldParent];
|
|
345
|
+
const oldIndexInParent = oldParentNode.children.indexOf(dragRowId) ?? originRowIndex.current;
|
|
346
|
+
await applyRowAnimation(async () => {
|
|
347
|
+
await apiRef.current.setRowPosition(dragRowId, dropTarget.current.targetRowId, dropTarget.current.dropPosition);
|
|
348
|
+
const updatedTree = gridRowTreeSelector(apiRef);
|
|
349
|
+
const updatedNode = updatedTree[dragRowId];
|
|
350
|
+
if (!updatedNode) {
|
|
351
|
+
throw new Error(`MUI X: Row node for id #${dragRowId} not found after move`);
|
|
352
|
+
}
|
|
353
|
+
const newParent = updatedNode.parent;
|
|
354
|
+
const newParentNode = updatedTree[newParent];
|
|
355
|
+
const newIndexInParent = newParentNode.children.indexOf(dragRowId);
|
|
311
356
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
357
|
+
// Only emit event and clear state after successful reorder
|
|
358
|
+
const rowOrderChangeParams = {
|
|
359
|
+
row: apiRef.current.getRow(dragRowId),
|
|
360
|
+
oldIndex: oldIndexInParent,
|
|
361
|
+
targetIndex: newIndexInParent,
|
|
362
|
+
oldParent: oldParent === GRID_ROOT_GROUP_ID ? null : oldParent,
|
|
363
|
+
newParent: newParent === GRID_ROOT_GROUP_ID ? null : newParent
|
|
364
|
+
};
|
|
365
|
+
applyDraggedState(dragRowId, false);
|
|
366
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
367
|
+
rowReorder: {
|
|
368
|
+
isActive: false,
|
|
369
|
+
draggedRowId: null
|
|
370
|
+
}
|
|
371
|
+
}));
|
|
372
|
+
apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
|
|
373
|
+
});
|
|
374
|
+
} catch (error) {
|
|
375
|
+
// Handle error: reset visual state but don't publish success event
|
|
376
|
+
applyDraggedState(dragRowId, false);
|
|
377
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
378
|
+
rowReorder: {
|
|
379
|
+
isActive: false,
|
|
380
|
+
draggedRowId: null
|
|
381
|
+
}
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
applyDraggedState(dragRowId, false);
|
|
386
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
387
|
+
rowReorder: _extends({}, state.rowReorder, {
|
|
388
|
+
dropTarget: undefined
|
|
389
|
+
})
|
|
390
|
+
}));
|
|
320
391
|
}
|
|
321
392
|
}
|
|
322
393
|
|
|
@@ -327,8 +398,9 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
327
398
|
dropPosition: null
|
|
328
399
|
};
|
|
329
400
|
setDragRowId('');
|
|
330
|
-
}, [apiRef, dragRowId, isRowReorderDisabled, logger,
|
|
331
|
-
const
|
|
401
|
+
}, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDraggedState, timeout, applyRowAnimation]);
|
|
402
|
+
const isValidRowReorderProp = props.isValidRowReorder;
|
|
403
|
+
const isRowReorderValid = React.useCallback((initialValue, {
|
|
332
404
|
sourceRowId,
|
|
333
405
|
targetRowId,
|
|
334
406
|
dropPosition,
|
|
@@ -341,24 +413,39 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
341
413
|
const targetRowIndex = sortedRowIndexLookup[targetRowId];
|
|
342
414
|
const sourceRowIndex = sortedRowIndexLookup[sourceRowId];
|
|
343
415
|
|
|
344
|
-
//
|
|
416
|
+
// Apply internal validation: check if this drop would result in no actual movement
|
|
345
417
|
const isAdjacentNode = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
|
|
346
418
|
// dragging to immediately below (above next row)
|
|
347
419
|
dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
|
|
348
420
|
|
|
349
421
|
if (isAdjacentNode || sourceRowIndex === targetRowIndex) {
|
|
350
|
-
|
|
351
|
-
return -1;
|
|
422
|
+
return false;
|
|
352
423
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
424
|
+
|
|
425
|
+
// Internal validation passed, now apply additional user validation if provided
|
|
426
|
+
if (isValidRowReorderProp) {
|
|
427
|
+
const expandedSortedRowIds = gridExpandedSortedRowIdsSelector(apiRef);
|
|
428
|
+
const rowTree = gridRowTreeSelector(apiRef);
|
|
429
|
+
const sourceNode = rowTree[sourceRowId];
|
|
430
|
+
const targetNode = rowTree[targetRowId];
|
|
431
|
+
const prevNode = targetRowIndex > 0 ? rowTree[expandedSortedRowIds[targetRowIndex - 1]] : null;
|
|
432
|
+
const nextNode = targetRowIndex < expandedSortedRowIds.length - 1 ? rowTree[expandedSortedRowIds[targetRowIndex + 1]] : null;
|
|
433
|
+
const context = {
|
|
434
|
+
apiRef,
|
|
435
|
+
sourceNode,
|
|
436
|
+
targetNode,
|
|
437
|
+
prevNode,
|
|
438
|
+
nextNode,
|
|
439
|
+
dropPosition,
|
|
440
|
+
dragDirection
|
|
441
|
+
};
|
|
442
|
+
if (!isValidRowReorderProp(context)) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
358
445
|
}
|
|
359
|
-
return
|
|
360
|
-
}, [apiRef]);
|
|
361
|
-
useGridRegisterPipeProcessor(apiRef, '
|
|
446
|
+
return true;
|
|
447
|
+
}, [apiRef, isValidRowReorderProp]);
|
|
448
|
+
useGridRegisterPipeProcessor(apiRef, 'isRowReorderValid', isRowReorderValid);
|
|
362
449
|
useGridEvent(apiRef, 'rowDragStart', handleDragStart);
|
|
363
450
|
useGridEvent(apiRef, 'rowDragOver', handleDragOver);
|
|
364
451
|
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
|
+
}
|