@mui/x-data-grid-pro 8.19.0 → 8.21.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/DataGridPro/DataGridPro.js +29 -2
  3. package/components/GridRowReorderCell.js +15 -3
  4. package/components/headerFiltering/GridHeaderFilterCell.js +2 -3
  5. package/esm/DataGridPro/DataGridPro.js +29 -2
  6. package/esm/components/GridRowReorderCell.js +15 -3
  7. package/esm/components/headerFiltering/GridHeaderFilterCell.js +2 -3
  8. package/esm/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
  9. package/esm/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
  10. package/esm/hooks/features/rowReorder/commonReorderConditions.js +78 -0
  11. package/esm/hooks/features/rowReorder/index.d.ts +2 -1
  12. package/esm/hooks/features/rowReorder/index.js +2 -1
  13. package/esm/hooks/features/rowReorder/models.d.ts +17 -0
  14. package/esm/hooks/features/rowReorder/models.js +1 -0
  15. package/esm/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
  16. package/esm/hooks/features/rowReorder/reorderExecutor.js +29 -0
  17. package/esm/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
  18. package/esm/hooks/features/rowReorder/reorderValidator.js +14 -0
  19. package/esm/hooks/features/rowReorder/types.d.ts +25 -0
  20. package/esm/hooks/features/rowReorder/types.js +1 -0
  21. package/esm/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
  22. package/esm/hooks/features/rowReorder/useGridRowReorder.js +167 -80
  23. package/esm/hooks/features/rowReorder/utils.d.ts +82 -0
  24. package/esm/hooks/features/rowReorder/utils.js +259 -0
  25. package/esm/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
  26. package/esm/hooks/features/rows/useGridRowsOverridableMethods.js +59 -0
  27. package/esm/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
  28. package/esm/hooks/features/treeData/treeDataReorderExecutor.js +534 -0
  29. package/esm/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
  30. package/esm/hooks/features/treeData/treeDataReorderValidator.js +35 -0
  31. package/esm/hooks/features/treeData/useGridTreeData.d.ts +3 -3
  32. package/esm/hooks/features/treeData/useGridTreeData.js +49 -4
  33. package/esm/hooks/features/treeData/utils.d.ts +8 -0
  34. package/esm/hooks/features/treeData/utils.js +96 -0
  35. package/esm/index.js +1 -1
  36. package/esm/internals/index.d.ts +8 -0
  37. package/esm/internals/index.js +6 -0
  38. package/esm/models/dataGridProProps.d.ts +32 -4
  39. package/esm/models/gridRowOrderChangeParams.d.ts +29 -5
  40. package/hooks/features/dataSource/useGridDataSourceBasePro.js +1 -1
  41. package/hooks/features/rowReorder/commonReorderConditions.d.ts +30 -0
  42. package/hooks/features/rowReorder/commonReorderConditions.js +84 -0
  43. package/hooks/features/rowReorder/index.d.ts +2 -1
  44. package/hooks/features/rowReorder/models.d.ts +17 -0
  45. package/hooks/features/rowReorder/models.js +5 -0
  46. package/hooks/features/rowReorder/reorderExecutor.d.ts +27 -0
  47. package/hooks/features/rowReorder/reorderExecutor.js +37 -0
  48. package/hooks/features/rowReorder/reorderValidator.d.ts +12 -0
  49. package/hooks/features/rowReorder/reorderValidator.js +21 -0
  50. package/hooks/features/rowReorder/types.d.ts +25 -0
  51. package/hooks/features/rowReorder/types.js +5 -0
  52. package/hooks/features/rowReorder/useGridRowReorder.d.ts +1 -1
  53. package/hooks/features/rowReorder/useGridRowReorder.js +168 -81
  54. package/hooks/features/rowReorder/utils.d.ts +82 -0
  55. package/hooks/features/rowReorder/utils.js +286 -0
  56. package/hooks/features/rows/useGridRowsOverridableMethods.d.ts +7 -0
  57. package/hooks/features/rows/useGridRowsOverridableMethods.js +67 -0
  58. package/hooks/features/treeData/treeDataReorderExecutor.d.ts +11 -0
  59. package/hooks/features/treeData/treeDataReorderExecutor.js +541 -0
  60. package/hooks/features/treeData/treeDataReorderValidator.d.ts +2 -0
  61. package/hooks/features/treeData/treeDataReorderValidator.js +41 -0
  62. package/hooks/features/treeData/useGridTreeData.d.ts +3 -3
  63. package/hooks/features/treeData/useGridTreeData.js +48 -3
  64. package/hooks/features/treeData/utils.d.ts +8 -0
  65. package/hooks/features/treeData/utils.js +109 -0
  66. package/index.js +1 -1
  67. package/internals/index.d.ts +8 -0
  68. package/internals/index.js +53 -1
  69. package/models/dataGridProProps.d.ts +32 -4
  70. package/models/gridRowOrderChangeParams.d.ts +29 -5
  71. package/package.json +4 -4
