From 40689257649513332c9dafe2980e7fbc6fe5f1ae Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 5 Jan 2022 14:31:44 -0800 Subject: [PATCH] Workaround forcing the datagrid to fully scroll cells into view when using keyboard navigation --- src/components/datagrid/data_grid.tsx | 7 ++++ src/components/datagrid/utils/focus.ts | 55 +++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index 15f49ec113d..430ec3cf7d9 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -38,6 +38,7 @@ import { useFocus, createKeyDownHandler, preventTabbing, + useKeyboardFocusScrollWorkaround, } from './utils/focus'; import { useInMemoryValues, @@ -266,6 +267,12 @@ export const EuiDataGrid: FunctionComponent = (props) => { gridItemsRendered, }); + useKeyboardFocusScrollWorkaround({ + focusedCell: focusContext.focusedCell, + gridRef, + visibleRowCount, + }); + /** * Toolbar & full-screen */ diff --git a/src/components/datagrid/utils/focus.ts b/src/components/datagrid/utils/focus.ts index f42af4ebf9d..ebcfe25130a 100644 --- a/src/components/datagrid/utils/focus.ts +++ b/src/components/datagrid/utils/focus.ts @@ -18,7 +18,10 @@ import { KeyboardEvent, MutableRefObject, } from 'react'; -import { GridOnItemsRenderedProps } from 'react-window'; +import { + VariableSizeGrid as Grid, + GridOnItemsRenderedProps, +} from 'react-window'; import tabbable from 'tabbable'; import { keys } from '../../../services'; import { @@ -317,3 +320,53 @@ export const useHeaderFocusWorkaround = (headerIsInteractive: boolean) => { } }, [headerIsInteractive, focusedCell, setFocusedCell]); }; + +// Force the grid to always scroll to/show the full cell on keyboard navigation +export const useKeyboardFocusScrollWorkaround = ({ + focusedCell, + gridRef, + visibleRowCount, +}: { + focusedCell?: EuiDataGridFocusedCell; + gridRef: MutableRefObject; + visibleRowCount: number; +}) => { + const [x, y] = focusedCell || []; + const previousFocusedCell = useRef([0, 0]); + + useEffect(() => { + if (!focusedCell || !gridRef.current) return; + + const isNavigatingDown = focusedCell[1] > previousFocusedCell.current[1]; + const isLastRow = focusedCell[1] >= visibleRowCount - 1; // Uses >= instead of === to account for footer rows + + // @see https://github.com/bvaughn/react-window/issues/586 + // Navigating downwards doesn't scroll to items as expected due to the sticky header, + // so we let .focus() auto scrolling handle that for us and not the scrollToItem API + if (!isNavigatingDown && !isLastRow) { + gridRef.current.scrollToItem({ columnIndex: x, rowIndex: y }); + } + + // The last row has incorrect scrollTop calculations, again due to the sticky header, + // so we can't use just scrollToItem - we have to manually calculate and scrollTo the bottom of the grid + if (isLastRow) { + // Left/right scrollToItem still works + gridRef.current.scrollToItem({ columnIndex: x }); + + // @ts-ignore - _outerRef is an internal variable + const gridOuterRef = gridRef.current._outerRef as HTMLElement; + const gridInnerRef = gridOuterRef.firstChild as HTMLElement; + const scrollBottom = + gridInnerRef.clientHeight - gridOuterRef.clientHeight; + + // @ts-ignore - despite react-window's typing, it's possible to pass in only a scrollTop + gridRef.current.scrollTo({ scrollTop: scrollBottom }); + } + + previousFocusedCell.current = focusedCell; + + // We specifically only want this to fire when the current focused cell location changes + // (since the focusedCell reference can change without the coords changing) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [x, y]); +};