Skip to content

Commit 7c3bd08

Browse files
authored
[react-interactions] Add more documentation for a11y components (#16894)
1 parent a06d181 commit 7c3bd08

File tree

5 files changed

+138
-8
lines changed

5 files changed

+138
-8
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# FocusControl
2+
3+
`FocusControl` is a module that exports a selection of helpful utility functions to be used
4+
in conjunction with the `ref` from a React Scope, such as `TabbableScope`.
5+
A ref from `FocusManager` can also be used instead.
6+
7+
## Example
8+
9+
```jsx
10+
const {
11+
focusFirst,
12+
focusNext,
13+
focusPrevious,
14+
getNextScope,
15+
getPreviousScope,
16+
} = FocusControl;
17+
18+
function KeyboardFocusMover(props) {
19+
const scopeRef = useRef(null);
20+
21+
useEffect(() => {
22+
const scope = scopeRef.current;
23+
24+
if (scope) {
25+
// Focus the first tabbable DOM node in my children
26+
focusFirst(scope);
27+
// Then focus the next chilkd
28+
focusNext(scope);
29+
}
30+
});
31+
32+
return (
33+
<TabbableScope ref={scopeRef}>
34+
{props.children}
35+
</TabbableScope>
36+
);
37+
}
38+
```
39+
40+
## FocusControl API
41+
42+
### `focusFirst`
43+
44+
Focus the first node that matches the given scope.
45+
46+
### `focusNext`
47+
48+
Focus the next sequential node that matchs the given scope.
49+
50+
### `focusPrevious`
51+
52+
Focus the previous sequential node that matchs the given scope.
53+
54+
### `getNextScope`
55+
56+
Focus the first node that matches the next sibling scope from the given scope.
57+
58+
### `getPreviousScope`
59+
60+
Focus the first node that matches the previous sibling scope from the given scope.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# FocusManager
2+
3+
`FocusManager` is a component that is designed to provide basic focus management
4+
control. These are the various props that `FocusManager` accepts:
5+
6+
## Usage
7+
8+
```jsx
9+
function MyDialog(props) {
10+
return (
11+
<FocusManager containFocus={true} autoFocus={true}>
12+
<div>
13+
<h2>{props.title}<h2>
14+
<p>{props.text}</p>
15+
<Button onPress={...}>Accept</Button>
16+
<Button onPress={...}>Close</Button>
17+
</div>
18+
</FocusManager>
19+
)
20+
}
21+
```
22+
23+
### `scope`
24+
`FocusManager` accepts a custom `ReactScope`. If a custom one is not supplied, `FocusManager`
25+
will default to using `TabbableScope`.
26+
27+
### `autoFocus`
28+
When enabled, the first host node that matches the `FocusManager` scope will be focused
29+
upon the `FocusManager` mounting.
30+
31+
### `restoreFocus`
32+
When enabled, the previous host node that was focused as `FocusManager` is mounted,
33+
has its focus restored upon `FocusManager` unmounting.
34+
35+
### `containFocus`
36+
This contains the user focus to only that of `FocusManager`s sub-tree. Tabbing or
37+
interacting with nodes outside the sub-tree will restore focus back into the `FocusManager`.
38+
This is useful for modals, dialogs, dropdowns and other UI elements that require
39+
a form of user-focus control that is similar to the `inert` property on the web.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# TabbableScope
2+
3+
`TabbableScope` is a custom scope implementation that can be used with
4+
`FocusManager`, `FocusList`, `FocusTable` and `FocusControl` modules.
5+
6+
## Usage
7+
8+
```jsx
9+
function FocusableNodeCollector(props) {
10+
const scopeRef = useRef(null);
11+
12+
useEffect(() => {
13+
const scope = scopeRef.current;
14+
15+
if (scope) {
16+
const tabFocusableNodes = scope.getScopedNodes();
17+
if (tabFocusableNodes && props.onFocusableNodes) {
18+
props.onFocusableNodes(tabFocusableNodes);
19+
}
20+
}
21+
});
22+
23+
return (
24+
<TabbableScope ref={scopeRef}>
25+
{props.children}
26+
</TabbableScope>
27+
);
28+
}
29+
```
30+
31+
## Implementation
32+
33+
`TabbableScope` uses the experimental `React.unstable_createScope` API. The query
34+
function used for the scope is designed to collect DOM nodes that are tab focusable
35+
to the browser. See the [implementation](../src/TabbableScope.js#L12-L33) here.

packages/react-interactions/accessibility/src/FocusControl.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export function focusPrevious(
110110
}
111111
}
112112

113-
export function getNextController(
113+
export function getNextScope(
114114
scope: ReactScopeMethods,
115115
): null | ReactScopeMethods {
116116
const allScopes = scope.getChildrenFromRoot();
@@ -124,7 +124,7 @@ export function getNextController(
124124
return allScopes[currentScopeIndex + 1];
125125
}
126126

127-
export function getPreviousController(
127+
export function getPreviousScope(
128128
scope: ReactScopeMethods,
129129
): null | ReactScopeMethods {
130130
const allScopes = scope.getChildrenFromRoot();

packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,16 +301,12 @@ describe('FocusManager', () => {
301301
FocusControl.focusPrevious(firstFocusController);
302302
expect(document.activeElement).toBe(buttonRef.current);
303303

304-
const nextController = FocusControl.getNextController(
305-
firstFocusController,
306-
);
304+
const nextController = FocusControl.getNextScope(firstFocusController);
307305
expect(nextController).toBe(secondFocusController);
308306
FocusControl.focusFirst(nextController);
309307
expect(document.activeElement).toBe(divRef.current);
310308

311-
const previousController = FocusControl.getPreviousController(
312-
nextController,
313-
);
309+
const previousController = FocusControl.getPreviousScope(nextController);
314310
expect(previousController).toBe(firstFocusController);
315311
FocusControl.focusFirst(previousController);
316312
expect(document.activeElement).toBe(buttonRef.current);

0 commit comments

Comments
 (0)