@mui/x-data-grid-pro 8.10.1 → 8.11.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.
@@ -2,15 +2,11 @@
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import * as React from 'react';
5
+ import useTimeout from '@mui/utils/useTimeout';
5
6
  import composeClasses from '@mui/utils/composeClasses';
6
- import { useGridLogger, useGridEvent, getDataGridUtilityClass, useGridSelector, gridSortModelSelector, gridRowMaximumTreeDepthSelector, useGridEventPriority, gridRowNodeSelector, useGridApiMethod } from '@mui/x-data-grid';
7
- import { gridEditRowsStateSelector, gridSortedRowIndexLookupSelector } from '@mui/x-data-grid/internals';
7
+ import { useGridLogger, useGridEvent, getDataGridUtilityClass, useGridSelector, gridSortModelSelector, useGridEventPriority, gridRowNodeSelector, gridRowMaximumTreeDepthSelector, useGridApiMethod } from '@mui/x-data-grid';
8
+ import { gridEditRowsStateSelector, useGridRegisterPipeProcessor, gridExpandedSortedRowIndexLookupSelector } from '@mui/x-data-grid/internals';
8
9
  import { GRID_REORDER_COL_DEF } from "./gridRowReorderColDef.js";
9
- var Direction = /*#__PURE__*/function (Direction) {
10
- Direction[Direction["UP"] = 0] = "UP";
11
- Direction[Direction["DOWN"] = 1] = "DOWN";
12
- return Direction;
13
- }(Direction || {});
14
10
  const EMPTY_REORDER_STATE = {
15
11
  previousTargetId: null,
16
12
  dragDirection: null,
@@ -35,13 +31,12 @@ export const rowReorderStateInitializer = state => _extends({}, state, {
35
31
  });
36
32
 
37
33
  /**
38
- * Only available in DataGridPro
34
+ * Hook for row reordering (Pro package)
39
35
  * @requires useGridRows (method)
40
36
  */
41
37
  export const useGridRowReorder = (apiRef, props) => {
42
38
  const logger = useGridLogger(apiRef, 'useGridRowReorder');
43
39
  const sortModel = useGridSelector(apiRef, gridSortModelSelector);
44
- const treeDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector);
45
40
  const dragRowNode = React.useRef(null);
46
41
  const originRowIndex = React.useRef(null);
47
42
  const removeDnDStylesTimeout = React.useRef(undefined);
@@ -51,9 +46,11 @@ export const useGridRowReorder = (apiRef, props) => {
51
46
  };
52
47
  const classes = useUtilityClasses(ownerState);
53
48
  const [dragRowId, setDragRowId] = React.useState('');
54
- const sortedRowIndexLookup = useGridSelector(apiRef, gridSortedRowIndexLookupSelector);
49
+ const sortedRowIndexLookup = useGridSelector(apiRef, gridExpandedSortedRowIndexLookupSelector);
50
+ const timeoutRowId = React.useRef('');
51
+ const timeout = useTimeout();
55
52
  const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
56
- const [dropTarget, setDropTarget] = React.useState({
53
+ const dropTarget = React.useRef({
57
54
  targetRowId: null,
58
55
  targetRowIndex: null,
59
56
  dropPosition: null
@@ -65,10 +62,10 @@ export const useGridRowReorder = (apiRef, props) => {
65
62
  }, []);
66
63
 
67
64
  // TODO: remove sortModel check once row reorder is sorting compatible
68
- // remove treeDepth once row reorder is tree compatible
65
+ // remove treeData check once row reorder is treeData compatible
69
66
  const isRowReorderDisabled = React.useMemo(() => {
70
- return !props.rowReordering || !!sortModel.length || treeDepth !== 1;
71
- }, [props.rowReordering, sortModel, treeDepth]);
67
+ return !props.rowReordering || !!sortModel.length || props.treeData;
68
+ }, [props.rowReordering, sortModel, props.treeData]);
72
69
  const applyDropIndicator = React.useCallback((targetRowId, position) => {
73
70
  // Remove existing drop indicator from previous target
74
71
  if (previousDropIndicatorRef.current) {
@@ -77,7 +74,7 @@ export const useGridRowReorder = (apiRef, props) => {
77
74
  }
78
75
 
79
76
  // Apply new drop indicator
80
- if (targetRowId && position) {
77
+ if (targetRowId !== undefined && position !== null) {
81
78
  const targetRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${targetRowId}"]`);
82
79
  if (targetRow) {
83
80
  targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
@@ -152,9 +149,14 @@ export const useGridRowReorder = (apiRef, props) => {
152
149
  const handleDragStart = React.useCallback((params, event) => {
153
150
  // Call the gridEditRowsStateSelector directly to avoid infnite loop
154
151
  const editRowsState = gridEditRowsStateSelector(apiRef);
152
+ event.dataTransfer.effectAllowed = 'copy';
155
153
  if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
156
154
  return;
157
155
  }
156
+ if (timeoutRowId.current) {
157
+ timeout.clear();
158
+ timeoutRowId.current = '';
159
+ }
158
160
  logger.debug(`Start dragging row ${params.id}`);
159
161
  // Prevent drag events propagation.
160
162
  // For more information check here https://github.com/mui/mui-x/issues/2680.
@@ -172,13 +174,14 @@ export const useGridRowReorder = (apiRef, props) => {
172
174
  });
173
175
  originRowIndex.current = sortedRowIndexLookup[params.id];
174
176
  apiRef.current.setCellFocus(params.id, GRID_REORDER_COL_DEF.field);
175
- }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, sortedRowIndexLookup, applyDraggedState]);
177
+ }, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, applyDraggedState, sortedRowIndexLookup, timeout]);
176
178
  const handleDragOver = React.useCallback((params, event) => {
177
179
  if (dragRowId === '') {
178
180
  return;
179
181
  }
180
- const rowNode = gridRowNodeSelector(apiRef, params.id);
181
- if (!rowNode || rowNode.type === 'footer' || rowNode.type === 'pinnedRow' || !event.target) {
182
+ const targetNode = gridRowNodeSelector(apiRef, params.id);
183
+ const sourceNode = gridRowNodeSelector(apiRef, dragRowId);
184
+ if (!sourceNode || !targetNode || targetNode.type === 'footer' || targetNode.type === 'pinnedRow' || !event.target) {
182
185
  return;
183
186
  }
184
187
 
@@ -191,66 +194,80 @@ export const useGridRowReorder = (apiRef, props) => {
191
194
  // Prevent drag events propagation.
192
195
  // For more information check here https://github.com/mui/mui-x/issues/2680.
193
196
  event.stopPropagation();
194
- if (params.id !== dragRowId) {
195
- const targetRowIndex = sortedRowIndexLookup[params.id];
196
- const sourceRowIndex = sortedRowIndexLookup[dragRowId];
197
+ if (timeoutRowId.current && timeoutRowId.current !== params.id) {
198
+ timeout.clear();
199
+ timeoutRowId.current = '';
200
+ }
201
+ if (targetNode.type === 'group' && targetNode.depth < sourceNode.depth && !targetNode.childrenExpanded && !timeoutRowId.current) {
202
+ timeout.start(500, () => {
203
+ const rowNode = gridRowNodeSelector(apiRef, params.id);
204
+ // TODO: Handle `dataSource` case with https://github.com/mui/mui-x/issues/18947
205
+ apiRef.current.setRowChildrenExpansion(params.id, !rowNode.childrenExpanded);
206
+ });
207
+ timeoutRowId.current = params.id;
208
+ return;
209
+ }
210
+ const targetRowIndex = sortedRowIndexLookup[params.id];
211
+ const sourceRowIndex = sortedRowIndexLookup[dragRowId];
197
212
 
198
- // Determine drop position based on relativeY position within the row
199
- const dropPosition = relativeY < midPoint ? 'above' : 'below';
213
+ // Determine drop position based on relativeY position within the row
214
+ const dropPosition = relativeY < midPoint ? 'above' : 'below';
215
+ const currentReorderState = {
216
+ dragDirection: targetRowIndex < sourceRowIndex ? 'up' : 'down',
217
+ previousTargetId: params.id,
218
+ previousDropPosition: dropPosition
219
+ };
200
220
 
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)
221
+ // Update visual indicator when dragging over a different row or position
222
+ if (previousReorderState.current.previousTargetId !== params.id || previousReorderState.current.previousDropPosition !== dropPosition) {
223
+ const isSameNode = targetRowIndex === sourceRowIndex;
205
224
 
206
- const currentReorderState = {
207
- dragDirection: targetRowIndex < sourceRowIndex ? Direction.UP : Direction.DOWN,
208
- previousTargetId: params.id,
209
- previousDropPosition: dropPosition
210
- };
225
+ // Check if this is an adjacent position
226
+ const isAdjacentPosition = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1;
227
+ const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', -1, {
228
+ sourceRowId: dragRowId,
229
+ targetRowId: params.id,
230
+ dropPosition,
231
+ dragDirection: currentReorderState.dragDirection
232
+ });
211
233
 
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;
234
+ // Show drop indicator for valid drops OR adjacent positions OR same node
235
+ if (validatedIndex !== -1 || isAdjacentPosition || isSameNode) {
236
+ dropTarget.current = {
237
+ targetRowId: params.id,
238
+ targetRowIndex,
239
+ dropPosition
240
+ };
241
+ applyDropIndicator(params.id, dropPosition);
242
+ } else {
243
+ // Clear indicators for invalid drops
244
+ dropTarget.current = {
245
+ targetRowId: null,
246
+ targetRowIndex: null,
247
+ dropPosition: null
248
+ };
249
+ applyDropIndicator(null, null);
233
250
  }
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
- };
251
+ previousReorderState.current = currentReorderState;
246
252
  }
247
- }, [dragRowId, apiRef, logger, sortedRowIndexLookup, applyDropIndicator]);
253
+
254
+ // Render the native 'copy' cursor for additional visual feedback
255
+ if (dropTarget.current.targetRowId === null) {
256
+ event.dataTransfer.dropEffect = 'none';
257
+ } else {
258
+ event.dataTransfer.dropEffect = 'copy';
259
+ }
260
+ }, [dragRowId, apiRef, logger, timeout, sortedRowIndexLookup, applyDropIndicator]);
248
261
  const handleDragEnd = React.useCallback((_, event) => {
249
262
  // Call the gridEditRowsStateSelector directly to avoid infnite loop
250
263
  const editRowsState = gridEditRowsStateSelector(apiRef);
251
264
  if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
252
265
  return;
253
266
  }
267
+ if (timeoutRowId.current) {
268
+ timeout.clear();
269
+ timeoutRowId.current = '';
270
+ }
254
271
  logger.debug('End dragging row');
255
272
  event.preventDefault();
256
273
  // Prevent drag events propagation.
@@ -269,55 +286,77 @@ export const useGridRowReorder = (apiRef, props) => {
269
286
  // Check if the row was dropped outside the grid.
270
287
  if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
271
288
  // Reset drop target state
272
- setDropTarget({
289
+ dropTarget.current = {
273
290
  targetRowId: null,
274
291
  targetRowIndex: null,
275
292
  dropPosition: null
276
- });
293
+ };
277
294
  originRowIndex.current = null;
278
- } else {
279
- if (dropTarget.targetRowIndex !== null && dropTarget.targetRowId !== null) {
280
- const sourceRowIndex = originRowIndex.current;
281
- const targetRowIndex = dropTarget.targetRowIndex;
282
- const dropPosition = dropTarget.dropPosition;
295
+ setDragRowId('');
296
+ return;
297
+ }
298
+ if (dropTarget.current.targetRowIndex !== null && dropTarget.current.targetRowId !== null) {
299
+ const sourceRowIndex = originRowIndex.current;
300
+ const targetRowIndex = dropTarget.current.targetRowIndex;
301
+ const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', targetRowIndex, {
302
+ sourceRowId: dragRowId,
303
+ targetRowId: dropTarget.current.targetRowId,
304
+ dropPosition: dropTarget.current.dropPosition,
305
+ dragDirection: dragDirection
306
+ });
307
+ if (validatedIndex !== -1) {
308
+ applyRowAnimation(() => {
309
+ apiRef.current.setRowIndex(dragRowId, validatedIndex);
283
310
 
284
- // Calculate the correct target index based on drop position
285
- let finalTargetIndex;
286
- if (dragDirection === Direction.UP) {
287
- finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1;
288
- } else {
289
- finalTargetIndex = dropPosition === 'above' ? targetRowIndex - 1 : targetRowIndex;
290
- }
291
- const isReorderInvalid = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
292
- // dragging to immediately below (above next row)
293
- dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1 ||
294
- // dragging to immediately above (below previous row)
295
- dropTarget.targetRowId === dragRowId; // dragging to the same row
311
+ // Emit the rowOrderChange event only once when the reordering stops.
312
+ const rowOrderChangeParams = {
313
+ row: apiRef.current.getRow(dragRowId),
314
+ targetIndex: validatedIndex,
315
+ oldIndex: sourceRowIndex
316
+ };
317
+ apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
318
+ });
319
+ }
320
+ }
296
321
 
