UNPKG

@mui/x-data-grid

Version:

The Community plan edition of the Data Grid components (MUI X).

461 lines (454 loc) 20.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useGridRows = exports.rowsStateInitializer = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _useGridApiMethod = require("../../utils/useGridApiMethod"); var _useGridLogger = require("../../utils/useGridLogger"); var _gridRowsSelector = require("./gridRowsSelector"); var _useTimeout = require("../../utils/useTimeout"); var _useGridApiEventHandler = require("../../utils/useGridApiEventHandler"); var _useGridVisibleRows = require("../../utils/useGridVisibleRows"); var _gridSortingSelector = require("../sorting/gridSortingSelector"); var _gridFilterSelector = require("../filter/gridFilterSelector"); var _gridRowsUtils = require("./gridRowsUtils"); var _pipeProcessing = require("../../core/pipeProcessing"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const rowsStateInitializer = (state, props, apiRef) => { apiRef.current.caches.rows = (0, _gridRowsUtils.createRowsInternalCache)({ rows: props.rows, getRowId: props.getRowId, loading: props.loading, rowCount: props.rowCount }); return (0, _extends2.default)({}, state, { rows: (0, _gridRowsUtils.getRowsStateFromCache)({ apiRef, rowCountProp: props.rowCount, loadingProp: props.loading, previousTree: null, previousTreeDepths: null }) }); }; exports.rowsStateInitializer = rowsStateInitializer; 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 = (0, _useGridLogger.useGridLogger)(apiRef, 'useGridRows'); const currentPage = (0, _useGridVisibleRows.useGridVisibleRows)(apiRef, props); const lastUpdateMs = React.useRef(Date.now()); const lastRowCount = React.useRef(props.rowCount); const timeout = (0, _useTimeout.useTimeout)(); const getRow = React.useCallback(id => { const model = (0, _gridRowsSelector.gridRowsLookupSelector)(apiRef)[id]; if (model) { return model; } const node = apiRef.current.getRowNode(id); if (node && (0, _gridRowsUtils.isAutoGeneratedRow)(node)) { return { [_gridRowsUtils.GRID_ID_AUTOGENERATED]: id }; } return null; }, [apiRef]); const getRowIdProp = props.getRowId; const getRowId = React.useCallback(row => { if (_gridRowsUtils.GRID_ID_AUTOGENERATED in row) { return row[_gridRowsUtils.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 => (0, _extends2.default)({}, state, { rows: (0, _gridRowsUtils.getRowsStateFromCache)({ apiRef, rowCountProp: props.rowCount, loadingProp: props.loading, previousTree: (0, _gridRowsSelector.gridRowTreeSelector)(apiRef), previousTreeDepths: (0, _gridRowsSelector.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 = (0, _gridRowsUtils.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 === _useGridApiEventHandler.GridSignature.DataGrid && updates.length > 1) { throw new Error(['MUI X: You cannot 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 = (0, _gridRowsUtils.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] = (0, _extends2.default)({}, prevModel, update); } } else { nonPinnedRowsUpdates.push(update); } }); const cache = (0, _gridRowsUtils.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 = (0, _gridRowsSelector.gridDataRowIdsSelector)(apiRef); const idRowsLookup = (0, _gridRowsSelector.gridRowsLookupSelector)(apiRef); return new Map(dataRows.map(id => [id, idRowsLookup[id] ?? {}])); }, [apiRef]); const getRowsCount = React.useCallback(() => (0, _gridRowsSelector.gridRowCountSelector)(apiRef), [apiRef]); const getAllRowIds = React.useCallback(() => (0, _gridRowsSelector.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 X: No row with id #${id} found.`); } if (currentNode.type !== 'group') { throw new Error('MUI X: Only group nodes can be expanded or collapsed.'); } const newNode = (0, _extends2.default)({}, currentNode, { childrenExpanded: isExpanded }); apiRef.current.setState(state => { return (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, state.rows, { tree: (0, _extends2.default)({}, state.rows.tree, { [id]: newNode }) }) }); }); apiRef.current.forceUpdate(); apiRef.current.publishEvent('rowExpansionChange', newNode); }, [apiRef]); const getRowNode = React.useCallback(id => (0, _gridRowsSelector.gridRowTreeSelector)(apiRef)[id] ?? null, [apiRef]); const getRowGroupChildren = React.useCallback(({ skipAutoGeneratedRows = true, groupId, applySorting, applyFiltering }) => { const tree = (0, _gridRowsSelector.gridRowTreeSelector)(apiRef); let children; if (applySorting) { const groupNode = tree[groupId]; if (!groupNode) { return []; } const sortedRowIds = (0, _gridSortingSelector.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 || !(0, _gridRowsUtils.isAutoGeneratedRow)(tree[id])) { children.push(id); } } } else { children = (0, _gridRowsUtils.getTreeNodeDescendants)(tree, groupId, skipAutoGeneratedRows); } if (applyFiltering) { const filteredRowsLookup = (0, _gridFilterSelector.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 X: No row with id #${rowId} found.`); } if (node.parent !== _gridRowsUtils.GRID_ROOT_GROUP_ID) { throw new Error(`MUI X: The row reordering do not support reordering of grouped rows yet.`); } if (node.type !== 'leaf') { throw new Error(`MUI X: The row reordering do not support reordering of footer or grouping rows.`); } apiRef.current.setState(state => { const group = (0, _gridRowsSelector.gridRowTreeSelector)(state, apiRef.current.instanceId)[_gridRowsUtils.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 (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, state.rows, { tree: (0, _extends2.default)({}, state.rows.tree, { [_gridRowsUtils.GRID_ROOT_GROUP_ID]: (0, _extends2.default)({}, group, { children: updatedRows }) }) }) }); }); apiRef.current.publishEvent('rowsSet'); }, [apiRef, logger]); const replaceRows = React.useCallback((firstRowToRender, newRows) => { if (props.signature === _useGridApiEventHandler.GridSignature.DataGrid && newRows.length > 1) { throw new Error(['MUI X: You cannot 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 = (0, _gridRowsSelector.gridRowMaximumTreeDepthSelector)(apiRef); if (treeDepth > 1) { throw new Error('`apiRef.current.unstable_replaceRows` is not compatible with tree data and row grouping'); } const tree = (0, _extends2.default)({}, (0, _gridRowsSelector.gridRowTreeSelector)(apiRef)); const dataRowIdToModelLookup = (0, _extends2.default)({}, (0, _gridRowsSelector.gridRowsLookupSelector)(apiRef)); const dataRowIdToIdLookup = (0, _extends2.default)({}, (0, _gridRowsSelector.gridRowsDataRowIdToIdLookupSelector)(apiRef)); const rootGroup = tree[_gridRowsUtils.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 = (0, _gridRowsUtils.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: _gridRowsUtils.GRID_ROOT_GROUP_ID, type: 'leaf', groupingKey: null }; dataRowIdToModelLookup[rowId] = rowModel; dataRowIdToIdLookup[rowId] = rowId; tree[rowId] = rowTreeNodeConfig; seenIds.add(rowId); } tree[_gridRowsUtils.GRID_ROOT_GROUP_ID] = (0, _extends2.default)({}, 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 => (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, 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 = (0, _extends2.default)({}, apiRef.current.caches.rows, { updates: { type: 'full', rows: (0, _gridRowsSelector.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 = (0, _gridRowsUtils.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') !== (0, _gridRowsSelector.gridRowGroupingNameSelector)(apiRef)) { groupRows(); } }, [apiRef, groupRows]); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'strategyAvailabilityChange', handleStrategyActivityChange); /** * APPLIERS */ const applyHydrateRowsProcessor = React.useCallback(() => { apiRef.current.setState(state => { const response = apiRef.current.unstable_applyPipeProcessors('hydrateRows', { tree: (0, _gridRowsSelector.gridRowTreeSelector)(state, apiRef.current.instanceId), treeDepths: (0, _gridRowsSelector.gridRowTreeDepthsSelector)(state, apiRef.current.instanceId), dataRowIds: (0, _gridRowsSelector.gridDataRowIdsSelector)(state, apiRef.current.instanceId), dataRowIdToModelLookup: (0, _gridRowsSelector.gridRowsLookupSelector)(state, apiRef.current.instanceId), dataRowIdToIdLookup: (0, _gridRowsSelector.gridRowsDataRowIdToIdLookupSelector)(state, apiRef.current.instanceId) }); return (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, state.rows, response, { totalTopLevelRowCount: (0, _gridRowsUtils.getTopLevelRowCount)({ tree: response.tree, rowCountProp: props.rowCount }) }) }); }); apiRef.current.publishEvent('rowsSet'); apiRef.current.forceUpdate(); }, [apiRef, props.rowCount]); (0, _pipeProcessing.useGridRegisterPipeApplier)(apiRef, 'hydrateRows', applyHydrateRowsProcessor); (0, _useGridApiMethod.useGridApiMethod)(apiRef, rowApi, 'public'); (0, _useGridApiMethod.useGridApiMethod)(apiRef, rowProApi, props.signature === _useGridApiEventHandler.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; } let isRowCountPropUpdated = false; if (props.rowCount !== lastRowCount.current) { isRowCountPropUpdated = true; lastRowCount.current = props.rowCount; } 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 => (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, state.rows, { loading: props.loading }) })); apiRef.current.caches.rows.loadingPropBeforePartialUpdates = props.loading; apiRef.current.forceUpdate(); } if (!isNewRowCountAlreadyInState) { apiRef.current.setState(state => (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, 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(); } if (!isRowCountPropUpdated) { return; } } logger.debug(`Updating all rows, new length ${props.rows.length}`); throttledRowsChange({ cache: (0, _gridRowsUtils.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]); }; exports.useGridRows = useGridRows;