@mui/x-data-grid-pro
Version:
The Pro plan edition of the MUI X Data Grid components.
286 lines (272 loc) • 9.85 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BatchRowUpdater = void 0;
exports.calculateTargetIndex = calculateTargetIndex;
exports.collectAllLeafDescendants = exports.collectAllDescendants = void 0;
exports.determineOperationType = determineOperationType;
exports.findCellElement = findCellElement;
exports.findExistingGroupWithSameKey = findExistingGroupWithSameKey;
Object.defineProperty(exports, "getNodePathInTree", {
enumerable: true,
get: function () {
return _utils.getNodePathInTree;
}
});
exports.handleProcessRowUpdateError = handleProcessRowUpdateError;
exports.isDescendantOf = void 0;
exports.removeEmptyAncestors = removeEmptyAncestors;
exports.updateDescendantDepths = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _xDataGrid = require("@mui/x-data-grid");
var _warning = require("@mui/x-internals/warning");
var _utils = require("../../../utils/tree/utils");
// Re-export to be made part of `rowReorderUtils`
/**
* Finds the closest cell element from the given event target.
* If the target itself is a cell, returns it.
* Otherwise, searches for the closest parent with 'cell' in its className.
* @param target - The event target to start searching from
* @returns The cell element or the original target if no cell is found
*/
function findCellElement(target) {
const element = target;
if (!element) {
return element;
}
// Check if the target itself is a cell
if (element instanceof Element && element.classList.contains(_xDataGrid.gridClasses.cell)) {
return element;
}
// Try to find the closest cell parent
const cellElement = element.closest(`[class*="${_xDataGrid.gridClasses.cell}"]`);
return cellElement || element;
}
function determineOperationType(sourceNode, targetNode) {
if (sourceNode.parent === targetNode.parent) {
return 'same-parent-swap';
}
if (sourceNode.type === 'leaf') {
return 'cross-parent-leaf';
}
return 'cross-parent-group';
}
function calculateTargetIndex(sourceNode, targetNode, isLastChild, rowTree) {
if (sourceNode.parent === targetNode.parent && !isLastChild) {
// Same parent: find target's position in parent's children
const parent = rowTree[sourceNode.parent];
return parent.children.findIndex(id => id === targetNode.id);
}
if (isLastChild) {
// Append at the end
const targetParent = rowTree[targetNode.parent];
return targetParent.children.length;
}
// Find position in target parent
const targetParent = rowTree[targetNode.parent];
const targetIndex = targetParent.children.findIndex(id => id === targetNode.id);
return targetIndex >= 0 ? targetIndex : 0;
}
// Recursively collect all leaf node IDs from a group
const collectAllLeafDescendants = (groupNode, tree) => {
const leafIds = [];
const collectFromNode = nodeId => {
const node = tree[nodeId];
if (node.type === 'leaf') {
leafIds.push(nodeId);
} else if (node.type === 'group') {
node.children.forEach(collectFromNode);
}
};
groupNode.children.forEach(collectFromNode);
return leafIds;
};
// Recursively collect all descendant nodes (groups and leaves) from a group
exports.collectAllLeafDescendants = collectAllLeafDescendants;
const collectAllDescendants = (groupNode, tree) => {
const descendants = [];
const collectFromNode = nodeId => {
const node = tree[nodeId];
if (node) {
descendants.push(node);
if (node.type === 'group') {
node.children.forEach(collectFromNode);
}
}
};
groupNode.children.forEach(collectFromNode);
return descendants;
};
// Check if a node is a descendant of another node
exports.collectAllDescendants = collectAllDescendants;
const isDescendantOf = (possibleDescendant, ancestor, tree) => {
let current = possibleDescendant;
while (current && current.id !== _xDataGrid.GRID_ROOT_GROUP_ID) {
if (current.id === ancestor.id) {
return true;
}
current = tree[current.parent];
}
return false;
};
// Update depths for all descendant nodes recursively
exports.isDescendantOf = isDescendantOf;
const updateDescendantDepths = (group, tree, depthDiff) => {
const updateNodeDepth = nodeId => {
const node = tree[nodeId];
if (node) {
tree[nodeId] = (0, _extends2.default)({}, node, {
depth: node.depth + depthDiff
});
if (node.type === 'group') {
node.children.forEach(updateNodeDepth);
}
}
};
group.children.forEach(updateNodeDepth);
};
/**
* Finds an existing group node with the same groupingKey and groupingField under a parent.
*
* @param parentNode - The parent group node to search in
* @param groupingKey - The grouping key to match
* @param groupingField - The grouping field to match
* @param tree - The row tree configuration
* @returns The existing group node if found, null otherwise
*/
exports.updateDescendantDepths = updateDescendantDepths;
function findExistingGroupWithSameKey(parentNode, groupingKey, groupingField, tree) {
for (const childId of parentNode.children) {
const childNode = tree[childId];
if (childNode && childNode.type === 'group' && childNode.groupingKey === groupingKey && childNode.groupingField === groupingField) {
return childNode;
}
}
return null;
}
/**
* Removes empty ancestor groups from the tree after a row move operation.
* Walks up the tree from the given group, removing any empty groups encountered.
*
* @param groupId - The ID of the group to start checking from
* @param tree - The row tree configuration
* @param removedGroups - Set to track which groups have been removed
* @returns The number of root-level groups that were removed
*/
function removeEmptyAncestors(groupId, tree, removedGroups) {
let rootLevelRemovals = 0;
let currentGroupId = groupId;
while (currentGroupId && currentGroupId !== _xDataGrid.GRID_ROOT_GROUP_ID) {
const group = tree[currentGroupId];
if (!group) {
break;
}
const remainingChildren = group.children.filter(childId => !removedGroups.has(childId));
if (remainingChildren.length > 0) {
break;
}
if (group.depth === 0) {
rootLevelRemovals += 1;
}
removedGroups.add(currentGroupId);
currentGroupId = group.parent;
}
return rootLevelRemovals;
}
function handleProcessRowUpdateError(error, onProcessRowUpdateError) {
if (onProcessRowUpdateError) {
onProcessRowUpdateError(error);
} else {
(0, _warning.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');
}
}
/**
* Handles batch row updates with partial failure tracking.
*
* This class is designed for operations that need to update multiple rows
* atomically (like moving entire groups), while gracefully handling cases
* where some updates succeed and others fail.
*
* @example
* ```tsx
* const updater = new BatchRowUpdater(apiRef, processRowUpdate, onError);
*
* // Queue multiple updates
* updater.queueUpdate('row1', originalRow1, newRow1);
* updater.queueUpdate('row2', originalRow2, newRow2);
*
* // Execute all updates
* const { successful, failed, updates } = await updater.executeAll();
*
* // Handle results
* if (successful.length > 0) {
* apiRef.current.updateRows(updates);
* }
* ```
*/
class BatchRowUpdater {
rowsToUpdate = new Map();
originalRows = new Map();
successfulRowIds = new Set();
failedRowIds = new Set();
pendingRowUpdates = [];
constructor(apiRef, processRowUpdate, onProcessRowUpdateError) {
this.apiRef = apiRef;
this.processRowUpdate = processRowUpdate;
this.onProcessRowUpdateError = onProcessRowUpdateError;
}
queueUpdate(rowId, originalRow, updatedRow) {
this.originalRows.set(rowId, originalRow);
this.rowsToUpdate.set(rowId, updatedRow);
}
async executeAll() {
const rowIds = Array.from(this.rowsToUpdate.keys());
if (rowIds.length === 0) {
return {
successful: [],
failed: [],
updates: []
};
}
// Handle each row update, tracking success/failure
const handleRowUpdate = async rowId => {
const newRow = this.rowsToUpdate.get(rowId);
const oldRow = this.originalRows.get(rowId);
try {
if (typeof this.processRowUpdate === 'function') {
const params = {
rowId,
previousRow: oldRow,
updatedRow: newRow
};
const finalRow = await this.processRowUpdate(newRow, oldRow, params);
this.pendingRowUpdates.push(finalRow || newRow);
this.successfulRowIds.add(rowId);
} else {
this.pendingRowUpdates.push(newRow);
this.successfulRowIds.add(rowId);
}
} catch (error) {
this.failedRowIds.add(rowId);
handleProcessRowUpdateError(error, this.onProcessRowUpdateError);
}
};
// Use Promise.all with wrapped promises to avoid Promise.allSettled (browser support)
const promises = rowIds.map(rowId => {
return new Promise(resolve => {
handleRowUpdate(rowId).then(resolve).catch(resolve);
});
});
this.apiRef.current.setLoading(true);
await Promise.all(promises);
this.apiRef.current.setLoading(false);
return {
successful: Array.from(this.successfulRowIds),
failed: Array.from(this.failedRowIds),
updates: this.pendingRowUpdates
};
}
}
exports.BatchRowUpdater = BatchRowUpdater;