@@ -0,0 +1,259 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import { GRID_ROOT_GROUP_ID, gridClasses } from '@mui/x-data-grid';
3
+ import { warnOnce } from '@mui/x-internals/warning';
4
+ // Re-export to be made part of `rowReorderUtils`
5
+ export { getNodePathInTree } from "../../../utils/tree/utils.js";
6
+
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 function findCellElement(target) {
15
+ const element = target;
16
+ if (!element) {
17
+ return element;
18
+ }
19
+
20
+ // Check if the target itself is a cell
21
+ if (element instanceof Element && element.classList.contains(gridClasses.cell)) {
22
+ return element;
23
+ }
24
+
25
+ // Try to find the closest cell parent
26
+ const cellElement = element.closest(`[class*="${gridClasses.cell}"]`);
27
+ return cellElement || element;
28
+ }
29
+ export function determineOperationType(sourceNode, targetNode) {
30
+ if (sourceNode.parent === targetNode.parent) {
31
+ return 'same-parent-swap';
32
+ }
33
+ if (sourceNode.type === 'leaf') {
34
+ return 'cross-parent-leaf';
35
+ }
36
+ return 'cross-parent-group';
37
+ }
38
+ export function calculateTargetIndex(sourceNode, targetNode, isLastChild, rowTree) {
39
+ if (sourceNode.parent === targetNode.parent && !isLastChild) {
40
+ // Same parent: find target's position in parent's children
41
+ const parent = rowTree[sourceNode.parent];
42
+ return parent.children.findIndex(id => id === targetNode.id);
43
+ }
44
+ if (isLastChild) {
45
+ // Append at the end
46
+ const targetParent = rowTree[targetNode.parent];
47
+ return targetParent.children.length;
48
+ }
49
+
50
+ // Find position in target parent
51
+ const targetParent = rowTree[targetNode.parent];
52
+ const targetIndex = targetParent.children.findIndex(id => id === targetNode.id);
53
+ return targetIndex >= 0 ? targetIndex : 0;
54
+ }
55
+
56
+ // Recursively collect all leaf node IDs from a group
57
+ export const collectAllLeafDescendants = (groupNode, tree) => {
58
+ const leafIds = [];
59
+ const collectFromNode = nodeId => {
60
+ const node = tree[nodeId];
61
+ if (node.type === 'leaf') {
62
+ leafIds.push(nodeId);
63
+ } else if (node.type === 'group') {
64
+ node.children.forEach(collectFromNode);
65
+ }
66
+ };
67
+ groupNode.children.forEach(collectFromNode);
68
+ return leafIds;
69
+ };
70
+
71
+ // Recursively collect all descendant nodes (groups and leaves) from a group
72
+ export const collectAllDescendants = (groupNode, tree) => {
73
+ const descendants = [];
74
+ const collectFromNode = nodeId => {
75
+ const node = tree[nodeId];
76
+ if (node) {
77
+ descendants.push(node);
78
+ if (node.type === 'group') {
79
+ node.children.forEach(collectFromNode);
80
+ }
81
+ }
82
+ };
83
+ groupNode.children.forEach(collectFromNode);
84
+ return descendants;
85
+ };
86
+
87
+ // Check if a node is a descendant of another node
88
+ export const isDescendantOf = (possibleDescendant, ancestor, tree) => {
89
+ let current = possibleDescendant;
90
+ while (current && current.id !== GRID_ROOT_GROUP_ID) {
91
+ if (current.id === ancestor.id) {
92
+ return true;
93
+ }
94
+ current = tree[current.parent];
95
+ }
96
+ return false;
97
+ };
98
+
99
+ // Update depths for all descendant nodes recursively
100
+ export const updateDescendantDepths = (group, tree, depthDiff) => {
101
+ const updateNodeDepth = nodeId => {
102
+ const node = tree[nodeId];
103
+ if (node) {
104
+ tree[nodeId] = _extends({}, node, {
105
+ depth: node.depth + depthDiff
106
+ });
107
+ if (node.type === 'group') {
108
+ node.children.forEach(updateNodeDepth);
109
+ }
110
+ }
111
+ };
112
+ group.children.forEach(updateNodeDepth);
113
+ };
114
+
115
+ /**
116
+ * Finds an existing group node with the same groupingKey and groupingField under a parent.
117
+ *
118
+ * @param parentNode - The parent group node to search in
119
+ * @param groupingKey - The grouping key to match
120
+ * @param groupingField - The grouping field to match
121
+ * @param tree - The row tree configuration
122
+ * @returns The existing group node if found, null otherwise
123
+ */
124
+ export function findExistingGroupWithSameKey(parentNode, groupingKey, groupingField, tree) {
125
+ for (const childId of parentNode.children) {
126
+ const childNode = tree[childId];
127
+ if (childNode && childNode.type === 'group' && childNode.groupingKey === groupingKey && childNode.groupingField === groupingField) {
128
+ return childNode;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Removes empty ancestor groups from the tree after a row move operation.
136
+ * Walks up the tree from the given group, removing any empty groups encountered.
137
+ *
138
+ * @param groupId - The ID of the group to start checking from
139
+ * @param tree - The row tree configuration
140
+ * @param removedGroups - Set to track which groups have been removed
141
+ * @returns The number of root-level groups that were removed
142
+ */
143
+ export function removeEmptyAncestors(groupId, tree, removedGroups) {
144
+ let rootLevelRemovals = 0;
145
+ let currentGroupId = groupId;
146
+ while (currentGroupId && currentGroupId !== GRID_ROOT_GROUP_ID) {
147
+ const group = tree[currentGroupId];
148
+ if (!group) {
149
+ break;
150
+ }
151
+ const remainingChildren = group.children.filter(childId => !removedGroups.has(childId));
152
+ if (remainingChildren.length > 0) {
153
+ break;
154
+ }
155
+ if (group.depth === 0) {
156
+ rootLevelRemovals += 1;
157
+ }
158
+ removedGroups.add(currentGroupId);
159
+ currentGroupId = group.parent;
160
+ }
161
+ return rootLevelRemovals;
162
+ }
163
+ export function handleProcessRowUpdateError(error, onProcessRowUpdateError) {
164
+ if (onProcessRowUpdateError) {
165
+ onProcessRowUpdateError(error);
166
+ } else {
167
+ warnOnce(['MUI X: A call to `processRowUpdate()` threw an error which was not handled because `onProcessRowUpdateError()` is missing.', 'To handle the error pass a callback to the `onProcessRowUpdateError()` prop, for example `<DataGrid onProcessRowUpdateError={(error) => ...} />`.', 'For more detail, see https://mui.com/x/react-data-grid/editing/persistence/.'], 'error');
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Handles batch row updates with partial failure tracking.
173
+ *
174
+ * This class is designed for operations that need to update multiple rows
175
+ * atomically (like moving entire groups), while gracefully handling cases
176
+ * where some updates succeed and others fail.
177
+ *
178
+ * @example
179
+ * ```tsx
180
+ * const updater = new BatchRowUpdater(apiRef, processRowUpdate, onError);
181
+ *
182
+ * // Queue multiple updates
183
+ * updater.queueUpdate('row1', originalRow1, newRow1);
184
+ * updater.queueUpdate('row2', originalRow2, newRow2);
185
+ *
186
+ * // Execute all updates
187
+ * const { successful, failed, updates } = await updater.executeAll();
188
+ *
189
+ * // Handle results
190
+ * if (successful.length > 0) {
191
+ * apiRef.current.updateRows(updates);
192
+ * }
193
+ * ```
194
+ */
195
+ export class BatchRowUpdater {
196
+ rowsToUpdate = (() => new Map())();
197
+ originalRows = (() => new Map())();
198
+ successfulRowIds = (() => new Set())();
199
+ failedRowIds = (() => new Set())();
200
+ pendingRowUpdates = [];
201
+ constructor(apiRef, processRowUpdate, onProcessRowUpdateError) {
202
+ this.apiRef = apiRef;
203
+ this.processRowUpdate = processRowUpdate;
204
+ this.onProcessRowUpdateError = onProcessRowUpdateError;
205
+ }
206
+ queueUpdate(rowId, originalRow, updatedRow) {
207
+ this.originalRows.set(rowId, originalRow);
208
+ this.rowsToUpdate.set(rowId, updatedRow);
209
+ }
210
+ async executeAll() {
211
+ const rowIds = Array.from(this.rowsToUpdate.keys());
212
+ if (rowIds.length === 0) {
213
+ return {
214
+ successful: [],
215
+ failed: [],
216
+ updates: []
217
+ };
218
+ }
219
+
220
+ // Handle each row update, tracking success/failure
221
+ const handleRowUpdate = async rowId => {
222
+ const newRow = this.rowsToUpdate.get(rowId);
223
+ const oldRow = this.originalRows.get(rowId);
224
+ try {
225
+ if (typeof this.processRowUpdate === 'function') {
226
+ const params = {
227
+ rowId,
228
+ previousRow: oldRow,
229
+ updatedRow: newRow
230
+ };
231
+ const finalRow = await this.processRowUpdate(newRow, oldRow, params);
232
+ this.pendingRowUpdates.push(finalRow || newRow);
233
+ this.successfulRowIds.add(rowId);
234
+ } else {
235
+ this.pendingRowUpdates.push(newRow);
236
+ this.successfulRowIds.add(rowId);
237
+ }
238
+ } catch (error) {
239
+ this.failedRowIds.add(rowId);
240
+ handleProcessRowUpdateError(error, this.onProcessRowUpdateError);
241
+ }
242
+ };
243
+
244
+ // Use Promise.all with wrapped promises to avoid Promise.allSettled (browser support)
245
+ const promises = rowIds.map(rowId => {
246
+ return new Promise(resolve => {
247
+ handleRowUpdate(rowId).then(resolve).catch(resolve);
248
+ });
249
+ });
250
+ this.apiRef.current.setLoading(true);
251
+ await Promise.all(promises);
252
+ this.apiRef.current.setLoading(false);
253
+ return {
254
+ successful: Array.from(this.successfulRowIds),
255
+ failed: Array.from(this.failedRowIds),
256
+ updates: this.pendingRowUpdates
257
+ };
258
+ }
259
+ }
@@ -0,0 +1,7 @@
1
+ import type { RefObject } from '@mui/x-internals/types';
2
+ import type { GridPrivateApiPro } from "../../../models/gridApiPro.js";
3
+ import type { DataGridProProcessedProps } from "../../../models/dataGridProProps.js";
4
+ export declare const useGridRowsOverridableMethods: (apiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "processRowUpdate" | "onProcessRowUpdateError" | "setTreeDataPath">) => {
5
+ setRowIndex: (rowId: import("@mui/x-data-grid").GridRowId, targetIndex: number) => void;
6
+ setRowPosition: (sourceRowId: import("@mui/x-data-grid").GridRowId, targetRowId: import("@mui/x-data-grid").GridRowId, position: import("@mui/x-data-grid/internals").RowReorderDropPosition) => void | Promise<void>;
7
+ };
@@ -0,0 +1,59 @@
1
+ import * as React from 'react';
2
+ import { gridRowTreeSelector, gridExpandedSortedRowIdsSelector, gridRowNodeSelector, useGridSelector, gridRowMaximumTreeDepthSelector, gridExpandedSortedRowIndexLookupSelector } from '@mui/x-data-grid';
3
+ import { useGridRowsOverridableMethodsCommunity } from '@mui/x-data-grid/internals';
4
+ import { treeDataReorderExecutor } from "../treeData/treeDataReorderExecutor.js";
5
+ export const useGridRowsOverridableMethods = (apiRef, props) => {
6
+ const {
7
+ processRowUpdate,
8
+ onProcessRowUpdateError,
9
+ setTreeDataPath
10
+ } = props;
11
+ const {
12
+ setRowIndex: setRowIndexFlat,
13
+ setRowPosition: setRowPositionFlat
14
+ } = useGridRowsOverridableMethodsCommunity(apiRef);
15
+ const flatTree = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector) === 1;
16
+ const setRowPosition = React.useCallback(async (sourceRowId, targetRowId, position) => {
17
+ const sortedFilteredRowIds = gridExpandedSortedRowIdsSelector(apiRef);
18
+ const sortedFilteredRowIndexLookup = gridExpandedSortedRowIndexLookupSelector(apiRef);
19
+ const rowTree = gridRowTreeSelector(apiRef);
20
+ const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
21
+ const targetNode = gridRowNodeSelector(apiRef, targetRowId);
22
+ if (!sourceNode) {
23
+ throw new Error(`MUI X: No row with id #${sourceRowId} found.`);
24
+ }
25
+ if (!targetNode) {
26
+ throw new Error(`MUI X: No row with id #${targetRowId} found.`);
27
+ }
28
+ if (sourceNode.type === 'footer') {
29
+ throw new Error(`MUI X: The row reordering do not support reordering of footer rows.`);
30
+ }
31
+
32
+ // Get the target index from the targetRowId using the lookup selector
33
+ const targetIndexUnadjusted = sortedFilteredRowIndexLookup[targetRowId];
34
+ if (targetIndexUnadjusted === undefined) {
35
+ throw new Error(`MUI X: Target row with id #${targetRowId} not found in current view.`);
36
+ }
37
+ const targetIndex = position === 'below' ? targetIndexUnadjusted + 1 : targetIndexUnadjusted;
38
+ const executionContext = {
39
+ sourceRowId,
40
+ dropPosition: position,
41
+ placeholderIndex: targetIndex,
42
+ sortedFilteredRowIds,
43
+ sortedFilteredRowIndexLookup,
44
+ rowTree,
45
+ apiRef,
46
+ processRowUpdate,
47
+ onProcessRowUpdateError,
48
+ setTreeDataPath
49
+ };
50
+ return treeDataReorderExecutor.execute(executionContext);
51
+ }, [apiRef, processRowUpdate, onProcessRowUpdateError, setTreeDataPath]);
52
+ const setRowIndex = React.useCallback(async () => {
53
+ throw new Error(`MUI X: \`setRowIndex()\` is not supported for tree data. Use \`setRowPosition()\` instead.`);
54
+ }, []);
55
+ return {
56
+ setRowIndex: flatTree ? setRowIndexFlat : setRowIndex,
57
+ setRowPosition: flatTree ? setRowPositionFlat : setRowPosition
58
+ };
59
+ };
@@ -0,0 +1,11 @@
1
+ import { BaseReorderOperation, RowReorderExecutor } from "../rowReorder/reorderExecutor.js";
2
+ import { type ReorderOperation, type ReorderExecutionContext } from "../rowReorder/types.js";
3
+ /**
4
+ * Handles reordering of items within the same parent group.
5
+ */
6
+ export declare class SameParentSwapOperation extends BaseReorderOperation {
7
+ readonly operationType = "same-parent-swap";
8
+ detectOperation(ctx: ReorderExecutionContext): ReorderOperation | null;
9
+ executeOperation(operation: ReorderOperation, ctx: ReorderExecutionContext): void;
10
+ }
11
+ export declare const treeDataReorderExecutor: RowReorderExecutor;