@mui/x-data-grid-premium
Version:
The Premium plan edition of the MUI X Data Grid Components.
394 lines (390 loc) • 15.6 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import { gridRowNodeSelector, gridRowTreeSelector, gridRowsLookupSelector, gridColumnLookupSelector } from '@mui/x-data-grid-pro';
import { BaseReorderOperation, rowReorderUtils } from '@mui/x-data-grid-pro/internals';
import { gridRowGroupingSanitizedModelSelector } from "../rowGrouping/index.js";
import { getGroupingRules, getCellGroupingCriteria } from "../rowGrouping/gridRowGroupingUtils.js";
/**
* Handles moving leaf nodes between different parent groups.
*/
export class CrossParentLeafOperation extends BaseReorderOperation {
operationType = 'cross-parent-leaf';
detectOperation(ctx) {
const {
sourceRowId,
placeholderIndex,
sortedFilteredRowIds,
rowTree,
apiRef
} = ctx;
const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
if (!sourceNode || sourceNode.type === 'footer') {
return null;
}
let targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[placeholderIndex]);
let isLastChild = false;
if (!targetNode) {
if (placeholderIndex >= sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[sortedFilteredRowIds.length - 1]);
isLastChild = true;
} else {
return null;
}
}
let adjustedTargetNode = targetNode;
// Case D adjustment
if (sourceNode.type === 'leaf' && targetNode.type === 'group' && targetNode.depth < sourceNode.depth) {
const prevIndex = placeholderIndex - 1;
if (prevIndex >= 0) {
const prevRowId = sortedFilteredRowIds[prevIndex];
const leafTargetNode = gridRowNodeSelector(apiRef, prevRowId);
if (leafTargetNode && leafTargetNode.type === 'leaf') {
adjustedTargetNode = leafTargetNode;
isLastChild = true;
}
}
}
const operationType = rowReorderUtils.determineOperationType(sourceNode, adjustedTargetNode);
if (operationType !== 'cross-parent-leaf') {
return null;
}
const actualTargetIndex = rowReorderUtils.calculateTargetIndex(sourceNode, adjustedTargetNode, isLastChild, rowTree);
targetNode = adjustedTargetNode;
// Validate depth constraints
if (sourceNode.type === 'leaf' && targetNode.type === 'leaf') {
if (sourceNode.depth !== targetNode.depth) {
return null;
}
} else if (sourceNode.type === 'leaf' && targetNode.type === 'group') {
if (targetNode.depth >= sourceNode.depth) {
return null;
}
}
return {
sourceNode,
targetNode: adjustedTargetNode,
actualTargetIndex,
isLastChild,
operationType
};
}
async executeOperation(operation, ctx) {
const {
sourceNode,
targetNode,
isLastChild
} = operation;
const {
apiRef,
sourceRowId,
processRowUpdate,
onProcessRowUpdateError
} = ctx;
let target = targetNode;
if (targetNode.type === 'group') {
const prevIndex = ctx.placeholderIndex - 1;
if (prevIndex >= 0) {
const prevRowId = ctx.sortedFilteredRowIds[prevIndex];
const prevNode = gridRowNodeSelector(apiRef, prevRowId);
if (prevNode && prevNode.type === 'leaf') {
target = prevNode;
}
}
}
const rowTree = gridRowTreeSelector(apiRef);
const sourceGroup = rowTree[sourceNode.parent];
const targetGroup = rowTree[target.parent];
const sourceChildren = sourceGroup.children;
const targetChildren = targetGroup.children;
const sourceIndex = sourceChildren.findIndex(row => row === sourceRowId);
const targetIndex = targetChildren.findIndex(row => row === target.id);
if (sourceIndex === -1 || targetIndex === -1) {
return;
}
const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef);
const columnsLookup = gridColumnLookupSelector(apiRef);
const sanitizedRowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef);
const originalSourceRow = dataRowIdToModelLookup[sourceRowId];
let updatedSourceRow = _extends({}, originalSourceRow);
const targetRow = dataRowIdToModelLookup[target.id];
const groupingRules = getGroupingRules({
sanitizedRowGroupingModel,
columnsLookup
});
for (const groupingRule of groupingRules) {
const colDef = columnsLookup[groupingRule.field];
if (groupingRule.groupingValueSetter && colDef) {
const targetGroupingValue = getCellGroupingCriteria({
row: targetRow,
colDef,
groupingRule,
apiRef
}).key;
updatedSourceRow = groupingRule.groupingValueSetter(targetGroupingValue, updatedSourceRow, colDef, apiRef);
} else {
updatedSourceRow[groupingRule.field] = targetRow[groupingRule.field];
}
}
const updater = new rowReorderUtils.BatchRowUpdater(apiRef, processRowUpdate, onProcessRowUpdateError);
updater.queueUpdate(sourceRowId, originalSourceRow, updatedSourceRow);
const {
successful,
updates
} = await updater.executeAll();
if (successful.length === 0) {
return;
}
const finalSourceRow = updates[0];
apiRef.current.setState(state => {
const updatedSourceChildren = sourceChildren.filter(rowId => rowId !== sourceRowId);
const updatedTree = _extends({}, state.rows.tree);
const removedGroups = new Set();
let rootLevelRemovals = 0;
if (updatedSourceChildren.length === 0) {
removedGroups.add(sourceGroup.id);
rootLevelRemovals = rowReorderUtils.removeEmptyAncestors(sourceGroup.parent, updatedTree, removedGroups);
}
removedGroups.forEach(groupId => {
const group = updatedTree[groupId];
if (group && group.parent && updatedTree[group.parent]) {
const parent = updatedTree[group.parent];
updatedTree[group.parent] = _extends({}, parent, {
children: parent.children.filter(childId => childId !== groupId)
});
}
delete updatedTree[groupId];
});
if (!removedGroups.has(sourceGroup.id)) {
updatedTree[sourceNode.parent] = _extends({}, sourceGroup, {
children: updatedSourceChildren
});
}
const updatedTargetChildren = isLastChild ? [...targetChildren, sourceRowId] : [...targetChildren.slice(0, targetIndex), sourceRowId, ...targetChildren.slice(targetIndex)];
updatedTree[target.parent] = _extends({}, targetGroup, {
children: updatedTargetChildren
});
updatedTree[sourceNode.id] = _extends({}, sourceNode, {
parent: target.parent
});
return _extends({}, state, {
rows: _extends({}, state.rows, {
totalTopLevelRowCount: state.rows.totalTopLevelRowCount - rootLevelRemovals,
tree: updatedTree
})
});
});
apiRef.current.updateRows([finalSourceRow]);
apiRef.current.publishEvent('rowsSet');
}
}
/**
* Handles moving entire groups between different parents.
*/
export class CrossParentGroupOperation extends BaseReorderOperation {
operationType = 'cross-parent-group';
detectOperation(ctx) {
const {
sourceRowId,
placeholderIndex,
sortedFilteredRowIds,
rowTree,
apiRef
} = ctx;
const sourceNode = gridRowNodeSelector(apiRef, sourceRowId);
if (!sourceNode || sourceNode.type === 'footer') {
return null;
}
let targetIndex = placeholderIndex;
let targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[placeholderIndex]);
let isLastChild = false;
if (!targetNode) {
if (placeholderIndex >= sortedFilteredRowIds.length && sortedFilteredRowIds.length > 0) {
targetNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[sortedFilteredRowIds.length - 1]);
targetIndex = sortedFilteredRowIds.length - 1;
isLastChild = true;
} else {
return null;
}
}
let adjustedTargetNode = targetNode;
// Case G adjustment
if (sourceNode.type === 'group' && targetNode.type === 'group' && sourceNode.parent !== targetNode.parent && sourceNode.depth > targetNode.depth) {
let prevIndex = targetIndex - 1;
if (prevIndex < 0) {
return null;
}
let prevNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[prevIndex]);
if (prevNode && prevNode.depth !== sourceNode.depth) {
while (prevNode.depth > sourceNode.depth && prevIndex >= 0) {
prevIndex -= 1;
prevNode = gridRowNodeSelector(apiRef, sortedFilteredRowIds[prevIndex]);
}
}
if (!prevNode || prevNode.type !== 'group' || prevNode.depth !== sourceNode.depth) {
return null;
}
isLastChild = true;
adjustedTargetNode = prevNode;
}
const operationType = rowReorderUtils.determineOperationType(sourceNode, adjustedTargetNode);
if (operationType !== 'cross-parent-group') {
return null;
}
const actualTargetIndex = rowReorderUtils.calculateTargetIndex(sourceNode, adjustedTargetNode, isLastChild, rowTree);
const operation = {
sourceNode,
targetNode: adjustedTargetNode,
actualTargetIndex,
isLastChild,
operationType
};
targetNode = adjustedTargetNode;
if (sourceNode.depth !== targetNode.depth) {
return null;
}
return operation;
}
async executeOperation(operation, ctx) {
const {
sourceNode,
targetNode,
isLastChild
} = operation;
const {
apiRef,
processRowUpdate,
onProcessRowUpdateError
} = ctx;
const tree = gridRowTreeSelector(apiRef);
const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef);
const columnsLookup = gridColumnLookupSelector(apiRef);
const sanitizedRowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef);
const allLeafIds = rowReorderUtils.collectAllLeafDescendants(sourceNode, tree);
if (allLeafIds.length === 0) {
return;
}
const updater = new rowReorderUtils.BatchRowUpdater(apiRef, processRowUpdate, onProcessRowUpdateError);
const groupingRules = getGroupingRules({
sanitizedRowGroupingModel,
columnsLookup
});
const targetParentPath = rowReorderUtils.getNodePathInTree({
id: targetNode.parent,
tree
});
for (const leafId of allLeafIds) {
const originalRow = dataRowIdToModelLookup[leafId];
let updatedRow = _extends({}, originalRow);
for (let depth = 0; depth < targetParentPath.length; depth += 1) {
const pathItem = targetParentPath[depth];
if (pathItem.field) {
const groupingRule = groupingRules.find(rule => rule.field === pathItem.field);
if (groupingRule) {
const colDef = columnsLookup[groupingRule.field];
if (groupingRule.groupingValueSetter && colDef) {
updatedRow = groupingRule.groupingValueSetter(pathItem.key, updatedRow, colDef, apiRef);
} else {
updatedRow[groupingRule.field] = pathItem.key;
}
}
}
}
updater.queueUpdate(leafId, originalRow, updatedRow);
}
const {
successful,
failed,
updates
} = await updater.executeAll();
if (successful.length > 0) {
apiRef.current.setState(state => {
const updatedTree = _extends({}, state.rows.tree);
const treeDepths = _extends({}, state.rows.treeDepths);
let rootLevelRemovals = 0;
if (failed.length === 0) {
const sourceParentNode = updatedTree[sourceNode.parent];
if (!sourceParentNode) {
const targetParentNode = updatedTree[targetNode.parent];
const targetIndex = targetParentNode.children.indexOf(targetNode.id);
const newTargetChildren = [...targetParentNode.children];
if (isLastChild) {
newTargetChildren.push(sourceNode.id);
} else {
newTargetChildren.splice(targetIndex, 0, sourceNode.id);
}
updatedTree[targetNode.parent] = _extends({}, targetParentNode, {
children: newTargetChildren
});
updatedTree[sourceNode.id] = _extends({}, sourceNode, {
parent: targetNode.parent
});
} else {
const updatedSourceParentChildren = sourceParentNode.children.filter(id => id !== sourceNode.id);
if (updatedSourceParentChildren.length === 0) {
const removedGroups = new Set();
removedGroups.add(sourceNode.parent);
const parentOfSourceParent = updatedTree[sourceNode.parent].parent;
if (parentOfSourceParent) {
rootLevelRemovals = rowReorderUtils.removeEmptyAncestors(parentOfSourceParent, updatedTree, removedGroups);
}
removedGroups.forEach(groupId => {
const group = updatedTree[groupId];
if (group && group.parent && updatedTree[group.parent]) {
const parent = updatedTree[group.parent];
updatedTree[group.parent] = _extends({}, parent, {
children: parent.children.filter(childId => childId !== groupId)
});
}
delete updatedTree[groupId];
});
} else {
updatedTree[sourceNode.parent] = _extends({}, sourceParentNode, {
children: updatedSourceParentChildren
});
}
const targetParentNode = updatedTree[targetNode.parent];
const sourceGroupNode = sourceNode;
const existingGroup = sourceGroupNode.groupingKey !== null && sourceGroupNode.groupingField !== null ? rowReorderUtils.findExistingGroupWithSameKey(targetParentNode, sourceGroupNode.groupingKey, sourceGroupNode.groupingField, updatedTree) : null;
if (existingGroup) {
const updatedExistingGroup = _extends({}, existingGroup, {
children: [...existingGroup.children, ...sourceGroupNode.children]
});
updatedTree[existingGroup.id] = updatedExistingGroup;
sourceGroupNode.children.forEach(childId => {
const childNode = updatedTree[childId];
if (childNode) {
updatedTree[childId] = _extends({}, childNode, {
parent: existingGroup.id
});
}
});
delete updatedTree[sourceNode.id];
} else {
const targetIndex = targetParentNode.children.indexOf(targetNode.id);
const newTargetChildren = [...targetParentNode.children];
if (isLastChild) {
newTargetChildren.push(sourceNode.id);
} else {
newTargetChildren.splice(targetIndex, 0, sourceNode.id);
}
updatedTree[targetNode.parent] = _extends({}, targetParentNode, {
children: newTargetChildren
});
updatedTree[sourceNode.id] = _extends({}, sourceNode, {
parent: targetNode.parent
});
}
}
}
return _extends({}, state, {
rows: _extends({}, state.rows, {
totalTopLevelRowCount: state.rows.totalTopLevelRowCount - rootLevelRemovals,
tree: updatedTree,
treeDepths
})
});
});
apiRef.current.updateRows(updates);
apiRef.current.publishEvent('rowsSet');
}
}
}