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

Expose scrollTo and scrollToItem on imperative ref #6042

Merged
merged 9 commits into from
Jul 12, 2022
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';

import { GuideSectionTypes } from '../../../components';
import { EuiCode, EuiSpacer, EuiCallOut } from '../../../../../src/components';
import {
EuiCode,
EuiSpacer,
EuiCallOut,
EuiTitle,
EuiLink,
} from '../../../../../src/components';

import { EuiDataGridRefProps } from '!!prop-loader!../../../../../src/components/datagrid/data_grid_types';

Expand Down Expand Up @@ -81,8 +87,6 @@ export const DataGridAdvancedExample = {
</li>
</ul>

<EuiSpacer size="s" />

<EuiCallOut title="Handling cell location">
When using <EuiCode>setFocusedCell</EuiCode> or{' '}
<EuiCode>openCellPopover</EuiCode>, keep in mind:
Expand All @@ -104,11 +108,74 @@ export const DataGridAdvancedExample = {
</ul>
</EuiCallOut>

<EuiSpacer size="s" />

<EuiTitle>
<h3>react-window methods</h3>
</EuiTitle>
<p>
<EuiCode>EuiDataGrid</EuiCode> also exposes several underlying
methods from{' '}
<EuiLink
href="https://react-window.vercel.app/#/api/VariableSizeGrid"
target="_blank"
>
react-window&apos;s <EuiCode>VariableSizeGrid</EuiCode> imperative
API
</EuiLink>{' '}
via its <EuiCode>ref</EuiCode>:
</p>
<ul>
<li>
<p>
<EuiCode>
scrollTo({'{ scrollLeft: number, scrollTop: number }'})
</EuiCode>{' '}
- scrolls the grid to the specified horizontal and vertical
pixel offsets.
</p>
</li>
<li>
<p>
<EuiCode>
scrollToItem(
{
'{ align: string = "auto", columnIndex?: number, rowIndex?: number }'
}
)
</EuiCode>{' '}
- scrolls the grid to the specified row and columns indices
</p>
</li>
</ul>
<EuiCallOut title="react-window vs. EUI">
<p>
Unlike EUI&apos;s ref APIs, <EuiCode>rowIndex</EuiCode> here
refers to the <strong>visible</strong> <EuiCode>rowIndex</EuiCode>{' '}
when passed to a method of a native{' '}
<EuiCode>react-window</EuiCode> API.
</p>
<p>
For example:{' '}
<EuiCode>
{'scrollToItem({ rowIndex: 50, columnIndex: 0 })'}
</EuiCode>{' '}
will always scroll to 51st visible row on the currently visible
page, regardless of the content in the cell. In contrast,{' '}
<EuiCode>
{'setFocusedCell({ rowIndex: 50, colIndex: 0 })'}
</EuiCode>{' '}
will scroll to the 51st row in your data, which may not be the
51st visible row in the grid if it is paginated or sorted.
</p>
</EuiCallOut>

<EuiSpacer />

<p>
The below example shows how to use the internal APIs for a data grid
that opens a modal via cell actions.
that opens a modal via cell actions, that scroll to specific cells,
and that can be put into full-screen mode.
</p>
</>
),
Expand Down
16 changes: 15 additions & 1 deletion src-docs/src/views/datagrid/advanced/ref.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ for (let i = 1; i < 100; i++) {
}

export default () => {
const dataGridRef = useRef<EuiDataGridRefProps>(null);
const dataGridRef = useRef<EuiDataGridRefProps | null>(null);

// Modal
const [isModalVisible, setIsModalVisible] = useState(false);
Expand Down Expand Up @@ -161,6 +161,20 @@ export default () => {
Set cell focus
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
onClick={() =>
dataGridRef.current!.scrollToItem?.({
rowIndex: rowIndexAction,
columnIndex: colIndexAction,
align: 'center',
})
}
>
Scroll to cell
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
Expand Down
1 change: 1 addition & 0 deletions src/components/datagrid/data_grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export const EuiDataGrid = forwardRef<EuiDataGridRefProps, EuiDataGridProps>(
*/
useImperativeGridRef({
ref,
gridRef,
setIsFullScreen,
focusContext,
cellPopoverContext,
Expand Down
11 changes: 11 additions & 0 deletions src/components/datagrid/data_grid_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ReactElement,
AriaAttributes,
MutableRefObject,
Component,
} from 'react';
import {
VariableSizeGridProps,
Expand All @@ -28,6 +29,8 @@ import { RowHeightUtils } from './utils/row_heights';
import { IconType } from '../icon';
import { EuiTokenProps } from '../token';

export type ImperativeGridApi = Omit<Grid, keyof Component>;

export interface EuiDataGridToolbarProps {
gridWidth: number;
minSizeForControls?: number;
Expand Down Expand Up @@ -358,6 +361,14 @@ export interface EuiDataGridRefProps {
* Closes any currently open popovers in the data grid.
*/
closeCellPopover: () => void;
/**
* Scrolls to a specified top and left offset.
*/
scrollTo?: ImperativeGridApi['scrollTo'];
/**
* Scrolls to a specified rowIndex.
*/
scrollToItem?: ImperativeGridApi['scrollToItem'];
}

export interface EuiDataGridColumnResizerProps {
Expand Down
31 changes: 26 additions & 5 deletions src/components/datagrid/utils/ref.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@

import React, { useState, createRef, forwardRef } from 'react';
import { EuiDataGrid } from '../';
import { EuiDataGridRefProps } from '../data_grid_types';
import type {
EuiDataGridRefProps,
EuiDataGridSorting,
} from '../data_grid_types';

// We need to set up a test component here for sorting/pagination state to work
// The underlying imperative ref should still be forwarded and work as normal
const GridTest = forwardRef<EuiDataGridRefProps, any>((_, ref) => {
// Pagination
const [pageIndex, setPageIndex] = useState(0);
const onChangePage = (pageIndex) => setPageIndex(pageIndex);
const [pageIndex, onChangePage] = useState(0);

// Sorting
const [sortingColumns, setSortingColumns] = useState([]);
const onSort = (sortingColumns) => setSortingColumns(sortingColumns);
const [sortingColumns, onSort] = useState<EuiDataGridSorting['columns']>([]);
weltenwort marked this conversation as resolved.
Show resolved Hide resolved

return (
<EuiDataGrid
Expand Down Expand Up @@ -197,4 +198,24 @@ describe('useImperativeGridRef', () => {
});
});
});

describe('scrollTo', () => {
it('scrolls the grid to a specified position', () => {
cy.get('.euiDataGrid__virtualized').should('have.prop', 'scrollTop', 0);
cy.then(() => {
ref.current.scrollTo({ scrollTop: 500, scrollLeft: 0 });
});
cy.get('.euiDataGrid__virtualized').should('have.prop', 'scrollTop', 500);
});
});

describe('scrollToItem', () => {
it('scrolls to a specific cell position, rendering the cell', () => {
cy.get('[data-gridcell-row-index="15"]').should('not.exist');
cy.then(() => {
ref.current.scrollToItem({ rowIndex: 15, columnIndex: 5 });
});
cy.get('[data-gridcell-row-index="15"]').should('exist');
});
});
});
28 changes: 25 additions & 3 deletions src/components/datagrid/utils/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import { useImperativeHandle, useCallback, Ref } from 'react';
import { useImperativeHandle, useCallback, Ref, RefObject } from 'react';
import type { VariableSizeGrid } from 'react-window';
import {
EuiDataGridRefProps,
EuiDataGridProps,
Expand All @@ -17,6 +18,7 @@ import {

interface Dependencies {
ref: Ref<unknown>;
gridRef: RefObject<VariableSizeGrid>;
setIsFullScreen: EuiDataGridRefProps['setIsFullScreen'];
focusContext: DataGridFocusContextShape;
cellPopoverContext: DataGridCellPopoverContextShape;
Expand All @@ -28,6 +30,7 @@ interface Dependencies {

export const useImperativeGridRef = ({
ref,
gridRef,
setIsFullScreen,
focusContext,
cellPopoverContext,
Expand Down Expand Up @@ -75,16 +78,35 @@ export const useImperativeGridRef = ({
[_openCellPopover, checkCellExists, findVisibleRowIndex]
);

const scrollTo = useCallback<VariableSizeGrid['scrollTo']>(
(...args) => gridRef.current?.scrollTo(...args),
[gridRef]
);

const scrollToItem = useCallback<VariableSizeGrid['scrollToItem']>(
(...args) => gridRef.current?.scrollToItem(...args),
[gridRef]
);

// Set the ref APIs
useImperativeHandle(
ref,
() => ({
(): EuiDataGridRefProps => ({
setIsFullScreen,
setFocusedCell,
openCellPopover,
closeCellPopover,
scrollTo,
scrollToItem,
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
}),
[setIsFullScreen, setFocusedCell, openCellPopover, closeCellPopover]
[
setIsFullScreen,
setFocusedCell,
openCellPopover,
closeCellPopover,
scrollTo,
scrollToItem,
]
);
};

Expand Down
1 change: 1 addition & 0 deletions upcoming_changelogs/6042.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `EuiDataGrid`'s imperative API now exposes the `scrollTo` and `scrollToItem` APIs of `react-window`.