diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json
index 1667cca1371e..b9c22994df5f 100644
--- a/docs/pages/x/api/data-grid/data-grid-premium.json
+++ b/docs/pages/x/api/data-grid/data-grid-premium.json
@@ -588,7 +588,11 @@
"rowGroupingModel": { "type": { "name": "arrayOf", "description": "Array<string>" } },
"rowHeight": { "type": { "name": "number" }, "default": "52" },
"rowModesModel": { "type": { "name": "object" } },
- "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" },
+ "rowPositionsDebounceMs": {
+ "type": { "name": "number" },
+ "default": "166",
+ "deprecated": true
+ },
"rowReordering": { "type": { "name": "bool" }, "default": "false" },
"rows": {
"type": { "name": "arrayOf", "description": "Array<object>" },
diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json
index cc714abf8fba..f0e2cc6f48c4 100644
--- a/docs/pages/x/api/data-grid/data-grid-pro.json
+++ b/docs/pages/x/api/data-grid/data-grid-pro.json
@@ -523,7 +523,11 @@
"rowCount": { "type": { "name": "number" } },
"rowHeight": { "type": { "name": "number" }, "default": "52" },
"rowModesModel": { "type": { "name": "object" } },
- "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" },
+ "rowPositionsDebounceMs": {
+ "type": { "name": "number" },
+ "default": "166",
+ "deprecated": true
+ },
"rowReordering": { "type": { "name": "bool" }, "default": "false" },
"rows": {
"type": { "name": "arrayOf", "description": "Array<object>" },
diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json
index 3fd158be8aa0..25e56f8402bc 100644
--- a/docs/pages/x/api/data-grid/data-grid.json
+++ b/docs/pages/x/api/data-grid/data-grid.json
@@ -439,7 +439,11 @@
"rowCount": { "type": { "name": "number" } },
"rowHeight": { "type": { "name": "number" }, "default": "52" },
"rowModesModel": { "type": { "name": "object" } },
- "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" },
+ "rowPositionsDebounceMs": {
+ "type": { "name": "number" },
+ "default": "166",
+ "deprecated": true
+ },
"rows": {
"type": { "name": "arrayOf", "description": "Array<object>" },
"default": "[]"
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
index 89e77912b74c..86be3303b736 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
+++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
@@ -967,6 +967,7 @@ DataGridPremiumRaw.propTypes = {
* Setting it to a lower value could be useful when using dynamic row height,
* but might reduce performance when displaying a large number of rows.
* @default 166
+ * @deprecated
*/
rowPositionsDebounceMs: PropTypes.number,
/**
diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
index 4590c79ccfdf..8cea3a676d0d 100644
--- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
+++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
@@ -873,6 +873,7 @@ DataGridProRaw.propTypes = {
* Setting it to a lower value could be useful when using dynamic row height,
* but might reduce performance when displaying a large number of rows.
* @default 166
+ * @deprecated
*/
rowPositionsDebounceMs: PropTypes.number,
/**
diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts
index f618ffe20a7f..2efc3fbe9250 100644
--- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts
@@ -313,7 +313,13 @@ export const useGridDetailPanel = (
const isFirstRender = React.useRef(true);
if (isFirstRender.current) {
- isFirstRender.current = false;
updateCachesIfNeeded();
}
+ React.useEffect(() => {
+ if (!isFirstRender.current) {
+ updateCachesIfNeeded();
+ apiRef.current.hydrateRowsMeta();
+ }
+ isFirstRender.current = false;
+ }, [apiRef, updateCachesIfNeeded]);
};
diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index 3acb545b4ed5..ba199d341d21 100644
--- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -887,77 +887,6 @@ describe(' - Rows', () => {
});
});
- describe('apiRef: setRowHeight', () => {
- const ROW_HEIGHT = 52;
-
- before(function beforeHook() {
- if (isJSDOM) {
- // Need layouting
- this.skip();
- }
- });
-
- beforeEach(() => {
- baselineProps = {
- rows: [
- {
- id: 0,
- brand: 'Nike',
- },
- {
- id: 1,
- brand: 'Adidas',
- },
- {
- id: 2,
- brand: 'Puma',
- },
- ],
- columns: [{ field: 'brand', headerName: 'Brand' }],
- };
- });
-
- let apiRef: React.MutableRefObject;
-
- function TestCase(props: Partial) {
- apiRef = useGridApiRef();
- return (
-
-
-
- );
- }
-
- it('should change row height', () => {
- const resizedRowId = 1;
- render();
-
- expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT);
-
- act(() => apiRef.current.unstable_setRowHeight(resizedRowId, 100));
- expect(getRow(resizedRowId).clientHeight).to.equal(100);
- });
-
- it('should preserve changed row height after sorting', () => {
- const resizedRowId = 0;
- const getRowHeight = spy();
- render();
-
- const row = getRow(resizedRowId);
- expect(row.clientHeight).to.equal(ROW_HEIGHT);
-
- getRowHeight.resetHistory();
- act(() => apiRef.current.unstable_setRowHeight(resizedRowId, 100));
- expect(row.clientHeight).to.equal(100);
-
- // sort
- fireEvent.click(getColumnHeaderCell(resizedRowId));
-
- expect(row.clientHeight).to.equal(100);
- expect(getRowHeight.neverCalledWithMatch({ id: resizedRowId })).to.equal(true);
- });
- });
-
describe('prop: rowCount', () => {
function TestCase(props: DataGridProProps) {
return (
diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx
index 72a2bb43cb78..ccda733ed0de 100644
--- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx
+++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx
@@ -739,6 +739,7 @@ DataGridRaw.propTypes = {
* Setting it to a lower value could be useful when using dynamic row height,
* but might reduce performance when displaying a large number of rows.
* @default 166
+ * @deprecated
*/
rowPositionsDebounceMs: PropTypes.number,
/**
diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx
index 03c8b20797fc..f326364029d1 100644
--- a/packages/x-data-grid/src/components/GridRow.tsx
+++ b/packages/x-data-grid/src/components/GridRow.tsx
@@ -6,7 +6,6 @@ import { fastMemo } from '@mui/x-internals/fastMemo';
import { GridRowEventLookup } from '../models/events';
import { GridRowId, GridRowModel } from '../models/gridRows';
import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel';
-import { useGridApiContext } from '../hooks/utils/useGridApiContext';
import { gridClasses } from '../constants/gridClasses';
import { composeGridClasses } from '../utils/composeGridClasses';
import { useGridRootProps } from '../hooks/utils/useGridRootProps';
@@ -29,6 +28,7 @@ import { PinnedPosition, gridPinnedColumnPositionLookup } from './cell/GridCell'
import { GridScrollbarFillerCell as ScrollbarFiller } from './GridScrollbarFillerCell';
import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset';
import { useGridConfiguration } from '../hooks/utils/useGridConfiguration';
+import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext';
export interface GridRowProps extends React.HTMLAttributes {
row: GridRowModel;
@@ -111,7 +111,7 @@ const GridRow = React.forwardRef(function GridRow(
onMouseOver,
...other
} = props;
- const apiRef = useGridApiContext();
+ const apiRef = useGridPrivateApiContext();
const configuration = useGridConfiguration();
const ref = React.useRef(null);
const rootProps = useGridRootProps();
@@ -153,37 +153,19 @@ const GridRow = React.forwardRef(function GridRow(
React.useLayoutEffect(() => {
if (currentPage.range) {
- // The index prop is relative to the rows from all pages. As example, the index prop of the
- // first row is 5 if `paginationModel.pageSize=5` and `paginationModel.page=1`. However, the index used by the virtualization
- // doesn't care about pagination and considers the rows from the current page only, so the
- // first row always has index=0. We need to subtract the index of the first row to make it
- // compatible with the index used by the virtualization.
const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(rowId);
- // pinned rows are not part of the visible rows
- if (rowIndex != null) {
+ // Pinned rows are not part of the visible rows
+ if (rowIndex !== undefined) {
apiRef.current.unstable_setLastMeasuredRowIndex(rowIndex);
}
}
- const rootElement = ref.current;
- const hasFixedHeight = rowHeight !== 'auto';
- if (!rootElement || hasFixedHeight || typeof ResizeObserver === 'undefined') {
- return undefined;
+ if (ref.current && rowHeight === 'auto') {
+ return apiRef.current.observeRowHeight(ref.current, rowId);
}
- const resizeObserver = new ResizeObserver((entries) => {
- const [entry] = entries;
- const height =
- entry.borderBoxSize && entry.borderBoxSize.length > 0
- ? entry.borderBoxSize[0].blockSize
- : entry.contentRect.height;
- apiRef.current.unstable_storeRowHeightMeasurement(rowId, height);
- });
-
- resizeObserver.observe(rootElement);
-
- return () => resizeObserver.disconnect();
- }, [apiRef, currentPage.range, index, rowHeight, rowId]);
+ return undefined;
+ }, [apiRef, currentPage.range, rowHeight, rowId]);
const publish = React.useCallback(
(
@@ -254,22 +236,12 @@ const GridRow = React.forwardRef(function GridRow(
const rowReordering = (rootProps as any).rowReordering as boolean;
- const sizes = useGridSelector(
+ const heightEntry = useGridSelector(
apiRef,
- () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }),
+ () => ({ ...apiRef.current.getRowHeightEntry(rowId) }),
objectShallowCompare,
);
- let minHeight = rowHeight;
- if (minHeight === 'auto' && sizes) {
- const numberOfBaseSizes = 1;
- const maximumSize = sizes.baseCenter ?? 0;
-
- if (maximumSize > 0 && numberOfBaseSizes > 1) {
- minHeight = maximumSize;
- }
- }
-
const style = React.useMemo(() => {
if (isNotVisible) {
return {
@@ -282,28 +254,28 @@ const GridRow = React.forwardRef(function GridRow(
const rowStyle = {
...styleProp,
maxHeight: rowHeight === 'auto' ? 'none' : rowHeight, // max-height doesn't support "auto"
- minHeight,
+ minHeight: rowHeight,
'--height': typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight,
};
- if (sizes?.spacingTop) {
+ if (heightEntry.spacingTop) {
const property = rootProps.rowSpacingType === 'border' ? 'borderTopWidth' : 'marginTop';
- rowStyle[property] = sizes.spacingTop;
+ rowStyle[property] = heightEntry.spacingTop;
}
- if (sizes?.spacingBottom) {
+ if (heightEntry.spacingBottom) {
const property = rootProps.rowSpacingType === 'border' ? 'borderBottomWidth' : 'marginBottom';
let propertyValue = rowStyle[property];
// avoid overriding existing value
if (typeof propertyValue !== 'number') {
propertyValue = parseInt(propertyValue || '0', 10);
}
- propertyValue += sizes.spacingBottom;
+ propertyValue += heightEntry.spacingBottom;
rowStyle[property] = propertyValue;
}
return rowStyle;
- }, [isNotVisible, rowHeight, styleProp, minHeight, sizes, rootProps.rowSpacingType]);
+ }, [isNotVisible, rowHeight, styleProp, heightEntry, rootProps.rowSpacingType]);
const rowClassNames = apiRef.current.unstable_applyPipeProcessors('rowClassName', [], rowId);
const ariaAttributes = rowNode ? getRowAriaAttributes(rowNode, index) : undefined;
diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts
index 5e065f1a572a..7cd516a68c06 100644
--- a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts
+++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts
@@ -20,6 +20,7 @@ import {
import { GridRowEntry, GridRowId } from '../../../models/gridRows';
import { GridHydrateRowsValue } from '../../features/rows/gridRowsInterfaces';
import { GridPreferencePanelsValue } from '../../features/preferencesPanel';
+import { HeightEntry } from '../../features/rows/gridRowsMetaInterfaces';
export type GridPipeProcessorGroup = keyof GridPipeProcessingLookup;
@@ -41,7 +42,7 @@ export interface GridPipeProcessingLookup {
value: GridRestoreStatePreProcessingValue;
context: GridRestoreStatePreProcessingContext;
};
- rowHeight: { value: Record; context: GridRowEntry };
+ rowHeight: { value: HeightEntry; context: GridRowEntry };
scrollToIndexes: {
value: Partial;
context: Partial;
diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaInterfaces.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaInterfaces.ts
new file mode 100644
index 000000000000..c9fee83fd2ad
--- /dev/null
+++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaInterfaces.ts
@@ -0,0 +1,20 @@
+import { GridRowId } from '../../../models/gridRows';
+
+export type HeightEntry = {
+ content: number;
+ spacingTop: number;
+ spacingBottom: number;
+ detail: number;
+
+ autoHeight: boolean;
+ needsFirstMeasurement: boolean;
+};
+
+export type HeightCache = Map;
+
+export interface GridRowsMetaInternalCache {
+ /**
+ * Map of height cache entries.
+ */
+ heights: HeightCache;
+}
diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts
index e7b217aaa9a3..5630a73d551d 100644
--- a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts
+++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts
@@ -464,3 +464,8 @@ export const rowHeightWarning = [
`MUI X: The \`rowHeight\` prop should be a number greater than 0.`,
`The default value will be used instead.`,
].join('\n');
+
+export const getRowHeightWarning = [
+ `MUI X: The \`getRowHeight\` prop should return a number greater than 0 or 'auto'.`,
+ `The default value will be used instead.`,
+].join('\n');
diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts
index bb49e04a9ae6..501b8690a710 100644
--- a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts
+++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts
@@ -1,11 +1,13 @@
import * as React from 'react';
-import { unstable_debounce as debounce } from '@mui/utils';
+import useLazyRef from '@mui/utils/useLazyRef';
+import { ResizeObserver } from '../../../utils/ResizeObserver';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridRowsMetaApi, GridRowsMetaPrivateApi } from '../../../models/api/gridRowsMetaApi';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { useGridVisibleRows } from '../../utils/useGridVisibleRows';
+import { eslintUseValue } from '../../../utils/utils';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
-import { GridRowEntry, GridRowId } from '../../../models/gridRows';
+import { GridRowEntry } from '../../../models/gridRows';
import { useGridSelector } from '../../utils/useGridSelector';
import { gridDensityFactorSelector } from '../density/densitySelector';
import { gridFilterModelSelector } from '../filter/gridFilterSelector';
@@ -15,22 +17,24 @@ import { GridStateInitializer } from '../../utils/useGridInitializeState';
import { useGridRegisterPipeApplier } from '../../core/pipeProcessing';
import { gridPinnedRowsSelector } from './gridRowsSelector';
import { gridDimensionsSelector } from '../dimensions/gridDimensionsSelectors';
-import { getValidRowHeight } from './gridRowsUtils';
+import { getValidRowHeight, getRowHeightWarning } from './gridRowsUtils';
+import type { HeightEntry } from './gridRowsMetaInterfaces';
-// TODO: I think the row heights can now be encoded as a single `size` instead of `sizes.baseXxxx`
+/* eslint-disable no-underscore-dangle */
-export const rowsMetaStateInitializer: GridStateInitializer = (state) => ({
- ...state,
- rowsMeta: {
- currentPageTotalHeight: 0,
- positions: [],
- },
-});
+export const rowsMetaStateInitializer: GridStateInitializer = (state, props, apiRef) => {
+ apiRef.current.caches.rowsMeta = {
+ heights: new Map(),
+ };
-const getRowHeightWarning = [
- `MUI X: The \`getRowHeight\` prop should return a number greater than 0 or 'auto'.`,
- `The default value will be used instead.`,
-].join('\n');
+ return {
+ ...state,
+ rowsMeta: {
+ currentPageTotalHeight: 0,
+ positions: [],
+ },
+ };
+};
/**
* @requires useGridPageSize (method)
@@ -50,18 +54,12 @@ export const useGridRowsMeta = (
>,
): void => {
const { getRowHeight: getRowHeightProp, getRowSpacing, getEstimatedRowHeight } = props;
- const rowsHeightLookup = React.useRef<{
- [key: GridRowId]: {
- isResized: boolean;
- sizes: Record;
- autoHeight: boolean; // Determines if the row has dynamic height
- needsFirstMeasurement: boolean; // Determines if the row was never measured. If true, use the estimated height as row height.
- };
- }>(Object.create(null));
-
- // Inspired by https://github.com/bvaughn/react-virtualized/blob/master/source/Grid/utils/CellSizeAndPositionManager.js
+ const heightCache = apiRef.current.caches.rowsMeta.heights;
+
const lastMeasuredRowIndex = React.useRef(-1);
const hasRowWithAutoHeight = React.useRef(false);
+ const isHeightMetaValid = React.useRef(false);
+
const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector);
const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
const paginationState = useGridSelector(apiRef, gridPaginationSelector);
@@ -73,55 +71,57 @@ export const useGridRowsMeta = (
() => gridDimensionsSelector(apiRef.current.state).rowHeight,
);
- const hydrateRowsMeta = React.useCallback(() => {
- hasRowWithAutoHeight.current = false;
+ const getRowHeightEntry: GridRowsMetaPrivateApi['getRowHeightEntry'] = (rowId) => {
+ let entry = heightCache.get(rowId);
+ if (entry === undefined) {
+ entry = {
+ content: rowHeight,
+ spacingTop: 0,
+ spacingBottom: 0,
+ detail: 0,
+ autoHeight: false,
+ needsFirstMeasurement: true,
+ };
+ heightCache.set(rowId, entry);
+ }
+ return entry;
+ };
- const calculateRowProcessedSizes = (row: GridRowEntry) => {
- 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 processHeightEntry = React.useCallback(
+ (row: GridRowEntry) => {
+ // HACK: rowHeight trails behind the most up-to-date value just enough to
+ // mess the initial rowsMeta hydration :/
+ const baseRowHeight = gridDimensionsSelector(apiRef.current.state).rowHeight;
+ eslintUseValue(rowHeight);
- const { isResized, needsFirstMeasurement, sizes } = rowsHeightLookup.current[row.id];
- let baseRowHeight = typeof rowHeight === 'number' && rowHeight > 0 ? rowHeight : 52;
- const existingBaseRowHeight = sizes.baseCenter;
+ const entry = apiRef.current.getRowHeightEntry(row.id);
- if (isResized) {
- // Do not recalculate resized row height and use the value from the lookup
- baseRowHeight = existingBaseRowHeight;
- } else if (getRowHeightProp) {
+ if (!getRowHeightProp) {
+ entry.content = baseRowHeight;
+ entry.needsFirstMeasurement = false;
+ } else {
const rowHeightFromUser = getRowHeightProp({ ...row, densityFactor });
if (rowHeightFromUser === 'auto') {
- if (needsFirstMeasurement) {
+ if (entry.needsFirstMeasurement) {
const estimatedRowHeight = getEstimatedRowHeight
? getEstimatedRowHeight({ ...row, densityFactor })
- : rowHeight;
+ : baseRowHeight;
// If the row was not measured yet use the estimated row height
- baseRowHeight = estimatedRowHeight ?? rowHeight;
- } else {
- baseRowHeight = existingBaseRowHeight;
+ entry.content = estimatedRowHeight ?? baseRowHeight;
}
hasRowWithAutoHeight.current = true;
- rowsHeightLookup.current[row.id].autoHeight = true;
+ entry.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;
+ entry.content = getValidRowHeight(rowHeightFromUser, baseRowHeight, getRowHeightWarning);
+ entry.needsFirstMeasurement = false;
+ entry.autoHeight = false;
}
- } else {
- rowsHeightLookup.current[row.id].needsFirstMeasurement = false;
}
- const initialHeights = { baseCenter: baseRowHeight } as Record;
-
if (getRowSpacing) {
const indexRelativeToCurrentPage = apiRef.current.getRowIndexRelativeToVisibleRows(row.id);
@@ -132,46 +132,48 @@ export const useGridRowsMeta = (
indexRelativeToCurrentPage,
});
- initialHeights.spacingTop = spacing.top ?? 0;
- initialHeights.spacingBottom = spacing.bottom ?? 0;
+ entry.spacingTop = spacing.top ?? 0;
+ entry.spacingBottom = spacing.bottom ?? 0;
+ } else {
+ entry.spacingTop = 0;
+ entry.spacingBottom = 0;
}
- const processedSizes = apiRef.current.unstable_applyPipeProcessors(
- 'rowHeight',
- initialHeights,
- row,
- ) as Record;
+ apiRef.current.unstable_applyPipeProcessors('rowHeight', entry, row) as HeightEntry;
- rowsHeightLookup.current[row.id].sizes = processedSizes;
+ return entry;
+ },
+ [
+ apiRef,
+ currentPage.rows.length,
+ getRowHeightProp,
+ getEstimatedRowHeight,
+ rowHeight,
+ getRowSpacing,
+ densityFactor,
+ ],
+ );
- return processedSizes;
- };
+ const hydrateRowsMeta = React.useCallback(() => {
+ hasRowWithAutoHeight.current = false;
+
+ pinnedRows.top.forEach(processHeightEntry);
+ pinnedRows.bottom.forEach(processHeightEntry);
const positions: number[] = [];
const currentPageTotalHeight = currentPage.rows.reduce((acc, row) => {
positions.push(acc);
- let otherSizes = 0;
+ const entry = processHeightEntry(row);
+ const total = entry.content + entry.spacingTop + entry.spacingBottom + entry.detail;
- const processedSizes = calculateRowProcessedSizes(row);
- /* eslint-disable-next-line guard-for-in */
- for (const key in processedSizes) {
- const value = processedSizes[key];
- if (key !== 'baseCenter') {
- otherSizes += value;
- }
- }
-
- return acc + processedSizes.baseCenter + otherSizes;
+ return acc + total;
}, 0);
- pinnedRows?.top?.forEach((row) => {
- calculateRowProcessedSizes(row);
- });
-
- pinnedRows?.bottom?.forEach((row) => {
- calculateRowProcessedSizes(row);
- });
+ if (!hasRowWithAutoHeight.current) {
+ // No row has height=auto, so all rows are already measured
+ lastMeasuredRowIndex.current = Infinity;
+ }
apiRef.current.setState((state) => {
return {
@@ -183,113 +185,93 @@ export const useGridRowsMeta = (
};
});
- if (!hasRowWithAutoHeight.current) {
- // No row has height=auto, so all rows are already measured
- lastMeasuredRowIndex.current = Infinity;
- }
+ isHeightMetaValid.current = true;
+ }, [apiRef, pinnedRows, currentPage.rows, processHeightEntry]);
- 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: GridRowId): Record | undefined =>
- rowsHeightLookup.current[rowId]?.sizes;
-
- const setRowHeight = React.useCallback(
- (id: GridRowId, height: number) => {
- 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 getRowHeight: GridRowsMetaApi['unstable_getRowHeight'] = (rowId) => {
+ return heightCache.get(rowId)?.content ?? rowHeight;
+ };
- const storeMeasuredRowHeight = React.useCallback<
- GridRowsMetaApi['unstable_storeRowHeightMeasurement']
- >(
- (id, height) => {
- if (!rowsHeightLookup.current[id] || !rowsHeightLookup.current[id].autoHeight) {
- return;
- }
+ const storeRowHeightMeasurement: GridRowsMetaApi['unstable_storeRowHeightMeasurement'] = (
+ id,
+ height,
+ ) => {
+ const entry = apiRef.current.getRowHeightEntry(id);
- // Only trigger hydration if the value is different, otherwise we trigger a loop
- const needsHydration = rowsHeightLookup.current[id].sizes.baseCenter !== height;
+ const didChange = entry.content !== height;
- rowsHeightLookup.current[id].needsFirstMeasurement = false;
- rowsHeightLookup.current[id].sizes.baseCenter = height;
+ entry.needsFirstMeasurement = false;
+ entry.content = height;
- if (needsHydration) {
- debouncedHydrateRowsMeta();
- }
- },
- [debouncedHydrateRowsMeta],
- );
+ isHeightMetaValid.current &&= !didChange;
+ };
- const rowHasAutoHeight = React.useCallback((id) => {
- return rowsHeightLookup.current[id]?.autoHeight || false;
- }, []);
+ const rowHasAutoHeight: GridRowsMetaPrivateApi['rowHasAutoHeight'] = (id) => {
+ return heightCache.get(id)?.autoHeight ?? false;
+ };
- const getLastMeasuredRowIndex = React.useCallback<
- GridRowsMetaPrivateApi['getLastMeasuredRowIndex']
- >(() => {
+ const getLastMeasuredRowIndex: GridRowsMetaPrivateApi['getLastMeasuredRowIndex'] = () => {
return lastMeasuredRowIndex.current;
- }, []);
+ };
- const setLastMeasuredRowIndex = React.useCallback<
- GridRowsMetaApi['unstable_setLastMeasuredRowIndex']
- >((index) => {
+ const setLastMeasuredRowIndex: GridRowsMetaApi['unstable_setLastMeasuredRowIndex'] = (index) => {
if (hasRowWithAutoHeight.current && index > lastMeasuredRowIndex.current) {
lastMeasuredRowIndex.current = index;
}
- }, []);
+ };
- const resetRowHeights = React.useCallback(() => {
- rowsHeightLookup.current = {};
+ const resetRowHeights: GridRowsMetaApi['resetRowHeights'] = () => {
+ heightCache.clear();
hydrateRowsMeta();
- }, [hydrateRowsMeta]);
+ };
+
+ const resizeObserver = useLazyRef(
+ () =>
+ new ResizeObserver((entries) => {
+ for (let i = 0; i < entries.length; i += 1) {
+ const entry = entries[i];
+ const height =
+ entry.borderBoxSize && entry.borderBoxSize.length > 0
+ ? entry.borderBoxSize[0].blockSize
+ : entry.contentRect.height;
+ const rowId = (entry.target as any).__mui_id;
+ apiRef.current.unstable_storeRowHeightMeasurement(rowId, height);
+ }
+ if (!isHeightMetaValid.current) {
+ apiRef.current.requestPipeProcessorsApplication('rowHeight');
+ }
+ }),
+ ).current;
+
+ const observeRowHeight: GridRowsMetaPrivateApi['observeRowHeight'] = (element, rowId) => {
+ (element as any).__mui_id = rowId;
+
+ resizeObserver.observe(element);
+
+ return () => resizeObserver.unobserve(element);
+ };
+
+ useGridRegisterPipeApplier(apiRef, 'rowHeight', 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);
+ }, [filterModel, paginationState, sortModel, hydrateRowsMeta]);
const rowsMetaApi: GridRowsMetaApi = {
- unstable_setLastMeasuredRowIndex: setLastMeasuredRowIndex,
unstable_getRowHeight: getRowHeight,
- unstable_getRowInternalSizes: getRowInternalSizes,
- unstable_setRowHeight: setRowHeight,
- unstable_storeRowHeightMeasurement: storeMeasuredRowHeight,
+ unstable_setLastMeasuredRowIndex: setLastMeasuredRowIndex,
+ unstable_storeRowHeightMeasurement: storeRowHeightMeasurement,
resetRowHeights,
};
const rowsMetaPrivateApi: GridRowsMetaPrivateApi = {
- getLastMeasuredRowIndex,
+ hydrateRowsMeta,
+ observeRowHeight,
rowHasAutoHeight,
+ getRowHeightEntry,
+ getLastMeasuredRowIndex,
};
useGridApiMethod(apiRef, rowsMetaApi, 'public');
diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts b/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts
index fb7935e9d913..bbaaa2b3eb6c 100644
--- a/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts
+++ b/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts
@@ -1,4 +1,5 @@
import * as React from 'react';
+import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { GridPrivateApiCommon } from '../../models/api/gridApiCommon';
type GetPublicApiType = PrivateApi extends { getPublicApi: () => infer PublicApi }
@@ -14,7 +15,7 @@ export function useGridApiMethod<
>(privateApiRef: React.MutableRefObject, apiMethods: T, visibility: V) {
const isFirstRender = React.useRef(true);
- React.useEffect(() => {
+ useEnhancedEffect(() => {
isFirstRender.current = false;
privateApiRef.current.register(visibility, apiMethods);
}, [privateApiRef, visibility, apiMethods]);
diff --git a/packages/x-data-grid/src/models/api/gridRowsMetaApi.ts b/packages/x-data-grid/src/models/api/gridRowsMetaApi.ts
index 77e744b599aa..9dba011f4175 100644
--- a/packages/x-data-grid/src/models/api/gridRowsMetaApi.ts
+++ b/packages/x-data-grid/src/models/api/gridRowsMetaApi.ts
@@ -1,4 +1,5 @@
import { GridRowId } from '../gridRows';
+import { HeightEntry } from '../../hooks/features/rows/gridRowsMetaInterfaces';
/**
* The Row Meta API interface that is available in the grid `apiRef`.
@@ -11,20 +12,6 @@ export interface GridRowsMetaApi {
* @ignore - do not document.
*/
unstable_getRowHeight: (id: GridRowId) => number;
- /**
- * Gets all sizes that compose the total height that the given row takes.
- * @param {GridRowId} id The id of the row.
- * @returns {Record} The object containing the sizes.
- * @ignore - do not document.
- */
- unstable_getRowInternalSizes: (id: GridRowId) => Record | undefined;
- /**
- * Updates the base height of a row.
- * @param {GridRowId} id The id of the row.
- * @param {number} height The new height.
- * @ignore - do not document.
- */
- unstable_setRowHeight: (id: GridRowId, height: number) => void;
/**
* Stores the row height measurement and triggers an hydration, if needed.
* @param {GridRowId} id The id of the row.
@@ -46,6 +33,14 @@ export interface GridRowsMetaApi {
}
export interface GridRowsMetaPrivateApi {
+ hydrateRowsMeta: () => void;
+ /**
+ * Observe row for 'auto' height changes.
+ * @param {Element} element The row element to observe.
+ * @param {GridRowId} rowId The id of the row.
+ * @returns {ReturnType} A dispose callback
+ */
+ observeRowHeight: (element: Element, rowId: GridRowId) => ReturnType;
/**
* Determines if the height of a row is "auto".
* @param {GridRowId} id The id of the row.
@@ -58,4 +53,10 @@ export interface GridRowsMetaPrivateApi {
* @returns {number} The index of the last measured row.
*/
getLastMeasuredRowIndex: () => number;
+ /**
+ * Get the height entry from the cache or create one.
+ * @param {GridRowId} id The id of the row.
+ * @returns {HeightEntry} The height cache entry
+ */
+ getRowHeightEntry: (id: GridRowId) => HeightEntry;
}
diff --git a/packages/x-data-grid/src/models/gridApiCaches.ts b/packages/x-data-grid/src/models/gridApiCaches.ts
index aa2e8932b91d..5b3652b69224 100644
--- a/packages/x-data-grid/src/models/gridApiCaches.ts
+++ b/packages/x-data-grid/src/models/gridApiCaches.ts
@@ -1,5 +1,7 @@
import { GridRowsInternalCache } from '../hooks/features/rows/gridRowsInterfaces';
+import { GridRowsMetaInternalCache } from '../hooks/features/rows/gridRowsMetaInterfaces';
export interface GridApiCaches {
rows: GridRowsInternalCache;
+ rowsMeta: GridRowsMetaInternalCache;
}
diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts
index 866b882977f6..e831f19f6eeb 100644
--- a/packages/x-data-grid/src/models/props/DataGridProps.ts
+++ b/packages/x-data-grid/src/models/props/DataGridProps.ts
@@ -377,8 +377,9 @@ export interface DataGridPropsWithDefaultValues