Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGridPro] Rework onRowsScrollEnd to use IntersectionObserver #8672

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b367d96
Rework onRowsScrollEnd to use IntersectionObserver
DanailH Apr 19, 2023
aa72ec4
Merge branch 'master' of https://github.com/mui/mui-x into issue/4371…
DanailH Jun 29, 2023
0d6cf9f
push solution forward
DanailH Jun 29, 2023
c93352f
remove unwanted changes
DanailH Jun 29, 2023
268313c
proptypes
DanailH Jun 29, 2023
4567916
apis
DanailH Jun 29, 2023
55f6c8a
depracate prop
DanailH Jul 5, 2023
c2b10a5
fix types
DanailH Jul 5, 2023
922573f
revert unwanted changes
DanailH Jul 5, 2023
cac873b
api
DanailH Jul 5, 2023
3b99b8e
Merge branch 'master' of https://github.com/mui/mui-x into issue/4371…
DanailH Jul 12, 2023
ab5fe1e
fix tests
DanailH Jul 13, 2023
1f52a50
adjust the test
DanailH Jul 13, 2023
fa4770c
update demo and limit onRowsChange calls
DanailH Jul 18, 2023
f15c000
enable all tests
DanailH Jul 18, 2023
0961cf5
resolve conflicts
DanailH Jul 28, 2023
0b73deb
Merge branch 'next' of https://github.com/mui/mui-x into issue/4371-r…
DanailH Nov 22, 2023
70e3089
resolve conflicts
DanailH Jan 4, 2024
9795939
fix formatting
DanailH Jan 4, 2024
053fa3c
reorganise tests
DanailH Jan 4, 2024
fea8f39
statics
DanailH Jan 4, 2024
ef4d716
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 19, 2024
39215d1
docs:api
cherniavskii Feb 19, 2024
39afb62
return scrollEndThreshold prop back
cherniavskii Feb 19, 2024
34730ce
PoC: enable intersections tracking only after rowsSet even
cherniavskii Feb 20, 2024
d54bf23
lint
cherniavskii Feb 20, 2024
b2809c3
fix unit test
cherniavskii Feb 20, 2024
cdd6c86
fix failing unit tests
cherniavskii Feb 21, 2024
954e394
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 21, 2024
1a0db41
fix failing test
cherniavskii Feb 21, 2024
10ebbdc
simplify infinite loader
cherniavskii Feb 21, 2024
9878e00
expand test case
cherniavskii Feb 21, 2024
b604cce
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 21, 2024
4ab7adc
fix infinite loading not working with horizontal scroll
cherniavskii Feb 21, 2024
cebc226
show initial loading state
cherniavskii Feb 21, 2024
6162672
prettier
cherniavskii Feb 21, 2024
318ec62
get rid of observer manager
cherniavskii Feb 22, 2024
777ac4b
rename the ref
cherniavskii Feb 22, 2024
e7df68d
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 22, 2024
52bcc62
fix scrollEndThreshold not working properly
cherniavskii Feb 22, 2024
74a91b2
add unit test
cherniavskii Feb 22, 2024
29c10cd
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 22, 2024
1274f40
pass column headers height
cherniavskii Feb 22, 2024
1b08d8f
lower the delay time
cherniavskii Feb 22, 2024
ace28e7
move infinite loading trigger to useGridVirtualScroller
cherniavskii Feb 22, 2024
a65dfb5
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 27, 2024
86e9f8b
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 28, 2024
3c8592c
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Feb 29, 2024
1568613
resolve conflicts
cherniavskii Feb 29, 2024
24ffe1a
add server-side sorting
cherniavskii Feb 29, 2024
259f09a
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Mar 2, 2024
b4639bc
update import
cherniavskii Mar 2, 2024
0d70d79
move infinite loading trigger element to useGridInfiniteLoader hook
cherniavskii Mar 2, 2024
bf1f72d
remove broken export
cherniavskii Mar 2, 2024
ad04683
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Mar 5, 2024
2a25c0e
fix ts issues
cherniavskii Mar 5, 2024
db3b679
Merge branch 'next' into issue/4371-rework-onRowsScrollEnd-to-use-Int…
cherniavskii Mar 7, 2024
3a707b1
update infinite loading demo to use server-side filtering
cherniavskii Mar 7, 2024
3224228
update docs
cherniavskii Mar 7, 2024
7813044
initialize columns asap
cherniavskii Mar 7, 2024
6b9edb8
remove unused import
cherniavskii Mar 7, 2024
9fa5de0
use disconnect instead of unobserve
cherniavskii Mar 7, 2024
e85f4b3
derive marginBottom outside of use effect to make dependencies cleaner
cherniavskii Mar 7, 2024
4ab0bd3
update docs
cherniavskii Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
GridRowMultiSelectionApi,
GridColumnReorderApi,
GridRowProApi,
GridInfiniteLoaderApi,
} from '@mui/x-data-grid-pro';
import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium';
import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks';
Expand All @@ -27,6 +28,7 @@ export interface GridApiPremium
GridAggregationApi,
GridRowPinningApi,
GridCellSelectionApi,
GridInfiniteLoaderApi,
// APIs that are private in Community plan, but public in Pro and Premium plans
GridRowMultiSelectionApi,
GridColumnReorderApi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridPro
*/
export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValue = {
...DATA_GRID_PROPS_DEFAULT_VALUES,
scrollEndThreshold: 80,
scrollEndThreshold: 1.0,
treeData: false,
defaultGroupingExpansionDepth: 0,
disableColumnPinning: false,
Expand Down
1 change: 1 addition & 0 deletions packages/grid/x-data-grid-pro/src/hooks/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './rowReorder';
export * from './treeData';
export * from './detailPanel';
export * from './rowPinning';
export * from './infiniteLoader';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface GridInfiniteLoaderApi {
/**
* Handles the last visible row ref.
* @param {HTMLElement} node The HTML element that the ref is attatched to.
* @ignore - do not document. Remove before releasing v5 stable version.
*/
unstable_lastVisibleRowRef: (node: HTMLElement) => void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './gridInfniteLoaderInterface';
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import * as React from 'react';
import {
useGridSelector,
GridEventListener,
GridScrollParams,
useGridApiEventHandler,
useGridApiOptionHandler,
gridVisibleColumnDefinitionsSelector,
gridRowsMetaSelector,
useGridApiMethod,
} from '@mui/x-data-grid';
import { useGridVisibleRows } from '@mui/x-data-grid/internals';
import { GridRowScrollEndParams } from '../../../models';
import { GridPrivateApiPro } from '../../../models/gridApiPro';
import { DataGridProProcessedProps } from '../../../models/dataGridProProps';
import { GridInfiniteLoaderApi } from './gridInfniteLoaderInterface';

/**
* @requires useGridColumns (state)
Expand All @@ -27,57 +25,63 @@ export const useGridInfiniteLoader = (
): void => {
const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector);
const currentPage = useGridVisibleRows(apiRef, props);
const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector);
const contentHeight = Math.max(rowsMeta.currentPageTotalHeight, 1);
const observer = React.useRef<IntersectionObserver>();
let previousY = 0;
let previousRatio = 0;

const isInScrollBottomArea = React.useRef<boolean>(false);
const handleLoadMoreRows = React.useCallback(
([entry]: any) => {
const currentY = entry.boundingClientRect.y;
const currentRatio = entry.intersectionRatio;
const isIntersecting = entry.isIntersecting;

const handleRowsScrollEnd = React.useCallback(
(scrollPosition: GridScrollParams) => {
const dimensions = apiRef.current.getRootDimensions();
// Scrolling down check
if (currentY < previousY || isIntersecting) {
if (currentRatio > previousRatio) {
const viewportPageSize = apiRef.current.getViewportPageSize();
const rowScrollEndParam: GridRowScrollEndParams = {
visibleColumns,
viewportPageSize,
visibleRowsCount: currentPage.rows.length,
};
apiRef.current.publishEvent('rowsScrollEnd', rowScrollEndParam);
}
}

// eslint-disable-next-line react-hooks/exhaustive-deps
previousY = currentY;
// eslint-disable-next-line react-hooks/exhaustive-deps
previousRatio = currentRatio;
},
[apiRef, visibleColumns, currentPage.rows.length],
);

const lastVisibleRowRef = React.useCallback<GridInfiniteLoaderApi['unstable_lastVisibleRowRef']>(
(node) => {
// Prevent the infite loading working in combination with lazy loading
if (!dimensions || props.rowsLoadingMode !== 'client') {
if (props.rowsLoadingMode !== 'client') {
return;
}

const scrollPositionBottom = scrollPosition.top + dimensions.viewportOuterSize.height;
const viewportPageSize = apiRef.current.getViewportPageSize();

if (scrollPositionBottom < contentHeight - props.scrollEndThreshold) {
isInScrollBottomArea.current = false;
if (observer.current) {
observer.current.disconnect();
}

if (
scrollPositionBottom >= contentHeight - props.scrollEndThreshold &&
!isInScrollBottomArea.current
) {
const rowScrollEndParam: GridRowScrollEndParams = {
visibleColumns,
viewportPageSize,
visibleRowsCount: currentPage.rows.length,
};
apiRef.current.publishEvent('rowsScrollEnd', rowScrollEndParam);
isInScrollBottomArea.current = true;
observer.current = new IntersectionObserver(handleLoadMoreRows, {
threshold: props.scrollEndThreshold,
});

if (node) {
observer.current.observe(node);
}
},
[
contentHeight,
props.scrollEndThreshold,
props.rowsLoadingMode,
visibleColumns,
apiRef,
currentPage.rows.length,
],
[props, handleLoadMoreRows],
);

const handleGridScroll = React.useCallback<GridEventListener<'scrollPositionChange'>>(
({ left, top }) => {
handleRowsScrollEnd({ left, top });
},
[handleRowsScrollEnd],
);
const infiteLoaderApi: GridInfiniteLoaderApi = {
unstable_lastVisibleRowRef: lastVisibleRowRef,
};

useGridApiEventHandler(apiRef, 'scrollPositionChange', handleGridScroll);
useGridApiMethod(apiRef, infiteLoaderApi, 'public');
useGridApiOptionHandler(apiRef, 'rowsScrollEnd', props.onRowsScrollEnd);
};
2 changes: 2 additions & 0 deletions packages/grid/x-data-grid-pro/src/models/gridApiPro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
GridDetailPanelApi,
GridRowPinningApi,
GridDetailPanelPrivateApi,
GridInfiniteLoaderApi,
} from '../hooks';

/**
Expand All @@ -22,6 +23,7 @@ export interface GridApiPro
GridColumnPinningApi,
GridDetailPanelApi,
GridRowPinningApi,
GridInfiniteLoaderApi,
// APIs that are private in Community plan, but public in Pro and Premium plans
GridRowMultiSelectionApi,
GridColumnReorderApi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,20 @@ describe('<DataGridPro /> - Events Params', () => {
});
});

it('publishing GRID_ROWS_SCROLL should call onRowsScrollEnd callback', () => {
it('publishing GRID_ROWS_SCROLL should call onRowsScrollEnd callback', function test() {
if (isJSDOM) {
this.skip(); // Needs layout
}
const handleRowsScrollEnd = spy();
render(<TestEvents onRowsScrollEnd={handleRowsScrollEnd} />);
act(() => apiRef.current.publishEvent('scrollPositionChange', { left: 0, top: 3 * 52 }));
expect(handleRowsScrollEnd.callCount).to.equal(1);
});

it('publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', () => {
it('publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', function test() {
if (isJSDOM) {
this.skip(); // Needs layout
}
const handleFetchRows = spy();
render(
<TestEvents
Expand Down
10 changes: 9 additions & 1 deletion packages/grid/x-data-grid/src/components/GridRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,17 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
}
: null;

const handleRowRef = (element: HTMLDivElement | null) => {
if (isLastVisible && (rootProps as any).onRowsScrollEnd) {
(apiRef as any).current.unstable_lastVisibleRowRef(element);
}

return handleRef && handleRef(element);
};

return (
<div
ref={handleRef}
ref={handleRowRef}
data-id={rowId}
data-rowindex={index}
role="row"
Expand Down
1 change: 1 addition & 0 deletions scripts/x-data-grid-premium.exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@
{ "name": "GridHeaderFilterMenuContainer", "kind": "Function" },
{ "name": "GridHeaderSelectionCheckboxParams", "kind": "Interface" },
{ "name": "GridIconSlotsComponent", "kind": "Interface" },
{ "name": "GridInfiniteLoaderApi", "kind": "Interface" },
{ "name": "GridInitialState", "kind": "TypeAlias" },
{ "name": "GridInputRowSelectionModel", "kind": "TypeAlias" },
{ "name": "GridKeyboardArrowRight", "kind": "Variable" },
Expand Down
1 change: 1 addition & 0 deletions scripts/x-data-grid-pro.exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@
{ "name": "GridHeaderFilterMenuContainer", "kind": "Function" },
{ "name": "GridHeaderSelectionCheckboxParams", "kind": "Interface" },
{ "name": "GridIconSlotsComponent", "kind": "Interface" },
{ "name": "GridInfiniteLoaderApi", "kind": "Interface" },
{ "name": "GridInitialState", "kind": "TypeAlias" },
{ "name": "GridInputRowSelectionModel", "kind": "TypeAlias" },
{ "name": "GridKeyboardArrowRight", "kind": "Variable" },
Expand Down