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 @@
export {};

View File

@@ -0,0 +1 @@
export const gridRowsMetaSelector = state => state.rowsMeta;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,49 @@
import { createSelector, createSelectorMemoized } from '../../../utils/createSelector';
const gridRowsStateSelector = state => state.rows;
export const gridRowCountSelector = createSelector(gridRowsStateSelector, rows => rows.totalRowCount);
export const gridRowsLoadingSelector = createSelector(gridRowsStateSelector, rows => rows.loading);
export const gridTopLevelRowCountSelector = createSelector(gridRowsStateSelector, rows => rows.totalTopLevelRowCount);
// TODO rows v6: Rename
export const gridRowsLookupSelector = createSelector(gridRowsStateSelector, rows => rows.dataRowIdToModelLookup);
export const gridRowsDataRowIdToIdLookupSelector = createSelector(gridRowsStateSelector, rows => rows.dataRowIdToIdLookup);
export const gridRowTreeSelector = createSelector(gridRowsStateSelector, rows => rows.tree);
export const gridRowGroupingNameSelector = createSelector(gridRowsStateSelector, rows => rows.groupingName);
export const gridRowTreeDepthsSelector = createSelector(gridRowsStateSelector, rows => rows.treeDepths);
export const gridRowMaximumTreeDepthSelector = createSelectorMemoized(gridRowsStateSelector, rows => {
const entries = Object.entries(rows.treeDepths);
if (entries.length === 0) {
return 1;
}
return entries.filter(([, nodeCount]) => nodeCount > 0).map(([depth]) => Number(depth)).sort((a, b) => b - a)[0] + 1;
});
export const gridDataRowIdsSelector = createSelector(gridRowsStateSelector, rows => rows.dataRowIds);
/**
* @ignore - do not document.
*/
export const gridAdditionalRowGroupsSelector = createSelector(gridRowsStateSelector, rows => rows?.additionalRowGroups);
/**
* @ignore - do not document.
*/
export const gridPinnedRowsSelector = createSelectorMemoized(gridAdditionalRowGroupsSelector, additionalRowGroups => {
const rawPinnedRows = additionalRowGroups?.pinnedRows;
return {
bottom: rawPinnedRows?.bottom?.map(rowEntry => ({
id: rowEntry.id,
model: rowEntry.model ?? {}
})),
top: rawPinnedRows?.top?.map(rowEntry => ({
id: rowEntry.id,
model: rowEntry.model ?? {}
}))
};
});
/**
* @ignore - do not document.
*/
export const gridPinnedRowsCountSelector = createSelector(gridPinnedRowsSelector, pinnedRows => {
return (pinnedRows?.top?.length || 0) + (pinnedRows?.bottom?.length || 0);
});

View File

