From 29f9f121dbb41779d896376cb11ec2b9705d78a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 5 Jul 2022 18:32:21 +0000 Subject: [PATCH 1/7] Expose scrollTo and scrollToItem on imperative ref --- src/components/datagrid/data_grid.tsx | 1 + src/components/datagrid/data_grid_types.ts | 11 +++++++++ src/components/datagrid/utils/ref.ts | 28 +++++++++++++++++++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index b62786d82bd..bf858177a02 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -287,6 +287,7 @@ export const EuiDataGrid = forwardRef( */ 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.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, + ] ); }; From da364e5653d5e5759c539c9347733518dab79b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Wed, 6 Jul 2022 15:24:50 +0000 Subject: [PATCH 2/7] Add documentation for `scrollTo` and `scrollToItem` --- .../advanced/datagrid_advanced_example.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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..e7ee17661de 100644 --- a/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js +++ b/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js @@ -79,6 +79,27 @@ export const DataGridAdvancedExample = { open cell popover.

+
  • +

    + + scrollTo({'{ scrollLeft: number, scrollTop: number }'}) + {' '} + - scrolls the grid to the specified horizontal and vertical + pixel offsets. +

    +
  • +
  • +

    + + scrollToItem( + { + '{align: string = "auto", columnIndex?: number, rowIndex?: number }' + } + ) + {' '} + - scrolls the grid to the specified row and columns indices +

    +
  • From f9894dbf311d59d82e87a79313ccbadbe48ba519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Mon, 11 Jul 2022 17:50:19 +0000 Subject: [PATCH 3/7] Add tests for `scrollTo` and `scrollToItem` --- src/components/datagrid/utils/ref.spec.tsx | 31 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) 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'); + }); + }); }); From 39f655eb376e744c3af1c087bfed2cf8419f5c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Mon, 11 Jul 2022 17:52:10 +0000 Subject: [PATCH 4/7] Add changelog entry --- upcoming_changelogs/6042.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 upcoming_changelogs/6042.md diff --git a/upcoming_changelogs/6042.md b/upcoming_changelogs/6042.md new file mode 100644 index 00000000000..0fb6d06d91b --- /dev/null +++ b/upcoming_changelogs/6042.md @@ -0,0 +1 @@ +- The `EuiDataGrid`'s imperative API now exposes the `scrollTo` and `scrollToItem` APIs of `react-window`. From c56d7056aee672a9423fd238fbb2e551b553939f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 12 Jul 2022 11:05:46 +0200 Subject: [PATCH 5/7] Update upcoming_changelogs/6042.md Co-authored-by: Constance --- upcoming_changelogs/6042.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upcoming_changelogs/6042.md b/upcoming_changelogs/6042.md index 0fb6d06d91b..3b7bc806aa5 100644 --- a/upcoming_changelogs/6042.md +++ b/upcoming_changelogs/6042.md @@ -1 +1 @@ -- The `EuiDataGrid`'s imperative API now exposes the `scrollTo` and `scrollToItem` APIs of `react-window`. +- `EuiDataGrid`'s imperative API now exposes the `scrollTo` and `scrollToItem` APIs of `react-window`. From 530a33ce5bc76e9b5cbda8d859e347b7f713bc9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 12 Jul 2022 10:47:28 +0000 Subject: [PATCH 6/7] Move react-window API docs into separate section --- .../advanced/datagrid_advanced_example.js | 95 ++++++++++++++----- src-docs/src/views/datagrid/advanced/ref.tsx | 16 +++- 2 files changed, 84 insertions(+), 27 deletions(-) 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 e7ee17661de..3c39965f2ac 100644 --- a/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js +++ b/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import { GuideSectionTypes } from '../../../components'; import { EuiCode, EuiSpacer, EuiCallOut } from '../../../../../src/components'; @@ -30,12 +31,6 @@ export const DataGridAdvancedExample = { sections: [ { title: 'Ref methods', - source: [ - { - type: GuideSectionTypes.TSX, - code: dataGridRefSource, - }, - ], text: ( <>

    @@ -79,6 +74,53 @@ export const DataGridAdvancedExample = { open cell popover.

    + + + + + + When using setFocusedCell or{' '} + openCellPopover, keep in mind: +
      +
    • + colIndex is affected by the user reordering + or hiding columns. +
    • +
    • + If the passed cell indices are outside the data grid's + total row count or visible column count, an error will be + thrown. +
    • +
    • + If the data grid is paginated or sorted, the grid will handle + automatically finding specified row index's correct + location for you. +
    • +
    +
    + + ), + }, + { + title: 'react-window methods', + source: [ + { + type: GuideSectionTypes.TSX, + code: dataGridRefSource, + }, + ], + text: ( + <> +

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

    +
    • @@ -93,7 +135,7 @@ export const DataGridAdvancedExample = { scrollToItem( { - '{align: string = "auto", columnIndex?: number, rowIndex?: number }' + '{ align: string = "auto", columnIndex?: number, rowIndex?: number }' } ) {' '} @@ -105,31 +147,32 @@ export const DataGridAdvancedExample = { - When using setFocusedCell or{' '} - openCellPopover, keep in mind: -

        -
      • - colIndex is affected by the user reordering - or hiding columns. -
      • -
      • - If the passed cell indices are outside the data grid's - total row count or visible column count, an error will be - thrown. -
      • -
      • - If the data grid is paginated or sorted, the grid will handle - automatically finding specified row index's correct - location for you. -
      • -
      +

      + Unlike in other EUI APIs, rowIndex refers to + the visible rowIndex when passed to a method of + the 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 what content is 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 + + Date: Tue, 12 Jul 2022 13:07:37 -0700 Subject: [PATCH 7/7] [PR feedback] Docs tweaks - combine sections and use EuiTitle tag for react-window methods - Fix link to react-window docs - minor grammar tweaks --- .../advanced/datagrid_advanced_example.js | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) 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 3c39965f2ac..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,8 +1,13 @@ import React from 'react'; -import { Link } from 'react-router-dom'; 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'; @@ -31,6 +36,12 @@ export const DataGridAdvancedExample = { sections: [ { title: 'Ref methods', + source: [ + { + type: GuideSectionTypes.TSX, + code: dataGridRefSource, + }, + ], text: ( <>

      @@ -76,8 +87,6 @@ export const DataGridAdvancedExample = {

    - - When using setFocusedCell or{' '} openCellPopover, keep in mind: @@ -98,26 +107,22 @@ export const DataGridAdvancedExample = { - - ), - }, - { - title: 'react-window methods', - source: [ - { - type: GuideSectionTypes.TSX, - code: dataGridRefSource, - }, - ], - text: ( - <> + + + + +

    react-window methods

    +

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

      @@ -143,14 +148,12 @@ export const DataGridAdvancedExample = {

    - - - - +

    - Unlike in other EUI APIs, rowIndex refers to - the visible rowIndex when passed to a method of - the native react-window API. + 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:{' '} @@ -158,7 +161,7 @@ export const DataGridAdvancedExample = { {'scrollToItem({ rowIndex: 50, columnIndex: 0 })'} {' '} will always scroll to 51st visible row on the currently visible - page, regardless of what content is in the cell. In contrast,{' '} + page, regardless of the content in the cell. In contrast,{' '} {'setFocusedCell({ rowIndex: 50, colIndex: 0 })'} {' '}