diff --git a/src/HeaderCell.tsx b/src/HeaderCell.tsx index 44b633c28b..6752c34c9d 100644 --- a/src/HeaderCell.tsx +++ b/src/HeaderCell.tsx @@ -155,7 +155,7 @@ export default function HeaderCell({ } function handleFocus(event: React.FocusEvent) { - onFocus(event); + onFocus?.(event); if (shouldFocusGrid) { // Select the first header cell if there is no selected cell selectCell(0); diff --git a/src/hooks/useRovingCellRef.ts b/src/hooks/useRovingCellRef.ts index 00458f4667..6933d4733a 100644 --- a/src/hooks/useRovingCellRef.ts +++ b/src/hooks/useRovingCellRef.ts @@ -1,39 +1,31 @@ -import { useRef, useState } from 'react'; -import { useLayoutEffect } from './useLayoutEffect'; +import { useCallback, useState } from 'react'; // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_roving_tabindex export function useRovingCellRef(isSelected: boolean) { - const ref = useRef(null); // https://www.w3.org/TR/wai-aria-practices-1.1/#gridNav_focus - const isChildFocused = useRef(false); - const [, forceRender] = useState({}); + const [isChildFocused, setIsChildFocused] = useState(false); - useLayoutEffect(() => { - if (!isSelected) { - isChildFocused.current = false; - return; - } + if (isChildFocused && !isSelected) { + setIsChildFocused(false); + } - if (isChildFocused.current) { - // When the child is focused, we need to rerender - // the cell again so tabIndex is updated to -1 - forceRender({}); - return; - } - ref.current?.focus({ preventScroll: true }); - }, [isSelected]); + const ref = useCallback((cell: HTMLDivElement | null) => { + if (cell === null || cell.contains(document.activeElement)) return; + + cell.focus({ preventScroll: true }); + }, []); function onFocus(event: React.FocusEvent) { - if (event.target !== ref.current) { - isChildFocused.current = true; + if (event.target !== event.currentTarget) { + setIsChildFocused(true); } } - const isFocused = isSelected && !isChildFocused.current; + const isFocused = isSelected && !isChildFocused; return { - ref, + ref: isSelected ? ref : undefined, tabIndex: isFocused ? 0 : -1, - onFocus + onFocus: isSelected ? onFocus : undefined }; } diff --git a/test/components.test.tsx b/test/components.test.tsx index 90716824ff..a08661c1dd 100644 --- a/test/components.test.tsx +++ b/test/components.test.tsx @@ -1,8 +1,8 @@ -import { StrictMode } from 'react'; +import { forwardRef, StrictMode } from 'react'; import { render, screen } from '@testing-library/react'; import DataGrid, { DataGridDefaultComponentsProvider, SelectColumn } from '../src'; -import type { Column, DataGridProps } from '../src'; +import type { Column, DataGridProps, CheckboxFormatterProps } from '../src'; import { getRows, setup } from './utils'; interface Row { @@ -25,13 +25,16 @@ function GlobalNoRowsFallback() { return
Global no rows fallback
; } -function Checkbox() { - return
Local checkbox
; -} +const Checkbox = forwardRef(function Checkbox(props, ref) { + return
Local checkbox
; +}); -function GlobalCheckbox() { - return
Global checkbox
; -} +const GlobalCheckbox = forwardRef(function GlobalCheckbox( + props, + ref +) { + return
Global checkbox
; +}); function setupProvider(props: DataGridProps) { return render(