297
- if (!isReorderInvalid) {
298
- applyRowAnimation(() => {
299
- apiRef.current.setRowIndex(dragRowId, finalTargetIndex);
322
+ // Reset drop target state
323
+ dropTarget.current = {
324
+ targetRowId: null,
325
+ targetRowIndex: null,
326
+ dropPosition: null
327
+ };
328
+ setDragRowId('');
329
+ }, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDropIndicator, applyDraggedState, timeout, applyRowAnimation]);
330
+ const getRowReorderTargetIndex = React.useCallback((initialValue, {
331
+ sourceRowId,
332
+ targetRowId,
333
+ dropPosition,
334
+ dragDirection
335
+ }) => {
336
+ if (gridRowMaximumTreeDepthSelector(apiRef) > 1) {
337
+ return initialValue;
338
+ }
339
+ const targetRowIndex = sortedRowIndexLookup[targetRowId];
340
+ const sourceRowIndex = sortedRowIndexLookup[sourceRowId];
300
341
 
301
- // Emit the rowOrderChange event only once when the reordering stops.
302
- const rowOrderChangeParams = {
303
- row: apiRef.current.getRow(dragRowId),
304
- targetIndex: finalTargetIndex,
305
- oldIndex: sourceRowIndex
306
- };
307
- apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
308
- });
309
- }
310
- }
342
+ // Check if this drop would result in no actual movement
343
+ const isAdjacentNode = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
344
+ // dragging to immediately below (above next row)
345
+ dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
311
346
 
