Skip to content

Commit

Permalink
Keep focused cell in the DOM
Browse files Browse the repository at this point in the history
  • Loading branch information
m4theushw committed Sep 14, 2022
1 parent 31038b4 commit cf78d03
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,11 @@ const DataGridProVirtualScroller = React.forwardRef<
const {
renderContext,
getRows,
getFocusedRow,
getRootProps,
getContentProps,
getRenderZoneProps,
getFocusRenderZoneProps,
updateRenderZonePosition,
} = useGridVirtualScroller({
ref,
Expand Down Expand Up @@ -425,6 +427,9 @@ const DataGridProVirtualScroller = React.forwardRef<
<GridVirtualScrollerRenderZone {...getRenderZoneProps()}>
{mainRows}
</GridVirtualScrollerRenderZone>
<GridVirtualScrollerRenderZone {...getFocusRenderZoneProps()}>
{getFocusedRow()}
</GridVirtualScrollerRenderZone>
{rightRenderContext && (
<VirtualScrollerPinnedColumns
ref={rightColumns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
clock.tick(500);
expect(input.value).to.equal('1970');
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());
await waitFor(() => {
expect(cell).to.have.text('1970');
});
Expand Down Expand Up @@ -198,6 +199,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
1: { year: { value: '1970' } },
});
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());
await waitFor(() => {
expect(cell).to.have.text('1970');
});
Expand Down Expand Up @@ -382,6 +384,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
expect(cell.querySelector('input')!.value).to.equal('n');
clock.tick(500);
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).to.have.class('MuiDataGrid-cell--editable');
Expand All @@ -402,6 +405,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
expect(cell.querySelector('input')!.value).to.equal('n');
clock.tick(500);
fireEvent.keyDown(input, { key: 'Tab' });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).to.have.class('MuiDataGrid-cell--editable');
Expand All @@ -422,6 +426,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
clock.tick(500);
expect(cell.querySelector('input')!.value).to.equal('1970');
fireEvent.keyDown(input, { key: 'Tab', shiftKey: true });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).to.have.class('MuiDataGrid-cell--editable');
Expand Down Expand Up @@ -553,6 +558,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
clock.tick(500);
expect(cell.querySelector('input')!.value).to.equal('1962');
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).not.to.have.class('MuiDataGrid-cell--editing');
Expand Down Expand Up @@ -661,6 +667,7 @@ describe('<DataGridPro /> - Cell Editing', () => {
clock.tick(500);
expect(cell.querySelector('input')!.value).to.equal('n');
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).to.have.class('MuiDataGrid-cell--editable');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ describe('<DataGridPro /> - Edit Components', () => {
fireEvent.change(input, { target: { value: '1942' } });
clock.tick(500);
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).to.have.text('1,942');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ describe('<DataGridPro /> - Events Params', () => {
clock.tick(500);
expect(handleEditCellPropsChange.callCount).to.equal(1);
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());

await waitFor(() => {
expect(cell).to.have.text('Jack');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,7 @@ describe('<DataGridPro /> - Row Editing', () => {
apiRef.current.setEditCellValue({ id: 0, field: 'currencyPair', value: 'USD GBP' });
});
fireEvent.keyDown(cell.querySelector('input'), { key: 'Enter' });
await act(() => Promise.resolve());
expect(spiedStopRowEditMode.callCount).to.equal(1);
expect(spiedStopRowEditMode.lastCall.args[0].ignoreModifications).to.equal(true);
});
Expand Down Expand Up @@ -1115,7 +1116,7 @@ describe('<DataGridPro /> - Row Editing', () => {
const cell = getCell(0, 2);
userEvent.mousePress(cell);
fireEvent.doubleClick(cell);
await act(() => {
act(() => {
apiRef.current.setEditCellValue({ id: 0, field: 'price1M', value: 'USD GBP' });
});
fireEvent.keyDown(cell.querySelector('input'), { key: 'Tab' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ describe('<DataGridPro /> - Row Editing', () => {
clock.tick(500);
expect(input!.value).to.equal('ADIDAS');
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());
await waitFor(() => {
expect(cell).to.have.text('ADIDAS');
});
Expand All @@ -133,6 +134,7 @@ describe('<DataGridPro /> - Row Editing', () => {
fireEvent.change(input, { target: { value: 'ADIDAS' } });
expect(input!.value).to.equal('ADIDAS');
fireEvent.keyDown(input, { key: 'Enter' });
await act(() => Promise.resolve());
await waitFor(() => {
expect(getCell(2, 0)).toHaveFocus();
});
Expand Down
11 changes: 11 additions & 0 deletions packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getColumnValues,
getRows,
getColumnHeaderCell,
getActiveCell,
} from 'test/utils/helperFn';
import {
GridRowModel,
Expand Down Expand Up @@ -469,6 +470,16 @@ describe('<DataGridPro /> - Rows', () => {
expect(firstColumn).to.have.attr('data-colindex', '3');
});

it('should keep the focused cell in the DOM while scrolling', () => {
render(<TestCaseVirtualization rowBuffer={0} columnBuffer={0} />);
userEvent.mousePress(getCell(0, 0));
expect(getActiveCell()).to.equal('0-0');
const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!;
virtualScroller.scrollTop = 10e6; // scroll to the bottom
act(() => virtualScroller.dispatchEvent(new Event('scroll')));
expect(getActiveCell()).to.equal('0-0');
});

describe('Pagination', () => {
it('should render only the pageSize', () => {
const rowHeight = 50;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ const DataGridVirtualScroller = React.forwardRef<HTMLDivElement, DataGridVirtual
function DataGridVirtualScroller(props, ref) {
const { className, disableVirtualization, ...other } = props;

const { getRootProps, getContentProps, getRenderZoneProps, getRows } = useGridVirtualScroller({
const {
getRootProps,
getContentProps,
getRenderZoneProps,
getFocusRenderZoneProps,
getRows,
getFocusedRow,
} = useGridVirtualScroller({
ref,
disableVirtualization,
});
Expand All @@ -23,6 +30,9 @@ const DataGridVirtualScroller = React.forwardRef<HTMLDivElement, DataGridVirtual
<GridVirtualScrollerRenderZone {...getRenderZoneProps()}>
{getRows()}
</GridVirtualScrollerRenderZone>
<GridVirtualScrollerRenderZone {...getFocusRenderZoneProps()}>
{getFocusedRow()}
</GridVirtualScrollerRenderZone>
</GridVirtualScrollerContent>
</GridVirtualScroller>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
const selectedRowsLookup = useGridSelector(apiRef, selectedIdsLookupSelector);
const currentPage = useGridVisibleRows(apiRef, rootProps);
const renderZoneRef = React.useRef<HTMLDivElement>(null);
const focusedRowRenderZoneRef = React.useRef<HTMLDivElement>(null);
const rootRef = React.useRef<HTMLDivElement>(null);
const handleRef = useForkRef(ref, rootRef);
const [renderContext, setRenderContext] = React.useState<GridRenderContext | null>(null);
const focusRenderContext = React.useRef<GridRenderContext | null>(null);
const prevRenderContext = React.useRef<GridRenderContext | null>(renderContext);
const scrollPosition = React.useRef({ top: 0, left: 0 });
const [containerWidth, setContainerWidth] = React.useState<number | null>(null);
Expand Down Expand Up @@ -254,18 +256,26 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
const left = gridColumnPositionsSelector(apiRef)[firstColumnToRender]; // Call directly the selector because it might be outdated when this method is called
renderZoneRef.current!.style.transform = `translate3d(${left}px, ${top}px, 0px)`;

if (focusRenderContext.current && !disableVirtualization) {
const focusedRowTop = gridRowsMetaSelector(apiRef.current.state).positions[
focusRenderContext.current.firstRowIndex
];
focusedRowRenderZoneRef.current!.style.transform = `translate3d(0px, ${focusedRowTop}px, 0px)`;
}

if (typeof onRenderZonePositioning === 'function') {
onRenderZonePositioning({ top, left });
}
},
[
apiRef,
currentPage.rows,
onRenderZonePositioning,
rootProps.rowBuffer,
rootProps.columnBuffer,
renderZoneMinColumnIndex,
renderZoneMaxColumnIndex,
rootProps.columnBuffer,
rootProps.rowBuffer,
apiRef,
disableVirtualization,
onRenderZonePositioning,
],
);

Expand All @@ -275,26 +285,59 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
}
}, [renderContext, updateRenderZonePosition]);

const updateRenderContext = React.useCallback(
(nextRenderContext: GridRenderContext) => {
setRenderContext(nextRenderContext);
const getRowsToRenderFromContext = React.useCallback(
(nextRenderContext: GridRenderContext, rowBufferOverride?: number) => {
const rowBuffer = !disableVirtualization ? rowBufferOverride ?? rootProps.rowBuffer : 0;

const [firstRowToRender, lastRowToRender] = getRenderableIndexes({
return getRenderableIndexes({
firstIndex: nextRenderContext.firstRowIndex,
lastIndex: nextRenderContext.lastRowIndex,
minFirstIndex: 0,
maxLastIndex: currentPage.rows.length,
buffer: rootProps.rowBuffer,
buffer: rowBuffer,
});
},
[currentPage.rows.length, disableVirtualization, rootProps.rowBuffer],
);

const updateRenderContext = React.useCallback(
(nextRenderContext: GridRenderContext) => {
setRenderContext(nextRenderContext);
prevRenderContext.current = nextRenderContext;

const [firstRowToRender, lastRowToRender] = getRowsToRenderFromContext(nextRenderContext);

apiRef.current.publishEvent('renderedRowsIntervalChange', {
firstRowToRender,
lastRowToRender,
});

prevRenderContext.current = nextRenderContext;
if (cellFocus && !disableVirtualization) {
const rowIndex = currentPage.rows.findIndex(({ id }) => cellFocus.id === id);
const isRowVisible = rowIndex >= firstRowToRender && rowIndex <= lastRowToRender;

if (!isRowVisible) {
focusRenderContext.current = {
firstRowIndex: rowIndex,
lastRowIndex: rowIndex + 1, // Last index is exclusive
firstColumnIndex: 0,
lastColumnIndex: visibleColumns.length,
};
} else {
focusRenderContext.current = null;
}
} else {
focusRenderContext.current = null;
}
},
[apiRef, setRenderContext, prevRenderContext, currentPage.rows.length, rootProps.rowBuffer],
[
apiRef,
cellFocus,
currentPage.rows,
disableVirtualization,
getRowsToRenderFromContext,
visibleColumns.length,
],
);

React.useEffect(() => {
Expand Down Expand Up @@ -383,6 +426,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
availableSpace?: number | null;
rows?: GridRowEntry[];
rowIndexOffset?: number;
rowBuffer?: number;
} = { renderContext },
) => {
const {
Expand All @@ -391,23 +435,20 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
maxLastColumn = renderZoneMaxColumnIndex,
availableSpace = containerWidth,
rowIndexOffset = 0,
rowBuffer: rowBufferOverride,
position = 'center',
} = params;

if (!nextRenderContext || availableSpace == null) {
return null;
}

const rowBuffer = !disableVirtualization ? rootProps.rowBuffer : 0;
const columnBuffer = !disableVirtualization ? rootProps.columnBuffer : 0;

const [firstRowToRender, lastRowToRender] = getRenderableIndexes({
firstIndex: nextRenderContext.firstRowIndex,
lastIndex: nextRenderContext.lastRowIndex,
minFirstIndex: 0,
maxLastIndex: currentPage.rows.length,
buffer: rowBuffer,
});
const [firstRowToRender, lastRowToRender] = getRowsToRenderFromContext(
nextRenderContext,
rowBufferOverride,
);

const renderedRows: GridRowEntry[] = [];

Expand Down Expand Up @@ -499,6 +540,19 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
return rows;
};

const getFocusedRow = () => {
if (disableVirtualization) {
return null;
}

return getRows({
renderContext: focusRenderContext.current,
rowBuffer: 0,
minFirstColumn: 0,
maxLastColumn: visibleColumns.length,
});
};

const needsHorizontalScrollbar = containerWidth && columnsTotalWidth > containerWidth;

const contentSize = React.useMemo(() => {
Expand Down Expand Up @@ -559,6 +613,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
renderContext,
updateRenderZonePosition,
getRows,
getFocusedRow,
getRootProps: ({ style = {}, ...other } = {}) => ({
ref: handleRef,
onScroll: handleScroll,
Expand All @@ -569,5 +624,6 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
}),
getContentProps: ({ style = {} } = {}) => ({ style: { ...style, ...contentSize } }),
getRenderZoneProps: () => ({ ref: renderZoneRef }),
getFocusRenderZoneProps: () => ({ ref: focusedRowRenderZoneRef }),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,9 @@ describe('<DataGrid /> - Keyboard', () => {
const input = screen.getByTestId('custom-input');
input.focus();
fireEvent.keyDown(input, { key: 'a' });
expect(renderCell.callCount).to.equal(4);
expect(renderCell.callCount).to.equal(6);
fireEvent.keyDown(input, { key: 'b' });
expect(renderCell.callCount).to.equal(4);
expect(renderCell.callCount).to.equal(6);
});

it('should not scroll horizontally when cell is wider than viewport', () => {
Expand Down

0 comments on commit cf78d03

Please sign in to comment.