diff --git a/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js b/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js index e978a551224..adf9825c347 100644 --- a/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js +++ b/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js @@ -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'; @@ -81,8 +87,6 @@ export const DataGridAdvancedExample = { - - When using setFocusedCell or{' '} openCellPopover, keep in mind: @@ -104,11 +108,74 @@ export const DataGridAdvancedExample = { + + + +

react-window methods

+
+

+ EuiDataGrid also exposes several underlying + methods from{' '} + + react-window's VariableSizeGrid imperative + API + {' '} + via its ref: +

+ + +

+ Unlike EUI's ref APIs, rowIndex here + refers to the visible rowIndex{' '} + when passed to a method of a native{' '} + react-window API. +

+

+ For example:{' '} + + {'scrollToItem({ rowIndex: 50, columnIndex: 0 })'} + {' '} + will always scroll to 51st visible row on the currently visible + page, regardless of the content in the cell. In contrast,{' '} + + {'setFocusedCell({ rowIndex: 50, colIndex: 0 })'} + {' '} + 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. +

+
+

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.

), diff --git a/src-docs/src/views/datagrid/advanced/ref.tsx b/src-docs/src/views/datagrid/advanced/ref.tsx index c755105a28f..880e493a1d1 100644 --- a/src-docs/src/views/datagrid/advanced/ref.tsx +++ b/src-docs/src/views/datagrid/advanced/ref.tsx @@ -31,7 +31,7 @@ for (let i = 1; i < 100; i++) { } export default () => { - const dataGridRef = useRef(null); + const dataGridRef = useRef(null); // Modal const [isModalVisible, setIsModalVisible] = useState(false); @@ -161,6 +161,20 @@ export default () => { Set cell focus + + + dataGridRef.current!.scrollToItem?.({ + rowIndex: rowIndexAction, + columnIndex: colIndexAction, + align: 'center', + }) + } + > + Scroll to cell + + ( */ useImperativeGridRef({ ref, + gridRef, setIsFullScreen, focusContext, cellPopoverContext, diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 9d98ac0f039..556c07faae3 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -15,6 +15,7 @@ import { ReactElement, AriaAttributes, MutableRefObject, + Component, } from 'react'; import { VariableSizeGridProps, @@ -28,6 +29,8 @@ import { RowHeightUtils } from './utils/row_heights'; import { IconType } from '../icon'; import { EuiTokenProps } from '../token'; +export type ImperativeGridApi = Omit; + export interface EuiDataGridToolbarProps { gridWidth: number; minSizeForControls?: number; @@ -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 { diff --git a/src/components/datagrid/utils/ref.spec.tsx b/src/components/datagrid/utils/ref.spec.tsx index af33ad4a9d0..e371976a4c1 100644 --- a/src/components/datagrid/utils/ref.spec.tsx +++ b/src/components/datagrid/utils/ref.spec.tsx @@ -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((_, 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([]); return ( { }); }); }); + + 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'); + }); + }); }); diff --git a/src/components/datagrid/utils/ref.ts b/src/components/datagrid/utils/ref.ts index 5c4d6a0085e..f3e2409651c 100644 --- a/src/components/datagrid/utils/ref.ts +++ b/src/components/datagrid/utils/ref.ts @@ -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, @@ -17,6 +18,7 @@ import { interface Dependencies { ref: Ref; + gridRef: RefObject; setIsFullScreen: EuiDataGridRefProps['setIsFullScreen']; focusContext: DataGridFocusContextShape; cellPopoverContext: DataGridCellPopoverContextShape; @@ -28,6 +30,7 @@ interface Dependencies { export const useImperativeGridRef = ({ ref, + gridRef, setIsFullScreen, focusContext, cellPopoverContext, @@ -75,16 +78,35 @@ export const useImperativeGridRef = ({ [_openCellPopover, checkCellExists, findVisibleRowIndex] ); + const scrollTo = useCallback( + (...args) => gridRef.current?.scrollTo(...args), + [gridRef] + ); + + const scrollToItem = useCallback( + (...args) => gridRef.current?.scrollToItem(...args), + [gridRef] + ); + // Set the ref APIs useImperativeHandle( ref, - () => ({ + (): EuiDataGridRefProps => ({ setIsFullScreen, setFocusedCell, openCellPopover, closeCellPopover, + scrollTo, + scrollToItem, }), - [setIsFullScreen, setFocusedCell, openCellPopover, closeCellPopover] + [ + setIsFullScreen, + setFocusedCell, + openCellPopover, + closeCellPopover, + scrollTo, + scrollToItem, + ] ); }; diff --git a/upcoming_changelogs/6042.md b/upcoming_changelogs/6042.md new file mode 100644 index 00000000000..3b7bc806aa5 --- /dev/null +++ b/upcoming_changelogs/6042.md @@ -0,0 +1 @@ +- `EuiDataGrid`'s imperative API now exposes the `scrollTo` and `scrollToItem` APIs of `react-window`.