UNPKG

@mui/x-data-grid-pro

Version:

The Pro plan edition of the MUI X Data Grid components.

373 lines (365 loc) 17.4 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.useGridDataSourceLazyLoader = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _throttle = require("@mui/x-internals/throttle"); var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); var _debounce = _interopRequireDefault(require("@mui/utils/debounce")); var _xDataGrid = require("@mui/x-data-grid"); var _internals = require("@mui/x-data-grid/internals"); var _utils = require("../lazyLoader/utils"); var _useGridLazyLoaderPreProcessors = require("../lazyLoader/useGridLazyLoaderPreProcessors"); var LoadingTrigger = /*#__PURE__*/function (LoadingTrigger) { LoadingTrigger[LoadingTrigger["VIEWPORT"] = 0] = "VIEWPORT"; LoadingTrigger[LoadingTrigger["SCROLL_END"] = 1] = "SCROLL_END"; return LoadingTrigger; }(LoadingTrigger || {}); const INTERVAL_CACHE_INITIAL_STATE = { firstRowToRender: 0, lastRowToRender: 0 }; const getSkeletonRowId = index => `${_useGridLazyLoaderPreProcessors.GRID_SKELETON_ROW_ROOT_ID}-${index}`; /** * @requires useGridRows (state) * @requires useGridPagination (state) * @requires useGridScroll (method */ const useGridDataSourceLazyLoader = (privateApiRef, props) => { const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability(_internals.GridStrategyGroup.DataSource, _internals.DataSourceRowsUpdateStrategy.LazyLoading, props.dataSource && props.lazyLoading ? () => true : () => false); }, [privateApiRef, props.lazyLoading, props.dataSource]); const [lazyLoadingRowsUpdateStrategyActive, setLazyLoadingRowsUpdateStrategyActive] = React.useState(false); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const previousLastRowIndex = React.useRef(0); const loadingTrigger = React.useRef(null); const rowsStale = React.useRef(false); const draggedRowId = React.useRef(null); const fetchRows = React.useCallback(params => { privateApiRef.current.dataSource.fetchRows(_xDataGrid.GRID_ROOT_GROUP_ID, params); }, [privateApiRef]); const debouncedFetchRows = React.useMemo(() => (0, _debounce.default)(fetchRows, 0), [fetchRows]); // Adjust the render context range to fit the pagination model's page size // First row index should be decreased to the start of the page, end row index should be increased to the end of the page const adjustRowParams = React.useCallback(params => { if (typeof params.start !== 'number') { return params; } const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef); return (0, _extends2.default)({}, params, { start: params.start - params.start % paginationModel.pageSize, end: params.end + paginationModel.pageSize - params.end % paginationModel.pageSize - 1 }); }, [privateApiRef]); const resetGrid = React.useCallback(() => { privateApiRef.current.setLoading(true); privateApiRef.current.dataSource.cache.clear(); rowsStale.current = true; previousLastRowIndex.current = 0; const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef); const sortModel = (0, _xDataGrid.gridSortModelSelector)(privateApiRef); const filterModel = (0, _xDataGrid.gridFilterModelSelector)(privateApiRef); const getRowsParams = { start: 0, end: paginationModel.pageSize - 1, sortModel, filterModel }; fetchRows(getRowsParams); }, [privateApiRef, fetchRows]); const ensureValidRowCount = React.useCallback((previousLoadingTrigger, newLoadingTrigger) => { // switching from lazy loading to infinite loading should always reset the grid // since there is no guarantee that the new data will be placed correctly // there might be some skeleton rows in between the data or the data has changed (row count became unknown) if (previousLoadingTrigger === LoadingTrigger.VIEWPORT && newLoadingTrigger === LoadingTrigger.SCROLL_END) { resetGrid(); return; } // switching from infinite loading to lazy loading should reset the grid only if the known row count // is smaller than the amount of rows rendered const tree = privateApiRef.current.state.rows.tree; const rootGroup = tree[_xDataGrid.GRID_ROOT_GROUP_ID]; const rootGroupChildren = [...rootGroup.children]; const pageRowCount = privateApiRef.current.state.pagination.rowCount; const rootChildrenCount = rootGroupChildren.length; if (rootChildrenCount > pageRowCount) { resetGrid(); } }, [privateApiRef, resetGrid]); const addSkeletonRows = React.useCallback(() => { const tree = privateApiRef.current.state.rows.tree; const rootGroup = tree[_xDataGrid.GRID_ROOT_GROUP_ID]; const rootGroupChildren = [...rootGroup.children]; const pageRowCount = privateApiRef.current.state.pagination.rowCount; const rootChildrenCount = rootGroupChildren.length; /** * Do nothing if * - children count is 0 */ if (rootChildrenCount === 0) { return; } const pageToSkip = adjustRowParams({ start: renderedRowsIntervalCache.current.firstRowToRender, end: renderedRowsIntervalCache.current.lastRowToRender }); let hasChanged = false; const isInitialPage = renderedRowsIntervalCache.current.firstRowToRender === 0 && renderedRowsIntervalCache.current.lastRowToRender === 0; for (let i = 0; i < rootChildrenCount; i += 1) { if (isInitialPage) { break; } // replace the rows not in the viewport with skeleton rows if (pageToSkip.start <= i && i <= pageToSkip.end || tree[rootGroupChildren[i]]?.type === 'skeletonRow' || // ignore rows that are already skeleton rows tree[rootGroupChildren[i]]?.id === draggedRowId.current // ignore row that is being dragged (https://github.com/mui/mui-x/issues/17854) ) { continue; } const rowId = tree[rootGroupChildren[i]].id; // keep the id, so that row related state is maintained const skeletonRowNode = { type: 'skeletonRow', id: rowId, parent: _xDataGrid.GRID_ROOT_GROUP_ID, depth: 0 }; tree[rowId] = skeletonRowNode; hasChanged = true; } // Should only happen with VIEWPORT loading trigger if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { // fill the grid with skeleton rows for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { const skeletonId = getSkeletonRowId(i + rootChildrenCount); // to avoid duplicate keys on rebuild rootGroupChildren.push(skeletonId); const skeletonRowNode = { type: 'skeletonRow', id: skeletonId, parent: _xDataGrid.GRID_ROOT_GROUP_ID, depth: 0 }; tree[skeletonId] = skeletonRowNode; hasChanged = true; } } if (!hasChanged) { return; } tree[_xDataGrid.GRID_ROOT_GROUP_ID] = (0, _extends2.default)({}, rootGroup, { children: rootGroupChildren }); privateApiRef.current.setState(state => (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, state.rows, { tree }) }), 'addSkeletonRows'); }, [privateApiRef, adjustRowParams]); const updateLoadingTrigger = React.useCallback(rowCount => { const newLoadingTrigger = rowCount === -1 ? LoadingTrigger.SCROLL_END : LoadingTrigger.VIEWPORT; if (loadingTrigger.current !== null) { ensureValidRowCount(loadingTrigger.current, newLoadingTrigger); } if (loadingTrigger.current !== newLoadingTrigger) { loadingTrigger.current = newLoadingTrigger; } }, [ensureValidRowCount]); const handleDataUpdate = React.useCallback(params => { if ('error' in params) { return; } const { response, fetchParams } = params; const pageRowCount = privateApiRef.current.state.pagination.rowCount; const tree = privateApiRef.current.state.rows.tree; const dataRowIdToModelLookup = privateApiRef.current.state.rows.dataRowIdToModelLookup; if (response.rowCount !== undefined || pageRowCount === undefined) { privateApiRef.current.setRowCount(response.rowCount === undefined ? -1 : response.rowCount); } // scroll to the top if the rows are stale and the new request is for the first page if (rowsStale.current && params.fetchParams.start === 0) { privateApiRef.current.scroll({ top: 0 }); // the rows can safely be replaced. skeleton rows will be added later privateApiRef.current.setRows(response.rows); } else { const rootGroup = tree[_xDataGrid.GRID_ROOT_GROUP_ID]; const rootGroupChildren = [...rootGroup.children]; const filteredSortedRowIds = (0, _xDataGrid.gridFilteredSortedRowIdsSelector)(privateApiRef); const startingIndex = typeof fetchParams.start === 'string' ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) : fetchParams.start; // Check for duplicate rows let duplicateRowCount = 0; response.rows.forEach(row => { const rowId = (0, _xDataGrid.gridRowIdSelector)(privateApiRef, row); if (tree[rowId] || dataRowIdToModelLookup[rowId]) { const index = rootGroupChildren.indexOf(rowId); if (index !== -1) { const skeletonId = getSkeletonRowId(index); rootGroupChildren[index] = skeletonId; tree[skeletonId] = { type: 'skeletonRow', id: skeletonId, parent: _xDataGrid.GRID_ROOT_GROUP_ID, depth: 0 }; } delete tree[rowId]; delete dataRowIdToModelLookup[rowId]; duplicateRowCount += 1; } }); if (duplicateRowCount > 0) { tree[_xDataGrid.GRID_ROOT_GROUP_ID] = (0, _extends2.default)({}, rootGroup, { children: rootGroupChildren }); privateApiRef.current.setState(state => (0, _extends2.default)({}, state, { rows: (0, _extends2.default)({}, state.rows, { tree, dataRowIdToModelLookup }) })); } privateApiRef.current.unstable_replaceRows(startingIndex, response.rows); } rowsStale.current = false; if (loadingTrigger.current === null) { updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); } addSkeletonRows(); privateApiRef.current.setLoading(false); privateApiRef.current.unstable_applyPipeProcessors('processDataSourceRows', { params: params.fetchParams, response }, false); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); }, [privateApiRef, updateLoadingTrigger, addSkeletonRows]); const handleRowCountChange = React.useCallback(() => { if (rowsStale.current || loadingTrigger.current === null) { return; } updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); addSkeletonRows(); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); }, [privateApiRef, updateLoadingTrigger, addSkeletonRows]); const handleIntersection = (0, _useEventCallback.default)(() => { if (rowsStale.current || loadingTrigger.current !== LoadingTrigger.SCROLL_END) { return; } const renderContext = (0, _internals.gridRenderContextSelector)(privateApiRef); if (previousLastRowIndex.current >= renderContext.lastRowIndex) { return; } previousLastRowIndex.current = renderContext.lastRowIndex; const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef); const sortModel = (0, _xDataGrid.gridSortModelSelector)(privateApiRef); const filterModel = (0, _xDataGrid.gridFilterModelSelector)(privateApiRef); const getRowsParams = { start: renderContext.lastRowIndex, end: renderContext.lastRowIndex + paginationModel.pageSize - 1, sortModel, filterModel }; privateApiRef.current.setLoading(true); fetchRows(adjustRowParams(getRowsParams)); }); const handleRenderedRowsIntervalChange = React.useCallback(params => { if (rowsStale.current) { return; } const sortModel = (0, _xDataGrid.gridSortModelSelector)(privateApiRef); const filterModel = (0, _xDataGrid.gridFilterModelSelector)(privateApiRef); const getRowsParams = { start: params.firstRowIndex, end: params.lastRowIndex - 1, sortModel, filterModel }; if (renderedRowsIntervalCache.current.firstRowToRender === params.firstRowIndex && renderedRowsIntervalCache.current.lastRowToRender === params.lastRowIndex) { return; } renderedRowsIntervalCache.current = { firstRowToRender: params.firstRowIndex, lastRowToRender: params.lastRowIndex }; const currentVisibleRows = (0, _internals.getVisibleRows)(privateApiRef); const skeletonRowsSection = (0, _utils.findSkeletonRowsSection)({ apiRef: privateApiRef, visibleRows: currentVisibleRows.rows, range: { firstRowIndex: params.firstRowIndex, lastRowIndex: params.lastRowIndex - 1 } }); if (!skeletonRowsSection) { return; } getRowsParams.start = skeletonRowsSection.firstRowIndex; getRowsParams.end = skeletonRowsSection.lastRowIndex; fetchRows(adjustRowParams(getRowsParams)); }, [privateApiRef, adjustRowParams, fetchRows]); const throttledHandleRenderedRowsIntervalChange = React.useMemo(() => (0, _throttle.throttle)(handleRenderedRowsIntervalChange, props.lazyLoadingRequestThrottleMs), [props.lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange]); React.useEffect(() => { return () => { throttledHandleRenderedRowsIntervalChange.clear(); }; }, [throttledHandleRenderedRowsIntervalChange]); const handleGridSortModelChange = React.useCallback(newSortModel => { rowsStale.current = true; throttledHandleRenderedRowsIntervalChange.clear(); previousLastRowIndex.current = 0; const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef); const filterModel = (0, _xDataGrid.gridFilterModelSelector)(privateApiRef); const getRowsParams = { start: 0, end: paginationModel.pageSize - 1, sortModel: newSortModel, filterModel }; privateApiRef.current.setLoading(true); debouncedFetchRows(getRowsParams); }, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange]); const handleGridFilterModelChange = React.useCallback(newFilterModel => { rowsStale.current = true; throttledHandleRenderedRowsIntervalChange.clear(); previousLastRowIndex.current = 0; const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef); const sortModel = (0, _xDataGrid.gridSortModelSelector)(privateApiRef); const getRowsParams = { start: 0, end: paginationModel.pageSize - 1, sortModel, filterModel: newFilterModel }; privateApiRef.current.setLoading(true); debouncedFetchRows(getRowsParams); }, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange]); const handleDragStart = React.useCallback(row => { draggedRowId.current = row.id; }, []); const handleDragEnd = React.useCallback(() => { draggedRowId.current = null; }, []); const handleStrategyActivityChange = React.useCallback(() => { setLazyLoadingRowsUpdateStrategyActive(privateApiRef.current.getActiveStrategy(_internals.GridStrategyGroup.DataSource) === _internals.DataSourceRowsUpdateStrategy.LazyLoading); }, [privateApiRef]); (0, _internals.useGridRegisterStrategyProcessor)(privateApiRef, _internals.DataSourceRowsUpdateStrategy.LazyLoading, 'dataSourceRowsUpdate', handleDataUpdate); (0, _xDataGrid.useGridEvent)(privateApiRef, 'strategyAvailabilityChange', handleStrategyActivityChange); (0, _xDataGrid.useGridEvent)(privateApiRef, 'rowCountChange', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, handleRowCountChange)); (0, _xDataGrid.useGridEvent)(privateApiRef, 'rowsScrollEndIntersection', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, handleIntersection)); (0, _xDataGrid.useGridEvent)(privateApiRef, 'renderedRowsIntervalChange', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, throttledHandleRenderedRowsIntervalChange)); (0, _xDataGrid.useGridEvent)(privateApiRef, 'sortModelChange', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, handleGridSortModelChange)); (0, _xDataGrid.useGridEvent)(privateApiRef, 'filterModelChange', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, handleGridFilterModelChange)); (0, _xDataGrid.useGridEvent)(privateApiRef, 'rowDragStart', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, handleDragStart)); (0, _xDataGrid.useGridEvent)(privateApiRef, 'rowDragEnd', (0, _internals.runIf)(lazyLoadingRowsUpdateStrategyActive, handleDragEnd)); React.useEffect(() => { setStrategyAvailability(); }, [setStrategyAvailability]); }; exports.useGridDataSourceLazyLoader = useGridDataSourceLazyLoader;