312
- // Reset drop target state
313
- setDropTarget({
314
- targetRowId: null,
315
- targetRowIndex: null,
316
- dropPosition: null
317
- });
347
+ if (isAdjacentNode || sourceRowIndex === targetRowIndex) {
348
+ // Return -1 to prevent actual movement (indicators handled separately)
349
+ return -1;
318
350
  }
319
- setDragRowId('');
320
- }, [apiRef, dragRowId, isRowReorderDisabled, logger, dropTarget, applyDropIndicator, applyDraggedState, applyRowAnimation]);
351
+ let finalTargetIndex;
352
+ if (dragDirection === 'up') {
353
+ finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1;
354
+ } else {
355
+ finalTargetIndex = dropPosition === 'above' ? targetRowIndex - 1 : targetRowIndex;
356
+ }
357
+ return finalTargetIndex;
358
+ }, [apiRef, sortedRowIndexLookup]);
359
+ useGridRegisterPipeProcessor(apiRef, 'getRowReorderTargetIndex', getRowReorderTargetIndex);
321
360
  useGridEvent(apiRef, 'rowDragStart', handleDragStart);
322
361
  useGridEvent(apiRef, 'rowDragOver', handleDragOver);
323
362
  useGridEvent(apiRef, 'rowDragEnd', handleDragEnd);
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-pro v8.10.1
2
+ * @mui/x-data-grid-pro v8.11.0
3
3
  *
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  * This source code is licensed under the SEE LICENSE IN LICENSE license found in the
@@ -10,6 +10,7 @@ exports.useGridColumnReorder = exports.columnReorderStateInitializer = void 0;
10
10
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
11
  var React = _interopRequireWildcard(require("react"));
