wip:milestone 0 fixes
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped

This commit is contained in:
2026-03-15 12:35:42 +02:00
parent 6708cf28a7
commit cffdf8af86
61266 changed files with 4511646 additions and 1938 deletions

View File

@@ -0,0 +1,10 @@
import { createSelector, createSelectorMemoized } from '../../../utils/createSelector';
/**
* @category ColumnGrouping
* @ignore - do not document.
*/
export const gridColumnGroupingSelector = state => state.columnGrouping;
export const gridColumnGroupsUnwrappedModelSelector = createSelectorMemoized(gridColumnGroupingSelector, columnGrouping => columnGrouping?.unwrappedGroupingModel ?? {});
export const gridColumnGroupsLookupSelector = createSelectorMemoized(gridColumnGroupingSelector, columnGrouping => columnGrouping?.lookup ?? {});
export const gridColumnGroupsHeaderStructureSelector = createSelectorMemoized(gridColumnGroupingSelector, columnGrouping => columnGrouping?.headerStructure ?? []);
export const gridColumnGroupsHeaderMaxDepthSelector = createSelector(gridColumnGroupingSelector, columnGrouping => columnGrouping?.maxDepth ?? 0);

View File

@@ -0,0 +1,82 @@
import { isLeaf } from '../../../models/gridColumnGrouping';
import { isDeepEqual } from '../../../utils/utils';
// This is the recurrence function that help writing `unwrapGroupingColumnModel()`
const recurrentUnwrapGroupingColumnModel = (columnGroupNode, parents, unwrappedGroupingModelToComplete) => {
if (isLeaf(columnGroupNode)) {
if (unwrappedGroupingModelToComplete[columnGroupNode.field] !== undefined) {
throw new Error([`MUI: columnGroupingModel contains duplicated field`, `column field ${columnGroupNode.field} occurs two times in the grouping model:`, `- ${unwrappedGroupingModelToComplete[columnGroupNode.field].join(' > ')}`, `- ${parents.join(' > ')}`].join('\n'));
}
unwrappedGroupingModelToComplete[columnGroupNode.field] = parents;
return;
}
const {
groupId,
children
} = columnGroupNode;
children.forEach(child => {
recurrentUnwrapGroupingColumnModel(child, [...parents, groupId], unwrappedGroupingModelToComplete);
});
};
/**
* This is a function that provide for each column the array of its parents.
* Parents are ordered from the root to the leaf.
* @param columnGroupingModel The model such as provided in DataGrid props
* @returns An object `{[field]: groupIds}` where `groupIds` is the parents of the column `field`
*/
export const unwrapGroupingColumnModel = columnGroupingModel => {
if (!columnGroupingModel) {
return {};
}
const unwrappedSubTree = {};
columnGroupingModel.forEach(columnGroupNode => {
recurrentUnwrapGroupingColumnModel(columnGroupNode, [], unwrappedSubTree);
});
return unwrappedSubTree;
};
export const getColumnGroupsHeaderStructure = (orderedColumns, unwrappedGroupingModel, pinnedFields) => {
const getParents = field => unwrappedGroupingModel[field] ?? [];
const groupingHeaderStructure = [];
const maxDepth = Math.max(...orderedColumns.map(field => getParents(field).length));
const haveSameParents = (field1, field2, depth) => isDeepEqual(getParents(field1).slice(0, depth + 1), getParents(field2).slice(0, depth + 1));
const haveDifferentContainers = (field1, field2) => {
if (pinnedFields?.left && pinnedFields.left.includes(field1) && !pinnedFields.left.includes(field2)) {
return true;
}
if (pinnedFields?.right && !pinnedFields.right.includes(field1) && pinnedFields.right.includes(field2)) {
return true;
}
return false;
};
for (let depth = 0; depth < maxDepth; depth += 1) {
const depthStructure = orderedColumns.reduce((structure, newField) => {
const groupId = getParents(newField)[depth] ?? null;
if (structure.length === 0) {
return [{
columnFields: [newField],
groupId
}];
}
const lastGroup = structure[structure.length - 1];
const prevField = lastGroup.columnFields[lastGroup.columnFields.length - 1];
const prevGroupId = lastGroup.groupId;
if (prevGroupId !== groupId || !haveSameParents(prevField, newField, depth) ||
// Fix for https://github.com/mui/mui-x/issues/7041
haveDifferentContainers(prevField, newField)) {
// It's a new group
return [...structure, {
columnFields: [newField],
groupId
}];
}
// It extends the previous group
return [...structure.slice(0, structure.length - 1), {
columnFields: [...lastGroup.columnFields, newField],
groupId
}];
}, []);
groupingHeaderStructure.push(depthStructure);
}
return groupingHeaderStructure;
};

View File

@@ -0,0 +1,2 @@
export * from './gridColumnGroupsSelector';
export {};

View File

