Skip to content

Commit 2b402e8

Browse files
authored
Refactor useRovingCellRef, fix test warnings (#2883)
* Refactor useRovingCellRef, fix test warnings * optimize * lazy focus listening * fix type issue
1 parent 625fad5 commit 2b402e8

File tree

3 files changed

+27
-32
lines changed

3 files changed

+27
-32
lines changed

src/HeaderCell.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export default function HeaderCell<R, SR>({
155155
}
156156

157157
function handleFocus(event: React.FocusEvent<HTMLDivElement>) {
158-
onFocus(event);
158+
onFocus?.(event);
159159
if (shouldFocusGrid) {
160160
// Select the first header cell if there is no selected cell
161161
selectCell(0);

src/hooks/useRovingCellRef.ts

+15-23
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,31 @@
1-
import { useRef, useState } from 'react';
2-
import { useLayoutEffect } from './useLayoutEffect';
1+
import { useCallback, useState } from 'react';
32

43
// https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_roving_tabindex
54
export function useRovingCellRef(isSelected: boolean) {
6-
const ref = useRef<HTMLDivElement>(null);
75
// https://www.w3.org/TR/wai-aria-practices-1.1/#gridNav_focus
8-
const isChildFocused = useRef(false);
9-
const [, forceRender] = useState<unknown>({});
6+
const [isChildFocused, setIsChildFocused] = useState(false);
107

11-
useLayoutEffect(() => {
12-
if (!isSelected) {
13-
isChildFocused.current = false;
14-
return;
15-
}
8+
if (isChildFocused && !isSelected) {
9+
setIsChildFocused(false);
10+
}
1611

17-
if (isChildFocused.current) {
18-
// When the child is focused, we need to rerender
19-
// the cell again so tabIndex is updated to -1
20-
forceRender({});
21-
return;
22-
}
23-
ref.current?.focus({ preventScroll: true });
24-
}, [isSelected]);
12+
const ref = useCallback((cell: HTMLDivElement | null) => {
13+
if (cell === null || cell.contains(document.activeElement)) return;
14+
15+
cell.focus({ preventScroll: true });
16+
}, []);
2517

2618
function onFocus(event: React.FocusEvent<HTMLDivElement>) {
27-
if (event.target !== ref.current) {
28-
isChildFocused.current = true;
19+
if (event.target !== event.currentTarget) {
20+
setIsChildFocused(true);
2921
}
3022
}
3123

32-
const isFocused = isSelected && !isChildFocused.current;
24+
const isFocused = isSelected && !isChildFocused;
3325

3426
return {
35-
ref,
27+
ref: isSelected ? ref : undefined,
3628
tabIndex: isFocused ? 0 : -1,
37-
onFocus
29+
onFocus: isSelected ? onFocus : undefined
3830
};
3931
}

test/components.test.tsx

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { StrictMode } from 'react';
1+
import { forwardRef, StrictMode } from 'react';
22
import { render, screen } from '@testing-library/react';
33

44
import DataGrid, { DataGridDefaultComponentsProvider, SelectColumn } from '../src';
5-
import type { Column, DataGridProps } from '../src';
5+
import type { Column, DataGridProps, CheckboxFormatterProps } from '../src';
66
import { getRows, setup } from './utils';
77

88
interface Row {
@@ -25,13 +25,16 @@ function GlobalNoRowsFallback() {
2525
return <div>Global no rows fallback</div>;
2626
}
2727

28-
function Checkbox() {
29-
return <div>Local checkbox</div>;
30-
}
28+
const Checkbox = forwardRef<HTMLDivElement, CheckboxFormatterProps>(function Checkbox(props, ref) {
29+
return <div ref={ref}>Local checkbox</div>;
30+
});
3131

32-
function GlobalCheckbox() {
33-
return <div>Global checkbox</div>;
34-
}
32+
const GlobalCheckbox = forwardRef<HTMLDivElement, CheckboxFormatterProps>(function GlobalCheckbox(
33+
props,
34+
ref
35+
) {
36+
return <div ref={ref}>Global checkbox</div>;
37+
});
3538

3639
function setupProvider<R, SR, K extends React.Key>(props: DataGridProps<R, SR, K>) {
3740
return render(

0 commit comments

Comments
 (0)