@@ -0,0 +1,284 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import { gridPinnedRowsSelector } from './gridRowsSelector';
import { gridDensityFactorSelector } from '../density/densitySelector';
export const GRID_ROOT_GROUP_ID = `auto-generated-group-node-root`;
export const GRID_ID_AUTOGENERATED = Symbol('mui.id_autogenerated');
export const buildRootGroup = () => ({
type: 'group',
id: GRID_ROOT_GROUP_ID,
depth: -1,
groupingField: null,
groupingKey: null,
isAutoGenerated: true,
children: [],
childrenFromPath: {},
childrenExpanded: true,
parent: null
});
/**
* A helper function to check if the id provided is valid.
* @param {GridRowId} id Id as [[GridRowId]].
* @param {GridRowModel | Partial<GridRowModel>} row Row as [[GridRowModel]].
* @param {string} detailErrorMessage A custom error message to display for invalid IDs
*/
export function checkGridRowIdIsValid(id, row, detailErrorMessage = 'A row was provided without id in the rows prop:') {
if (id == null) {
throw new Error(['MUI: The data grid component requires all rows to have a unique `id` property.', 'Alternatively, you can use the `getRowId` prop to specify a custom id for each row.', detailErrorMessage, JSON.stringify(row)].join('\n'));
}
}
export const getRowIdFromRowModel = (rowModel, getRowId, detailErrorMessage) => {
const id = getRowId ? getRowId(rowModel) : rowModel.id;
checkGridRowIdIsValid(id, rowModel, detailErrorMessage);
return id;
};
export const createRowsInternalCache = ({
rows,
getRowId,
loading,
rowCount
}) => {
const updates = {
type: 'full',
rows: []
};
const dataRowIdToModelLookup = {};
const dataRowIdToIdLookup = {};
for (let i = 0; i < rows.length; i += 1) {
const model = rows[i];
const id = getRowIdFromRowModel(model, getRowId);
dataRowIdToModelLookup[id] = model;
dataRowIdToIdLookup[id] = id;
updates.rows.push(id);
}
return {
rowsBeforePartialUpdates: rows,
loadingPropBeforePartialUpdates: loading,
rowCountPropBeforePartialUpdates: rowCount,
updates,
dataRowIdToIdLookup,
dataRowIdToModelLookup
};
};
export const getTopLevelRowCount = ({
tree,
rowCountProp = 0
}) => {
const rootGroupNode = tree[GRID_ROOT_GROUP_ID];
return Math.max(rowCountProp, rootGroupNode.children.length + (rootGroupNode.footerId == null ? 0 : 1));
};
export const getRowsStateFromCache = ({
apiRef,
rowCountProp = 0,
loadingProp,
previousTree,
previousTreeDepths
}) => {
const cache = apiRef.current.caches.rows;
// 1. Apply the "rowTreeCreation" family processing.
const {
tree: unProcessedTree,
treeDepths: unProcessedTreeDepths,
dataRowIds: unProcessedDataRowIds,
groupingName
} = apiRef.current.applyStrategyProcessor('rowTreeCreation', {
previousTree,
previousTreeDepths,
updates: cache.updates,
dataRowIdToIdLookup: cache.dataRowIdToIdLookup,
dataRowIdToModelLookup: cache.dataRowIdToModelLookup
});
// 2. Apply the "hydrateRows" pipe-processing.
const groupingParamsWithHydrateRows = apiRef.current.unstable_applyPipeProcessors('hydrateRows', {
tree: unProcessedTree,
treeDepths: unProcessedTreeDepths,
dataRowIdToIdLookup: cache.dataRowIdToIdLookup,
dataRowIds: unProcessedDataRowIds,
dataRowIdToModelLookup: cache.dataRowIdToModelLookup
});
// 3. Reset the cache updates
apiRef.current.caches.rows.updates = {
type: 'partial',
actions: {
insert: [],
modify: [],
remove: []
},
idToActionLookup: {}
};
return _extends({}, groupingParamsWithHydrateRows, {
totalRowCount: Math.max(rowCountProp, groupingParamsWithHydrateRows.dataRowIds.length),
totalTopLevelRowCount: getTopLevelRowCount({
tree: groupingParamsWithHydrateRows.tree,
rowCountProp
}),
groupingName,
loading: loadingProp
});
};
export const isAutoGeneratedRow = rowNode => rowNode.type === 'skeletonRow' || rowNode.type === 'footer' || rowNode.type === 'group' && rowNode.isAutoGenerated || rowNode.type === 'pinnedRow' && rowNode.isAutoGenerated;
export const getTreeNodeDescendants = (tree, parentId, skipAutoGeneratedRows) => {
const node = tree[parentId];
if (node.type !== 'group') {
return [];
}
const validDescendants = [];
for (let i = 0; i < node.children.length; i += 1) {
const child = node.children[i];
if (!skipAutoGeneratedRows || !isAutoGeneratedRow(tree[child])) {
validDescendants.push(child);
}
const childDescendants = getTreeNodeDescendants(tree, child, skipAutoGeneratedRows);
for (let j = 0; j < childDescendants.length; j += 1) {
validDescendants.push(childDescendants[j]);
}
}
if (!skipAutoGeneratedRows && node.footerId != null) {
validDescendants.push(node.footerId);
}
return validDescendants;
};
export const updateCacheWithNewRows = ({
previousCache,
getRowId,
updates
}) => {
if (previousCache.updates.type === 'full') {
throw new Error('MUI: Unable to prepare a partial update if a full update is not applied yet');
}
// Remove duplicate updates.
// A server can batch updates, and send several updates for the same row in one fn call.
const uniqueUpdates = new Map();
updates.forEach(update => {
const id = getRowIdFromRowModel(update, getRowId, 'A row was provided without id when calling updateRows():');
if (uniqueUpdates.has(id)) {
uniqueUpdates.set(id, _extends({}, uniqueUpdates.get(id), update));
} else {
uniqueUpdates.set(id, update);
}
});
const partialUpdates = {
type: 'partial',
actions: {
insert: [...(previousCache.updates.actions.insert ?? [])],
modify: [...(previousCache.updates.actions.modify ?? [])],
remove: [...(previousCache.updates.actions.remove ?? [])]
},
idToActionLookup: _extends({}, previousCache.updates.idToActionLookup)
};
const dataRowIdToModelLookup = _extends({}, previousCache.dataRowIdToModelLookup);
const dataRowIdToIdLookup = _extends({}, previousCache.dataRowIdToIdLookup);
const alreadyAppliedActionsToRemove = {
insert: {},
modify: {},
remove: {}
};
// Depending on the action already applied to the data row,
// We might want drop the already-applied-update.
// For instance:
// - if you delete then insert, then you don't want to apply the deletion in the tree.
// - if you insert, then modify, then you just want to apply the insertion in the tree.
uniqueUpdates.forEach((partialRow, id) => {
const actionAlreadyAppliedToRow = partialUpdates.idToActionLookup[id];
// Action === "delete"
// eslint-disable-next-line no-underscore-dangle
if (partialRow._action === 'delete') {
// If the data row has been removed since the last state update,
// Then do nothing.
if (actionAlreadyAppliedToRow === 'remove' || !dataRowIdToModelLookup[id]) {
return;
}
// If the data row has been inserted / modified since the last state update,
// Then drop this "insert" / "modify" update.
if (actionAlreadyAppliedToRow != null) {
alreadyAppliedActionsToRemove[actionAlreadyAppliedToRow][id] = true;
}
// Remove the data row from the lookups and add it to the "delete" update.
partialUpdates.actions.remove.push(id);
delete dataRowIdToModelLookup[id];
delete dataRowIdToIdLookup[id];
return;
}
const oldRow = dataRowIdToModelLookup[id];
// Action === "modify"
if (oldRow) {
// If the data row has been removed since the last state update,
// Then drop this "remove" update and add it to the "modify" update instead.
if (actionAlreadyAppliedToRow === 'remove') {
alreadyAppliedActionsToRemove.remove[id] = true;
partialUpdates.actions.modify.push(id);
}
// If the date has not been inserted / modified since the last state update,
// Then add it to the "modify" update (if it has been inserted it should just remain "inserted").
else if (actionAlreadyAppliedToRow == null) {
partialUpdates.actions.modify.push(id);
}
// Update the data row lookups.
dataRowIdToModelLookup[id] = _extends({}, oldRow, partialRow);
return;
}
// Action === "insert"
// If the data row has been removed since the last state update,
// Then drop the "remove" update and add it to the "insert" update instead.
if (actionAlreadyAppliedToRow === 'remove') {
alreadyAppliedActionsToRemove.remove[id] = true;
partialUpdates.actions.insert.push(id);
}
// If the data row has not been inserted since the last state update,
// Then add it to the "insert" update.
// `actionAlreadyAppliedToRow` can't be equal to "modify", otherwise we would have an `oldRow` above.
else if (actionAlreadyAppliedToRow == null) {
partialUpdates.actions.insert.push(id);
}
// Update the data row lookups.
dataRowIdToModelLookup[id] = partialRow;
dataRowIdToIdLookup[id] = id;
});
const actionTypeWithActionsToRemove = Object.keys(alreadyAppliedActionsToRemove);
for (let i = 0; i < actionTypeWithActionsToRemove.length; i += 1) {
const actionType = actionTypeWithActionsToRemove[i];
const idsToRemove = alreadyAppliedActionsToRemove[actionType];
if (Object.keys(idsToRemove).length > 0) {
partialUpdates.actions[actionType] = partialUpdates.actions[actionType].filter(id => !idsToRemove[id]);
}
}
return {
dataRowIdToModelLookup,
dataRowIdToIdLookup,
updates: partialUpdates,
rowsBeforePartialUpdates: previousCache.rowsBeforePartialUpdates,
loadingPropBeforePartialUpdates: previousCache.loadingPropBeforePartialUpdates,
rowCountPropBeforePartialUpdates: previousCache.rowCountPropBeforePartialUpdates
};
};
export function calculatePinnedRowsHeight(apiRef) {
const pinnedRows = gridPinnedRowsSelector(apiRef);
const topPinnedRowsHeight = pinnedRows?.top?.reduce((acc, value) => {
acc += apiRef.current.unstable_getRowHeight(value.id);
return acc;
}, 0) || 0;
const bottomPinnedRowsHeight = pinnedRows?.bottom?.reduce((acc, value) => {
acc += apiRef.current.unstable_getRowHeight(value.id);
return acc;
}, 0) || 0;
return {
top: topPinnedRowsHeight,
bottom: bottomPinnedRowsHeight
};
}
export function getMinimalContentHeight(apiRef, rowHeight) {
const densityFactor = gridDensityFactorSelector(apiRef);
return `var(--DataGrid-overlayHeight, ${2 * Math.floor(rowHeight * densityFactor)}px)`;
}

