Skip to content

Commit

Permalink
[DataGrid] Fix scrollbar position not being updated after `scrollToIn…
Browse files Browse the repository at this point in the history
…dexes` (#14888)
  • Loading branch information
arminmeh authored Oct 10, 2024
1 parent 9a902f3 commit 010f526
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { createRenderer, waitFor } from '@mui/internal-test-utils';
import { act, createRenderer, waitFor } from '@mui/internal-test-utils';
import { expect } from 'chai';
import { DataGridPro } from '@mui/x-data-grid-pro';
import { spy, restore } from 'sinon';
Expand Down Expand Up @@ -40,24 +40,37 @@ describe('<DataGridPro /> - Infnite loader', () => {
}
const { container, setProps } = render(<TestCase rows={baseRows} />);
const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!;
// arbitrary number to make sure that the bottom of the grid window is reached.
virtualScroller.scrollTop = 12345;
virtualScroller.dispatchEvent(new Event('scroll'));

await act(async () => {
// arbitrary number to make sure that the bottom of the grid window is reached.
virtualScroller.scrollTop = 12345;
virtualScroller.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(handleRowsScrollEnd.callCount).to.equal(1);
});
setProps({
rows: baseRows.concat(
{ id: 6, brand: 'Gucci' },
{ id: 7, brand: "Levi's" },
{ id: 8, brand: 'Ray-Ban' },
),

await act(async () => {
setProps({
rows: baseRows.concat(
{ id: 6, brand: 'Gucci' },
{ id: 7, brand: "Levi's" },
{ id: 8, brand: 'Ray-Ban' },
),
});

// Trigger a scroll again to notify the grid that we're not in the bottom area anymore
virtualScroller.dispatchEvent(new Event('scroll'));
});
// Trigger a scroll again to notify the grid that we're not in the bottom area anymore
virtualScroller.dispatchEvent(new Event('scroll'));

expect(handleRowsScrollEnd.callCount).to.equal(1);
virtualScroller.scrollTop = 12345;
virtualScroller.dispatchEvent(new Event('scroll'));

await act(async () => {
virtualScroller.scrollTop = 12345;
virtualScroller.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(handleRowsScrollEnd.callCount).to.equal(2);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ const GridVirtualScrollbar = React.forwardRef<HTMLDivElement, GridVirtualScrollb
function GridVirtualScrollbar(props, ref) {
const apiRef = useGridPrivateApiContext();
const rootProps = useGridRootProps();
const lastPositionScroller = React.useRef(0);
const lastPositionScrollbar = React.useRef(0);
const isLocked = React.useRef(false);
const lastPosition = React.useRef(0);
const scrollbarRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
const classes = useUtilityClasses(rootProps, props.position);
Expand All @@ -96,28 +96,34 @@ const GridVirtualScrollbar = React.forwardRef<HTMLDivElement, GridVirtualScrollb
const scroller = apiRef.current.virtualScrollerRef.current!;
const scrollbar = scrollbarRef.current!;

if (scroller[propertyScroll] === lastPositionScroller.current) {
if (scroller[propertyScroll] === lastPosition.current) {
return;
}

if (isLocked.current) {
isLocked.current = false;
return;
}
isLocked.current = true;

const value = scroller[propertyScroll] / contentSize;
scrollbar[propertyScroll] = value * scrollbarInnerSize;

lastPositionScrollbar.current = scrollbar[propertyScroll];
lastPosition.current = scroller[propertyScroll];
});

const onScrollbarScroll = useEventCallback(() => {
const scroller = apiRef.current.virtualScrollerRef.current!;
const scrollbar = scrollbarRef.current!;

if (scrollbar[propertyScroll] === lastPositionScrollbar.current) {
if (isLocked.current) {
isLocked.current = false;
return;
}
isLocked.current = true;

const value = scrollbar[propertyScroll] / scrollbarInnerSize;
scroller[propertyScroll] = value * contentSize;

lastPositionScroller.current = scroller[propertyScroll];
});

useOnMount(() => {
Expand Down
4 changes: 4 additions & 0 deletions packages/x-data-grid/src/hooks/core/useGridRefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const useGridRefs = <PrivateApi extends GridPrivateApiCommon>(
const rootElementRef = React.useRef<HTMLDivElement>(null);
const mainElementRef = React.useRef<HTMLDivElement>(null);
const virtualScrollerRef = React.useRef<HTMLDivElement>(null);
const virtualScrollbarVerticalRef = React.useRef<HTMLDivElement>(null);
const virtualScrollbarHorizontalRef = React.useRef<HTMLDivElement>(null);
const columnHeadersContainerRef = React.useRef<HTMLDivElement>(null);

apiRef.current.register('public', {
Expand All @@ -16,6 +18,8 @@ export const useGridRefs = <PrivateApi extends GridPrivateApiCommon>(
apiRef.current.register('private', {
mainElementRef,
virtualScrollerRef,
virtualScrollbarVerticalRef,
virtualScrollbarHorizontalRef,
columnHeadersContainerRef,
});
};
26 changes: 23 additions & 3 deletions packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export const useGridScroll = (
const logger = useGridLogger(apiRef, 'useGridScroll');
const colRef = apiRef.current.columnHeadersContainerRef;
const virtualScrollerRef = apiRef.current.virtualScrollerRef!;
const virtualScrollbarHorizontalRef = apiRef.current.virtualScrollbarHorizontalRef!;
const virtualScrollbarVerticalRef = apiRef.current.virtualScrollbarVerticalRef!;
const visibleSortedRows = useGridSelector(apiRef, gridExpandedSortedRowEntriesSelector);

const scrollToIndexes = React.useCallback<GridScrollApi['scrollToIndexes']>(
Expand Down Expand Up @@ -144,19 +146,37 @@ export const useGridScroll = (

const scroll = React.useCallback<GridScrollApi['scroll']>(
(params: Partial<GridScrollParams>) => {
if (virtualScrollerRef.current && params.left !== undefined && colRef.current) {
if (
virtualScrollerRef.current &&
virtualScrollbarHorizontalRef.current &&
params.left !== undefined &&
colRef.current
) {
const direction = isRtl ? -1 : 1;
colRef.current.scrollLeft = params.left;
virtualScrollerRef.current.scrollLeft = direction * params.left;
virtualScrollbarHorizontalRef.current.scrollLeft = direction * params.left;
logger.debug(`Scrolling left: ${params.left}`);
}
if (virtualScrollerRef.current && params.top !== undefined) {
if (
virtualScrollerRef.current &&
virtualScrollbarVerticalRef.current &&
params.top !== undefined
) {
virtualScrollerRef.current.scrollTop = params.top;
virtualScrollbarVerticalRef.current.scrollTop = params.top;
logger.debug(`Scrolling top: ${params.top}`);
}
logger.debug(`Scrolling, updating container, and viewport`);
},
[virtualScrollerRef, isRtl, colRef, logger],
[
virtualScrollerRef,
virtualScrollbarHorizontalRef,
virtualScrollbarVerticalRef,
isRtl,
colRef,
logger,
],
);

const getScrollPosition = React.useCallback<GridScrollApi['getScrollPosition']>(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ export const useGridVirtualScroller = () => {
const gridRootRef = apiRef.current.rootElementRef;
const mainRef = apiRef.current.mainElementRef;
const scrollerRef = apiRef.current.virtualScrollerRef;
const scrollbarVerticalRef = React.useRef<HTMLDivElement>(null);
const scrollbarHorizontalRef = React.useRef<HTMLDivElement>(null);
const scrollbarVerticalRef = apiRef.current.virtualScrollbarVerticalRef;
const scrollbarHorizontalRef = apiRef.current.virtualScrollbarHorizontalRef;
const contentHeight = dimensions.contentSize.height;
const columnsTotalWidth = dimensions.columnsTotalWidth;
const hasColSpan = useGridSelector(apiRef, gridHasColSpanSelector);
Expand Down
10 changes: 9 additions & 1 deletion packages/x-data-grid/src/models/api/gridCoreApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@ export interface GridCorePrivateApi<
*/
mainElementRef: React.RefObject<HTMLDivElement>;
/**
* The React ref of the grid virtual scroller container element.
* The React ref of the grid's virtual scroller container element.
*/
virtualScrollerRef: React.RefObject<HTMLDivElement>;
/**
* The React ref of the grid's vertical virtual scrollbar container element.
*/
virtualScrollbarVerticalRef: React.RefObject<HTMLDivElement>;
/**
* The React ref of the grid's horizontal virtual scrollbar container element.
*/
virtualScrollbarHorizontalRef: React.RefObject<HTMLDivElement>;
/**
* The React ref of the grid column container virtualized div element.
*/
Expand Down

0 comments on commit 010f526

Please sign in to comment.