Skip to content

Commit

Permalink
[DataGrid] Vertical scrolling performance (mui#11924)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Cherniavskyi <[email protected]>
  • Loading branch information
2 people authored and thomasmoon committed Sep 6, 2024
1 parent c463d6d commit e263ebe
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 44 deletions.
6 changes: 6 additions & 0 deletions docs/pages/x/api/data-grid/data-grid-premium.json
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,12 @@
"description": "Styles applied to the empty cell element.",
"isGlobal": false
},
{
"key": "cellOffsetLeft",
"className": "MuiDataGridPremium-cellOffsetLeft",
"description": "",
"isGlobal": false
},
{
"key": "cellSkeleton",
"className": "MuiDataGridPremium-cellSkeleton",
Expand Down
6 changes: 6 additions & 0 deletions docs/pages/x/api/data-grid/data-grid-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,12 @@
"description": "Styles applied to the empty cell element.",
"isGlobal": false
},
{
"key": "cellOffsetLeft",
"className": "MuiDataGridPro-cellOffsetLeft",
"description": "",
"isGlobal": false
},
{
"key": "cellSkeleton",
"className": "MuiDataGridPro-cellSkeleton",
Expand Down
6 changes: 6 additions & 0 deletions docs/pages/x/api/data-grid/data-grid.json
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,12 @@
"description": "Styles applied to the empty cell element.",
"isGlobal": false
},
{
"key": "cellOffsetLeft",
"className": "MuiDataGrid-cellOffsetLeft",
"description": "",
"isGlobal": false
},
{
"key": "cellSkeleton",
"className": "MuiDataGrid-cellSkeleton",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the empty cell element"
},
"cellOffsetLeft": { "description": "" },
"cellSkeleton": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the skeleton cell element"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the empty cell element"
},
"cellOffsetLeft": { "description": "" },
"cellSkeleton": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the skeleton cell element"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the empty cell element"
},
"cellOffsetLeft": { "description": "" },
"cellSkeleton": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the skeleton cell element"
Expand Down
44 changes: 18 additions & 26 deletions packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { spy } from 'sinon';
import { expect } from 'chai';
import {
$,
$$,
grid,
getCell,
getRow,
Expand Down Expand Up @@ -450,7 +451,7 @@ describe('<DataGridPro /> - Rows', () => {
virtualScroller.scrollTop = 10e6; // scroll to the bottom
act(() => virtualScroller.dispatchEvent(new Event('scroll')));

const lastCell = $('[role="row"]:last-child [role="gridcell"]:first-child')!;
const lastCell = $$('[role="row"]:last-child [role="gridcell"]')[0];
expect(lastCell).to.have.text('995');
expect(renderingZone.children.length).to.equal(
Math.floor((height - 1) / rowHeight) + rowBuffer,
Expand Down Expand Up @@ -486,11 +487,13 @@ describe('<DataGridPro /> - Rows', () => {
/>,
);
const firstRow = getRow(0);
expect(firstRow.children).to.have.length(Math.floor(width / columnWidth) + columnBuffer);
expect($$(firstRow, '[role="gridcell"]')).to.have.length(
Math.floor(width / columnWidth) + columnBuffer,
);
const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!;
virtualScroller.scrollLeft = 301;
act(() => virtualScroller.dispatchEvent(new Event('scroll')));
expect(firstRow.children).to.have.length(
expect($$(firstRow, '[role="gridcell"]')).to.have.length(
columnBuffer + 1 + Math.floor(width / columnWidth) + columnBuffer,
);
});
Expand All @@ -517,15 +520,14 @@ describe('<DataGridPro /> - Rows', () => {
render(
<TestCaseVirtualization nbRows={1} columnBuffer={0} columnThreshold={columnThreshold} />,
);
const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!;
const renderingZone = document.querySelector('.MuiDataGrid-virtualScrollerRenderZone')!;
let firstRow = renderingZone.querySelector('[role="row"]:first-child')!;
let firstColumn = firstRow.firstChild!;
const virtualScroller = grid('virtualScroller')!;
const renderingZone = grid('virtualScrollerRenderZone')!;
const firstRow = $(renderingZone, '[role="row"]:first-child')!;
let firstColumn = $$(firstRow, '[role="gridcell"]')[0];
expect(firstColumn).to.have.attr('data-colindex', '0');
virtualScroller.scrollLeft = columnThreshold * columnWidth;
act(() => virtualScroller.dispatchEvent(new Event('scroll')));
firstRow = renderingZone.querySelector('[role="row"]:first-child')!;
firstColumn = firstRow.firstChild!;
firstColumn = $(renderingZone, '[role="row"] > [role="gridcell"]')!;
expect(firstColumn).to.have.attr('data-colindex', '3');
});

Expand All @@ -546,16 +548,14 @@ describe('<DataGridPro /> - Rows', () => {
act(() => virtualScroller.dispatchEvent(new Event('scroll')));

const dimensions = apiRef.current.state.dimensions;
const lastCell = document.querySelector(
'[role="row"]:last-child [role="gridcell"]:first-child',
)!;
const lastCell = $$('[role="row"]:last-child [role="gridcell"]')[0];
expect(lastCell).to.have.text('31');
expect(virtualScroller.scrollHeight).to.equal(
dimensions.headerHeight + nbRows * rowHeight + dimensions.scrollbarSize,
);
});

it('should not virtualized the last page if smaller than viewport', () => {
it('should not virtualize the last page if smaller than viewport', () => {
render(
<TestCaseVirtualization
pagination
Expand All @@ -564,19 +564,15 @@ describe('<DataGridPro /> - Rows', () => {
height={500}
/>,
);
const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!;
const virtualScroller = grid('virtualScroller')!;
virtualScroller.scrollTop = 10e6; // scroll to the bottom
virtualScroller.dispatchEvent(new Event('scroll'));

const lastCell = document.querySelector(
'[role="row"]:last-child [role="gridcell"]:first-child',
)!;
const lastCell = $$('[role="row"]:last-child [role="gridcell"]')[0];
expect(lastCell).to.have.text('99');
expect(virtualScroller.scrollTop).to.equal(0);
expect(virtualScroller.scrollHeight).to.equal(virtualScroller.clientHeight);
expect(
document.querySelector('.MuiDataGrid-virtualScrollerRenderZone')!.children,
).to.have.length(4);
expect(grid('virtualScrollerRenderZone')!.children).to.have.length(4);
});

it('should paginate small dataset in auto page-size #1492', () => {
Expand All @@ -585,18 +581,14 @@ describe('<DataGridPro /> - Rows', () => {
);
const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!;

const lastCell = document.querySelector(
'[role="row"]:last-child [role="gridcell"]:first-child',
)!;
const lastCell = $$('[role="row"]:last-child [role="gridcell"]')[0];
expect(lastCell).to.have.text('6');
const rows = document.querySelectorAll('.MuiDataGrid-row[role="row"]')!;
expect(rows.length).to.equal(7);

expect(virtualScroller.scrollTop).to.equal(0);
expect(virtualScroller.scrollHeight).to.equal(virtualScroller.clientHeight);
expect(
document.querySelector('.MuiDataGrid-virtualScrollerRenderZone')!.children,
).to.have.length(7);
expect(grid('virtualScrollerRenderZone')!.children).to.have.length(7);
});
});

Expand Down
3 changes: 1 addition & 2 deletions packages/x-data-grid/src/components/GridRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ const useUtilityClasses = (ownerState: OwnerState) => {
isLastVisible && 'row--lastVisible',
rowHeight === 'auto' && 'row--dynamicHeight',
],
pinnedLeft: ['pinnedLeft'],
pinnedRight: ['pinnedRight'],
};

return composeClasses(slots, getDataGridUtilityClass, classes);
Expand Down Expand Up @@ -517,6 +515,7 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
{...other}
>
{leftCells}
<div className={gridClasses.cellOffsetLeft} role="presentation" />
{cells}
{emptyCellWidth > 0 && <EmptyCell width={emptyCellWidth} />}
{rightCells.length > 0 && <div role="presentation" style={{ flex: '1' }} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ const Style = styled('div')({
position: 'sticky',
right: 0,
},
[`&:not(.${classes.header}):not(.${classes.pinnedRight})`]: {
transform: 'translate3d(var(--DataGrid-offsetLeft), 0, 0)',
},
});

function GridScrollbarFillerCell({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,10 @@ export const GridRootStyles = styled('div', {
},
},
},
[`& .${c.cell}:not(.${c['cell--pinnedLeft']}):not(.${c['cell--pinnedRight']})`]: {
transform: 'translate3d(var(--DataGrid-offsetLeft), 0, 0)',
[`& .${c.cellOffsetLeft}`]: {
flex: '0 0 auto',
display: 'inline-block',
width: 'var(--DataGrid-offsetLeft)',
},
[`& .${c.columnHeaderDraggableContainer}`]: {
display: 'flex',
Expand Down
6 changes: 6 additions & 0 deletions packages/x-data-grid/src/constants/gridClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ export interface GridClasses {
* Styles applied to the skeleton cell element.
*/
cellSkeleton: string;
/**
* @ignore - do not document.
* Styles applied to the left offset cell element.
*/
cellOffsetLeft: string;
/**
* Styles applied to the selection checkbox element.
*/
Expand Down Expand Up @@ -634,6 +639,7 @@ export const gridClasses = generateUtilityClasses<GridClassKey>('MuiDataGrid', [
'cellCheckbox',
'cellEmpty',
'cellSkeleton',
'cellOffsetLeft',
'checkboxInput',
'columnHeader--alignCenter',
'columnHeader--alignLeft',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const useGridVirtualScroller = () => {
const gridRootRef = apiRef.current.rootElementRef;
const mainRef = apiRef.current.mainElementRef;
const scrollerRef = apiRef.current.virtualScrollerRef;
const renderZoneRef = React.useRef<HTMLDivElement>(null);
const scrollbarVerticalRef = React.useRef<HTMLDivElement>(null);
const scrollbarHorizontalRef = React.useRef<HTMLDivElement>(null);
const contentHeight = dimensions.contentSize.height;
Expand Down Expand Up @@ -343,6 +342,16 @@ export const useGridVirtualScroller = () => {

for (let i = 0; i < renderedRows.length; i += 1) {
const { id, model } = renderedRows[i];

const rowIndexInPage = (currentPage?.range?.firstRowIndex || 0) + firstRowToRender + i;
let index = rowIndexOffset + rowIndexInPage;
if (isRowWithFocusedCellNotInRange && cellFocus?.id === id) {
index = indexOfRowWithFocusedCell;
isRowWithFocusedCellRendered = true;
} else if (isRowWithFocusedCellRendered) {
index -= 1;
}

const isRowNotVisible = isRowWithFocusedCellNotInRange && cellFocus!.id === id;

const baseRowHeight = !apiRef.current.rowHasAutoHeight(id)
Expand All @@ -358,7 +367,7 @@ export const useGridVirtualScroller = () => {

let isFirstVisible = false;
if (params.position === undefined) {
isFirstVisible = i === 0;
isFirstVisible = rowIndexInPage === 0;
}

let isLastVisible = false;
Expand Down Expand Up @@ -394,14 +403,6 @@ export const useGridVirtualScroller = () => {
tabbableCell = cellParams.cellMode === 'view' ? cellTabIndex.field : null;
}

let index = rowIndexOffset + (currentPage?.range?.firstRowIndex || 0) + firstRowToRender + i;
if (isRowWithFocusedCellNotInRange && cellFocus?.id === id) {
index = indexOfRowWithFocusedCell;
isRowWithFocusedCellRendered = true;
} else if (isRowWithFocusedCellRendered) {
index -= 1;
}

rows.push(
<rootProps.slots.row
key={id}
Expand Down Expand Up @@ -532,7 +533,7 @@ export const useGridVirtualScroller = () => {
style: contentSize,
role: 'presentation',
}),
getRenderZoneProps: () => ({ ref: renderZoneRef, role: 'rowgroup' }),
getRenderZoneProps: () => ({ role: 'rowgroup' }),
getScrollbarVerticalProps: () => ({ ref: scrollbarVerticalRef, role: 'presentation' }),
getScrollbarHorizontalProps: () => ({ ref: scrollbarHorizontalRef, role: 'presentation' }),
};
Expand Down

0 comments on commit e263ebe

Please sign in to comment.