View File

@@ -0,0 +1,4 @@
export * from './gridRowsMetaSelector';
export * from './gridRowsMetaState';
export { gridRowCountSelector, gridRowsLoadingSelector, gridTopLevelRowCountSelector, gridRowsLookupSelector, gridRowsDataRowIdToIdLookupSelector, gridRowTreeSelector, gridRowGroupingNameSelector, gridRowTreeDepthsSelector, gridRowMaximumTreeDepthSelector, gridDataRowIdsSelector } from './gridRowsSelector';
export { GRID_ROOT_GROUP_ID, checkGridRowIdIsValid } from './gridRowsUtils';

View File

@@ -0,0 +1,158 @@
import * as React from 'react';
import { getGridCellElement, getGridColumnHeaderElement, getGridRowElement } from '../../../utils/domUtils';
import { GRID_ID_AUTOGENERATED } from './gridRowsUtils';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridFocusCellSelector, gridTabIndexCellSelector } from '../focus/gridFocusStateSelector';
export class MissingRowIdError extends Error {}
/**
* @requires useGridColumns (method)
* @requires useGridRows (method)
* @requires useGridFocus (state)
* @requires useGridEditing (method)
* TODO: Impossible priority - useGridEditing also needs to be after useGridParamsApi
* TODO: Impossible priority - useGridFocus also needs to be after useGridParamsApi
*/
export function useGridParamsApi(apiRef, props) {
const {
getRowId
} = props;
const getColumnHeaderParams = React.useCallback(field => ({
field,
colDef: apiRef.current.getColumn(field)
}), [apiRef]);
const getRowParams = React.useCallback(id => {
const row = apiRef.current.getRow(id);
if (!row) {
throw new MissingRowIdError(`No row with id #${id} found`);
}
const params = {
id,
columns: apiRef.current.getAllColumns(),
row
};
return params;
}, [apiRef]);
const getBaseCellParams = React.useCallback((id, field) => {
const row = apiRef.current.getRow(id);
const rowNode = apiRef.current.getRowNode(id);
if (!row || !rowNode) {
throw new MissingRowIdError(`No row with id #${id} found`);
}
const cellFocus = gridFocusCellSelector(apiRef);
const cellTabIndex = gridTabIndexCellSelector(apiRef);
const params = {
id,
field,
row,
rowNode,
value: row[field],
colDef: apiRef.current.getColumn(field),
cellMode: apiRef.current.getCellMode(id, field),
api: apiRef.current,
hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id,
tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1
};
return params;
}, [apiRef]);
const getCellParams = React.useCallback((id, field) => {
const colDef = apiRef.current.getColumn(field);
const value = apiRef.current.getCellValue(id, field);
const row = apiRef.current.getRow(id);
const rowNode = apiRef.current.getRowNode(id);
if (!row || !rowNode) {
throw new MissingRowIdError(`No row with id #${id} found`);
}
const cellFocus = gridFocusCellSelector(apiRef);
const cellTabIndex = gridTabIndexCellSelector(apiRef);
const params = {
id,
field,
row,
rowNode,
colDef,
cellMode: apiRef.current.getCellMode(id, field),
hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id,
tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1,
value,
formattedValue: value,
isEditable: false
};
if (colDef && colDef.valueFormatter) {
params.formattedValue = colDef.valueFormatter({
id,
field: params.field,
value: params.value,
api: apiRef.current
});
}
params.isEditable = colDef && apiRef.current.isCellEditable(params);
return params;
}, [apiRef]);
const getCellValue = React.useCallback((id, field) => {
const colDef = apiRef.current.getColumn(field);
if (!colDef || !colDef.valueGetter) {
const rowModel = apiRef.current.getRow(id);
if (!rowModel) {
throw new MissingRowIdError(`No row with id #${id} found`);
}
return rowModel[field];
}
return colDef.valueGetter(getBaseCellParams(id, field));
}, [apiRef, getBaseCellParams]);
const getRowValue = React.useCallback((row, colDef) => {
const id = GRID_ID_AUTOGENERATED in row ? row[GRID_ID_AUTOGENERATED] : getRowId?.(row) ?? row.id;
const field = colDef.field;
if (!colDef || !colDef.valueGetter) {
return row[field];
}
return colDef.valueGetter(getBaseCellParams(id, field));
}, [getBaseCellParams, getRowId]);
const getRowFormattedValue = React.useCallback((row, colDef) => {
const value = getRowValue(row, colDef);
if (!colDef || !colDef.valueFormatter) {
return value;
}
const id = (getRowId ? getRowId(row) : row.id) ?? row[GRID_ID_AUTOGENERATED];
const field = colDef.field;
return colDef.valueFormatter({
id,
field,
value,
api: apiRef.current
});
}, [apiRef, getRowId, getRowValue]);
const getColumnHeaderElement = React.useCallback(field => {
if (!apiRef.current.rootElementRef.current) {
return null;
}
return getGridColumnHeaderElement(apiRef.current.rootElementRef.current, field);
}, [apiRef]);
const getRowElement = React.useCallback(id => {
if (!apiRef.current.rootElementRef.current) {
return null;
}
return getGridRowElement(apiRef.current.rootElementRef.current, id);
}, [apiRef]);
const getCellElement = React.useCallback((id, field) => {
if (!apiRef.current.rootElementRef.current) {
return null;
}
return getGridCellElement(apiRef.current.rootElementRef.current, {
id,
field
});
}, [apiRef]);
const paramsApi = {
getCellValue,
getCellParams,
getCellElement,
getRowValue,
getRowFormattedValue,
getRowParams,
getRowElement,
getColumnHeaderParams,
getColumnHeaderElement
};
useGridApiMethod(apiRef, paramsApi, 'public');
}

