Skip to content

Commit 1a6294d

Browse files
authored
[react-interaction] Refactor a11y components more (#16866)
1 parent 1758b3f commit 1a6294d

File tree

9 files changed

+220
-180
lines changed

9 files changed

+220
-180
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
'use strict';
11+
12+
module.exports = require('./src/FocusControl');
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {ReactScopeMethods} from 'shared/ReactTypes';
11+
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
12+
13+
function getTabbableNodes(scope: ReactScopeMethods) {
14+
const tabbableNodes = scope.getScopedNodes();
15+
if (tabbableNodes === null || tabbableNodes.length === 0) {
16+
return [null, null, null, 0, null];
17+
}
18+
const firstTabbableElem = tabbableNodes[0];
19+
const lastTabbableElem = tabbableNodes[tabbableNodes.length - 1];
20+
const currentIndex = tabbableNodes.indexOf(document.activeElement);
21+
let focusedElement = null;
22+
if (currentIndex !== -1) {
23+
focusedElement = tabbableNodes[currentIndex];
24+
}
25+
return [
26+
tabbableNodes,
27+
firstTabbableElem,
28+
lastTabbableElem,
29+
currentIndex,
30+
focusedElement,
31+
];
32+
}
33+
34+
export function focusFirst(scope: ReactScopeMethods): void {
35+
const [, firstTabbableElem] = getTabbableNodes(scope);
36+
focusElem(firstTabbableElem);
37+
}
38+
39+
function focusElem(elem: null | HTMLElement): void {
40+
if (elem !== null) {
41+
elem.focus();
42+
}
43+
}
44+
45+
export function focusNext(
46+
scope: ReactScopeMethods,
47+
event?: KeyboardEvent,
48+
contain?: boolean,
49+
): void {
50+
const [
51+
tabbableNodes,
52+
firstTabbableElem,
53+
lastTabbableElem,
54+
currentIndex,
55+
focusedElement,
56+
] = getTabbableNodes(scope);
57+
58+
if (focusedElement === null) {
59+
if (event) {
60+
event.continuePropagation();
61+
}
62+
} else if (focusedElement === lastTabbableElem) {
63+
if (contain) {
64+
focusElem(firstTabbableElem);
65+
if (event) {
66+
event.preventDefault();
67+
}
68+
} else if (event) {
69+
event.continuePropagation();
70+
}
71+
} else {
72+
focusElem((tabbableNodes: any)[currentIndex + 1]);
73+
if (event) {
74+
event.preventDefault();
75+
}
76+
}
77+
}
78+
79+
export function focusPrevious(
80+
scope: ReactScopeMethods,
81+
event?: KeyboardEvent,
82+
contain?: boolean,
83+
): void {
84+
const [
85+
tabbableNodes,
86+
firstTabbableElem,
87+
lastTabbableElem,
88+
currentIndex,
89+
focusedElement,
90+
] = getTabbableNodes(scope);
91+
92+
if (focusedElement === null) {
93+
if (event) {
94+
event.continuePropagation();
95+
}
96+
} else if (focusedElement === firstTabbableElem) {
97+
if (contain) {
98+
focusElem(lastTabbableElem);
99+
if (event) {
100+
event.preventDefault();
101+
}
102+
} else if (event) {
103+
event.continuePropagation();
104+
}
105+
} else {
106+
focusElem((tabbableNodes: any)[currentIndex - 1]);
107+
if (event) {
108+
event.preventDefault();
109+
}
110+
}
111+
}
112+
113+
export function getNextController(
114+
scope: ReactScopeMethods,
115+
): null | ReactScopeMethods {
116+
const allScopes = scope.getChildrenFromRoot();
117+
if (allScopes === null) {
118+
return null;
119+
}
120+
const currentScopeIndex = allScopes.indexOf(scope);
121+
if (currentScopeIndex === -1 || currentScopeIndex === allScopes.length - 1) {
122+
return null;
123+
}
124+
return allScopes[currentScopeIndex + 1];
125+
}
126+
127+
export function getPreviousController(
128+
scope: ReactScopeMethods,
129+
): null | ReactScopeMethods {
130+
const allScopes = scope.getChildrenFromRoot();
131+
if (allScopes === null) {
132+
return null;
133+
}
134+
const currentScopeIndex = allScopes.indexOf(scope);
135+
if (currentScopeIndex <= 0) {
136+
return null;
137+
}
138+
return allScopes[currentScopeIndex - 1];
139+
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type {ReactScopeMethods} from 'shared/ReactTypes';
1111
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
1212

1313
import React from 'react';
14-
import {tabFocusableImpl} from 'react-interactions/accessibility/tabbable-scope';
1514
import {useKeyboard} from 'react-interactions/events/keyboard';
1615

1716
type FocusCellProps = {
@@ -128,8 +127,10 @@ function triggerNavigateOut(
128127
}
129128
}
130129

131-
export function createFocusTable(): Array<React.Component> {
132-
const TableScope = React.unstable_createScope(tabFocusableImpl);
130+
export function createFocusTable(
131+
scopeImpl: (type: string, props: Object) => boolean,
132+
): Array<React.Component> {
133+
const TableScope = React.unstable_createScope(scopeImpl);
133134

134135
function Table({children, onKeyboardOut, id}): FocusTableProps {
135136
return (

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

Lines changed: 15 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -7,158 +7,26 @@
77
* @flow
88
*/
99

10-
import type {ReactScopeMethods} from 'shared/ReactTypes';
10+
import type {ReactScope} from 'shared/ReactTypes';
1111
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
1212

1313
import React from 'react';
14-
import {TabbableScope} from 'react-interactions/accessibility/tabbable-scope';
1514
import {useKeyboard} from 'react-interactions/events/keyboard';
15+
import {
16+
focusPrevious,
17+
focusNext,
18+
} from 'react-interactions/accessibility/focus-control';
1619

17-
type TabFocusControllerProps = {
20+
type TabFocusProps = {
1821
children: React.Node,
1922
contain?: boolean,
23+
scope: ReactScope,
2024
};
2125

2226
const {useRef} = React;
2327

24-
function getTabbableNodes(scope: ReactScopeMethods) {
25-
const tabbableNodes = scope.getScopedNodes();
26-
if (tabbableNodes === null || tabbableNodes.length === 0) {
27-
return [null, null, null, 0, null];
28-
}
29-
const firstTabbableElem = tabbableNodes[0];
30-
const lastTabbableElem = tabbableNodes[tabbableNodes.length - 1];
31-
const currentIndex = tabbableNodes.indexOf(document.activeElement);
32-
let focusedElement = null;
33-
if (currentIndex !== -1) {
34-
focusedElement = tabbableNodes[currentIndex];
35-
}
36-
return [
37-
tabbableNodes,
38-
firstTabbableElem,
39-
lastTabbableElem,
40-
currentIndex,
41-
focusedElement,
42-
];
43-
}
44-
45-
export function focusFirst(scope: ReactScopeMethods): void {
46-
const [, firstTabbableElem] = getTabbableNodes(scope);
47-
focusElem(firstTabbableElem);
48-
}
49-
50-
function focusElem(elem: null | HTMLElement): void {
51-
if (elem !== null) {
52-
elem.focus();
53-
}
54-
}
55-
56-
function internalFocusNext(
57-
scope: ReactScopeMethods,
58-
event?: KeyboardEvent,
59-
contain?: boolean,
60-
): void {
61-
const [
62-
tabbableNodes,
63-
firstTabbableElem,
64-
lastTabbableElem,
65-
currentIndex,
66-
focusedElement,
67-
] = getTabbableNodes(scope);
68-
69-
if (focusedElement === null) {
70-
if (event) {
71-
event.continuePropagation();
72-
}
73-
} else if (focusedElement === lastTabbableElem) {
74-
if (contain) {
75-
focusElem(firstTabbableElem);
76-
if (event) {
77-
event.preventDefault();
78-
}
79-
} else if (event) {
80-
event.continuePropagation();
81-
}
82-
} else {
83-
focusElem((tabbableNodes: any)[currentIndex + 1]);
84-
if (event) {
85-
event.preventDefault();
86-
}
87-
}
88-
}
89-
90-
function internalFocusPrevious(
91-
scope: ReactScopeMethods,
92-
event?: KeyboardEvent,
93-
contain?: boolean,
94-
): void {
95-
const [
96-
tabbableNodes,
97-
firstTabbableElem,
98-
lastTabbableElem,
99-
currentIndex,
100-
focusedElement,
101-
] = getTabbableNodes(scope);
102-
103-
if (focusedElement === null) {
104-
if (event) {
105-
event.continuePropagation();
106-
}
107-
} else if (focusedElement === firstTabbableElem) {
108-
if (contain) {
109-
focusElem(lastTabbableElem);
110-
if (event) {
111-
event.preventDefault();
112-
}
113-
} else if (event) {
114-
event.continuePropagation();
115-
}
116-
} else {
117-
focusElem((tabbableNodes: any)[currentIndex - 1]);
118-
if (event) {
119-
event.preventDefault();
120-
}
121-
}
122-
}
123-
124-
export function focusPrevious(scope: ReactScopeMethods): void {
125-
internalFocusPrevious(scope);
126-
}
127-
128-
export function focusNext(scope: ReactScopeMethods): void {
129-
internalFocusNext(scope);
130-
}
131-
132-
export function getNextController(
133-
scope: ReactScopeMethods,
134-
): null | ReactScopeMethods {
135-
const allScopes = scope.getChildrenFromRoot();
136-
if (allScopes === null) {
137-
return null;
138-
}
139-
const currentScopeIndex = allScopes.indexOf(scope);
140-
if (currentScopeIndex === -1 || currentScopeIndex === allScopes.length - 1) {
141-
return null;
142-
}
143-
return allScopes[currentScopeIndex + 1];
144-
}
145-
146-
export function getPreviousController(
147-
scope: ReactScopeMethods,
148-
): null | ReactScopeMethods {
149-
const allScopes = scope.getChildrenFromRoot();
150-
if (allScopes === null) {
151-
return null;
152-
}
153-
const currentScopeIndex = allScopes.indexOf(scope);
154-
if (currentScopeIndex <= 0) {
155-
return null;
156-
}
157-
return allScopes[currentScopeIndex - 1];
158-
}
159-
160-
export const TabFocusController = React.forwardRef(
161-
({children, contain}: TabFocusControllerProps, ref): React.Node => {
28+
const TabFocus = React.forwardRef(
29+
({children, contain, scope: Scope}: TabFocusProps, ref): React.Node => {
16230
const scopeRef = useRef(null);
16331
const keyboard = useKeyboard({
16432
onKeyDown(event: KeyboardEvent): void {
@@ -169,16 +37,16 @@ export const TabFocusController = React.forwardRef(
16937
const scope = scopeRef.current;
17038
if (scope !== null) {
17139
if (event.shiftKey) {
172-
internalFocusPrevious(scope, event, contain);
40+
focusPrevious(scope, event, contain);
17341
} else {
174-
internalFocusNext(scope, event, contain);
42+
focusNext(scope, event, contain);
17543
}
17644
}
17745
},
17846
});
17947

18048
return (
181-
<TabbableScope
49+
<Scope
18250
ref={node => {
18351
if (ref) {
18452
if (typeof ref === 'function') {
@@ -191,7 +59,9 @@ export const TabFocusController = React.forwardRef(
19159
}}
19260
listeners={keyboard}>
19361
{children}
194-
</TabbableScope>
62+
</Scope>
19563
);
19664
},
19765
);
66+
67+
export default TabFocus;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ export const tabFocusableImpl = (type: string, props: Object): boolean => {
3232
);
3333
};
3434

35-
export const TabbableScope = React.unstable_createScope(tabFocusableImpl);
35+
const TabbableScope = React.unstable_createScope(tabFocusableImpl);
36+
37+
export default TabbableScope;

0 commit comments

Comments
 (0)