@mui/x-data-grid-pro 8.7.0 → 8.9.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.
@@ -17,17 +17,20 @@ var Direction = /*#__PURE__*/function (Direction) {
17
17
  Direction[Direction["DOWN"] = 1] = "DOWN";
18
18
  return Direction;
19
19
  }(Direction || {});
20
- let previousMousePosition = null;
21
- let previousReorderState = {
20
+ const EMPTY_REORDER_STATE = {
22
21
  previousTargetId: null,
23
- dragDirection: null
22
+ dragDirection: null,
23
+ previousDropPosition: null
24
24
  };
25
25
  const useUtilityClasses = ownerState => {
26
26
  const {
27
27
  classes
28
28
  } = ownerState;
29
29
  const slots = {
30
- rowDragging: ['row--dragging']
30
+ rowDragging: ['row--dragging'],
31
+ rowDropAbove: ['row--dropAbove'],
32
+ rowDropBelow: ['row--dropBelow'],
33
+ rowBeingDragged: ['row--beingDragged']
31
34
  };
32
35
  return (0, _composeClasses.default)(slots, _xDataGrid.getDataGridUtilityClass, classes);
33
36
  };
@@ -43,12 +46,19 @@ const useGridRowReorder = (apiRef, props) => {
43
46
  const dragRowNode = React.useRef(null);
44
47
  const originRowIndex = React.useRef(null);
45
48
  const removeDnDStylesTimeout = React.useRef(undefined);
49
+ const previousDropIndicatorRef = React.useRef(null);
46
50
  const ownerState = {
47
51
  classes: props.classes
48
52
  };
49
53
  const classes = useUtilityClasses(ownerState);
50
54
  const [dragRowId, setDragRowId] = React.useState('');
51
55
  const sortedRowIndexLookup = (0, _xDataGrid.useGridSelector)(apiRef, _internals.gridSortedRowIndexLookupSelector);
56
+ const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
57
+ const [dropTarget, setDropTarget] = React.useState({
58
+ targetRowId: null,
59
+ targetRowIndex: null,
60
+ dropPosition: null
61
+ });
52
62
  React.useEffect(() => {
53
63
  return () => {
54
64
  clearTimeout(removeDnDStylesTimeout.current);
@@ -60,6 +70,86 @@ const useGridRowReorder = (apiRef, props) => {
60
70
  const isRowReorderDisabled = React.useMemo(() => {
61
71
  return !props.rowReordering || !!sortModel.length || treeDepth !== 1;
62
72
  }, [props.rowReordering, sortModel, treeDepth]);
73
+ const applyDropIndicator = React.useCallback((targetRowId, position) => {
74
+ // Remove existing drop indicator from previous target
75
+ if (previousDropIndicatorRef.current) {
76
+ previousDropIndicatorRef.current.classList.remove(classes.rowDropAbove, classes.rowDropBelow);
77
+ previousDropIndicatorRef.current = null;
78
+ }
79
+
80
+ // Apply new drop indicator
81
+ if (targetRowId && position) {
82
+ const targetRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${targetRowId}"]`);
83
+ if (targetRow) {
84
+ targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
85
+ previousDropIndicatorRef.current = targetRow;
86
+ }
87
+ }
88
+ }, [apiRef, classes]);
89
+ const applyDraggedState = React.useCallback((rowId, isDragged) => {
90
+ if (rowId) {
91
+ const draggedRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${rowId}"]`);
92
+ if (draggedRow) {
93
+ if (isDragged) {
94
+ draggedRow.classList.add(classes.rowBeingDragged);
95
+ } else {
96
+ draggedRow.classList.remove(classes.rowBeingDragged);
97
+ }
98
+ }
99
+ }
100
+ }, [apiRef, classes.rowBeingDragged]);
101
+ const applyRowAnimation = React.useCallback(callback => {
102
+ const rootElement = apiRef.current.rootElementRef?.current;
103
+ if (!rootElement) {
104
+ return;
105
+ }
106
+ const visibleRows = rootElement.querySelectorAll('[data-id]');
107
+ if (!visibleRows.length) {
108
+ return;
109
+ }
110
+ const rowsArray = Array.from(visibleRows);
111
+ const initialPositions = new Map();
112
+ rowsArray.forEach(row => {
113
+ const rowId = row.getAttribute('data-id');
114
+ if (rowId) {
115
+ initialPositions.set(rowId, row.getBoundingClientRect());
116
+ }
117
+ });
118
+ callback();
119
+
120
+ // Use `requestAnimationFrame` to ensure DOM has updated
121
+ requestAnimationFrame(() => {
122
+ const newRows = rootElement.querySelectorAll('[data-id]');
123
+ const animations = [];
124
+ newRows.forEach(row => {
125
+ const rowId = row.getAttribute('data-id');
126
+ if (!rowId) {
127
+ return;
128
+ }
129
+ const prevRect = initialPositions.get(rowId);
130
+ if (!prevRect) {
131
+ return;
132
+ }
133
+ const currentRect = row.getBoundingClientRect();
134
+ const deltaY = prevRect.top - currentRect.top;
135
+ if (Math.abs(deltaY) > 1) {
136
+ const animation = row.animate([{
137
+ transform: `translateY(${deltaY}px)`
138
+ }, {
139
+ transform: 'translateY(0)'
140
+ }], {
141
+ duration: 200,
142
+ easing: 'ease-in-out',
143
+ fill: 'forwards'
144
+ });
145
+ animations.push(animation);
146
+ }
147
+ });
148
+ if (animations.length > 0) {
149
+ Promise.allSettled(animations.map(a => a.finished)).then(() => {});
150
+ }
151
+ });
152
+ }, [apiRef]);
63
153
  const handleDragStart = React.useCallback((params, event) => {
64
154
  // Call the gridEditRowsStateSelector directly to avoid infnite loop
65
155
  const editRowsState = (0, _internals.gridEditRowsStateSelector)(apiRef);
@@ -71,47 +161,91 @@ const useGridRowReorder = (apiRef, props) => {
71
161
  // For more information check here https://github.com/mui/mui-x/issues/2680.
72
162
  event.stopPropagation();
73
163
  dragRowNode.current = event.currentTarget;
164
+ // Apply cell-level dragging class to the drag handle
74
165
  dragRowNode.current.classList.add(classes.rowDragging);
75
166
  setDragRowId(params.id);
167
+
168
+ // Apply the dragged state to the entire row
169
+ applyDraggedState(params.id, true);
76
170
  removeDnDStylesTimeout.current = setTimeout(() => {
77
171
  dragRowNode.current.classList.remove(classes.rowDragging);
78
172
  });
79
173
  originRowIndex.current = sortedRowIndexLookup[params.id];
80
174
  apiRef.current.setCellFocus(params.id, _gridRowReorderColDef.GRID_REORDER_COL_DEF.field);
81
- }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, sortedRowIndexLookup]);
175
+ }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, sortedRowIndexLookup, applyDraggedState]);
82
176
  const handleDragOver = React.useCallback((params, event) => {
83
177
  if (dragRowId === '') {
84
178
  return;
85
179
  }
86
180
  const rowNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, params.id);
87
- if (!rowNode || rowNode.type === 'footer' || rowNode.type === 'pinnedRow') {
181
+ if (!rowNode || rowNode.type === 'footer' || rowNode.type === 'pinnedRow' || !event.target) {
88
182
  return;
89
183
  }
184
+
185
+ // Find the relative 'y' mouse position based on the event.target
186
+ const targetRect = event.target.getBoundingClientRect();
187
+ const relativeY = Math.floor(event.clientY - targetRect.top);
188
+ const midPoint = Math.floor(targetRect.height / 2);
90
189
  logger.debug(`Dragging over row ${params.id}`);
91
190
  event.preventDefault();
92
191
  // Prevent drag events propagation.
93
192
  // For more information check here https://github.com/mui/mui-x/issues/2680.
94
193
  event.stopPropagation();
95
- const mouseMovementDiff = previousMousePosition ? previousMousePosition.y - event.clientY : event.clientY;
96
194
  if (params.id !== dragRowId) {
97
195
  const targetRowIndex = sortedRowIndexLookup[params.id];
98
- const dragDirection = mouseMovementDiff > 0 ? Direction.DOWN : Direction.UP;
196
+ const sourceRowIndex = sortedRowIndexLookup[dragRowId];
197
+
198
+ // Determine drop position based on relativeY position within the row
199
+ const dropPosition = relativeY < midPoint ? 'above' : 'below';
200
+
201
+ // Check if this drop would result in no actual movement
202
+ const wouldResultInNoMovement = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
203
+ // dragging to immediately below (above next row)
204
+ dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
205
+
99
206
  const currentReorderState = {
100
- dragDirection,
101
- previousTargetId: params.id
207
+ dragDirection: targetRowIndex < sourceRowIndex ? Direction.UP : Direction.DOWN,
208
+ previousTargetId: params.id,
209
+ previousDropPosition: dropPosition
102
210
  };
103
- const isStateChanged = currentReorderState.dragDirection !== previousReorderState.dragDirection || currentReorderState.previousTargetId !== previousReorderState.previousTargetId;
104
- if (previousReorderState.dragDirection === null || Math.abs(mouseMovementDiff) >= 1 && isStateChanged) {
105
- apiRef.current.setRowIndex(dragRowId, targetRowIndex);
106
- previousReorderState = currentReorderState;
211
+
212
+ // Only update visual indicator:
213
+ // 1. When dragging over a different row
214
+ // 2. When it would result in actual movement
215
+ if (previousReorderState.current.previousTargetId !== params.id || previousReorderState.current.previousDropPosition !== dropPosition) {
216
+ if (wouldResultInNoMovement) {
217
+ // Clear any existing indicators since this wouldn't result in movement
218
+ setDropTarget({
219
+ targetRowId: null,
220
+ targetRowIndex: null,
221
+ dropPosition: null
222
+ });
223
+ applyDropIndicator(null, null);
224
+ } else {
225
+ setDropTarget({
226
+ targetRowId: params.id,
227
+ targetRowIndex,
228
+ dropPosition
229
+ });
230
+ applyDropIndicator(params.id, dropPosition);
231
+ }
232
+ previousReorderState.current = currentReorderState;
107
233
  }
234
+ } else if (previousReorderState.current.previousTargetId !== null) {
235
+ setDropTarget({
236
+ targetRowId: null,
237
+ targetRowIndex: null,
238
+ dropPosition: null
239
+ });
240
+ applyDropIndicator(null, null);
241
+ previousReorderState.current = {
242
+ previousTargetId: null,
243
+ dragDirection: null,
244
+ previousDropPosition: null
245
+ };
108
246
  }
109
- previousMousePosition = {
110
- x: event.clientX,
111
- y: event.clientY
112
- };
113
- }, [dragRowId, apiRef, logger, sortedRowIndexLookup]);
114
- const handleDragEnd = React.useCallback((params, event) => {
247
+ }, [dragRowId, apiRef, logger, sortedRowIndexLookup, applyDropIndicator]);
248
+ const handleDragEnd = React.useCallback((_, event) => {
115
249
  // Call the gridEditRowsStateSelector directly to avoid infnite loop
116
250
  const editRowsState = (0, _internals.gridEditRowsStateSelector)(apiRef);
117
251
  if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
@@ -124,24 +258,65 @@ const useGridRowReorder = (apiRef, props) => {
124
258
  event.stopPropagation();
125
259
  clearTimeout(removeDnDStylesTimeout.current);
126
260
  dragRowNode.current = null;
127
- previousReorderState.dragDirection = null;
261
+ const dragDirection = previousReorderState.current.dragDirection;
262
+ previousReorderState.current = EMPTY_REORDER_STATE;
263
+
264
+ // Clear visual indicators and dragged state
265
+ applyDropIndicator(null, null);
266
+ applyDraggedState(dragRowId, false);
128
267
 
129
268
  // Check if the row was dropped outside the grid.
130
- if (event.dataTransfer.dropEffect === 'none') {
131
- // Accessing params.field may contain the wrong field as header elements are reused
132
- apiRef.current.setRowIndex(dragRowId, originRowIndex.current);
269
+ if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
270
+ // Reset drop target state
271
+ setDropTarget({
272
+ targetRowId: null,
273
+ targetRowIndex: null,
274
+ dropPosition: null
275
+ });
133
276
  originRowIndex.current = null;
134
277
  } else {
135
- // Emit the rowOrderChange event only once when the reordering stops.
136
- const rowOrderChangeParams = {
137
- row: apiRef.current.getRow(dragRowId),
138
- targetIndex: sortedRowIndexLookup[params.id],
139
- oldIndex: originRowIndex.current
140
- };
141
- apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
278
+ if (dropTarget.targetRowIndex !== null && dropTarget.targetRowId !== null) {
279
+ const sourceRowIndex = originRowIndex.current;
280
+ const targetRowIndex = dropTarget.targetRowIndex;
281
+ const dropPosition = dropTarget.dropPosition;
282
+
283
+ // Calculate the correct target index based on drop position
284
+ let finalTargetIndex;
285
+ if (dragDirection === Direction.UP) {
286
+ finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1;
287
+ } else {
288
+ finalTargetIndex = dropPosition === 'above' ? targetRowIndex - 1 : targetRowIndex;
289
+ }
290
+ const isReorderInvalid = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
291
+ // dragging to immediately below (above next row)
292
+ dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1 ||
293
+ // dragging to immediately above (below previous row)
294
+ dropTarget.targetRowId === dragRowId; // dragging to the same row
295
+
296
+ if (!isReorderInvalid) {
297
+ applyRowAnimation(() => {
298
+ apiRef.current.setRowIndex(dragRowId, finalTargetIndex);
299
+
300
+ // Emit the rowOrderChange event only once when the reordering stops.
301
+ const rowOrderChangeParams = {
302
+ row: apiRef.current.getRow(dragRowId),
303
+ targetIndex: finalTargetIndex,
304
+ oldIndex: sourceRowIndex
305
+ };
306
+ apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
307
+ });
308
+ }
309
+ }
310
+
311
+ // Reset drop target state
312
+ setDropTarget({
313
+ targetRowId: null,
314
+ targetRowIndex: null,
315
+ dropPosition: null
316
+ });
142
317
  }
143
318
  setDragRowId('');
144
- }, [apiRef, dragRowId, isRowReorderDisabled, logger, sortedRowIndexLookup]);
319
+ }, [apiRef, dragRowId, isRowReorderDisabled, logger, dropTarget, applyDropIndicator, applyDraggedState, applyRowAnimation]);
145
320
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragStart', handleDragStart);
146
321
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragOver', handleDragOver);
147
322
  (0, _xDataGrid.useGridEvent)(apiRef, 'rowDragEnd', handleDragEnd);
@@ -82,8 +82,8 @@ const useGridDataSourceTreeDataPreProcessors = (privateApiRef, props) => {
82
82
  if (!getChildrenCount) {
83
83
  throw new Error('MUI X: No `getChildrenCount` method provided with the dataSource.');
84
84
  }
85
- const parentPath = params.updates.groupKeys ?? [];
86
85
  const getRowTreeBuilderNode = rowId => {
86
+ const parentPath = params.updates.groupKeys ?? (0, _utils.getParentPath)(rowId, params);
87
87
  const count = getChildrenCount(params.dataRowIdToModelLookup[rowId]);
88
88
  return {
89
89
  id: rowId,
@@ -1,7 +1,13 @@
1
1
  import { GridRowId, GridRowTreeConfig } from '@mui/x-data-grid';
2
+ import { GridRowTreeCreationParams } from '@mui/x-data-grid/internals';
2
3
  export declare function skipFiltering(rowTree: GridRowTreeConfig): {
3
4
  filteredRowsLookup: {};
4
5
  filteredChildrenCountLookup: Record<GridRowId, number>;
5
6
  filteredDescendantCountLookup: {};
6
7
  };
7
- export declare function skipSorting(rowTree: GridRowTreeConfig): GridRowId[];
8
+ export declare function skipSorting(rowTree: GridRowTreeConfig): GridRowId[];
9
+ /**
10
+ * Retrieves the parent path for a row from the previous tree state.
11
+ * Used during full tree updates to maintain correct hierarchy.
12
+ */
13
+ export declare function getParentPath(rowId: GridRowId, treeCreationParams: GridRowTreeCreationParams): string[];
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.getParentPath = getParentPath;
6
7
  exports.skipFiltering = skipFiltering;
7
8
  exports.skipSorting = skipSorting;
8
9
  var _xDataGrid = require("@mui/x-data-grid");
@@ -22,4 +23,15 @@ function skipFiltering(rowTree) {
22
23
  }
23
24
  function skipSorting(rowTree) {
24
25
  return (0, _internals.getTreeNodeDescendants)(rowTree, _xDataGrid.GRID_ROOT_GROUP_ID, false);
26
+ }
27
+
28
+ /**
29
+ * Retrieves the parent path for a row from the previous tree state.
30
+ * Used during full tree updates to maintain correct hierarchy.
31
+ */
32
+ function getParentPath(rowId, treeCreationParams) {
33
+ if (treeCreationParams.updates.type !== 'full' || !treeCreationParams.previousTree?.[rowId] || treeCreationParams.previousTree[rowId].depth < 1 || !('path' in treeCreationParams.previousTree[rowId])) {
34
+ return [];
35
+ }
36
+ return treeCreationParams.previousTree[rowId].path || [];
25
37
  }
package/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @mui/x-data-grid-pro v8.7.0
2
+ * @mui/x-data-grid-pro v8.9.0
3
3
  *
4
- * @license MUI X Commercial
5
- * This source code is licensed under the commercial license found in the
4
+ * @license SEE LICENSE IN LICENSE
5
+ * This source code is licensed under the SEE LICENSE IN LICENSE license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  "use strict";
@@ -34,5 +34,5 @@ export { updateRowTree } from "../utils/tree/updateRowTree.js";
34
34
  export { sortRowTree } from "../utils/tree/sortRowTree.js";
35
35
  export { insertNodeInTree, removeNodeFromTree, getVisibleRowsLookup } from "../utils/tree/utils.js";
36
36
  export type { RowTreeBuilderGroupingCriterion } from "../utils/tree/models.js";
37
- export { skipSorting, skipFiltering } from "../hooks/features/serverSideTreeData/utils.js";
37
+ export { skipSorting, skipFiltering, getParentPath } from "../hooks/features/serverSideTreeData/utils.js";
38
38
  export * from "./propValidation.js";
@@ -43,7 +43,8 @@ var _exportNames = {
43
43
  removeNodeFromTree: true,
44
44
  getVisibleRowsLookup: true,
45
45
  skipSorting: true,
46
- skipFiltering: true
46
+ skipFiltering: true,
47
+ getParentPath: true
47
48
  };
48
49
  Object.defineProperty(exports, "DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS", {
49
50
  enumerable: true,
@@ -99,6 +100,12 @@ Object.defineProperty(exports, "getGroupKeys", {
99
100
  return _utils.getGroupKeys;
100
101
  }
101
102
  });
103
+ Object.defineProperty(exports, "getParentPath", {
104
+ enumerable: true,
105
+ get: function () {
106
+ return _utils3.getParentPath;
107
+ }
108
+ });
102
109
  Object.defineProperty(exports, "getVisibleRowsLookup", {
103
110
  enumerable: true,
104
111
  get: function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-data-grid-pro",
3
- "version": "8.7.0",
3
+ "version": "8.9.0",
4
4
  "author": "MUI Team",
5
5
  "description": "The Pro plan edition of the MUI X Data Grid components.",
6
6
  "main": "./index.js",
@@ -36,13 +36,13 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@babel/runtime": "^7.27.6",
39
- "@mui/utils": "^7.1.1",
39
+ "@mui/utils": "^7.2.0",
40
40
  "@types/format-util": "^1.0.4",
41
41
  "clsx": "^2.1.1",
42
42
  "prop-types": "^15.8.1",
43
- "@mui/x-data-grid": "8.7.0",
44
- "@mui/x-license": "8.7.0",
45
- "@mui/x-internals": "8.7.0"
43
+ "@mui/x-internals": "8.8.0",
44
+ "@mui/x-data-grid": "8.8.0",
45
+ "@mui/x-license": "8.9.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@emotion/react": "^11.9.0",