diff --git a/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js
new file mode 100644
index 000000000000..433301b5a5c7
--- /dev/null
+++ b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js
@@ -0,0 +1,172 @@
+import * as React from 'react';
+import DeleteIcon from '@mui/icons-material/Delete';
+import EditIcon from '@mui/icons-material/Edit';
+import PrintIcon from '@mui/icons-material/Print';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro';
+import {
+ randomCreatedDate,
+ randomTraderName,
+ randomEmail,
+ randomUpdatedDate,
+} from '@mui/x-data-grid-generator';
+
+export default function ColumnPinningDynamicRowHeight() {
+ const apiRef = useGridApiRef();
+ const [showEditDelete, setShowEditDelete] = React.useState(true);
+
+ const columns = React.useMemo(
+ () => [
+ { field: 'name', headerName: 'Name', width: 160, editable: true },
+ { field: 'email', headerName: 'Email', width: 200, editable: true },
+ { field: 'age', headerName: 'Age', type: 'number', editable: true },
+ {
+ field: 'dateCreated',
+ headerName: 'Date Created',
+ type: 'date',
+ width: 180,
+ editable: true,
+ },
+ {
+ field: 'lastLogin',
+ headerName: 'Last Login',
+ type: 'dateTime',
+ width: 220,
+ editable: true,
+ },
+ {
+ field: 'actions',
+ headerName: 'Actions',
+ width: 100,
+ renderCell: () => (
+
+ {showEditDelete && (
+
+ }>
+ Edit
+
+ }>
+ Delete
+
+
+ )}
+
+ }>
+ Print
+
+
+ ),
+ },
+ ],
+ [showEditDelete],
+ );
+
+ const handleToggleClick = React.useCallback(() => {
+ setShowEditDelete((prevShowEditDelete) => !prevShowEditDelete);
+ }, []);
+
+ React.useLayoutEffect(() => {
+ apiRef.current.resetRowHeights();
+ }, [apiRef, showEditDelete]);
+
+ return (
+
+
+
+ 'auto'}
+ initialState={{ pinnedColumns: { left: ['name'], right: ['actions'] } }}
+ />
+
+
+ );
+}
+
+const rows = [
+ {
+ id: 1,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 25,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 2,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 36,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 3,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 19,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 4,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 28,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 5,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 23,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 6,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 27,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 7,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 18,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 8,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 31,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 9,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 24,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 10,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 35,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+];
diff --git a/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.tsx b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.tsx
new file mode 100644
index 000000000000..b3f8cafbcdb0
--- /dev/null
+++ b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.tsx
@@ -0,0 +1,176 @@
+import * as React from 'react';
+import DeleteIcon from '@mui/icons-material/Delete';
+import EditIcon from '@mui/icons-material/Edit';
+import PrintIcon from '@mui/icons-material/Print';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import {
+ DataGridPro,
+ GridColumns,
+ GridRowsProp,
+ useGridApiRef,
+} from '@mui/x-data-grid-pro';
+import {
+ randomCreatedDate,
+ randomTraderName,
+ randomEmail,
+ randomUpdatedDate,
+} from '@mui/x-data-grid-generator';
+
+export default function ColumnPinningDynamicRowHeight() {
+ const apiRef = useGridApiRef();
+ const [showEditDelete, setShowEditDelete] = React.useState(true);
+
+ const columns: GridColumns = React.useMemo(
+ () => [
+ { field: 'name', headerName: 'Name', width: 160, editable: true },
+ { field: 'email', headerName: 'Email', width: 200, editable: true },
+ { field: 'age', headerName: 'Age', type: 'number', editable: true },
+ {
+ field: 'dateCreated',
+ headerName: 'Date Created',
+ type: 'date',
+ width: 180,
+ editable: true,
+ },
+ {
+ field: 'lastLogin',
+ headerName: 'Last Login',
+ type: 'dateTime',
+ width: 220,
+ editable: true,
+ },
+ {
+ field: 'actions',
+ headerName: 'Actions',
+ width: 100,
+ renderCell: () => (
+
+ {showEditDelete && (
+
+ }>
+ Edit
+
+ }>
+ Delete
+
+
+ )}
+ }>
+ Print
+
+
+ ),
+ },
+ ],
+ [showEditDelete],
+ );
+
+ const handleToggleClick = React.useCallback(() => {
+ setShowEditDelete((prevShowEditDelete) => !prevShowEditDelete);
+ }, []);
+
+ React.useLayoutEffect(() => {
+ apiRef.current.resetRowHeights();
+ }, [apiRef, showEditDelete]);
+
+ return (
+
+
+
+ 'auto'}
+ initialState={{ pinnedColumns: { left: ['name'], right: ['actions'] } }}
+ />
+
+
+ );
+}
+
+const rows: GridRowsProp = [
+ {
+ id: 1,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 25,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 2,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 36,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 3,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 19,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 4,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 28,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 5,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 23,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 6,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 27,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 7,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 18,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 8,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 31,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 9,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 24,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+ {
+ id: 10,
+ name: randomTraderName(),
+ email: randomEmail(),
+ age: 35,
+ dateCreated: randomCreatedDate(),
+ lastLogin: randomUpdatedDate(),
+ },
+];
diff --git a/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.tsx.preview b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.tsx.preview
new file mode 100644
index 000000000000..8cab3f590970
--- /dev/null
+++ b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.tsx.preview
@@ -0,0 +1,12 @@
+
+
+ 'auto'}
+ initialState={{ pinnedColumns: { left: ['name'], right: ['actions'] } }}
+ />
+
\ No newline at end of file
diff --git a/docs/data/data-grid/column-pinning/column-pinning.md b/docs/data/data-grid/column-pinning/column-pinning.md
index adfbe360021e..1f9931265b04 100644
--- a/docs/data/data-grid/column-pinning/column-pinning.md
+++ b/docs/data/data-grid/column-pinning/column-pinning.md
@@ -77,6 +77,16 @@ To pin the checkbox column added when using `checkboxSelection`, add `GRID_CHECK
{{"demo": "ColumnPinningWithCheckboxSelection.js", "disableAd": true, "bg": "inline"}}
+## Usage with dynamic row height
+
+You can have both pinned columns and [dynamic row height](/x/react-data-grid/row-height/#dynamic-row-height) enabled at the same time.
+However, if the rows change their content after the initial calculation, you may need to trigger a manual recalculation to avoid incorrect measurements.
+You can do this by calling `apiRef.current.resetRowHeights()` every time that the content changes.
+
+The demo below contains an example of both features enabled:
+
+{{"demo": "ColumnPinningDynamicRowHeight.js", "disableAd": true, "bg": "inline"}}
+
## apiRef
{{"demo": "ColumnPinningApiNoSnap.js", "bg": "inline", "hideToolbar": true}}
diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md
index 046966d80cee..c53c0a5b3984 100644
--- a/docs/pages/x/api/data-grid/grid-api.md
+++ b/docs/pages/x/api/data-grid/grid-api.md
@@ -71,6 +71,7 @@ import { GridApi } from '@mui/x-data-grid-pro';
| pinColumn [](/x/introduction/licensing/#pro-plan) | (field: string, side: GridPinnedPosition) => void | Pins a column to the left or right side of the grid. |
| publishEvent | GridEventPublisher | Emits an event. |
| removeRowGroupingCriteria [](https://mui.com/store/items/material-ui-premium/) | (groupingCriteriaField: string) => void | Remove the field from the row grouping model. |
+| resetRowHeights | () => void | Forces the recalculation of the heights of all rows. |
| resize | () => void | Triggers a resize of the component and recalculation of width and height. |
| restoreState | (stateToRestore: InitialState) => void | Inject the given values into the state of the DataGrid. |
| scroll | (params: Partial<GridScrollParams>) => void | Triggers the viewport to scroll to the given positions (in pixels). |
diff --git a/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx b/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx
index a691b282005f..7acc3f9041be 100644
--- a/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx
+++ b/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx
@@ -331,19 +331,21 @@ const DataGridProVirtualScroller = React.forwardRef<
const detailPanels = getDetailPanels();
- const topPinnedRows = getRows({ renderContext, rows: topPinnedRowsData });
+ const topPinnedRows = getRows({ renderContext, rows: topPinnedRowsData, position: 'center' });
const pinnedRowsHeight = calculatePinnedRowsHeight(apiRef);
const mainRows = getRows({
renderContext,
rowIndexOffset: topPinnedRowsData.length,
+ position: 'center',
});
const bottomPinnedRows = getRows({
renderContext,
rows: bottomPinnedRowsData,
rowIndexOffset: topPinnedRowsData.length + (mainRows ? mainRows.length : 0),
+ position: 'center',
});
const contentProps = getContentProps();
@@ -373,8 +375,8 @@ const DataGridProVirtualScroller = React.forwardRef<
minFirstColumn: leftRenderContext.firstColumnIndex,
maxLastColumn: leftRenderContext.lastColumnIndex,
availableSpace: 0,
- ignoreAutoHeight: true,
rows: topPinnedRowsData,
+ position: 'left',
})}
)}
@@ -394,9 +396,9 @@ const DataGridProVirtualScroller = React.forwardRef<
renderContext: rightRenderContext,
minFirstColumn: rightRenderContext.firstColumnIndex,
maxLastColumn: rightRenderContext.lastColumnIndex,
- ignoreAutoHeight: true,
availableSpace: 0,
rows: topPinnedRowsData,
+ position: 'right',
})}
)}
@@ -415,8 +417,8 @@ const DataGridProVirtualScroller = React.forwardRef<
minFirstColumn: leftRenderContext.firstColumnIndex,
maxLastColumn: leftRenderContext.lastColumnIndex,
availableSpace: 0,
- ignoreAutoHeight: true,
rowIndexOffset: topPinnedRowsData.length,
+ position: 'left',
})}
)}
@@ -435,8 +437,8 @@ const DataGridProVirtualScroller = React.forwardRef<
minFirstColumn: rightRenderContext.firstColumnIndex,
maxLastColumn: rightRenderContext.lastColumnIndex,
availableSpace: 0,
- ignoreAutoHeight: true,
rowIndexOffset: topPinnedRowsData.length,
+ position: 'right',
})}
)}
@@ -463,9 +465,9 @@ const DataGridProVirtualScroller = React.forwardRef<
minFirstColumn: leftRenderContext.firstColumnIndex,
maxLastColumn: leftRenderContext.lastColumnIndex,
availableSpace: 0,
- ignoreAutoHeight: true,
rows: bottomPinnedRowsData,
rowIndexOffset: topPinnedRowsData.length + (mainRows ? mainRows.length : 0),
+ position: 'left',
})}
)}
@@ -486,9 +488,9 @@ const DataGridProVirtualScroller = React.forwardRef<
minFirstColumn: rightRenderContext.firstColumnIndex,
maxLastColumn: rightRenderContext.lastColumnIndex,
availableSpace: 0,
- ignoreAutoHeight: true,
rows: bottomPinnedRowsData,
rowIndexOffset: topPinnedRowsData.length + (mainRows ? mainRows.length : 0),
+ position: 'right',
})}
)}
diff --git a/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
index 3c9e7e5aa6bb..589bb4958b68 100644
--- a/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
@@ -20,6 +20,7 @@ import {
} from '@mui/monorepo/test/utils';
import { getCell, getColumnHeaderCell, getColumnHeadersTextContent } from 'test/utils/helperFn';
import { useData } from 'storybook/src/hooks/useData';
+import { getData } from 'storybook/src/data/data-service';
// TODO Move to utils
// Fix https://github.com/mui/mui-x/pull/2085/files/058f56ac3c729b2142a9a28b79b5b13535cdb819#diff-db85480a519a5286d7341e9b8957844762cf04cdacd946331ebaaaff287482ec
@@ -50,6 +51,39 @@ describe(' - Column pinning', () => {
);
};
+ function ResizeObserverMock(
+ callback: (entries: { borderBoxSize: [{ blockSize: number }] }[]) => void,
+ ) {
+ let timeout: NodeJS.Timeout;
+
+ return {
+ observe: (element: HTMLElement) => {
+ // Simulates the async behavior of the native ResizeObserver
+ timeout = setTimeout(() => {
+ callback([{ borderBoxSize: [{ blockSize: element.clientHeight }] }]);
+ });
+ },
+ disconnect: () => {
+ clearTimeout(timeout);
+ },
+ };
+ }
+
+ const originalResizeObserver = window.ResizeObserver;
+
+ beforeEach(() => {
+ const { userAgent } = window.navigator;
+
+ if (userAgent.includes('Chrome') && !userAgent.includes('Headless')) {
+ // Only use the mock in non-headless Chrome
+ window.ResizeObserver = ResizeObserverMock as any;
+ }
+ });
+
+ afterEach(() => {
+ window.ResizeObserver = originalResizeObserver;
+ });
+
it('should scroll when the next cell to focus is covered by the left pinned columns', function test() {
if (isJSDOM) {
// Need layouting
@@ -229,6 +263,97 @@ describe(' - Column pinning', () => {
expect(getColumnHeadersTextContent()).to.deep.equal(['id', '', 'Currency Pair']);
});
+ describe('dynamic row height', () => {
+ it('should work with dynamic row height', async function test() {
+ if (isJSDOM) {
+ // Need layouting
+ this.skip();
+ }
+
+ const Test = ({ bioHeight }: { bioHeight: number }) => {
+ const data = React.useMemo(() => getData(1, 2), []);
+
+ const columns = [
+ ...data.columns,
+ { field: 'bio', renderCell: () => },
+ ];
+
+ return (
+ 'auto'}
+ initialState={{ pinnedColumns: { left: ['id'], right: ['bio'] } }}
+ />
+ );
+ };
+
+ render();
+ await act(() => Promise.resolve());
+ clock.runToLast();
+ const leftRow = document.querySelector(`.${gridClasses['pinnedColumns--left']} [role="row"]`);
+ expect(leftRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '101px' });
+ const centerRow = document.querySelector(
+ `.${gridClasses.virtualScrollerRenderZone} [role="row"]`,
+ );
+ expect(centerRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '101px' });
+ const rightRow = document.querySelector(
+ `.${gridClasses['pinnedColumns--right']} [role="row"]`,
+ );
+ expect(rightRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '101px' });
+ });
+
+ it('should react to content height changes', async function test() {
+ if (isJSDOM) {
+ // Need layouting
+ this.skip();
+ }
+
+ const Test = ({ bioHeight }: { bioHeight: number }) => {
+ const data = React.useMemo(() => getData(1, 2), []);
+
+ const columns = [
+ ...data.columns,
+ { field: 'bio', renderCell: () => },
+ ];
+
+ return (
+ 'auto'}
+ initialState={{ pinnedColumns: { left: ['id'], right: ['bio'] } }}
+ />
+ );
+ };
+
+ const { setProps } = render();
+ await act(() => Promise.resolve());
+ clock.runToLast();
+ const centerRow = document.querySelector(
+ `.${gridClasses.virtualScrollerRenderZone} [role="row"]`,
+ );
+ expect(centerRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '101px' });
+
+ setProps({ bioHeight: 200 });
+ await act(() => Promise.resolve());
+ clock.runToLast();
+ expect(centerRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '201px' });
+
+ setProps({ bioHeight: 100 });
+ await act(() => Promise.resolve());
+ clock.runToLast();
+ // If the new height is smaller than the current one, it won't be reflected unless
+ // apiRef.current.resetRowHeights() is called
+ expect(centerRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '201px' });
+
+ act(() => apiRef.current.resetRowHeights());
+ await act(() => Promise.resolve());
+ clock.runToLast();
+ expect(centerRow).toHaveInlineStyle({ maxHeight: 'none', minHeight: '101px' });
+ });
+ });
+
describe('prop: onPinnedColumnsChange', () => {
it('should call when a column is pinned', () => {
const handlePinnedColumnsChange = spy();
diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx
index 4dc39966e32c..2a3d2fbf9202 100644
--- a/packages/grid/x-data-grid/src/components/GridRow.tsx
+++ b/packages/grid/x-data-grid/src/components/GridRow.tsx
@@ -48,6 +48,7 @@ export interface GridRowProps {
cellFocus: GridCellIdentifier | null;
cellTabIndex: GridCellIdentifier | null;
editRowsState: GridEditRowsModel;
+ position: 'left' | 'center' | 'right';
row?: GridRowModel;
isLastVisible?: boolean;
onClick?: React.MouseEventHandler;
@@ -97,6 +98,7 @@ function GridRow(props: React.HTMLAttributes & GridRowProps) {
row,
index,
style: styleProp,
+ position,
rowHeight,
className,
visibleColumns,
@@ -143,9 +145,9 @@ function GridRow(props: React.HTMLAttributes & GridRowProps) {
React.useLayoutEffect(() => {
if (rowHeight === 'auto' && ref.current && typeof ResizeObserver === 'undefined') {
// Fallback for IE
- apiRef.current.unstable_storeRowHeightMeasurement(rowId, ref.current.clientHeight);
+ apiRef.current.unstable_storeRowHeightMeasurement(rowId, ref.current.clientHeight, position);
}
- }, [apiRef, rowHeight, rowId]);
+ }, [apiRef, rowHeight, rowId, position]);
React.useLayoutEffect(() => {
if (currentPage.range) {
@@ -173,13 +175,13 @@ function GridRow(props: React.HTMLAttributes & GridRowProps) {
entry.borderBoxSize && entry.borderBoxSize.length > 0
? entry.borderBoxSize[0].blockSize
: entry.contentRect.height;
- apiRef.current.unstable_storeRowHeightMeasurement(rowId, height);
+ apiRef.current.unstable_storeRowHeightMeasurement(rowId, height, position);
});
resizeObserver.observe(rootElement);
return () => resizeObserver.disconnect();
- }, [apiRef, currentPage.range, index, rowHeight, rowId]);
+ }, [apiRef, currentPage.range, index, rowHeight, rowId, position]);
const publish = React.useCallback(
(
@@ -365,14 +367,34 @@ function GridRow(props: React.HTMLAttributes & GridRowProps) {
],
);
+ const sizes = apiRef.current.unstable_getRowInternalSizes(rowId);
+
+ let minHeight = rowHeight;
+ if (minHeight === 'auto' && sizes) {
+ let numberOfBaseSizes = 0;
+ const maximumSize = Object.entries(sizes).reduce((acc, [key, size]) => {
+ const isBaseHeight = /^base[A-Z]/.test(key);
+ if (!isBaseHeight) {
+ return acc;
+ }
+ numberOfBaseSizes += 1;
+ if (size > acc) {
+ return size;
+ }
+ return acc;
+ }, 0);
+
+ if (maximumSize > 0 && numberOfBaseSizes > 1) {
+ minHeight = maximumSize;
+ }
+ }
+
const style = {
...styleProp,
maxHeight: rowHeight === 'auto' ? 'none' : rowHeight, // max-height doesn't support "auto"
- minHeight: rowHeight,
+ minHeight,
};
- const sizes = apiRef.current.unstable_getRowInternalSizes(rowId);
-
if (sizes?.spacingTop) {
const property = rootProps.rowSpacingType === 'border' ? 'borderTopWidth' : 'marginTop';
style[property] = sizes.spacingTop;
@@ -484,6 +506,7 @@ GridRow.propTypes = {
index: PropTypes.number.isRequired,
isLastVisible: PropTypes.bool,
lastColumnToRender: PropTypes.number.isRequired,
+ position: PropTypes.oneOf(['center', 'left', 'right']).isRequired,
renderedColumns: PropTypes.arrayOf(PropTypes.object).isRequired,
row: PropTypes.object,
rowHeight: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired,
diff --git a/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts b/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts
index 3174e6ed9cbf..d848f746098b 100644
--- a/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts
+++ b/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { debounce } from '@mui/material/utils';
+import { debounce, capitalize } from '@mui/material/utils';
import { GridApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridRowsMetaApi } from '../../../models/api/gridRowsMetaApi';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
@@ -69,7 +69,7 @@ export const useGridRowsMeta = (
const calculateRowProcessedSizes = (row: GridRowEntry) => {
if (!rowsHeightLookup.current[row.id]) {
rowsHeightLookup.current[row.id] = {
- sizes: { base: rowHeightFromDensity },
+ sizes: { baseCenter: rowHeightFromDensity },
isResized: false,
autoHeight: false,
needsFirstMeasurement: true, // Assume all rows will need to be measured by default
@@ -78,7 +78,7 @@ export const useGridRowsMeta = (
const { isResized, needsFirstMeasurement, sizes } = rowsHeightLookup.current[row.id];
let baseRowHeight = rowHeightFromDensity;
- const existingBaseRowHeight = sizes.base;
+ const existingBaseRowHeight = sizes.baseCenter;
if (isResized) {
// Do not recalculate resized row height and use the value from the lookup
@@ -110,8 +110,21 @@ export const useGridRowsMeta = (
rowsHeightLookup.current[row.id].needsFirstMeasurement = false;
}
+ const existingBaseSizes = Object.entries(sizes).reduce>(
+ (acc, [key, size]) => {
+ if (/^base[A-Z]/.test(key)) {
+ acc[key] = size;
+ }
+ return acc;
+ },
+ {},
+ );
+
// We use an object to make simple to check if a height is already added or not
- const initialHeights: Record = { base: baseRowHeight };
+ const initialHeights: Record = {
+ ...existingBaseSizes,
+ baseCenter: baseRowHeight,
+ };
if (getRowSpacing) {
const indexRelativeToCurrentPage = apiRef.current.getRowIndexRelativeToVisibleRows(row.id);
@@ -142,10 +155,19 @@ export const useGridRowsMeta = (
const currentPageTotalHeight = currentPage.rows.reduce((acc, row) => {
positions.push(acc);
+ let maximumBaseSize = 0;
+ let otherSizes = 0;
+
const processedSizes = calculateRowProcessedSizes(row);
+ Object.entries(processedSizes).forEach(([size, value]) => {
+ if (/^base[A-Z]/.test(size)) {
+ maximumBaseSize = value > maximumBaseSize ? value : maximumBaseSize;
+ } else {
+ otherSizes += value;
+ }
+ });
- const finalRowHeight = Object.values(processedSizes).reduce((acc2, value) => acc2 + value, 0);
- return acc + finalRowHeight;
+ return acc + maximumBaseSize + otherSizes;
}, 0);
pinnedRows?.top?.forEach((row) => {
@@ -185,7 +207,7 @@ export const useGridRowsMeta = (
const getRowHeight = React.useCallback(
(rowId) => {
const height = rowsHeightLookup.current[rowId];
- return height ? height.sizes.base : rowHeightFromDensity;
+ return height ? height.sizes.baseCenter : rowHeightFromDensity;
},
[rowHeightFromDensity],
);
@@ -195,7 +217,7 @@ export const useGridRowsMeta = (
const setRowHeight = React.useCallback(
(id: GridRowId, height: number) => {
- rowsHeightLookup.current[id].sizes.base = height;
+ rowsHeightLookup.current[id].sizes.baseCenter = height;
rowsHeightLookup.current[id].isResized = true;
rowsHeightLookup.current[id].needsFirstMeasurement = false;
hydrateRowsMeta();
@@ -211,16 +233,17 @@ export const useGridRowsMeta = (
const storeMeasuredRowHeight = React.useCallback<
GridRowsMetaApi['unstable_storeRowHeightMeasurement']
>(
- (id, height) => {
+ (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 !== height;
+ const needsHydration =
+ rowsHeightLookup.current[id].sizes[`base${capitalize(position)}`] !== height;
rowsHeightLookup.current[id].needsFirstMeasurement = false;
- rowsHeightLookup.current[id].sizes.base = height;
+ rowsHeightLookup.current[id].sizes[`base${capitalize(position)}`] = height;
if (needsHydration) {
debouncedHydrateRowsMeta();
@@ -247,6 +270,11 @@ export const useGridRowsMeta = (
}
}, []);
+ 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(() => {
@@ -263,6 +291,7 @@ export const useGridRowsMeta = (
unstable_getRowInternalSizes: getRowInternalSizes,
unstable_setRowHeight: setRowHeight,
unstable_storeRowHeightMeasurement: storeMeasuredRowHeight,
+ resetRowHeights,
};
useGridApiMethod(apiRef, rowsMetaApi, 'GridRowsMetaApi');
diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
index a93119b8fcff..4b720107f2cc 100644
--- a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
+++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
@@ -377,10 +377,10 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
const getRows = (
params: {
renderContext: GridRenderContext | null;
+ position?: string;
minFirstColumn?: number;
maxLastColumn?: number;
availableSpace?: number | null;
- ignoreAutoHeight?: boolean;
rows?: GridRowEntry[];
rowIndexOffset?: number;
} = { renderContext },
@@ -390,8 +390,8 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
minFirstColumn = renderZoneMinColumnIndex,
maxLastColumn = renderZoneMaxColumnIndex,
availableSpace = containerWidth,
- ignoreAutoHeight,
rowIndexOffset = 0,
+ position = 'center',
} = params;
if (!nextRenderContext || availableSpace == null) {
@@ -461,10 +461,9 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
for (let i = 0; i < renderedRows.length; i += 1) {
const { id, model } = renderedRows[i];
const lastVisibleRowIndex = firstRowToRender + i === currentPage.rows.length - 1;
- const baseRowHeight =
- !apiRef.current.unstable_rowHasAutoHeight(id) || ignoreAutoHeight
- ? apiRef.current.unstable_getRowHeight(id)
- : 'auto';
+ const baseRowHeight = !apiRef.current.unstable_rowHasAutoHeight(id)
+ ? apiRef.current.unstable_getRowHeight(id)
+ : 'auto';
let isSelected: boolean;
if (selectedRowsLookup[id] == null) {
@@ -490,6 +489,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
index={rowIndexOffset + (currentPage?.range?.firstRowIndex || 0) + firstRowToRender + i}
containerWidth={availableSpace}
isLastVisible={lastVisibleRowIndex}
+ position={position}
{...(typeof getRowProps === 'function' ? getRowProps(id, model) : {})}
{...rootProps.componentsProps?.row}
/>,
diff --git a/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts b/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts
index 6917d286b626..8adf0b0236c8 100644
--- a/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts
+++ b/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts
@@ -29,9 +29,14 @@ export interface GridRowsMetaApi {
* Stores the row height measurement and triggers an hydration, if needed.
* @param {GridRowId} id The id of the row.
* @param {number} height The new height.
+ * @param {string} position The position to it the row belongs to.
* @ignore - do not document.
*/
- unstable_storeRowHeightMeasurement: (id: GridRowId, height: number) => void;
+ unstable_storeRowHeightMeasurement: (
+ id: GridRowId,
+ height: number,
+ position: 'left' | 'center' | 'right',
+ ) => void;
/**
* Determines if the height of a row is "auto".
* @ignore - do not document.
@@ -49,4 +54,8 @@ export interface GridRowsMetaApi {
* @ignore - do not document.
*/
unstable_setLastMeasuredRowIndex: (index: number) => void;
+ /**
+ * Forces the recalculation of the heights of all rows.
+ */
+ resetRowHeights: () => void;
}