@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
|
@@ -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,14 +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
|
|
50
|
-
const timeoutRowId = React.useRef('');
|
|
51
|
+
const timeoutInfoRef = React.useRef(EMPTY_TIMEOUT_INFO);
|
|
51
52
|
const timeout = useTimeout();
|
|
52
53
|
const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
|
|
53
54
|
const dropTarget = React.useRef({
|
|
@@ -62,26 +63,30 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
62
63
|
}, []);
|
|
63
64
|
|
|
64
65
|
// TODO: remove sortModel check once row reorder is sorting compatible
|
|
65
|
-
// remove treeData check once row reorder is treeData compatible
|
|
66
66
|
const isRowReorderDisabled = React.useMemo(() => {
|
|
67
|
-
return !props.rowReordering || !!sortModel.length
|
|
68
|
-
}, [props.rowReordering, sortModel
|
|
69
|
-
const
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
|
|
81
|
-
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';
|
|
82
80
|
}
|
|
81
|
+
if (relativeY > bottomThreshold) {
|
|
82
|
+
return 'below';
|
|
83
|
+
}
|
|
84
|
+
return 'inside';
|
|
83
85
|
}
|
|
84
|
-
|
|
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]);
|
|
85
90
|
const applyDraggedState = React.useCallback((rowId, isDragged) => {
|
|
86
91
|
if (rowId) {
|
|
87
92
|
const draggedRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${rowId}"]`);
|
|
@@ -94,7 +99,7 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
}, [apiRef, classes.rowBeingDragged]);
|
|
97
|
-
const applyRowAnimation = React.useCallback(callback => {
|
|
102
|
+
const applyRowAnimation = React.useCallback(async callback => {
|
|
98
103
|
const rootElement = apiRef.current.rootElementRef?.current;
|
|
99
104
|
if (!rootElement) {
|
|
100
105
|
return;
|
|
@@ -111,7 +116,7 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
111
116
|
initialPositions.set(rowId, row.getBoundingClientRect());
|
|
112
117
|
}
|
|
113
118
|
});
|
|
114
|
-
callback();
|
|
119
|
+
await callback();
|
|
115
120
|
|
|
116
121
|
// Use `requestAnimationFrame` to ensure DOM has updated
|
|
117
122
|
requestAnimationFrame(() => {
|
|
@@ -153,15 +158,20 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
153
158
|
if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
|
-
if (
|
|
161
|
+
if (timeoutInfoRef.current) {
|
|
157
162
|
timeout.clear();
|
|
158
|
-
|
|
163
|
+
timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
|
|
159
164
|
}
|
|
160
165
|
logger.debug(`Start dragging row ${params.id}`);
|
|
161
166
|
// Prevent drag events propagation.
|
|
162
167
|
// For more information check here https://github.com/mui/mui-x/issues/2680.
|
|
163
168
|
event.stopPropagation();
|
|
164
|
-
apiRef.current.
|
|
169
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
170
|
+
rowReorder: _extends({}, state.rowReorder, {
|
|
171
|
+
isActive: true,
|
|
172
|
+
draggedRowId: params.id
|
|
173
|
+
})
|
|
174
|
+
}));
|
|
165
175
|
dragRowNode.current = event.currentTarget;
|
|
166
176
|
// Apply cell-level dragging class to the drag handle
|
|
167
177
|
dragRowNode.current.classList.add(classes.rowDragging);
|
|
@@ -172,9 +182,10 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
172
182
|
removeDnDStylesTimeout.current = setTimeout(() => {
|
|
173
183
|
dragRowNode.current.classList.remove(classes.rowDragging);
|
|
174
184
|
});
|
|
185
|
+
const sortedRowIndexLookup = gridExpandedSortedRowIndexLookupSelector(apiRef);
|
|
175
186
|
originRowIndex.current = sortedRowIndexLookup[params.id];
|
|
176
187
|
apiRef.current.setCellFocus(params.id, GRID_REORDER_COL_DEF.field);
|
|
177
|
-
}, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, applyDraggedState,
|
|
188
|
+
}, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, applyDraggedState, timeout]);
|
|
178
189
|
const handleDragOver = React.useCallback((params, event) => {
|
|
179
190
|
if (dragRowId === '') {
|
|
180
191
|
return;
|
|
@@ -184,34 +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
|
}
|
|
225
|
+
const sortedRowIndexLookup = gridExpandedSortedRowIndexLookupSelector(apiRef);
|
|
210
226
|
const targetRowIndex = sortedRowIndexLookup[params.id];
|
|
211
227
|
const sourceRowIndex = sortedRowIndexLookup[dragRowId];
|
|
212
|
-
|
|
213
|
-
// Determine drop position based on relativeY position within the row
|
|
214
|
-
const dropPosition = relativeY < midPoint ? 'above' : 'below';
|
|
215
228
|
const currentReorderState = {
|
|
216
229
|
dragDirection: targetRowIndex < sourceRowIndex ? 'up' : 'down',
|
|
217
230
|
previousTargetId: params.id,
|
|
@@ -224,7 +237,7 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
224
237
|
|
|
225
238
|
// Check if this is an adjacent position
|
|
226
239
|
const isAdjacentPosition = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1;
|
|
227
|
-
const
|
|
240
|
+
const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
|
|
228
241
|
sourceRowId: dragRowId,
|
|
229
242
|
targetRowId: params.id,
|
|
230
243
|
dropPosition,
|
|
@@ -232,13 +245,21 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
232
245
|
});
|
|
233
246
|
|
|
234
247
|
// Show drop indicator for valid drops OR adjacent positions OR same node
|
|
235
|
-
if (
|
|
248
|
+
if (isRowReorderValid || isAdjacentPosition || isSameNode) {
|
|
236
249
|
dropTarget.current = {
|
|
237
250
|
targetRowId: params.id,
|
|
238
251
|
targetRowIndex,
|
|
239
252
|
dropPosition
|
|
240
253
|
};
|
|
241
|
-
|
|
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
|
+
}));
|
|
242
263
|
} else {
|
|
243
264
|
// Clear indicators for invalid drops
|
|
244
265
|
dropTarget.current = {
|
|
@@ -246,7 +267,12 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
246
267
|
targetRowIndex: null,
|
|
247
268
|
dropPosition: null
|
|
248
269
|
};
|
|
249
|
-
|
|
270
|
+
// Clear state drop target
|
|
271
|
+
apiRef.current.setState(state => _extends({}, state, {
|
|
272
|
+
rowReorder: _extends({}, state.rowReorder, {
|
|
273
|
+
dropTarget: undefined
|
|
274
|
+
})
|
|
275
|
+
}));
|
|
250
276
|
}
|
|
251
277
|
previousReorderState.current = currentReorderState;
|
|
252
278
|
}
|
|
@@ -257,16 +283,16 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
257
283
|
} else {
|
|
258
284
|
event.dataTransfer.dropEffect = 'copy';
|
|
259
285
|
}
|
|
260
|
-
}, [dragRowId, apiRef, logger, timeout,
|
|
261
|
-
const handleDragEnd = React.useCallback((_, event) => {
|
|
286
|
+
}, [dragRowId, apiRef, logger, timeout, calculateDropPosition]);
|
|
287
|
+
const handleDragEnd = React.useCallback(async (_, event) => {
|
|
262
288
|
// Call the gridEditRowsStateSelector directly to avoid infnite loop
|
|
263
289
|
const editRowsState = gridEditRowsStateSelector(apiRef);
|
|
264
290
|
if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
|
|
265
291
|
return;
|
|
266
292
|
}
|
|
267
|
-
if (
|
|
293
|
+
if (timeoutInfoRef.current) {
|
|
268
294
|
timeout.clear();
|
|
269
|
-
|
|
295
|
+
timeoutInfoRef.current = EMPTY_TIMEOUT_INFO;
|
|
270
296
|
}
|
|
271
297
|
logger.debug('End dragging row');
|
|
272
298
|
event.preventDefault();
|
|
@@ -278,11 +304,6 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
278
304
|
const dragDirection = previousReorderState.current.dragDirection;
|
|
279
305
|
previousReorderState.current = EMPTY_REORDER_STATE;
|
|
280
306
|
|
|
281
|
-
// Clear visual indicators and dragged state
|
|
282
|
-
applyDropIndicator(null, null);
|
|
283
|
-
applyDraggedState(dragRowId, false);
|
|
284
|
-
apiRef.current.setRowDragActive(false);
|
|
285
|
-
|
|
286
307
|
// Check if the row was dropped outside the grid.
|
|
287
308
|
if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
|
|
288
309
|
// Reset drop target state
|
|
@@ -293,29 +314,80 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
293
314
|
};
|
|
294
315
|
originRowIndex.current = null;
|
|
295
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
|
+
}));
|
|
296
325
|
return;
|
|
297
326
|
}
|
|
298
327
|
if (dropTarget.current.targetRowIndex !== null && dropTarget.current.targetRowId !== null) {
|
|
299
|
-
const
|
|
300
|
-
const targetRowIndex = dropTarget.current.targetRowIndex;
|
|
301
|
-
const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', targetRowIndex, {
|
|
328
|
+
const isRowReorderValid = apiRef.current.unstable_applyPipeProcessors('isRowReorderValid', false, {
|
|
302
329
|
sourceRowId: dragRowId,
|
|
303
330
|
targetRowId: dropTarget.current.targetRowId,
|
|
304
331
|
dropPosition: dropTarget.current.dropPosition,
|
|
305
332
|
dragDirection: dragDirection
|
|
306
333
|
});
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
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
|
+
}
|
|
310
341
|
|
|
311
|
-
//
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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);
|
|
356
|
+
|
|
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
|
+
}));
|
|
319
391
|
}
|
|
320
392
|
}
|
|
321
393
|
|
|
@@ -326,8 +398,9 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
326
398
|
dropPosition: null
|
|
327
399
|
};
|
|
328
400
|
setDragRowId('');
|
|
329
|
-
}, [apiRef, dragRowId, isRowReorderDisabled, logger,
|
|
330
|
-
const
|
|
401
|
+
}, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDraggedState, timeout, applyRowAnimation]);
|
|
402
|
+
const isValidRowReorderProp = props.isValidRowReorder;
|
|
403
|
+
const isRowReorderValid = React.useCallback((initialValue, {
|
|
331
404
|
sourceRowId,
|
|
332
405
|
targetRowId,
|
|
333
406
|
dropPosition,
|
|
@@ -336,27 +409,43 @@ export const useGridRowReorder = (apiRef, props) => {
|
|
|
336
409
|
if (gridRowMaximumTreeDepthSelector(apiRef) > 1) {
|
|
337
410
|
return initialValue;
|
|
338
411
|
}
|
|
412
|
+
const sortedRowIndexLookup = gridExpandedSortedRowIndexLookupSelector(apiRef);
|
|
339
413
|
const targetRowIndex = sortedRowIndexLookup[targetRowId];
|
|
340
414
|
const sourceRowIndex = sortedRowIndexLookup[sourceRowId];
|
|
341
415
|
|
|
342
|
-
//
|
|
416
|
+
// Apply internal validation: check if this drop would result in no actual movement
|
|
343
417
|
const isAdjacentNode = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
|
|
344
418
|
// dragging to immediately below (above next row)
|
|
345
419
|
dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
|
|
346
420
|
|
|
347
421
|
if (isAdjacentNode || sourceRowIndex === targetRowIndex) {
|
|
348
|
-
|
|
349
|
-
return -1;
|
|
422
|
+
return false;
|
|
350
423
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
}
|
|
356
445
|
}
|
|
357
|
-
return
|
|
358
|
-
}, [apiRef,
|
|
359
|
-
useGridRegisterPipeProcessor(apiRef, '
|
|
446
|
+
return true;
|
|
447
|
+
}, [apiRef, isValidRowReorderProp]);
|
|
448
|
+
useGridRegisterPipeProcessor(apiRef, 'isRowReorderValid', isRowReorderValid);
|
|
360
449
|
useGridEvent(apiRef, 'rowDragStart', handleDragStart);
|
|
361
450
|
useGridEvent(apiRef, 'rowDragOver', handleDragOver);
|
|
362
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
|
+
}
|