View File

@@ -0,0 +1,442 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridRowCountSelector, gridRowsLookupSelector, gridRowTreeSelector, gridRowGroupingNameSelector, gridRowTreeDepthsSelector, gridDataRowIdsSelector, gridRowsDataRowIdToIdLookupSelector, gridRowMaximumTreeDepthSelector } from './gridRowsSelector';
import { useTimeout } from '../../utils/useTimeout';
import { GridSignature, useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { useGridVisibleRows } from '../../utils/useGridVisibleRows';
import { gridSortedRowIdsSelector } from '../sorting/gridSortingSelector';
import { gridFilteredRowsLookupSelector } from '../filter/gridFilterSelector';
import { getTreeNodeDescendants, createRowsInternalCache, getRowsStateFromCache, isAutoGeneratedRow, GRID_ROOT_GROUP_ID, GRID_ID_AUTOGENERATED, updateCacheWithNewRows, getTopLevelRowCount, getRowIdFromRowModel } from './gridRowsUtils';
import { useGridRegisterPipeApplier } from '../../core/pipeProcessing';
export const rowsStateInitializer = (state, props, apiRef) => {
apiRef.current.caches.rows = createRowsInternalCache({
rows: props.rows,
getRowId: props.getRowId,
loading: props.loading,
rowCount: props.rowCount
});
return _extends({}, state, {
rows: getRowsStateFromCache({
apiRef,
rowCountProp: props.rowCount,
loadingProp: props.loading,
previousTree: null,
previousTreeDepths: null
})
});
};
export const useGridRows = (apiRef, props) => {
if (process.env.NODE_ENV !== 'production') {
try {
// Freeze the `rows` prop so developers have a fast failure if they try to use Array.prototype.push().
Object.freeze(props.rows);
} catch (error) {
// Sometimes, it's impossible to freeze, so we give up on it.
}
}
const logger = useGridLogger(apiRef, 'useGridRows');
const currentPage = useGridVisibleRows(apiRef, props);
const lastUpdateMs = React.useRef(Date.now());
const timeout = useTimeout();
const getRow = React.useCallback(id => {
const model = gridRowsLookupSelector(apiRef)[id];
if (model) {
return model;
}
const node = apiRef.current.getRowNode(id);
if (node && isAutoGeneratedRow(node)) {
return {
[GRID_ID_AUTOGENERATED]: id
};
}
return null;
}, [apiRef]);
const getRowIdProp = props.getRowId;
const getRowId = React.useCallback(row => {
if (GRID_ID_AUTOGENERATED in row) {
return row[GRID_ID_AUTOGENERATED];
}
if (getRowIdProp) {
return getRowIdProp(row);
}
return row.id;
}, [getRowIdProp]);
const lookup = React.useMemo(() => currentPage.rows.reduce((acc, {
id
}, index) => {
acc[id] = index;
return acc;
}, {}), [currentPage.rows]);
const throttledRowsChange = React.useCallback(({
cache,
throttle
}) => {
const run = () => {
lastUpdateMs.current = Date.now();
apiRef.current.setState(state => _extends({}, state, {
rows: getRowsStateFromCache({
apiRef,
rowCountProp: props.rowCount,
loadingProp: props.loading,
previousTree: gridRowTreeSelector(apiRef),
previousTreeDepths: gridRowTreeDepthsSelector(apiRef)
})
}));
apiRef.current.publishEvent('rowsSet');
apiRef.current.forceUpdate();
};
timeout.clear();
apiRef.current.caches.rows = cache;
if (!throttle) {
run();
return;
}
const throttleRemainingTimeMs = props.throttleRowsMs - (Date.now() - lastUpdateMs.current);
if (throttleRemainingTimeMs > 0) {
timeout.start(throttleRemainingTimeMs, run);
return;
}
run();
}, [props.throttleRowsMs, props.rowCount, props.loading, apiRef, timeout]);
/**
* API METHODS
*/
const setRows = React.useCallback(rows => {
logger.debug(`Updating all rows, new length ${rows.length}`);
const cache = createRowsInternalCache({
rows,
getRowId: props.getRowId,
loading: props.loading,
rowCount: props.rowCount
});
const prevCache = apiRef.current.caches.rows;
cache.rowsBeforePartialUpdates = prevCache.rowsBeforePartialUpdates;
throttledRowsChange({
cache,
throttle: true
});
}, [logger, props.getRowId, props.loading, props.rowCount, throttledRowsChange, apiRef]);
const updateRows = React.useCallback(updates => {
if (props.signature === GridSignature.DataGrid && updates.length > 1) {
throw new Error(["MUI: You can't update several rows at once in `apiRef.current.updateRows` on the DataGrid.", 'You need to upgrade to DataGridPro or DataGridPremium component to unlock this feature.'].join('\n'));
}
const nonPinnedRowsUpdates = [];
updates.forEach(update => {
const id = getRowIdFromRowModel(update, props.getRowId, 'A row was provided without id when calling updateRows():');
const rowNode = apiRef.current.getRowNode(id);
if (rowNode?.type === 'pinnedRow') {
// @ts-ignore because otherwise `release:build` doesn't work
const pinnedRowsCache = apiRef.current.caches.pinnedRows;
const prevModel = pinnedRowsCache.idLookup[id];
if (prevModel) {
pinnedRowsCache.idLookup[id] = _extends({}, prevModel, update);
}
} else {
nonPinnedRowsUpdates.push(update);
}
});
const cache = updateCacheWithNewRows({
updates: nonPinnedRowsUpdates,
getRowId: props.getRowId,
previousCache: apiRef.current.caches.rows
});
throttledRowsChange({
cache,
throttle: true
});
}, [props.signature, props.getRowId, throttledRowsChange, apiRef]);
const getRowModels = React.useCallback(() => {
const dataRows = gridDataRowIdsSelector(apiRef);
const idRowsLookup = gridRowsLookupSelector(apiRef);
return new Map(dataRows.map(id => [id, idRowsLookup[id] ?? {}]));
}, [apiRef]);
const getRowsCount = React.useCallback(() => gridRowCountSelector(apiRef), [apiRef]);
const getAllRowIds = React.useCallback(() => gridDataRowIdsSelector(apiRef), [apiRef]);
const getRowIndexRelativeToVisibleRows = React.useCallback(id => lookup[id], [lookup]);
const setRowChildrenExpansion = React.useCallback((id, isExpanded) => {
const currentNode = apiRef.current.getRowNode(id);
if (!currentNode) {
throw new Error(`MUI: No row with id #${id} found`);
}
if (currentNode.type !== 'group') {
throw new Error('MUI: Only group nodes can be expanded or collapsed');
}
const newNode = _extends({}, currentNode, {
childrenExpanded: isExpanded
});
apiRef.current.setState(state => {
return _extends({}, state, {
rows: _extends({}, state.rows, {
tree: _extends({}, state.rows.tree, {
[id]: newNode
})
})
});
});
apiRef.current.forceUpdate();
apiRef.current.publishEvent('rowExpansionChange', newNode);
}, [apiRef]);
const getRowNode = React.useCallback(id => gridRowTreeSelector(apiRef)[id] ?? null, [apiRef]);
const getRowGroupChildren = React.useCallback(({
skipAutoGeneratedRows = true,
groupId,
applySorting,
applyFiltering
}) => {
const tree = gridRowTreeSelector(apiRef);
let children;
if (applySorting) {
const groupNode = tree[groupId];
if (!groupNode) {
return [];
}
const sortedRowIds = gridSortedRowIdsSelector(apiRef);
children = [];
const startIndex = sortedRowIds.findIndex(id => id === groupId) + 1;
for (let index = startIndex; index < sortedRowIds.length && tree[sortedRowIds[index]].depth > groupNode.depth; index += 1) {
const id = sortedRowIds[index];
if (!skipAutoGeneratedRows || !isAutoGeneratedRow(tree[id])) {
children.push(id);
}
}
} else {
children = getTreeNodeDescendants(tree, groupId, skipAutoGeneratedRows);
}
if (applyFiltering) {
const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
children = children.filter(childId => filteredRowsLookup[childId] !== false);
}
return children;
}, [apiRef]);
const setRowIndex = React.useCallback((rowId, targetIndex) => {
const node = apiRef.current.getRowNode(rowId);
if (!node) {
throw new Error(`MUI: No row with id #${rowId} found`);
}
if (node.parent !== GRID_ROOT_GROUP_ID) {
throw new Error(`MUI: The row reordering do not support reordering of grouped rows yet`);
}
if (node.type !== 'leaf') {
throw new Error(`MUI: The row reordering do not support reordering of footer or grouping rows`);
}
apiRef.current.setState(state => {
const group = gridRowTreeSelector(state, apiRef.current.instanceId)[GRID_ROOT_GROUP_ID];
const allRows = group.children;
const oldIndex = allRows.findIndex(row => row === rowId);
if (oldIndex === -1 || oldIndex === targetIndex) {
return state;
}
logger.debug(`Moving row ${rowId} to index ${targetIndex}`);
const updatedRows = [...allRows];
updatedRows.splice(targetIndex, 0, updatedRows.splice(oldIndex, 1)[0]);
return _extends({}, state, {
rows: _extends({}, state.rows, {
tree: _extends({}, state.rows.tree, {
[GRID_ROOT_GROUP_ID]: _extends({}, group, {
children: updatedRows
})
})
})
});
});
apiRef.current.publishEvent('rowsSet');
}, [apiRef, logger]);
const replaceRows = React.useCallback((firstRowToRender, newRows) => {
if (props.signature === GridSignature.DataGrid && newRows.length > 1) {
throw new Error(["MUI: You can't replace rows using `apiRef.current.unstable_replaceRows` on the DataGrid.", 'You need to upgrade to DataGridPro or DataGridPremium component to unlock this feature.'].join('\n'));
}
if (newRows.length === 0) {
return;
}
const treeDepth = gridRowMaximumTreeDepthSelector(apiRef);
if (treeDepth > 1) {
throw new Error('`apiRef.current.unstable_replaceRows` is not compatible with tree data and row grouping');
}
const tree = _extends({}, gridRowTreeSelector(apiRef));
const dataRowIdToModelLookup = _extends({}, gridRowsLookupSelector(apiRef));
const dataRowIdToIdLookup = _extends({}, gridRowsDataRowIdToIdLookupSelector(apiRef));
const rootGroup = tree[GRID_ROOT_GROUP_ID];
const rootGroupChildren = [...rootGroup.children];
const seenIds = new Set();
for (let i = 0; i < newRows.length; i += 1) {
const rowModel = newRows[i];
const rowId = getRowIdFromRowModel(rowModel, props.getRowId, 'A row was provided without id when calling replaceRows().');
const [removedRowId] = rootGroupChildren.splice(firstRowToRender + i, 1, rowId);
if (!seenIds.has(removedRowId)) {
delete dataRowIdToModelLookup[removedRowId];
delete dataRowIdToIdLookup[removedRowId];
delete tree[removedRowId];
}
const rowTreeNodeConfig = {
id: rowId,
depth: 0,
parent: GRID_ROOT_GROUP_ID,
type: 'leaf',
groupingKey: null
};
dataRowIdToModelLookup[rowId] = rowModel;
dataRowIdToIdLookup[rowId] = rowId;
tree[rowId] = rowTreeNodeConfig;
seenIds.add(rowId);
}
tree[GRID_ROOT_GROUP_ID] = _extends({}, rootGroup, {
children: rootGroupChildren
});
// Removes potential remaining skeleton rows from the dataRowIds.
const dataRowIds = rootGroupChildren.filter(childId => tree[childId].type === 'leaf');
apiRef.current.caches.rows.dataRowIdToModelLookup = dataRowIdToModelLookup;
apiRef.current.caches.rows.dataRowIdToIdLookup = dataRowIdToIdLookup;
apiRef.current.setState(state => _extends({}, state, {
rows: _extends({}, state.rows, {
dataRowIdToModelLookup,
dataRowIdToIdLookup,
dataRowIds,
tree
})
}));
apiRef.current.publishEvent('rowsSet');
}, [apiRef, props.signature, props.getRowId]);
const rowApi = {
getRow,
getRowId,
getRowModels,
getRowsCount,
getAllRowIds,
setRows,
updateRows,
getRowNode,
getRowIndexRelativeToVisibleRows,
unstable_replaceRows: replaceRows
};
const rowProApi = {
setRowIndex,
setRowChildrenExpansion,
getRowGroupChildren
};
/**
* EVENTS
*/
const groupRows = React.useCallback(() => {
logger.info(`Row grouping pre-processing have changed, regenerating the row tree`);
let cache;
if (apiRef.current.caches.rows.rowsBeforePartialUpdates === props.rows) {
// The `props.rows` did not change since the last row grouping
// We can use the current rows cache which contains the partial updates done recently.
cache = _extends({}, apiRef.current.caches.rows, {
updates: {
type: 'full',
rows: gridDataRowIdsSelector(apiRef)
}
});
} else {
// The `props.rows` has changed since the last row grouping
// We must use the new `props.rows` on the new grouping
// This occurs because this event is triggered before the `useEffect` on the rows when both the grouping pre-processing and the rows changes on the same render
cache = createRowsInternalCache({
rows: props.rows,
getRowId: props.getRowId,
loading: props.loading,
rowCount: props.rowCount
});
}
throttledRowsChange({
cache,
throttle: false
});
}, [logger, apiRef, props.rows, props.getRowId, props.loading, props.rowCount, throttledRowsChange]);
const handleStrategyProcessorChange = React.useCallback(methodName => {
if (methodName === 'rowTreeCreation') {
groupRows();
}
}, [groupRows]);
const handleStrategyActivityChange = React.useCallback(() => {
// `rowTreeCreation` is the only processor ran when `strategyAvailabilityChange` is fired.
// All the other processors listen to `rowsSet` which will be published by the `groupRows` method below.
if (apiRef.current.getActiveStrategy('rowTree') !== gridRowGroupingNameSelector(apiRef)) {
groupRows();
}
}, [apiRef, groupRows]);
useGridApiEventHandler(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange);
useGridApiEventHandler(apiRef, 'strategyAvailabilityChange', handleStrategyActivityChange);
/**
* APPLIERS
*/
const applyHydrateRowsProcessor = React.useCallback(() => {
apiRef.current.setState(state => {
const response = apiRef.current.unstable_applyPipeProcessors('hydrateRows', {
tree: gridRowTreeSelector(state, apiRef.current.instanceId),
treeDepths: gridRowTreeDepthsSelector(state, apiRef.current.instanceId),
dataRowIds: gridDataRowIdsSelector(state, apiRef.current.instanceId),
dataRowIdToModelLookup: gridRowsLookupSelector(state, apiRef.current.instanceId),
dataRowIdToIdLookup: gridRowsDataRowIdToIdLookupSelector(state, apiRef.current.instanceId)
});
return _extends({}, state, {
rows: _extends({}, state.rows, response, {
totalTopLevelRowCount: getTopLevelRowCount({
tree: response.tree,
rowCountProp: props.rowCount
})
})
});
});
apiRef.current.publishEvent('rowsSet');
apiRef.current.forceUpdate();
}, [apiRef, props.rowCount]);
useGridRegisterPipeApplier(apiRef, 'hydrateRows', applyHydrateRowsProcessor);
useGridApiMethod(apiRef, rowApi, 'public');
useGridApiMethod(apiRef, rowProApi, props.signature === GridSignature.DataGrid ? 'private' : 'public');
// The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridRows`
// As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
const areNewRowsAlreadyInState = apiRef.current.caches.rows.rowsBeforePartialUpdates === props.rows;
const isNewLoadingAlreadyInState = apiRef.current.caches.rows.loadingPropBeforePartialUpdates === props.loading;
const isNewRowCountAlreadyInState = apiRef.current.caches.rows.rowCountPropBeforePartialUpdates === props.rowCount;
// The new rows have already been applied (most likely in the `'rowGroupsPreProcessingChange'` listener)
if (areNewRowsAlreadyInState) {
// If the loading prop has changed, we need to update its value in the state because it won't be done by `throttledRowsChange`
if (!isNewLoadingAlreadyInState) {
apiRef.current.setState(state => _extends({}, state, {
rows: _extends({}, state.rows, {
loading: props.loading
})
}));
apiRef.current.caches.rows.loadingPropBeforePartialUpdates = props.loading;
apiRef.current.forceUpdate();
}
if (!isNewRowCountAlreadyInState) {
apiRef.current.setState(state => _extends({}, state, {
rows: _extends({}, state.rows, {
totalRowCount: Math.max(props.rowCount || 0, state.rows.totalRowCount),
totalTopLevelRowCount: Math.max(props.rowCount || 0, state.rows.totalTopLevelRowCount)
})
}));
apiRef.current.caches.rows.rowCountPropBeforePartialUpdates = props.rowCount;
apiRef.current.forceUpdate();
}
return;
}
logger.debug(`Updating all rows, new length ${props.rows.length}`);
throttledRowsChange({
cache: createRowsInternalCache({
rows: props.rows,
getRowId: props.getRowId,
loading: props.loading,
rowCount: props.rowCount
}),
throttle: false
});
}, [props.rows, props.rowCount, props.getRowId, props.loading, logger, throttledRowsChange, apiRef]);
};

