diff --git a/CHANGELOG.md b/CHANGELOG.md index d87c4fa97f7..1d185defb40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ **Bug fixes** - Fixed building dev & docs on Windows ([#2847](https://github.com/elastic/eui/pull/2847)) +- Fixed a bug in `EuiDataGrid` causing the first cell to autofocus if interactive ([#2872](https://github.com/elastic/eui/pull/2872)) ## [`19.0.0`](https://github.com/elastic/eui/tree/v19.0.0) diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index a999237e25a..df6a7f4ed5e 100644 --- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -178,6 +178,8 @@ Array [ >
); + // enable the grid to accept focus + act(() => + component + .find('div [data-test-subj="dataGridWrapper"][onFocus]') + .props().onFocus!({} as React.FocusEvent) + ); + component.update(); + let focusableCell = getFocusableCell(component); // focus should begin at the first cell expect(focusableCell.length).toEqual(1); @@ -1981,6 +1989,14 @@ Array [ /> ); + // enable the grid to accept focus + act(() => + component + .find('div [data-test-subj="dataGridWrapper"][onFocus]') + .props().onFocus!({} as React.FocusEvent) + ); + component.update(); + let focusableCell = getFocusableCell(component); expect( focusableCell.find('[data-test-subj="cell-content"]').text() diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index d020d39eebe..598ff5786fc 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -8,6 +8,8 @@ import React, { Fragment, ReactChild, useMemo, + Dispatch, + SetStateAction, } from 'react'; import classNames from 'classnames'; import tabbable from 'tabbable'; @@ -341,12 +343,14 @@ function createKeyDownHandler( visibleColumns: EuiDataGridProps['columns'], leadingControlColumns: EuiDataGridProps['leadingControlColumns'] = [], trailingControlColumns: EuiDataGridProps['trailingControlColumns'] = [], - focusedCell: EuiDataGridFocusedCell, + focusedCell: EuiDataGridFocusedCell | undefined, headerIsInteractive: boolean, setFocusedCell: (focusedCell: EuiDataGridFocusedCell) => void, updateFocus: Function ) { return (event: KeyboardEvent) => { + if (focusedCell == null) return; + const colCount = visibleColumns.length + leadingControlColumns.length + @@ -452,16 +456,46 @@ function useAfterRender(fn: Function): Function { }; } +type FocusProps = Pick, 'tabIndex' | 'onFocus'>; +const useFocus = ( + headerIsInteractive: boolean +): [ + FocusProps, + EuiDataGridFocusedCell | undefined, + Dispatch> +] => { + const [focusedCell, setFocusedCell] = useState< + EuiDataGridFocusedCell | undefined + >(undefined); + + const canCellsBeFocused = useMemo(() => focusedCell != null, [focusedCell]); + + const focusProps = useMemo( + () => + canCellsBeFocused + ? {} + : { + tabIndex: 0, + onFocus: () => + setFocusedCell(headerIsInteractive ? [0, -1] : [0, 0]), + }, + [canCellsBeFocused, setFocusedCell, headerIsInteractive] + ); + + return [focusProps, focusedCell, setFocusedCell]; +}; + export const EuiDataGrid: FunctionComponent = props => { const [isFullScreen, setIsFullScreen] = useState(false); const [hasRoomForGridControls, setHasRoomForGridControls] = useState(true); - const [focusedCell, setFocusedCell] = useState( - null - ); const [containerRef, setContainerRef] = useState(null); const [interactiveCellId] = useState(htmlIdGenerator()()); - const [headerIsInteractive, setHeaderIsInteractive] = useState(false); + + const [wrappingDivFocusProps, focusedCell, setFocusedCell] = useFocus( + headerIsInteractive + ); + const handleHeaderChange = useCallback( records => { const [{ target }] = records; @@ -693,9 +727,6 @@ export const EuiDataGrid: FunctionComponent = props => { delete rest['aria-labelledby']; } - const realizedFocusedCell: EuiDataGridFocusedCell = - focusedCell || (headerIsInteractive ? [0, -1] : [0, 0]); - const fullScreenSelector = ( = props => {
@@ -784,7 +817,7 @@ export const EuiDataGrid: FunctionComponent = props => { orderedVisibleColumns, leadingControlColumns, trailingControlColumns, - realizedFocusedCell, + focusedCell, headerIsInteractive, setFocusedCell, focusAfterRender @@ -830,7 +863,7 @@ export const EuiDataGrid: FunctionComponent = props => { schema={mergedSchema} sorting={sorting} headerIsInteractive={headerIsInteractive} - focusedCell={realizedFocusedCell} + focusedCell={focusedCell} setFocusedCell={setFocusedCell} /> )} @@ -846,7 +879,7 @@ export const EuiDataGrid: FunctionComponent = props => { schema={mergedSchema} schemaDetectors={allSchemaDetectors} popoverContents={popoverContents} - focusedCell={realizedFocusedCell} + focusedCell={focusedCell} onCellFocus={setFocusedCell} pagination={pagination} sorting={sorting} diff --git a/src/components/datagrid/data_grid_body.tsx b/src/components/datagrid/data_grid_body.tsx index 4abf985f2e9..bfcc81d8ad5 100644 --- a/src/components/datagrid/data_grid_body.tsx +++ b/src/components/datagrid/data_grid_body.tsx @@ -31,7 +31,7 @@ export interface EuiDataGridBodyProps { schema: EuiDataGridSchema; schemaDetectors: EuiDataGridSchemaDetector[]; popoverContents?: EuiDataGridPopoverContents; - focusedCell: EuiDataGridFocusedCell; + focusedCell?: EuiDataGridFocusedCell; onCellFocus: EuiDataGridDataRowProps['onCellFocus']; rowCount: number; renderCellValue: EuiDataGridCellProps['renderCellValue']; @@ -186,7 +186,7 @@ export const EuiDataGridBody: FunctionComponent< columnWidths={columnWidths} defaultColumnWidth={defaultColumnWidth} focusedCellPositionInTheRow={ - i === focusedCell[1] ? focusedCell[0] : null + focusedCell != null && i === focusedCell[1] ? focusedCell[0] : null } onCellFocus={onCellFocus} renderCellValue={renderCellValue} diff --git a/src/components/datagrid/data_grid_control_header_cell.tsx b/src/components/datagrid/data_grid_control_header_cell.tsx index ead022c8262..d1fb67846db 100644 --- a/src/components/datagrid/data_grid_control_header_cell.tsx +++ b/src/components/datagrid/data_grid_control_header_cell.tsx @@ -11,7 +11,7 @@ import { EuiDataGridDataRowProps } from './data_grid_data_row'; export interface EuiDataGridControlHeaderRowProps { index: number; controlColumn: EuiDataGridControlColumn; - focusedCell: EuiDataGridFocusedCell; + focusedCell?: EuiDataGridFocusedCell; setFocusedCell: EuiDataGridDataRowProps['onCellFocus']; headerIsInteractive: boolean; className?: string; @@ -34,7 +34,8 @@ export const EuiDataGridControlHeaderCell: FunctionComponent< const classes = classnames('euiDataGridHeaderCell', className); const headerRef = useRef(null); - const isFocused = focusedCell[0] === index && focusedCell[1] === -1; + const isFocused = + focusedCell != null && focusedCell[0] === index && focusedCell[1] === -1; const [isCellEntered, setIsCellEntered] = useState(false); useEffect(() => { diff --git a/src/components/datagrid/data_grid_header_cell.tsx b/src/components/datagrid/data_grid_header_cell.tsx index 4c1279bc9b0..1b4995dd2c8 100644 --- a/src/components/datagrid/data_grid_header_cell.tsx +++ b/src/components/datagrid/data_grid_header_cell.tsx @@ -87,7 +87,8 @@ export const EuiDataGridHeaderCell: FunctionComponent< ); const headerRef = useRef(null); - const isFocused = focusedCell[0] === index && focusedCell[1] === -1; + const isFocused = + focusedCell != null && focusedCell[0] === index && focusedCell[1] === -1; const [isCellEntered, setIsCellEntered] = useState(false); useEffect(() => { diff --git a/src/components/datagrid/data_grid_header_row.tsx b/src/components/datagrid/data_grid_header_row.tsx index 81786a5b459..9763575d033 100644 --- a/src/components/datagrid/data_grid_header_row.tsx +++ b/src/components/datagrid/data_grid_header_row.tsx @@ -22,7 +22,7 @@ export interface EuiDataGridHeaderRowPropsSpecificProps { defaultColumnWidth?: number | null; setColumnWidth: (columnId: string, width: number) => void; sorting?: EuiDataGridSorting; - focusedCell: EuiDataGridFocusedCell; + focusedCell?: EuiDataGridFocusedCell; setFocusedCell: EuiDataGridDataRowProps['onCellFocus']; headerIsInteractive: boolean; }