12
12
  var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
13
+ var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument"));
13
14
  var _RtlProvider = require("@mui/system/RtlProvider");
14
15
  var _xDataGrid = require("@mui/x-data-grid");
15
16
  var _columnReorderSelector = require("./columnReorderSelector");
@@ -247,6 +248,24 @@ const useGridColumnReorder = (apiRef, props) => {
247
248
  cursorPosition.current = coordinates;
248
249
  }
249
250
  }, [apiRef, logger, isRtl]);
251
+ React.useEffect(() => {
252
+ if (!props.keepColumnPositionIfDraggedOutside) {
253
+ return () => {};
254
+ }
255
+ const doc = (0, _ownerDocument.default)(apiRef.current.rootElementRef.current);
256
+ const listener = event => {
257
+ if (event.dataTransfer) {
258
+ // keep the drop effect if we are keeping the column position if dragged outside
259
+ // https://github.com/mui/mui-x/issues/19183#issuecomment-3202307783
260
+ event.preventDefault();
261
+ event.dataTransfer.dropEffect = 'move';
262
+ }
263
+ };
264
+ doc.addEventListener('dragover', listener);
265
+ return () => {
266
+ doc.removeEventListener('dragover', listener);
267
+ };
268
+ }, [apiRef, props.keepColumnPositionIfDraggedOutside]);
250
269
  (0, _xDataGrid.useGridEvent)(apiRef, 'columnHeaderDragStart', handleDragStart);