@@ -0,0 +1,136 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["groupId", "children"];
import * as React from 'react';
import { isLeaf } from '../../../models/gridColumnGrouping';
import { gridColumnGroupsLookupSelector, gridColumnGroupsUnwrappedModelSelector } from './gridColumnGroupsSelector';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { getColumnGroupsHeaderStructure, unwrapGroupingColumnModel } from './gridColumnGroupsUtils';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { gridColumnFieldsSelector, gridVisibleColumnFieldsSelector } from '../columns';
const createGroupLookup = columnGroupingModel => {
let groupLookup = {};
columnGroupingModel.forEach(node => {
if (isLeaf(node)) {
return;
}
const {
groupId,
children
} = node,
other = _objectWithoutPropertiesLoose(node, _excluded);
if (!groupId) {
throw new Error('MUI: An element of the columnGroupingModel does not have either `field` or `groupId`.');
}
if (!children) {
console.warn(`MUI: group groupId=${groupId} has no children.`);
}
const groupParam = _extends({}, other, {
groupId
});
const subTreeLookup = createGroupLookup(children);
if (subTreeLookup[groupId] !== undefined || groupLookup[groupId] !== undefined) {
throw new Error(`MUI: The groupId ${groupId} is used multiple times in the columnGroupingModel.`);
}
groupLookup = _extends({}, groupLookup, subTreeLookup, {
[groupId]: groupParam
});
});
return _extends({}, groupLookup);
};
export const columnGroupsStateInitializer = (state, props, apiRef) => {
if (!props.experimentalFeatures?.columnGrouping) {
return state;
}
const columnFields = gridColumnFieldsSelector(apiRef);
const visibleColumnFields = gridVisibleColumnFieldsSelector(apiRef);
const groupLookup = createGroupLookup(props.columnGroupingModel ?? []);
const unwrappedGroupingModel = unwrapGroupingColumnModel(props.columnGroupingModel ?? []);
const columnGroupsHeaderStructure = getColumnGroupsHeaderStructure(columnFields, unwrappedGroupingModel,
// @ts-expect-error Move this part to `Pro` package
apiRef.current.state.pinnedColumns ?? {});
const maxDepth = visibleColumnFields.length === 0 ? 0 : Math.max(...visibleColumnFields.map(field => unwrappedGroupingModel[field]?.length ?? 0));
return _extends({}, state, {
columnGrouping: {
lookup: groupLookup,
unwrappedGroupingModel,
headerStructure: columnGroupsHeaderStructure,
maxDepth
}
});
};
/**
* @requires useGridColumns (method, event)
* @requires useGridParamsApi (method)
*/
export const useGridColumnGrouping = (apiRef, props) => {
/**
* API METHODS
*/
const getColumnGroupPath = React.useCallback(field => {
const unwrappedGroupingModel = gridColumnGroupsUnwrappedModelSelector(apiRef);
return unwrappedGroupingModel[field] ?? [];
}, [apiRef]);
const getAllGroupDetails = React.useCallback(() => {
const columnGroupLookup = gridColumnGroupsLookupSelector(apiRef);
return columnGroupLookup;
}, [apiRef]);
const columnGroupingApi = {
unstable_getColumnGroupPath: getColumnGroupPath,
unstable_getAllGroupDetails: getAllGroupDetails
};
useGridApiMethod(apiRef, columnGroupingApi, 'public');
const handleColumnIndexChange = React.useCallback(() => {
const unwrappedGroupingModel = unwrapGroupingColumnModel(props.columnGroupingModel ?? []);
apiRef.current.setState(state => {
const orderedFields = state.columns?.orderedFields ?? [];
// @ts-expect-error Move this logic to `Pro` package
const pinnedColumns = state.pinnedColumns ?? {};
const columnGroupsHeaderStructure = getColumnGroupsHeaderStructure(orderedFields, unwrappedGroupingModel, pinnedColumns);
return _extends({}, state, {
columnGrouping: _extends({}, state.columnGrouping, {
headerStructure: columnGroupsHeaderStructure
})
});
});
}, [apiRef, props.columnGroupingModel]);
const updateColumnGroupingState = React.useCallback(columnGroupingModel => {
if (!props.experimentalFeatures?.columnGrouping) {
return;
}
// @ts-expect-error Move this logic to `Pro` package
const pinnedColumns = apiRef.current.getPinnedColumns?.() ?? {};
const columnFields = gridColumnFieldsSelector(apiRef);
const visibleColumnFields = gridVisibleColumnFieldsSelector(apiRef);
const groupLookup = createGroupLookup(columnGroupingModel ?? []);
const unwrappedGroupingModel = unwrapGroupingColumnModel(columnGroupingModel ?? []);
const columnGroupsHeaderStructure = getColumnGroupsHeaderStructure(columnFields, unwrappedGroupingModel, pinnedColumns);
const maxDepth = visibleColumnFields.length === 0 ? 0 : Math.max(...visibleColumnFields.map(field => unwrappedGroupingModel[field]?.length ?? 0));
apiRef.current.setState(state => {
return _extends({}, state, {
columnGrouping: {
lookup: groupLookup,
unwrappedGroupingModel,
headerStructure: columnGroupsHeaderStructure,
maxDepth
}
});
});
}, [apiRef, props.experimentalFeatures?.columnGrouping]);
useGridApiEventHandler(apiRef, 'columnIndexChange', handleColumnIndexChange);
useGridApiEventHandler(apiRef, 'columnsChange', () => {
updateColumnGroupingState(props.columnGroupingModel);
});
useGridApiEventHandler(apiRef, 'columnVisibilityModelChange', () => {
updateColumnGroupingState(props.columnGroupingModel);
});
/**
* EFFECTS
*/
React.useEffect(() => {
updateColumnGroupingState(props.columnGroupingModel);
}, [updateColumnGroupingState, props.columnGroupingModel]);
};