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(() => { var _pinnedRows$top, _pinnedRows$bottom; 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 != null ? 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) { var _spacing$top, _spacing$bottom; 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 = spacing.top) != null ? _spacing$top : 0; initialHeights.spacingBottom = (_spacing$bottom = spacing.bottom) != null ? _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 == null || (_pinnedRows$top = pinnedRows.top) == null || _pinnedRows$top.forEach(row => { calculateRowProcessedSizes(row); }); pinnedRows == null || (_pinnedRows$bottom = pinnedRows.bottom) == null || _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 => { var _rowsHeightLookup$cur; return (_rowsHeightLookup$cur = rowsHeightLookup.current[rowId]) == null ? void 0 : _rowsHeightLookup$cur.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 => { var _rowsHeightLookup$cur2; return ((_rowsHeightLookup$cur2 = rowsHeightLookup.current[id]) == null ? void 0 : _rowsHeightLookup$cur2.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'); };