251
270
  (0, _xDataGrid.useGridEvent)(apiRef, 'columnHeaderDragEnter', handleDragEnter);
252
271
  (0, _xDataGrid.useGridEvent)(apiRef, 'columnHeaderDragOver', handleDragOver);
@@ -7,9 +7,9 @@ export interface GridDataSourceState {
7
7
  export interface GridGetRowsResponsePro extends GridGetRowsResponse {}
8
8
  export interface GridGetRowsParamsPro extends GridGetRowsParams {
9
9
  /**
10
- * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested
11
- * `getGroupKey` prop must be implemented to use this.
12
- * Useful for `treeData` and `rowGrouping` only.
10
+ * Array of keys returned by `getGroupKey()` of all the parent rows until the row for which the data is requested
11
+ * `getGroupKey()` prop must be implemented to use this.
12
+ * Used with "tree data" and "row grouping" features only.
13
13
  */
14
14
  groupKeys?: string[];
15
15
  }
@@ -21,7 +21,9 @@ export interface GridDataSourcePro extends Omit<GridDataSource, 'getRows'> {
21
21
  */
22
22
  getRows(params: GridGetRowsParamsPro): Promise<GridGetRowsResponsePro>;
23
23
  /**
24
- * Used to group rows by their parent group. Replaces `getTreeDataPath` used in client side tree-data.
24
+ * Used to group rows by their parent group.
25
+ * Replaces `getTreeDataPath()` used in client side tree-data
26
+ * Replaces `colDef.groupingValueGetter` used in client side row grouping
25
27
  * @param {GridRowModel} row The row to get the group key of.
26
28
  * @returns {string} The group key for the row.
27
29
  */
@@ -1,10 +1,10 @@
1
1
  import { RefObject } from '@mui/x-internals/types';
2
- import { GridStateInitializer } from '@mui/x-data-grid/internals';
2
+ import { type GridStateInitializer } from '@mui/x-data-grid/internals';
3
3
  import { GridPrivateApiPro } from "../../../models/gridApiPro.js";
4
4
  import { DataGridProProcessedProps } from "../../../models/dataGridProProps.js";
5
5
  export declare const rowReorderStateInitializer: GridStateInitializer;
6
6
  /**
7
- * Only available in DataGridPro
7
+ * Hook for row reordering (Pro package)
8
8
  * @requires useGridRows (method)
9
9
  */
10
- export declare const useGridRowReorder: (apiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "rowReordering" | "onRowOrderChange" | "classes">) => void;
10
+ export declare const useGridRowReorder: (apiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "rowReordering" | "onRowOrderChange" | "classes" | "treeData" | "dataSource">) => void;