UNPKG

@mui/x-data-grid

Version:

The community edition of the data grid component (MUI X).

465 lines (459 loc) 19.5 kB
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; 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 var rowsStateInitializer = function 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: apiRef, rowCountProp: props.rowCount, loadingProp: props.loading, previousTree: null, previousTreeDepths: null }) }); }; export var useGridRows = function 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. } } var logger = useGridLogger(apiRef, 'useGridRows'); var currentPage = useGridVisibleRows(apiRef, props); var lastUpdateMs = React.useRef(Date.now()); var timeout = useTimeout(); var getRow = React.useCallback(function (id) { var model = gridRowsLookupSelector(apiRef)[id]; if (model) { return model; } var node = apiRef.current.getRowNode(id); if (node && isAutoGeneratedRow(node)) { return _defineProperty({}, GRID_ID_AUTOGENERATED, id); } return null; }, [apiRef]); var getRowIdProp = props.getRowId; var getRowId = React.useCallback(function (row) { if (GRID_ID_AUTOGENERATED in row) { return row[GRID_ID_AUTOGENERATED]; } if (getRowIdProp) { return getRowIdProp(row); } return row.id; }, [getRowIdProp]); var lookup = React.useMemo(function () { return currentPage.rows.reduce(function (acc, _ref2, index) { var id = _ref2.id; acc[id] = index; return acc; }, {}); }, [currentPage.rows]); var throttledRowsChange = React.useCallback(function (_ref3) { var cache = _ref3.cache, throttle = _ref3.throttle; var run = function run() { lastUpdateMs.current = Date.now(); apiRef.current.setState(function (state) { return _extends({}, state, { rows: getRowsStateFromCache({ apiRef: 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; } var 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 */ var setRows = React.useCallback(function (rows) { logger.debug("Updating all rows, new length ".concat(rows.length)); var cache = createRowsInternalCache({ rows: rows, getRowId: props.getRowId, loading: props.loading, rowCount: props.rowCount }); var prevCache = apiRef.current.caches.rows; cache.rowsBeforePartialUpdates = prevCache.rowsBeforePartialUpdates; throttledRowsChange({ cache: cache, throttle: true }); }, [logger, props.getRowId, props.loading, props.rowCount, throttledRowsChange, apiRef]); var updateRows = React.useCallback(function (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')); } var nonPinnedRowsUpdates = []; updates.forEach(function (update) { var id = getRowIdFromRowModel(update, props.getRowId, 'A row was provided without id when calling updateRows():'); var rowNode = apiRef.current.getRowNode(id); if ((rowNode == null ? void 0 : rowNode.type) === 'pinnedRow') { // @ts-ignore because otherwise `release:build` doesn't work var pinnedRowsCache = apiRef.current.caches.pinnedRows; var prevModel = pinnedRowsCache.idLookup[id]; if (prevModel) { pinnedRowsCache.idLookup[id] = _extends({}, prevModel, update); } } else { nonPinnedRowsUpdates.push(update); } }); var cache = updateCacheWithNewRows({ updates: nonPinnedRowsUpdates, getRowId: props.getRowId, previousCache: apiRef.current.caches.rows }); throttledRowsChange({ cache: cache, throttle: true }); }, [props.signature, props.getRowId, throttledRowsChange, apiRef]); var getRowModels = React.useCallback(function () { var dataRows = gridDataRowIdsSelector(apiRef); var idRowsLookup = gridRowsLookupSelector(apiRef); return new Map(dataRows.map(function (id) { var _idRowsLookup$id; return [id, (_idRowsLookup$id = idRowsLookup[id]) != null ? _idRowsLookup$id : {}]; })); }, [apiRef]); var getRowsCount = React.useCallback(function () { return gridRowCountSelector(apiRef); }, [apiRef]); var getAllRowIds = React.useCallback(function () { return gridDataRowIdsSelector(apiRef); }, [apiRef]); var getRowIndexRelativeToVisibleRows = React.useCallback(function (id) { return lookup[id]; }, [lookup]); var setRowChildrenExpansion = React.useCallback(function (id, isExpanded) { var currentNode = apiRef.current.getRowNode(id); if (!currentNode) { throw new Error("MUI: No row with id #".concat(id, " found")); } if (currentNode.type !== 'group') { throw new Error('MUI: Only group nodes can be expanded or collapsed'); } var newNode = _extends({}, currentNode, { childrenExpanded: isExpanded }); apiRef.current.setState(function (state) { return _extends({}, state, { rows: _extends({}, state.rows, { tree: _extends({}, state.rows.tree, _defineProperty({}, id, newNode)) }) }); }); apiRef.current.forceUpdate(); apiRef.current.publishEvent('rowExpansionChange', newNode); }, [apiRef]); var getRowNode = React.useCallback(function (id) { var _ref4; return (_ref4 = gridRowTreeSelector(apiRef)[id]) != null ? _ref4 : null; }, [apiRef]); var getRowGroupChildren = React.useCallback(function (_ref5) { var _ref5$skipAutoGenerat = _ref5.skipAutoGeneratedRows, skipAutoGeneratedRows = _ref5$skipAutoGenerat === void 0 ? true : _ref5$skipAutoGenerat, groupId = _ref5.groupId, applySorting = _ref5.applySorting, applyFiltering = _ref5.applyFiltering; var tree = gridRowTreeSelector(apiRef); var children; if (applySorting) { var groupNode = tree[groupId]; if (!groupNode) { return []; } var sortedRowIds = gridSortedRowIdsSelector(apiRef); children = []; var startIndex = sortedRowIds.findIndex(function (id) { return id === groupId; }) + 1; for (var index = startIndex; index < sortedRowIds.length && tree[sortedRowIds[index]].depth > groupNode.depth; index += 1) { var id = sortedRowIds[index]; if (!skipAutoGeneratedRows || !isAutoGeneratedRow(tree[id])) { children.push(id); } } } else { children = getTreeNodeDescendants(tree, groupId, skipAutoGeneratedRows); } if (applyFiltering) { var filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef); children = children.filter(function (childId) { return filteredRowsLookup[childId] !== false; }); } return children; }, [apiRef]); var setRowIndex = React.useCallback(function (rowId, targetIndex) { var node = apiRef.current.getRowNode(rowId); if (!node) { throw new Error("MUI: No row with id #".concat(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(function (state) { var group = gridRowTreeSelector(state, apiRef.current.instanceId)[GRID_ROOT_GROUP_ID]; var allRows = group.children; var oldIndex = allRows.findIndex(function (row) { return row === rowId; }); if (oldIndex === -1 || oldIndex === targetIndex) { return state; } logger.debug("Moving row ".concat(rowId, " to index ").concat(targetIndex)); var updatedRows = _toConsumableArray(allRows); updatedRows.splice(targetIndex, 0, updatedRows.splice(oldIndex, 1)[0]); return _extends({}, state, { rows: _extends({}, state.rows, { tree: _extends({}, state.rows.tree, _defineProperty({}, GRID_ROOT_GROUP_ID, _extends({}, group, { children: updatedRows }))) }) }); }); apiRef.current.publishEvent('rowsSet'); }, [apiRef, logger]); var replaceRows = React.useCallback(function (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; } var treeDepth = gridRowMaximumTreeDepthSelector(apiRef); if (treeDepth > 1) { throw new Error('`apiRef.current.unstable_replaceRows` is not compatible with tree data and row grouping'); } var tree = _extends({}, gridRowTreeSelector(apiRef)); var dataRowIdToModelLookup = _extends({}, gridRowsLookupSelector(apiRef)); var dataRowIdToIdLookup = _extends({}, gridRowsDataRowIdToIdLookupSelector(apiRef)); var rootGroup = tree[GRID_ROOT_GROUP_ID]; var rootGroupChildren = _toConsumableArray(rootGroup.children); for (var i = 0; i < newRows.length; i += 1) { var rowModel = newRows[i]; var rowId = getRowIdFromRowModel(rowModel, props.getRowId, 'A row was provided without id when calling replaceRows().'); var _rootGroupChildren$sp = rootGroupChildren.splice(firstRowToRender + i, 1, rowId), _rootGroupChildren$sp2 = _slicedToArray(_rootGroupChildren$sp, 1), replacedRowId = _rootGroupChildren$sp2[0]; delete dataRowIdToModelLookup[replacedRowId]; delete dataRowIdToIdLookup[replacedRowId]; delete tree[replacedRowId]; var rowTreeNodeConfig = { id: rowId, depth: 0, parent: GRID_ROOT_GROUP_ID, type: 'leaf', groupingKey: null }; dataRowIdToModelLookup[rowId] = rowModel; dataRowIdToIdLookup[rowId] = rowId; tree[rowId] = rowTreeNodeConfig; } tree[GRID_ROOT_GROUP_ID] = _extends({}, rootGroup, { children: rootGroupChildren }); // Removes potential remaining skeleton rows from the dataRowIds. var dataRowIds = rootGroupChildren.filter(function (childId) { return tree[childId].type === 'leaf'; }); apiRef.current.caches.rows.dataRowIdToModelLookup = dataRowIdToModelLookup; apiRef.current.caches.rows.dataRowIdToIdLookup = dataRowIdToIdLookup; apiRef.current.setState(function (state) { return _extends({}, state, { rows: _extends({}, state.rows, { dataRowIdToModelLookup: dataRowIdToModelLookup, dataRowIdToIdLookup: dataRowIdToIdLookup, dataRowIds: dataRowIds, tree: tree }) }); }); apiRef.current.publishEvent('rowsSet'); }, [apiRef, props.signature, props.getRowId]); var rowApi = { getRow: getRow, getRowId: getRowId, getRowModels: getRowModels, getRowsCount: getRowsCount, getAllRowIds: getAllRowIds, setRows: setRows, updateRows: updateRows, getRowNode: getRowNode, getRowIndexRelativeToVisibleRows: getRowIndexRelativeToVisibleRows, unstable_replaceRows: replaceRows }; var rowProApi = { setRowIndex: setRowIndex, setRowChildrenExpansion: setRowChildrenExpansion, getRowGroupChildren: getRowGroupChildren }; /** * EVENTS */ var groupRows = React.useCallback(function () { logger.info("Row grouping pre-processing have changed, regenerating the row tree"); var 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: cache, throttle: false }); }, [logger, apiRef, props.rows, props.getRowId, props.loading, props.rowCount, throttledRowsChange]); var handleStrategyProcessorChange = React.useCallback(function (methodName) { if (methodName === 'rowTreeCreation') { groupRows(); } }, [groupRows]); var handleStrategyActivityChange = React.useCallback(function () { // `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 */ var applyHydrateRowsProcessor = React.useCallback(function () { apiRef.current.setState(function (state) { var 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 var isFirstRender = React.useRef(true); React.useEffect(function () { if (isFirstRender.current) { isFirstRender.current = false; return; } var areNewRowsAlreadyInState = apiRef.current.caches.rows.rowsBeforePartialUpdates === props.rows; var isNewLoadingAlreadyInState = apiRef.current.caches.rows.loadingPropBeforePartialUpdates === props.loading; var 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(function (state) { return _extends({}, state, { rows: _extends({}, state.rows, { loading: props.loading }) }); }); apiRef.current.caches.rows.loadingPropBeforePartialUpdates = props.loading; apiRef.current.forceUpdate(); } if (!isNewRowCountAlreadyInState) { apiRef.current.setState(function (state) { return _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 ".concat(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]); };