View File

@@ -0,0 +1,226 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { unstable_debounce as debounce, unstable_capitalize as capitalize } from '@mui/utils';
import { useGridVisibleRows } from '../../utils/useGridVisibleRows';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridSelector } from '../../utils/useGridSelector';
import { gridDensityFactorSelector } from '../density/densitySelector';
import { gridFilterModelSelector } from '../filter/gridFilterSelector';
import { gridPaginationSelector } from '../pagination/gridPaginationSelector';
import { gridSortModelSelector } from '../sorting/gridSortingSelector';
import { useGridRegisterPipeApplier } from '../../core/pipeProcessing';
import { gridPinnedRowsSelector } from './gridRowsSelector';
import { DATA_GRID_PROPS_DEFAULT_VALUES } from '../../../DataGrid/useDataGridProps';
export const rowsMetaStateInitializer = state => _extends({}, state, {
rowsMeta: {
currentPageTotalHeight: 0,
positions: []
}
});
let warnedOnceInvalidRowHeight = false;
const getValidRowHeight = (rowHeightProp, defaultRowHeight, warningMessage) => {
if (typeof rowHeightProp === 'number' && rowHeightProp > 0) {
return rowHeightProp;
}
if (process.env.NODE_ENV !== 'production' && !warnedOnceInvalidRowHeight && typeof rowHeightProp !== 'undefined' && rowHeightProp !== null) {
console.warn(warningMessage);
warnedOnceInvalidRowHeight = true;
}
return defaultRowHeight;
};
const rowHeightWarning = [`MUI: The \`rowHeight\` prop should be a number greater than 0.`, `The default value will be used instead.`].join('\n');
const getRowHeightWarning = [`MUI: The \`getRowHeight\` prop should return a number greater than 0 or 'auto'.`, `The default value will be used instead.`].join('\n');
/**
* @requires useGridPageSize (method)
* @requires useGridPage (method)
*/
export const useGridRowsMeta = (apiRef, props) => {
const {
getRowHeight: getRowHeightProp,
getRowSpacing,
getEstimatedRowHeight
} = props;
const rowsHeightLookup = React.useRef(Object.create(null));
// Inspired by https://github.com/bvaughn/react-virtualized/blob/master/source/Grid/utils/CellSizeAndPositionManager.js
const lastMeasuredRowIndex = React.useRef(-1);
const hasRowWithAutoHeight = React.useRef(false);
const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector);
const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
const paginationState = useGridSelector(apiRef, gridPaginationSelector);
const sortModel = useGridSelector(apiRef, gridSortModelSelector);
const currentPage = useGridVisibleRows(apiRef, props);
const pinnedRows = useGridSelector(apiRef, gridPinnedRowsSelector);
const validRowHeight = getValidRowHeight(props.rowHeight, DATA_GRID_PROPS_DEFAULT_VALUES.rowHeight, rowHeightWarning);
const rowHeight = Math.floor(validRowHeight * densityFactor);
const hydrateRowsMeta = React.useCallback(() => {
hasRowWithAutoHeight.current = false;
const calculateRowProcessedSizes = row => {
if (!rowsHeightLookup.current[row.id]) {
rowsHeightLookup.current[row.id] = {
sizes: {
baseCenter: rowHeight
},
isResized: false,
autoHeight: false,
needsFirstMeasurement: true // Assume all rows will need to be measured by default
};
}
const {
isResized,
needsFirstMeasurement,
sizes
} = rowsHeightLookup.current[row.id];
let baseRowHeight = typeof rowHeight === 'number' && rowHeight > 0 ? rowHeight : 52;
const existingBaseRowHeight = sizes.baseCenter;
if (isResized) {
// Do not recalculate resized row height and use the value from the lookup
baseRowHeight = existingBaseRowHeight;
} else if (getRowHeightProp) {
const rowHeightFromUser = getRowHeightProp(_extends({}, row, {
densityFactor
}));
if (rowHeightFromUser === 'auto') {
if (needsFirstMeasurement) {
const estimatedRowHeight = getEstimatedRowHeight ? getEstimatedRowHeight(_extends({}, row, {
densityFactor
})) : rowHeight;
// If the row was not measured yet use the estimated row height
baseRowHeight = estimatedRowHeight ?? rowHeight;
} else {
baseRowHeight = existingBaseRowHeight;
}
hasRowWithAutoHeight.current = true;
rowsHeightLookup.current[row.id].autoHeight = true;
} else {
// Default back to base rowHeight if getRowHeight returns invalid value.
baseRowHeight = getValidRowHeight(rowHeightFromUser, rowHeight, getRowHeightWarning);
rowsHeightLookup.current[row.id].needsFirstMeasurement = false;
rowsHeightLookup.current[row.id].autoHeight = false;
}
} else {
rowsHeightLookup.current[row.id].needsFirstMeasurement = false;
}
const initialHeights = {};
/* eslint-disable-next-line no-restricted-syntax */
for (const key in sizes) {
if (/^base[A-Z]/.test(key)) {
initialHeights[key] = sizes[key];
}
}
initialHeights.baseCenter = baseRowHeight;
if (getRowSpacing) {
const indexRelativeToCurrentPage = apiRef.current.getRowIndexRelativeToVisibleRows(row.id);
const spacing = getRowSpacing(_extends({}, row, {
isFirstVisible: indexRelativeToCurrentPage === 0,
isLastVisible: indexRelativeToCurrentPage === currentPage.rows.length - 1,
indexRelativeToCurrentPage
}));
initialHeights.spacingTop = spacing.top ?? 0;
initialHeights.spacingBottom = spacing.bottom ?? 0;
}
const processedSizes = apiRef.current.unstable_applyPipeProcessors('rowHeight', initialHeights, row);
rowsHeightLookup.current[row.id].sizes = processedSizes;
return processedSizes;
};
const positions = [];
const currentPageTotalHeight = currentPage.rows.reduce((acc, row) => {
positions.push(acc);
let maximumBaseSize = 0;
let otherSizes = 0;
const processedSizes = calculateRowProcessedSizes(row);
/* eslint-disable-next-line no-restricted-syntax, guard-for-in */
for (const key in processedSizes) {
const value = processedSizes[key];
if (/^base[A-Z]/.test(key)) {
maximumBaseSize = value > maximumBaseSize ? value : maximumBaseSize;
} else {
otherSizes += value;
}
}
return acc + maximumBaseSize + otherSizes;
}, 0);
pinnedRows?.top?.forEach(row => {
calculateRowProcessedSizes(row);
});
pinnedRows?.bottom?.forEach(row => {
calculateRowProcessedSizes(row);
});
apiRef.current.setState(state => {
return _extends({}, state, {
rowsMeta: {
currentPageTotalHeight,
positions
}
});
});
if (!hasRowWithAutoHeight.current) {
// No row has height=auto, so all rows are already measured
lastMeasuredRowIndex.current = Infinity;
}
apiRef.current.forceUpdate();
}, [apiRef, currentPage.rows, rowHeight, getRowHeightProp, getRowSpacing, getEstimatedRowHeight, pinnedRows, densityFactor]);
const getRowHeight = React.useCallback(rowId => {
const height = rowsHeightLookup.current[rowId];
return height ? height.sizes.baseCenter : rowHeight;
}, [rowHeight]);
const getRowInternalSizes = rowId => rowsHeightLookup.current[rowId]?.sizes;
const setRowHeight = React.useCallback((id, height) => {
rowsHeightLookup.current[id].sizes.baseCenter = height;
rowsHeightLookup.current[id].isResized = true;
rowsHeightLookup.current[id].needsFirstMeasurement = false;
hydrateRowsMeta();
}, [hydrateRowsMeta]);
const debouncedHydrateRowsMeta = React.useMemo(() => debounce(hydrateRowsMeta, props.rowPositionsDebounceMs), [hydrateRowsMeta, props.rowPositionsDebounceMs]);
const storeMeasuredRowHeight = React.useCallback((id, height, position) => {
if (!rowsHeightLookup.current[id] || !rowsHeightLookup.current[id].autoHeight) {
return;
}
// Only trigger hydration if the value is different, otherwise we trigger a loop
const needsHydration = rowsHeightLookup.current[id].sizes[`base${capitalize(position)}`] !== height;
rowsHeightLookup.current[id].needsFirstMeasurement = false;
rowsHeightLookup.current[id].sizes[`base${capitalize(position)}`] = height;
if (needsHydration) {
debouncedHydrateRowsMeta();
}
}, [debouncedHydrateRowsMeta]);
const rowHasAutoHeight = React.useCallback(id => {
return rowsHeightLookup.current[id]?.autoHeight || false;
}, []);
const getLastMeasuredRowIndex = React.useCallback(() => {
return lastMeasuredRowIndex.current;
}, []);
const setLastMeasuredRowIndex = React.useCallback(index => {
if (hasRowWithAutoHeight.current && index > lastMeasuredRowIndex.current) {
lastMeasuredRowIndex.current = index;
}
}, []);
const resetRowHeights = React.useCallback(() => {
rowsHeightLookup.current = {};
hydrateRowsMeta();
}, [hydrateRowsMeta]);
// The effect is used to build the rows meta data - currentPageTotalHeight and positions.
// Because of variable row height this is needed for the virtualization
React.useEffect(() => {
hydrateRowsMeta();
}, [rowHeight, filterModel, paginationState, sortModel, hydrateRowsMeta]);
useGridRegisterPipeApplier(apiRef, 'rowHeight', hydrateRowsMeta);
const rowsMetaApi = {
unstable_setLastMeasuredRowIndex: setLastMeasuredRowIndex,
unstable_getRowHeight: getRowHeight,
unstable_getRowInternalSizes: getRowInternalSizes,
unstable_setRowHeight: setRowHeight,
unstable_storeRowHeightMeasurement: storeMeasuredRowHeight,
resetRowHeights
};
const rowsMetaPrivateApi = {
getLastMeasuredRowIndex,
rowHasAutoHeight
};
useGridApiMethod(apiRef, rowsMetaApi, 'public');
useGridApiMethod(apiRef, rowsMetaPrivateApi, 'private');
};

