From 58be1fe33d3cb5a8080aea31651bc70037f2b104 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Wed, 26 Jan 2022 18:19:06 +0100 Subject: [PATCH 1/8] [DataGrid] Add unstable_setRowHeight method to apiRef --- .../hooks/features/rows/useGridRowsMeta.ts | 41 +++++++++++++++---- .../grid/models/api/gridRowsMetaApi.ts | 7 ++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts index 0061d1ce85ac4..73d3384c14e31 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts @@ -24,7 +24,9 @@ export const useGridRowsMeta = ( props: Pick<DataGridProcessedProps, 'getRowHeight' | 'pagination' | 'paginationMode'>, ): void => { const { getRowHeight, pagination, paginationMode } = props; - const rowsHeightLookup = React.useRef<{ [key: GridRowId]: number }>({}); + const rowsHeightLookup = React.useRef<{ + [key: GridRowId]: { value: number; isResized: boolean }; + }>({}); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); const filterState = useGridSelector(apiRef, gridFilterStateSelector); const paginationState = useGridSelector(apiRef, gridPaginationSelector); @@ -50,14 +52,25 @@ export const useGridRowsMeta = ( const currentRowHeight = gridDensityRowHeightSelector(state); const currentPageTotalHeight = rows.reduce((acc: number, row) => { positions.push(acc); - let targetRowHeight = currentRowHeight; - if (getRowHeight) { - // Default back to base rowHeight if getRowHeight returns null or undefined. - targetRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight; - } + let targetRowHeight: number; + + if (rowsHeightLookup.current[row.id] && rowsHeightLookup.current[row.id].isResized) { + // do not recalculate resized row height and use the value from the lookup + targetRowHeight = rowsHeightLookup.current[row.id].value; + } else { + targetRowHeight = currentRowHeight; - rowsHeightLookup.current[row.id] = targetRowHeight; + if (getRowHeight) { + // Default back to base rowHeight if getRowHeight returns null or undefined. + targetRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight; + } + + rowsHeightLookup.current[row.id] = { + value: targetRowHeight, + isResized: false, + }; + } return acc + targetRowHeight; }, 0); @@ -71,7 +84,18 @@ export const useGridRowsMeta = ( }, [apiRef, pagination, paginationMode, getRowHeight]); const getTargetRowHeight = (rowId: GridRowId): number => - rowsHeightLookup.current[rowId] || rowHeight; + rowsHeightLookup.current[rowId]?.value || rowHeight; + + const setRowHeight = React.useCallback<GridRowsMetaApi['unstable_setRowHeight']>( + (id: GridRowId, height: number) => { + rowsHeightLookup.current[id] = { + value: height, + isResized: true, + }; + 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 @@ -81,6 +105,7 @@ export const useGridRowsMeta = ( const rowsMetaApi: GridRowsMetaApi = { unstable_getRowHeight: getTargetRowHeight, + unstable_setRowHeight: setRowHeight, }; useGridApiMethod(apiRef, rowsMetaApi, 'GridRowsMetaApi'); diff --git a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts index 1251f19ed9e40..f5d8ced38bcf8 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts @@ -11,4 +11,11 @@ export interface GridRowsMetaApi { * @ignore - do not document. */ unstable_getRowHeight: (id: GridRowId) => number; + /** + * Updates the 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; } From 038e6114da98f81034b4386f2688341a0eae522f Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Wed, 26 Jan 2022 18:25:38 +0100 Subject: [PATCH 2/8] add story --- .../src/stories/grid-rows.stories.tsx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 3bfeecd50ff8a..3967814bf96c1 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -6,6 +6,7 @@ import Button from '@mui/material/Button'; import Popper from '@mui/material/Popper'; import Paper from '@mui/material/Paper'; import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; import { GridCellValue, GridCellParams, @@ -18,6 +19,7 @@ import { GridEvents, MuiEvent, GridEventListener, + GridSelectionModel, } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; import { action } from '@storybook/addon-actions'; @@ -1072,3 +1074,67 @@ export function VariableRowHeight() { </div> ); } + +export function SetRowHeight() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 1000, + }); + + const [selectionModel, setSelectionModel] = React.useState<GridSelectionModel>([]); + React.useEffect(() => { + if (data.rows.length > 0) { + setSelectionModel([data.rows[0].id]); + } + }, [data.rows]); + + const apiRef = useGridApiRef(); + + const handleSubmit = (event: React.SyntheticEvent) => { + event.preventDefault(); + const target = event.target as typeof event.target & { + height: { value: string }; + }; + + const height = Number(target.height.value); + + selectionModel.forEach((id) => { + apiRef.current.unstable_setRowHeight(id, height); + }); + }; + + return ( + <div style={{ height: 600 }}> + <form style={{ display: 'flex', margin: '16px 0' }} onSubmit={handleSubmit}> + <TextField + name="height" + label="Row height" + size="small" + defaultValue="120" + sx={{ mr: 1 }} + /> + <Button type="submit" variant="outlined"> + Set row height + </Button> + </form> + <DataGridPro + {...data} + apiRef={apiRef} + selectionModel={selectionModel} + onSelectionModelChange={(newModel) => setSelectionModel(newModel)} + getRowHeight={({ model }) => { + if ( + model.commodity.includes('Oats') || + model.commodity.includes('Milk') || + model.commodity.includes('Soybean') || + model.commodity.includes('Rice') + ) { + return 80; + } + + return null; + }} + /> + </div> + ); +} From 0297e8e5a009f4038c52a6b08f0c29f888585f29 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Wed, 26 Jan 2022 18:57:28 +0100 Subject: [PATCH 3/8] add unit tests --- .../src/tests/rows.DataGridPro.test.tsx | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index 53c779a85b361..7168d0f67ad24 100644 --- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -2,7 +2,13 @@ import * as React from 'react'; import { createRenderer, fireEvent } from '@mui/monorepo/test/utils'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { getCell, getRow, getColumnValues, getRows } from 'test/utils/helperFn'; +import { + getCell, + getRow, + getColumnValues, + getRows, + getColumnHeaderCell, +} from 'test/utils/helperFn'; import { GridApiRef, GridRowModel, @@ -792,4 +798,64 @@ describe('<DataGridPro /> - Rows', () => { }).not.to.throw(); }); }); + + describe('apiRef: setRowHeight', () => { + const ROW_HEIGHT = 52; + + beforeEach(() => { + baselineProps = { + autoHeight: isJSDOM, + rows: [ + { + id: 0, + brand: 'Nike', + }, + { + id: 1, + brand: 'Adidas', + }, + { + id: 2, + brand: 'Puma', + }, + ], + columns: [{ field: 'brand', headerName: 'Brand' }], + }; + }); + + let apiRef: GridApiRef; + + const TestCase = (props: Partial<DataGridProProps>) => { + apiRef = useGridApiRef(); + return ( + <div style={{ width: 300, height: 300 }}> + <DataGridPro {...baselineProps} apiRef={apiRef} rowHeight={ROW_HEIGHT} {...props} /> + </div> + ); + }; + + it('should change row height', () => { + render(<TestCase />); + + expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT); + + apiRef.current.unstable_setRowHeight(1, 100); + expect(getRow(1).clientHeight).to.equal(100); + }); + + it('should preserve changed row height after sorting', () => { + render(<TestCase />); + + const row = getRow(0); + expect(row.clientHeight).to.equal(ROW_HEIGHT); + + apiRef.current.unstable_setRowHeight(0, 100); + expect(row.clientHeight).to.equal(100); + + // sort + fireEvent.click(getColumnHeaderCell(0)); + + expect(row.clientHeight).to.equal(100); + }); + }); }); From 28320949b3cde6f6ca3e058d2e14aa7c37db23b2 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Wed, 26 Jan 2022 19:38:25 +0100 Subject: [PATCH 4/8] skip tests in jsdom --- .../x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index 7168d0f67ad24..ac9d920012168 100644 --- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -802,6 +802,13 @@ describe('<DataGridPro /> - Rows', () => { describe('apiRef: setRowHeight', () => { const ROW_HEIGHT = 52; + before(function beforeHook() { + if (isJSDOM) { + // Need layouting + this.skip(); + } + }); + beforeEach(() => { baselineProps = { autoHeight: isJSDOM, From 9702d254ae48352eff6284d36c5937baa39d26b1 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Fri, 28 Jan 2022 11:20:12 +0100 Subject: [PATCH 5/8] check getRowHeight spy in test --- .../src/tests/rows.DataGridPro.test.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index ac9d920012168..e02497efb0cca 100644 --- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -811,7 +811,6 @@ describe('<DataGridPro /> - Rows', () => { beforeEach(() => { baselineProps = { - autoHeight: isJSDOM, rows: [ { id: 0, @@ -842,27 +841,32 @@ describe('<DataGridPro /> - Rows', () => { }; it('should change row height', () => { + const RESIZED_ROW_ID = 1; render(<TestCase />); expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT); - apiRef.current.unstable_setRowHeight(1, 100); - expect(getRow(1).clientHeight).to.equal(100); + apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100); + expect(getRow(RESIZED_ROW_ID).clientHeight).to.equal(100); }); it('should preserve changed row height after sorting', () => { - render(<TestCase />); + const RESIZED_ROW_ID = 0; + const getRowHeight = spy(); + render(<TestCase getRowHeight={getRowHeight} />); - const row = getRow(0); + const row = getRow(RESIZED_ROW_ID); expect(row.clientHeight).to.equal(ROW_HEIGHT); - apiRef.current.unstable_setRowHeight(0, 100); + getRowHeight.resetHistory(); + apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100); expect(row.clientHeight).to.equal(100); // sort - fireEvent.click(getColumnHeaderCell(0)); + fireEvent.click(getColumnHeaderCell(RESIZED_ROW_ID)); expect(row.clientHeight).to.equal(100); + expect(getRowHeight.neverCalledWithMatch({ id: RESIZED_ROW_ID })).to.equal(true); }); }); }); From 9672b2789da09141239d5c98d9690701345791c2 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Tue, 8 Feb 2022 21:07:22 +0100 Subject: [PATCH 6/8] resolve conflicts --- .../hooks/features/rows/useGridRowsMeta.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts index 8650dce206138..968bacd950d57 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRowsMeta.ts @@ -55,11 +55,21 @@ export const useGridRowsMeta = ( const currentRowHeight = gridDensityRowHeightSelector(state, apiRef.current.instanceId); const currentPageTotalHeight = rows.reduce((acc: number, row) => { positions.push(acc); - let baseRowHeight = currentRowHeight; + let baseRowHeight: number; - if (getRowHeight) { - // Default back to base rowHeight if getRowHeight returns null or undefined. - baseRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight; + const isResized = + (rowsHeightLookup.current[row.id] && rowsHeightLookup.current[row.id].isResized) || false; + + if (isResized) { + // do not recalculate resized row height and use the value from the lookup + baseRowHeight = rowsHeightLookup.current[row.id].value; + } else { + baseRowHeight = currentRowHeight; + + if (getRowHeight) { + // Default back to base rowHeight if getRowHeight returns null or undefined. + baseRowHeight = getRowHeight({ ...row, densityFactor }) ?? currentRowHeight; + } } const heights = apiRef.current.unstable_applyPreProcessors( @@ -70,7 +80,10 @@ export const useGridRowsMeta = ( const finalRowHeight = Object.values(heights).reduce((acc2, value) => acc2 + value, 0); - rowsHeightLookup.current[row.id] = baseRowHeight; + rowsHeightLookup.current[row.id] = { + value: baseRowHeight, + isResized, + }; return acc + finalRowHeight; }, 0); From b4260cc1b39da8cb52ef97de7473c1d1103e1267 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Thu, 10 Feb 2022 10:48:39 +0100 Subject: [PATCH 7/8] code review: Matheus --- .../_modules_/grid/models/api/gridRowsMetaApi.ts | 2 +- .../src/tests/rows.DataGridPro.test.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts index d724e53c67a7a..082b14148d94f 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowsMetaApi.ts @@ -12,7 +12,7 @@ export interface GridRowsMetaApi { */ unstable_getRowHeight: (id: GridRowId) => number; /** - * Updates the height of a row. + * 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. diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index e02497efb0cca..fe978eebe9dd0 100644 --- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -841,32 +841,32 @@ describe('<DataGridPro /> - Rows', () => { }; it('should change row height', () => { - const RESIZED_ROW_ID = 1; + const resizedRowId = 1; render(<TestCase />); expect(getRow(1).clientHeight).to.equal(ROW_HEIGHT); - apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100); - expect(getRow(RESIZED_ROW_ID).clientHeight).to.equal(100); + apiRef.current.unstable_setRowHeight(resizedRowId, 100); + expect(getRow(resizedRowId).clientHeight).to.equal(100); }); it('should preserve changed row height after sorting', () => { - const RESIZED_ROW_ID = 0; + const resizedRowId = 0; const getRowHeight = spy(); render(<TestCase getRowHeight={getRowHeight} />); - const row = getRow(RESIZED_ROW_ID); + const row = getRow(resizedRowId); expect(row.clientHeight).to.equal(ROW_HEIGHT); getRowHeight.resetHistory(); - apiRef.current.unstable_setRowHeight(RESIZED_ROW_ID, 100); + apiRef.current.unstable_setRowHeight(resizedRowId, 100); expect(row.clientHeight).to.equal(100); // sort - fireEvent.click(getColumnHeaderCell(RESIZED_ROW_ID)); + fireEvent.click(getColumnHeaderCell(resizedRowId)); expect(row.clientHeight).to.equal(100); - expect(getRowHeight.neverCalledWithMatch({ id: RESIZED_ROW_ID })).to.equal(true); + expect(getRowHeight.neverCalledWithMatch({ id: resizedRowId })).to.equal(true); }); }); }); From bb2c54d9585b4371bbbdee03b427c776e16e51a6 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi <andrew@mui.com> Date: Mon, 14 Feb 2022 13:51:22 +0100 Subject: [PATCH 8/8] fix apiRef type --- .../grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index c1d883a861848..365e87b791e18 100644 --- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -829,7 +829,7 @@ describe('<DataGridPro /> - Rows', () => { }; }); - let apiRef: GridApiRef; + let apiRef: React.MutableRefObject<GridApi>; const TestCase = (props: Partial<DataGridProProps>) => { apiRef = useGridApiRef();