@mui/x-data-grid
Version:
The Community plan edition of the Data Grid components (MUI X).
280 lines (275 loc) • 11.9 kB
JavaScript
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useGridRowSpanning = exports.rowSpanningStateInitializer = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _useLazyRef = _interopRequireDefault(require("@mui/utils/useLazyRef"));
var _constants = require("../../../internals/constants");
var _gridColumnsSelector = require("../columns/gridColumnsSelector");
var _useGridVisibleRows = require("../../utils/useGridVisibleRows");
var _gridVirtualizationSelectors = require("../virtualization/gridVirtualizationSelectors");
var _gridRowSpanningUtils = require("./gridRowSpanningUtils");
var _gridCheckboxSelectionColDef = require("../../../colDef/gridCheckboxSelectionColDef");
var _useGridApiEventHandler = require("../../utils/useGridApiEventHandler");
var _utils = require("../../../utils/utils");
var _pagination = require("../pagination");
var _gridRowsSelector = require("./gridRowsSelector");
const EMPTY_STATE = {
spannedCells: {},
hiddenCells: {},
hiddenCellOriginMap: {}
};
const EMPTY_RANGE = {
firstRowIndex: 0,
lastRowIndex: 0
};
const skippedFields = new Set([_gridCheckboxSelectionColDef.GRID_CHECKBOX_SELECTION_FIELD, '__reorder__', _constants.GRID_DETAIL_PANEL_TOGGLE_FIELD]);
/**
* Default number of rows to process during state initialization to avoid flickering.
* Number `20` is arbitrarily chosen to be large enough to cover most of the cases without
* compromising performance.
*/
const DEFAULT_ROWS_TO_PROCESS = 20;
const computeRowSpanningState = (apiRef, colDefs, visibleRows, range, rangeToProcess, resetState, processedRange) => {
const spannedCells = resetState ? {} : (0, _extends2.default)({}, apiRef.current.state.rowSpanning.spannedCells);
const hiddenCells = resetState ? {} : (0, _extends2.default)({}, apiRef.current.state.rowSpanning.hiddenCells);
const hiddenCellOriginMap = resetState ? {} : (0, _extends2.default)({}, apiRef.current.state.rowSpanning.hiddenCellOriginMap);
if (resetState) {
processedRange = EMPTY_RANGE;
}
colDefs.forEach(colDef => {
if (skippedFields.has(colDef.field)) {
return;
}
for (let index = rangeToProcess.firstRowIndex; index < rangeToProcess.lastRowIndex; index += 1) {
const row = visibleRows[index];
if (hiddenCells[row.id]?.[colDef.field]) {
continue;
}
const cellValue = (0, _gridRowSpanningUtils.getCellValue)(row.model, colDef, apiRef);
if (cellValue == null) {
continue;
}
let spannedRowId = row.id;
let spannedRowIndex = index;
let rowSpan = 0;
// For first index, also scan in the previous rows to handle the reset state case e.g by sorting
const backwardsHiddenCells = [];
if (index === rangeToProcess.firstRowIndex) {
let prevIndex = index - 1;
let prevRowEntry = visibleRows[prevIndex];
while (prevIndex >= range.firstRowIndex && prevRowEntry && (0, _gridRowSpanningUtils.getCellValue)(prevRowEntry.model, colDef, apiRef) === cellValue) {
const currentRow = visibleRows[prevIndex + 1];
if (hiddenCells[currentRow.id]) {
hiddenCells[currentRow.id][colDef.field] = true;
} else {
hiddenCells[currentRow.id] = {
[colDef.field]: true
};
}
backwardsHiddenCells.push(index);
rowSpan += 1;
spannedRowId = prevRowEntry.id;
spannedRowIndex = prevIndex;
prevIndex -= 1;
prevRowEntry = visibleRows[prevIndex];
}
}
backwardsHiddenCells.forEach(hiddenCellIndex => {
if (hiddenCellOriginMap[hiddenCellIndex]) {
hiddenCellOriginMap[hiddenCellIndex][colDef.field] = spannedRowIndex;
} else {
hiddenCellOriginMap[hiddenCellIndex] = {
[colDef.field]: spannedRowIndex
};
}
});
// Scan the next rows
let relativeIndex = index + 1;
while (relativeIndex <= range.lastRowIndex && visibleRows[relativeIndex] && (0, _gridRowSpanningUtils.getCellValue)(visibleRows[relativeIndex].model, colDef, apiRef) === cellValue) {
const currentRow = visibleRows[relativeIndex];
if (hiddenCells[currentRow.id]) {
hiddenCells[currentRow.id][colDef.field] = true;
} else {
hiddenCells[currentRow.id] = {
[colDef.field]: true
};
}
if (hiddenCellOriginMap[relativeIndex]) {
hiddenCellOriginMap[relativeIndex][colDef.field] = spannedRowIndex;
} else {
hiddenCellOriginMap[relativeIndex] = {
[colDef.field]: spannedRowIndex
};
}
relativeIndex += 1;
rowSpan += 1;
}
if (rowSpan > 0) {
if (spannedCells[spannedRowId]) {
spannedCells[spannedRowId][colDef.field] = rowSpan + 1;
} else {
spannedCells[spannedRowId] = {
[colDef.field]: rowSpan + 1
};
}
}
}
processedRange = {
firstRowIndex: Math.min(processedRange.firstRowIndex, rangeToProcess.firstRowIndex),
lastRowIndex: Math.max(processedRange.lastRowIndex, rangeToProcess.lastRowIndex)
};
});
return {
spannedCells,
hiddenCells,
hiddenCellOriginMap,
processedRange
};
};
const getInitialRangeToProcess = (props, apiRef) => {
const rowCount = (0, _gridRowsSelector.gridDataRowIdsSelector)(apiRef).length;
if (props.pagination) {
const pageSize = (0, _pagination.gridPageSizeSelector)(apiRef);
let paginationLastRowIndex = DEFAULT_ROWS_TO_PROCESS;
if (pageSize > 0) {
paginationLastRowIndex = pageSize - 1;
}
return {
firstRowIndex: 0,
lastRowIndex: Math.min(paginationLastRowIndex, rowCount)
};
}
return {
firstRowIndex: 0,
lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, rowCount)
};
};
/**
* @requires columnsStateInitializer (method) - should be initialized before
* @requires rowsStateInitializer (method) - should be initialized before
* @requires filterStateInitializer (method) - should be initialized before
*/
const rowSpanningStateInitializer = (state, props, apiRef) => {
if (!props.unstable_rowSpanning) {
return (0, _extends2.default)({}, state, {
rowSpanning: EMPTY_STATE
});
}
const rowIds = state.rows.dataRowIds || [];
const orderedFields = state.columns.orderedFields || [];
const dataRowIdToModelLookup = state.rows.dataRowIdToModelLookup;
const columnsLookup = state.columns.lookup;
const isFilteringPending = Boolean(state.filter.filterModel.items.length) || Boolean(state.filter.filterModel.quickFilterValues?.length);
if (!rowIds.length || !orderedFields.length || !dataRowIdToModelLookup || !columnsLookup || isFilteringPending) {
return (0, _extends2.default)({}, state, {
rowSpanning: EMPTY_STATE
});
}
const rangeToProcess = getInitialRangeToProcess(props, apiRef);
const rows = rowIds.map(id => ({
id,
model: dataRowIdToModelLookup[id]
}));
const colDefs = orderedFields.map(field => columnsLookup[field]);
const {
spannedCells,
hiddenCells,
hiddenCellOriginMap
} = computeRowSpanningState(apiRef, colDefs, rows, rangeToProcess, rangeToProcess, true, EMPTY_RANGE);
return (0, _extends2.default)({}, state, {
rowSpanning: {
spannedCells,
hiddenCells,
hiddenCellOriginMap
}
});
};
exports.rowSpanningStateInitializer = rowSpanningStateInitializer;
const useGridRowSpanning = (apiRef, props) => {
const processedRange = (0, _useLazyRef.default)(() => {
return apiRef.current.state.rowSpanning !== EMPTY_STATE ? getInitialRangeToProcess(props, apiRef) : EMPTY_RANGE;
});
const updateRowSpanningState = React.useCallback((renderContext, resetState = false) => {
const {
range,
rows: visibleRows
} = (0, _useGridVisibleRows.getVisibleRows)(apiRef, {
pagination: props.pagination,
paginationMode: props.paginationMode
});
if (range === null || !(0, _gridRowSpanningUtils.isRowContextInitialized)(renderContext)) {
return;
}
if (resetState) {
processedRange.current = EMPTY_RANGE;
}
const rangeToProcess = (0, _gridRowSpanningUtils.getUnprocessedRange)({
firstRowIndex: renderContext.firstRowIndex,
lastRowIndex: Math.min(renderContext.lastRowIndex, range.lastRowIndex + 1)
}, processedRange.current);
if (rangeToProcess === null) {
return;
}
const colDefs = (0, _gridColumnsSelector.gridVisibleColumnDefinitionsSelector)(apiRef);
const {
spannedCells,
hiddenCells,
hiddenCellOriginMap,
processedRange: newProcessedRange
} = computeRowSpanningState(apiRef, colDefs, visibleRows, range, rangeToProcess, resetState, processedRange.current);
processedRange.current = newProcessedRange;
const newSpannedCellsCount = Object.keys(spannedCells).length;
const newHiddenCellsCount = Object.keys(hiddenCells).length;
const currentSpannedCellsCount = Object.keys(apiRef.current.state.rowSpanning.spannedCells).length;
const currentHiddenCellsCount = Object.keys(apiRef.current.state.rowSpanning.hiddenCells).length;
const shouldUpdateState = resetState || newSpannedCellsCount !== currentSpannedCellsCount || newHiddenCellsCount !== currentHiddenCellsCount;
const hasNoSpannedCells = newSpannedCellsCount === 0 && currentSpannedCellsCount === 0;
if (!shouldUpdateState || hasNoSpannedCells) {
return;
}
apiRef.current.setState(state => {
return (0, _extends2.default)({}, state, {
rowSpanning: {
spannedCells,
hiddenCells,
hiddenCellOriginMap
}
});
});
}, [apiRef, processedRange, props.pagination, props.paginationMode]);
// Reset events trigger a full re-computation of the row spanning state:
// - The `unstable_rowSpanning` prop is updated (feature flag)
// - The filtering is applied
// - The sorting is applied
// - The `paginationModel` is updated
// - The rows are updated
const resetRowSpanningState = React.useCallback(() => {
const renderContext = (0, _gridVirtualizationSelectors.gridRenderContextSelector)(apiRef);
if (!(0, _gridRowSpanningUtils.isRowContextInitialized)(renderContext)) {
return;
}
updateRowSpanningState(renderContext, true);
}, [apiRef, updateRowSpanningState]);
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'renderedRowsIntervalChange', (0, _utils.runIf)(props.unstable_rowSpanning, updateRowSpanningState));
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'sortedRowsSet', (0, _utils.runIf)(props.unstable_rowSpanning, resetRowSpanningState));
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'paginationModelChange', (0, _utils.runIf)(props.unstable_rowSpanning, resetRowSpanningState));
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'filteredRowsSet', (0, _utils.runIf)(props.unstable_rowSpanning, resetRowSpanningState));
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnsChange', (0, _utils.runIf)(props.unstable_rowSpanning, resetRowSpanningState));
React.useEffect(() => {
if (!props.unstable_rowSpanning) {
if (apiRef.current.state.rowSpanning !== EMPTY_STATE) {
apiRef.current.setState(state => (0, _extends2.default)({}, state, {
rowSpanning: EMPTY_STATE
}));
}
} else if (apiRef.current.state.rowSpanning === EMPTY_STATE) {
resetRowSpanningState();
}
}, [apiRef, resetRowSpanningState, props.unstable_rowSpanning]);
};
exports.useGridRowSpanning = useGridRowSpanning;
;