View File

@@ -0,0 +1,81 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import { GRID_DEFAULT_STRATEGY, useGridRegisterStrategyProcessor } from '../../core/strategyProcessing';
import { buildRootGroup, GRID_ROOT_GROUP_ID } from './gridRowsUtils';
const createFlatRowTree = rows => {
const tree = {
[GRID_ROOT_GROUP_ID]: _extends({}, buildRootGroup(), {
children: rows
})
};
for (let i = 0; i < rows.length; i += 1) {
const rowId = rows[i];
tree[rowId] = {
id: rowId,
depth: 0,
parent: GRID_ROOT_GROUP_ID,
type: 'leaf',
groupingKey: null
};
}
return {
groupingName: GRID_DEFAULT_STRATEGY,
tree,
treeDepths: {
0: rows.length
},
dataRowIds: rows
};
};
const updateFlatRowTree = ({
previousTree,
actions
}) => {
const tree = _extends({}, previousTree);
const idsToRemoveFromRootGroup = {};
for (let i = 0; i < actions.remove.length; i += 1) {
const idToDelete = actions.remove[i];
idsToRemoveFromRootGroup[idToDelete] = true;
delete tree[idToDelete];
}
for (let i = 0; i < actions.insert.length; i += 1) {
const idToInsert = actions.insert[i];
tree[idToInsert] = {
id: idToInsert,
depth: 0,
parent: GRID_ROOT_GROUP_ID,
type: 'leaf',
groupingKey: null
};
}
// TODO rows v6: Support row unpinning
const rootGroup = tree[GRID_ROOT_GROUP_ID];
let rootGroupChildren = [...rootGroup.children, ...actions.insert];
if (Object.values(idsToRemoveFromRootGroup).length) {
rootGroupChildren = rootGroupChildren.filter(id => !idsToRemoveFromRootGroup[id]);
}
tree[GRID_ROOT_GROUP_ID] = _extends({}, rootGroup, {
children: rootGroupChildren
});
return {
groupingName: GRID_DEFAULT_STRATEGY,
tree,
treeDepths: {
0: rootGroupChildren.length
},
dataRowIds: rootGroupChildren
};
};
const flatRowTreeCreationMethod = params => {
if (params.updates.type === 'full') {
return createFlatRowTree(params.updates.rows);
}
return updateFlatRowTree({
previousTree: params.previousTree,
actions: params.updates.actions
});
};
export const useGridRowsPreProcessors = apiRef => {
useGridRegisterStrategyProcessor(apiRef, GRID_DEFAULT_STRATEGY, 'rowTreeCreation', flatRowTreeCreationMethod);
};