[react-interactions] Modify Scope query mechanism (#17095)
Dominic Gannaway committed
Oct 15, 2019 at 15:57 UTC
4cb399a433771c84e861d5ca3d38a24733d23ad8
20 files changed
+263
-213
packages/react-interactions/accessibility/docs/FocusContain.md
+2
-2
@@ -10,11 +10,11 @@ using the `tabFocus` prop.
10
11
```jsx
12
import FocusContain from 'react-interactions/accessibility/focus-contain';
13
-import TabbableScope from 'react-interactions/accessibility/tabbable-scope';
13
+import tabbableScopeQuery from 'react-interactions/accessibility/tabbable-scope-query';
14
15
function MyDialog(props) {
16
return (
17
- <FocusContain tabScope={TabbableScope} disabled={false}>
17
+ <FocusContain scopeQuery={tabbableScopeQuery} disabled={false}>
18
<div>
19
<h2>{props.title}<h2>
20
<p>{props.text}</p>
packages/react-interactions/accessibility/docs/FocusManager.md
+6
-2
@@ -14,6 +14,10 @@ import {
14
getPreviousScope,
15
} from 'react-interactions/accessibility/focus-manager';
16
17
+function scopeQuery(type) {
18
+ return type === 'div';
19
+}
20
+
21
function KeyboardFocusMover(props) {
22
const scopeRef = useRef(null);
23
@@ -22,9 +26,9 @@ function KeyboardFocusMover(props) {
26
27
if (scope) {
28
// Focus the first tabbable DOM node in my children
25
- focusFirst(scope);
29
+ focusFirst(scopeQuery, scope);
30
// Then focus the next chilkd
27
- focusNext(scope);
31
+ focusNext(scopeQuery, scope);
32
}
33
});
34
packages/react-interactions/accessibility/docs/TabbableScope.md
deleted
-37
@@ -1,37 +0,0 @@
1
-# TabbableScope
2
-
3
-`TabbableScope` is a custom scope implementation that can be used with
4
-`FocusContain`, `FocusGroup`, `FocusTable` and `FocusManager` modules.
5
-
6
-## Usage
7
-
8
-```jsx
9
-import TabbableScope from 'react-interactions/accessibility/tabbable-scope';
10
-
11
-function FocusableNodeCollector(props) {
12
- const scopeRef = useRef(null);
13
-
14
- useEffect(() => {
15
- const scope = scopeRef.current;
16
-
17
- if (scope) {
18
- const tabFocusableNodes = scope.getAllNodes();
19
- if (tabFocusableNodes && props.onFocusableNodes) {
20
- props.onFocusableNodes(tabFocusableNodes);
21
- }
22
- }
23
- });
24
-
25
- return (
26
- <TabbableScope ref={scopeRef}>
27
- {props.children}
28
- </TabbableScope>
29
- );
30
-}
31
-```
32
-
33
-## Implementation
34
-
35
-`TabbableScope` uses the experimental `React.unstable_createScope` API. The query
36
-function used for the scope is designed to collect DOM nodes that are tab focusable
37
-to the browser. See the [implementation](../src/TabbableScope.js#L12-L33) here.
packages/react-interactions/accessibility/docs/TabbableScopeQuery.md
new
+31
@@ -0,0 +1,31 @@
1
+# TabbableScopeQuery
2
+
3
+`TabbableScopeQuery` is a custom scope implementation that can be used with
4
+`FocusContain`, `FocusGroup`, `FocusTable` and `FocusManager` modules.
5
+
6
+## Usage
7
+
8
+```jsx
9
+import tabbableScopeQuery from 'react-interactions/accessibility/tabbable-scope-query';
10
+
11
+function FocusableNodeCollector(props) {
12
+ const scopeRef = useRef(null);
13
+
14
+ useEffect(() => {
15
+ const scope = scopeRef.current;
16
+
17
+ if (scope) {
18
+ const tabFocusableNodes = scope.queryAllNodes(tabbableScopeQuery);
19
+ if (tabFocusableNodes && props.onFocusableNodes) {
20
+ props.onFocusableNodes(tabFocusableNodes);
21
+ }
22
+ }
23
+ });
24
+
25
+ return (
26
+ <TabbableScope ref={scopeRef}>
27
+ {props.children}
28
+ </TabbableScope>
29
+ );
30
+}
31
+```
packages/react-interactions/accessibility/src/FocusContain.js
+9
-8
@@ -7,7 +7,6 @@
7
* @flow
8
*/
9
10
-import type {ReactScope} from 'shared/ReactTypes';
10
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
11
12
import React from 'react';
@@ -21,15 +20,17 @@ import {
20
type FocusContainProps = {|
21
children: React.Node,
22
disabled?: boolean,
24
- tabScope: ReactScope,
23
+ scopeQuery: (type: string | Object, props: Object) => boolean,
24
|};
25
26
const {useLayoutEffect, useRef} = React;
27
28
+const FocusContainScope = React.unstable_createScope();
29
+
30
export default function FocusContain({
31
children,
32
disabled,
32
- tabScope: TabScope,
33
+ scopeQuery,
34
}: FocusContainProps): React.Node {
35
const scopeRef = useRef(null);
36
// This ensures tabbing works through the React tree (including Portals and Suspense nodes)
@@ -42,9 +43,9 @@ export default function FocusContain({
43
const scope = scopeRef.current;
44
if (scope !== null) {
45
if (event.shiftKey) {
45
- focusPrevious(scope, event, true);
46
+ focusPrevious(scopeQuery, scope, event, true);
47
} else {
47
- focusNext(scope, event, true);
48
+ focusNext(scopeQuery, scope, event, true);
49
}
50
}
51
},
@@ -71,7 +72,7 @@ export default function FocusContain({
72
disabled !== true &&
73
!scope.containsNode(document.activeElement)
74
) {
74
- const fistElem = scope.getFirstNode();
75
+ const fistElem = scope.queryFirstNode(scopeQuery);
76
if (fistElem !== null) {
77
fistElem.focus();
78
}
@@ -81,8 +82,8 @@ export default function FocusContain({
82
);
83
84
return (
84
- <TabScope ref={scopeRef} listeners={[keyboard, focusWithin]}>
85
+ <FocusContainScope ref={scopeRef} listeners={[keyboard, focusWithin]}>
86
{children}
86
- </TabScope>
87
+ </FocusContainScope>
88
);
89
}
packages/react-interactions/accessibility/src/FocusGroup.js
+31
-29
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {ReactScope, ReactScopeMethods} from 'shared/ReactTypes';
10
+import type {ReactScopeMethods} from 'shared/ReactTypes';
11
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
12
13
import React from 'react';
@@ -23,14 +23,18 @@ type FocusGroupProps = {|
23
children: React.Node,
24
portrait: boolean,
25
wrap?: boolean,
26
- tabScope?: ReactScope,
26
+ tabScopeQuery?: (type: string | Object, props: Object) => boolean,
27
allowModifiers?: boolean,
28
|};
29
30
const {useRef} = React;
31
32
-function focusGroupItem(cell: ReactScopeMethods, event: KeyboardEvent): void {
33
- const firstScopedNode = cell.getFirstNode();
32
+function focusGroupItem(
33
+ scopeQuery: (type: string | Object, props: Object) => boolean,
34
+ cell: ReactScopeMethods,
35
+ event: KeyboardEvent,
36
+): void {
37
+ const firstScopedNode = cell.queryFirstNode(scopeQuery);
38
if (firstScopedNode !== null) {
39
firstScopedNode.focus();
40
event.preventDefault();
@@ -91,30 +95,25 @@ function hasModifierKey(event: KeyboardEvent): boolean {
95
}
96
97
export function createFocusGroup(
94
- scope: ReactScope,
98
+ scopeQuery: (type: string | Object, props: Object) => boolean,
99
): [(FocusGroupProps) => React.Node, (FocusItemProps) => React.Node] {
96
- const TableScope = React.unstable_createScope(scope.fn);
100
+ const TableScope = React.unstable_createScope();
101
102
function Group({
103
children,
104
portrait,
105
wrap,
102
- tabScope: TabScope,
106
+ tabScopeQuery,
107
allowModifiers,
108
}: FocusGroupProps): React.Node {
105
- const tabScopeRef = useRef(null);
109
return (
110
<TableScope
111
type="group"
112
portrait={portrait}
113
wrap={wrap}
111
- tabScopeRef={tabScopeRef}
114
+ tabScopeQuery={tabScopeQuery}
115
allowModifiers={allowModifiers}>
113
- {TabScope ? (
114
- <TabScope ref={tabScopeRef}>{children}</TabScope>
115
- ) : (
116
- children
117
- )}
116
+ {children}
117
</TableScope>
118
);
119
}
@@ -132,19 +131,22 @@ export function createFocusGroup(
131
const key = event.key;
132
133
if (key === 'Tab') {
135
- const tabScope = getGroupProps(currentItem).tabScopeRef.current;
136
- if (tabScope) {
137
- const activeNode = document.activeElement;
138
- const nodes = tabScope.getAllNodes();
139
- for (let i = 0; i < nodes.length; i++) {
140
- const node = nodes[i];
141
- if (node !== activeNode) {
142
- setElementCanTab(node, false);
143
- } else {
144
- setElementCanTab(node, true);
134
+ const tabScopeQuery = getGroupProps(currentItem).tabScopeQuery;
135
+ if (tabScopeQuery) {
136
+ const groupScope = currentItem.getParent();
137
+ if (groupScope) {
138
+ const activeNode = document.activeElement;
139
+ const nodes = groupScope.queryAllNodes(tabScopeQuery);
140
+ for (let i = 0; i < nodes.length; i++) {
141
+ const node = nodes[i];
142
+ if (node !== activeNode) {
143
+ setElementCanTab(node, false);
144
+ } else {
145
+ setElementCanTab(node, true);
146
+ }
147
}
148
+ return;
149
}
147
- return;
150
}
151
event.continuePropagation();
152
return;
@@ -166,7 +168,7 @@ export function createFocusGroup(
168
currentItem,
169
);
170
if (previousGroupItem) {
169
- focusGroupItem(previousGroupItem, event);
171
+ focusGroupItem(scopeQuery, previousGroupItem, event);
172
return;
173
}
174
}
@@ -176,7 +178,7 @@ export function createFocusGroup(
178
if (portrait) {
179
const nextGroupItem = getNextGroupItem(group, currentItem);
180
if (nextGroupItem) {
179
- focusGroupItem(nextGroupItem, event);
181
+ focusGroupItem(scopeQuery, nextGroupItem, event);
182
return;
183
}
184
}
@@ -189,7 +191,7 @@ export function createFocusGroup(
191
currentItem,
192
);
193
if (previousGroupItem) {
192
- focusGroupItem(previousGroupItem, event);
194
+ focusGroupItem(scopeQuery, previousGroupItem, event);
195
return;
196
}
197
}
@@ -199,7 +201,7 @@ export function createFocusGroup(
201
if (!portrait) {
202
const nextGroupItem = getNextGroupItem(group, currentItem);
203
if (nextGroupItem) {
202
- focusGroupItem(nextGroupItem, event);
204
+ focusGroupItem(scopeQuery, nextGroupItem, event);
205
return;
206
}
207
}
packages/react-interactions/accessibility/src/FocusManager.js
+12
-5
@@ -12,9 +12,14 @@ import type {KeyboardEvent} from 'react-interactions/events/keyboard';
12
13
import getTabbableNodes from './shared/getTabbableNodes';
14
15
-export function focusFirst(scope: ReactScopeMethods): void {
16
- const [, firstTabbableElem] = getTabbableNodes(scope);
17
- focusElem(firstTabbableElem);
15
+export function focusFirst(
16
+ scopeQuery: (type: string | Object, props: Object) => boolean,
17
+ scope: ReactScopeMethods,
18
+): void {
19
+ const firstNode = scope.queryFirstNode(scopeQuery);
20
+ if (firstNode) {
21
+ focusElem(firstNode);
22
+ }
23
}
24
25
function focusElem(elem: null | HTMLElement): void {
@@ -24,6 +29,7 @@ function focusElem(elem: null | HTMLElement): void {
29
}
30
31
export function focusNext(
32
+ scopeQuery: (type: string | Object, props: Object) => boolean,
33
scope: ReactScopeMethods,
34
event?: KeyboardEvent,
35
contain?: boolean,
@@ -34,7 +40,7 @@ export function focusNext(
40
lastTabbableElem,
41
currentIndex,
42
focusedElement,
37
- ] = getTabbableNodes(scope);
43
+ ] = getTabbableNodes(scopeQuery, scope);
44
45
if (focusedElement === null) {
46
if (event) {
@@ -58,6 +64,7 @@ export function focusNext(
64
}
65
66
export function focusPrevious(
67
+ scopeQuery: (type: string | Object, props: Object) => boolean,
68
scope: ReactScopeMethods,
69
event?: KeyboardEvent,
70
contain?: boolean,
@@ -68,7 +75,7 @@ export function focusPrevious(
75
lastTabbableElem,
76
currentIndex,
77
focusedElement,
71
- ] = getTabbableNodes(scope);
78
+ ] = getTabbableNodes(scopeQuery, scope);
79
80
if (focusedElement === null) {
81
if (event) {
packages/react-interactions/accessibility/src/FocusTable.js
+60
-34
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {ReactScope, ReactScopeMethods} from 'shared/ReactTypes';
10
+import type {ReactScopeMethods} from 'shared/ReactTypes';
11
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
12
13
import React from 'react';
@@ -32,14 +32,18 @@ type FocusTableProps = {|
32
) => void,
33
wrapX?: boolean,
34
wrapY?: boolean,
35
- tabScope?: ReactScope,
35
+ tabScopeQuery?: (type: string | Object, props: Object) => boolean,
36
allowModifiers?: boolean,
37
|};
38
39
const {useRef} = React;
40
41
-function focusScope(cell: ReactScopeMethods, event?: KeyboardEvent): void {
42
- const firstScopedNode = cell.getFirstNode();
41
+function focusScope(
42
+ scopeQuery: (type: string | Object, props: Object) => boolean,
43
+ cell: ReactScopeMethods,
44
+ event?: KeyboardEvent,
45
+): void {
46
+ const firstScopedNode = cell.queryFirstNode(scopeQuery);
47
if (firstScopedNode !== null) {
48
firstScopedNode.focus();
49
if (event) {
@@ -50,6 +54,7 @@ function focusScope(cell: ReactScopeMethods, event?: KeyboardEvent): void {
54
55
// This takes into account colSpan
56
function focusCellByColumnIndex(
57
+ scopeQuery: (type: string | Object, props: Object) => boolean,
58
row: ReactScopeMethods,
59
columnIndex: number,
60
event?: KeyboardEvent,
@@ -62,7 +67,7 @@ function focusCellByColumnIndex(
67
if (cell) {
68
colSize += cell.getProps().colSpan || 1;
69
if (colSize > columnIndex) {
65
- focusScope(cell, event);
70
+ focusScope(scopeQuery, cell, event);
71
return;
72
}
73
}
@@ -157,36 +162,31 @@ function hasModifierKey(event: KeyboardEvent): boolean {
162
}
163
164
export function createFocusTable(
160
- scope: ReactScope,
165
+ scopeQuery: (type: string | Object, props: Object) => boolean,
166
): [
167
(FocusTableProps) => React.Node,
168
(FocusRowProps) => React.Node,
169
(FocusCellProps) => React.Node,
170
] {
166
- const TableScope = React.unstable_createScope(scope.fn);
171
+ const TableScope = React.unstable_createScope();
172
173
function Table({
174
children,
175
onKeyboardOut,
176
wrapX,
177
wrapY,
173
- tabScope: TabScope,
178
+ tabScopeQuery,
179
allowModifiers,
180
}: FocusTableProps): React.Node {
176
- const tabScopeRef = useRef(null);
181
return (
182
<TableScope
183
type="table"
184
onKeyboardOut={onKeyboardOut}
185
wrapX={wrapX}
186
wrapY={wrapY}
183
- tabScopeRef={tabScopeRef}
187
+ tabScopeQuery={tabScopeQuery}
188
allowModifiers={allowModifiers}>
185
- {TabScope ? (
186
- <TabScope ref={tabScopeRef}>{children}</TabScope>
187
- ) : (
188
- children
189
- )}
189
+ {children}
190
</TableScope>
191
);
192
}
@@ -206,19 +206,25 @@ export function createFocusTable(
206
}
207
const key = event.key;
208
if (key === 'Tab') {
209
- const tabScope = getTableProps(currentCell).tabScopeRef.current;
210
- if (tabScope) {
211
- const activeNode = document.activeElement;
212
- const nodes = tabScope.getAllNodes();
213
- for (let i = 0; i < nodes.length; i++) {
214
- const node = nodes[i];
215
- if (node !== activeNode) {
216
- setElementCanTab(node, false);
217
- } else {
218
- setElementCanTab(node, true);
209
+ const tabScopeQuery = getTableProps(currentCell).tabScopeQuery;
210
+ if (tabScopeQuery) {
211
+ const rowScope = currentCell.getParent();
212
+ if (rowScope) {
213
+ const tableScope = rowScope.getParent();
214
+ if (tableScope) {
215
+ const activeNode = document.activeElement;
216
+ const nodes = tableScope.queryAllNodes(tabScopeQuery);
217
+ for (let i = 0; i < nodes.length; i++) {
218
+ const node = nodes[i];
219
+ if (node !== activeNode) {
220
+ setElementCanTab(node, false);
221
+ } else {
222
+ setElementCanTab(node, true);
223
+ }
224
+ }
225
+ return;
226
}
227
}
221
- return;
228
}
229
event.continuePropagation();
230
return;
@@ -240,12 +246,22 @@ export function createFocusTable(
246
if (rows !== null) {
247
if (rowIndex > 0) {
248
const row = rows[rowIndex - 1];
243
- focusCellByColumnIndex(row, cellIndexWithColSpan, event);
249
+ focusCellByColumnIndex(
250
+ scopeQuery,
251
+ row,
252
+ cellIndexWithColSpan,
253
+ event,
254
+ );
255
} else if (rowIndex === 0) {
256
const wrapY = getTableProps(currentCell).wrapY;
257
if (wrapY) {
258
const row = rows[rows.length - 1];
248
- focusCellByColumnIndex(row, cellIndexWithColSpan, event);
259
+ focusCellByColumnIndex(
260
+ scopeQuery,
261
+ row,
262
+ cellIndexWithColSpan,
263
+ event,
264
+ );
265
} else {
266
triggerNavigateOut(currentCell, 'up', event);
267
}
@@ -264,13 +280,23 @@ export function createFocusTable(
280
const wrapY = getTableProps(currentCell).wrapY;
281
if (wrapY) {
282
const row = rows[0];
267
- focusCellByColumnIndex(row, cellIndexWithColSpan, event);
283
+ focusCellByColumnIndex(
284
+ scopeQuery,
285
+ row,
286
+ cellIndexWithColSpan,
287
+ event,
288
+ );
289
} else {
290
triggerNavigateOut(currentCell, 'down', event);
291
}
292
} else {
293
const row = rows[rowIndex + 1];
273
- focusCellByColumnIndex(row, cellIndexWithColSpan, event);
294
+ focusCellByColumnIndex(
295
+ scopeQuery,
296
+ row,
297
+ cellIndexWithColSpan,
298
+ event,
299
+ );
300
}
301
}
302
}
@@ -281,12 +307,12 @@ export function createFocusTable(
307
const [cells, rowIndex] = getRowCells(currentCell);
308
if (cells !== null) {
309
if (rowIndex > 0) {
284
- focusScope(cells[rowIndex - 1]);
310
+ focusScope(scopeQuery, cells[rowIndex - 1]);
311
event.preventDefault();
312
} else if (rowIndex === 0) {
313
const wrapX = getTableProps(currentCell).wrapX;
314
if (wrapX) {
289
- focusScope(cells[cells.length - 1], event);
315
+ focusScope(scopeQuery, cells[cells.length - 1], event);
316
} else {
317
triggerNavigateOut(currentCell, 'left', event);
318
}
@@ -301,12 +327,12 @@ export function createFocusTable(
327
if (rowIndex === cells.length - 1) {
328
const wrapX = getTableProps(currentCell).wrapX;
329
if (wrapX) {
304
- focusScope(cells[0], event);
330
+ focusScope(scopeQuery, cells[0], event);
331
} else {
332
triggerNavigateOut(currentCell, 'right', event);
333
}
334
} else {
309
- focusScope(cells[rowIndex + 1], event);
335
+ focusScope(scopeQuery, cells[rowIndex + 1], event);
336
}
337
}
338
}
packages/react-interactions/accessibility/src/TabbableScopeQuery.js
renamed
+2
-6
@@ -7,9 +7,7 @@
7
* @flow
8
*/
9
10
-import React from 'react';
11
-
12
-const tabFocusableImpl = (type: string, props: Object): boolean => {
10
+const tabbableScopeQuery = (type: string, props: Object): boolean => {
11
if (props.tabIndex === -1 || props.disabled) {
12
return false;
13
}
@@ -32,6 +30,4 @@ const tabFocusableImpl = (type: string, props: Object): boolean => {
30
);
31
};
32
35
-const TabbableScope = React.unstable_createScope(tabFocusableImpl);
36
-
37
-export default TabbableScope;
33
+export default tabbableScopeQuery;
packages/react-interactions/accessibility/src/__tests__/FocusContain-test.internal.js
+9
-9
@@ -12,7 +12,7 @@ import {createEventTarget} from 'react-interactions/events/src/dom/testing-libra
12
let React;
13
let ReactFeatureFlags;
14
let FocusContain;
15
-let TabbableScope;
15
+let tabbableScopeQuery;
16
17
describe('FocusContain', () => {
18
beforeEach(() => {
@@ -21,7 +21,7 @@ describe('FocusContain', () => {
21
ReactFeatureFlags.enableScopeAPI = true;
22
ReactFeatureFlags.enableFlareAPI = true;
23
FocusContain = require('../FocusContain').default;
24
- TabbableScope = require('../TabbableScope').default;
24
+ tabbableScopeQuery = require('../TabbableScopeQuery').default;
25
React = require('react');
26
});
27
@@ -48,7 +48,7 @@ describe('FocusContain', () => {
48
const divRef = React.createRef();
49
50
const Test = () => (
51
- <FocusContain tabScope={TabbableScope}>
51
+ <FocusContain scopeQuery={tabbableScopeQuery}>
52
<input ref={inputRef} />
53
<button ref={buttonRef} />
54
<div ref={divRef} tabIndex={0} />
@@ -78,7 +78,7 @@ describe('FocusContain', () => {
78
79
const Test = () => (
80
<div>
81
- <FocusContain tabScope={TabbableScope}>
81
+ <FocusContain scopeQuery={tabbableScopeQuery}>
82
<input ref={inputRef} tabIndex={-1} />
83
<button ref={buttonRef} id={1} />
84
<button ref={button2Ref} id={2} />
@@ -118,10 +118,10 @@ describe('FocusContain', () => {
118
const button4Ref = React.createRef();
119
120
const Test = () => (
121
- <FocusContain tabScope={TabbableScope}>
121
+ <FocusContain scopeQuery={tabbableScopeQuery}>
122
<input ref={inputRef} tabIndex={-1} />
123
<button ref={buttonRef} id={1} />
124
- <FocusContain tabScope={TabbableScope} disabled={true}>
124
+ <FocusContain scopeQuery={tabbableScopeQuery} disabled={true}>
125
<button ref={button2Ref} id={2} />
126
<button ref={button3Ref} id={3} />
127
</FocusContain>
@@ -153,10 +153,10 @@ describe('FocusContain', () => {
153
const button4Ref = React.createRef();
154
155
const Test = () => (
156
- <FocusContain tabScope={TabbableScope}>
156
+ <FocusContain scopeQuery={tabbableScopeQuery}>
157
<input ref={inputRef} tabIndex={-1} />
158
<button ref={buttonRef} id={1} />
159
- <FocusContain tabScope={TabbableScope} disabled={false}>
159
+ <FocusContain scopeQuery={tabbableScopeQuery} disabled={false}>
160
<button ref={button2Ref} id={2} />
161
<button ref={button3Ref} id={3} />
162
</FocusContain>
@@ -200,7 +200,7 @@ describe('FocusContain', () => {
200
}
201
202
const Test = () => (
203
- <FocusContain tabScope={TabbableScope}>
203
+ <FocusContain scopeQuery={tabbableScopeQuery}>
204
<button ref={buttonRef} id={1} />
205
<button ref={button2Ref} id={2} />
206
<React.Suspense fallback={<button ref={button3Ref} id={3} />}>
packages/react-interactions/accessibility/src/__tests__/FocusGroup-test.internal.js
+5
-5
@@ -13,7 +13,7 @@ import {emulateBrowserTab} from '../shared/emulateBrowserTab';
13
let React;
14
let ReactFeatureFlags;
15
let createFocusGroup;
16
-let TabbableScope;
16
+let tabbableScopeQuery;
17
18
describe('FocusGroup', () => {
19
beforeEach(() => {
@@ -22,7 +22,7 @@ describe('FocusGroup', () => {
22
ReactFeatureFlags.enableScopeAPI = true;
23
ReactFeatureFlags.enableFlareAPI = true;
24
createFocusGroup = require('../FocusGroup').createFocusGroup;
25
- TabbableScope = require('../TabbableScope').default;
25
+ tabbableScopeQuery = require('../TabbableScopeQuery').default;
26
React = require('react');
27
});
28
@@ -42,7 +42,7 @@ describe('FocusGroup', () => {
42
});
43
44
function createFocusGroupComponent() {
45
- const [FocusGroup, FocusItem] = createFocusGroup(TabbableScope);
45
+ const [FocusGroup, FocusItem] = createFocusGroup(tabbableScopeQuery);
46
47
return ({portrait, wrap, allowModifiers}) => (
48
<FocusGroup
@@ -185,7 +185,7 @@ describe('FocusGroup', () => {
185
});
186
187
it('handles keyboard arrow operations mixed with tabbing', () => {
188
- const [FocusGroup, FocusItem] = createFocusGroup(TabbableScope);
188
+ const [FocusGroup, FocusItem] = createFocusGroup(tabbableScopeQuery);
189
const beforeRef = React.createRef();
190
const afterRef = React.createRef();
191
@@ -193,7 +193,7 @@ describe('FocusGroup', () => {
193
return (
194
<>
195
<input placeholder="Before" ref={beforeRef} />
196
- <FocusGroup tabScope={TabbableScope} portrait={true}>
196
+ <FocusGroup tabScopeQuery={tabbableScopeQuery} portrait={true}>
197
<ul>
198
<FocusItem>
199
<li>
packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js
+12
-11
@@ -10,7 +10,7 @@
10
let React;
11
let ReactFeatureFlags;
12
let FocusManager;
13
-let TabbableScope;
13
+let tabbableScopeQuery;
14
15
describe('FocusManager', () => {
16
beforeEach(() => {
@@ -19,7 +19,7 @@ describe('FocusManager', () => {
19
ReactFeatureFlags.enableScopeAPI = true;
20
ReactFeatureFlags.enableFlareAPI = true;
21
FocusManager = require('../FocusManager');
22
- TabbableScope = require('../TabbableScope').default;
22
+ tabbableScopeQuery = require('../TabbableScopeQuery').default;
23
React = require('react');
24
});
25
@@ -44,19 +44,20 @@ describe('FocusManager', () => {
44
const buttonRef = React.createRef();
45
const button2Ref = React.createRef();
46
const divRef = React.createRef();
47
+ const TestScope = React.unstable_createScope();
48
49
const Test = () => (
50
<div>
50
- <TabbableScope ref={firstFocusControllerRef}>
51
+ <TestScope ref={firstFocusControllerRef}>
52
<input tabIndex={-1} />
53
<button ref={buttonRef} />
54
<button ref={button2Ref} />
55
<input tabIndex={-1} />
55
- </TabbableScope>
56
- <TabbableScope ref={secondFocusControllerRef}>
56
+ </TestScope>
57
+ <TestScope ref={secondFocusControllerRef}>
58
<input tabIndex={-1} />
59
<div ref={divRef} tabIndex={0} />
59
- </TabbableScope>
60
+ </TestScope>
61
</div>
62
);
63
@@ -64,21 +65,21 @@ describe('FocusManager', () => {
65
const firstFocusController = firstFocusControllerRef.current;
66
const secondFocusController = secondFocusControllerRef.current;
67
67
- FocusManager.focusFirst(firstFocusController);
68
+ FocusManager.focusFirst(tabbableScopeQuery, firstFocusController);
69
expect(document.activeElement).toBe(buttonRef.current);
69
- FocusManager.focusNext(firstFocusController);
70
+ FocusManager.focusNext(tabbableScopeQuery, firstFocusController);
71
expect(document.activeElement).toBe(button2Ref.current);
71
- FocusManager.focusPrevious(firstFocusController);
72
+ FocusManager.focusPrevious(tabbableScopeQuery, firstFocusController);
73
expect(document.activeElement).toBe(buttonRef.current);
74
75
const nextController = FocusManager.getNextScope(firstFocusController);
76
expect(nextController).toBe(secondFocusController);
76
- FocusManager.focusFirst(nextController);
77
+ FocusManager.focusFirst(tabbableScopeQuery, nextController);
78
expect(document.activeElement).toBe(divRef.current);
79
80
const previousController = FocusManager.getPreviousScope(nextController);
81
expect(previousController).toBe(firstFocusController);
81
- FocusManager.focusFirst(previousController);
82
+ FocusManager.focusFirst(tabbableScopeQuery, previousController);
83
expect(document.activeElement).toBe(buttonRef.current);
84
});
85
});
packages/react-interactions/accessibility/src/__tests__/FocusTable-test.internal.js
+17
-11
@@ -13,7 +13,7 @@ import {emulateBrowserTab} from '../shared/emulateBrowserTab';
13
let React;
14
let ReactFeatureFlags;
15
let createFocusTable;
16
-let TabbableScope;
16
+let tabbableScopeQuery;
17
18
describe('FocusTable', () => {
19
beforeEach(() => {
@@ -22,7 +22,7 @@ describe('FocusTable', () => {
22
ReactFeatureFlags.enableScopeAPI = true;
23
ReactFeatureFlags.enableFlareAPI = true;
24
createFocusTable = require('../FocusTable').createFocusTable;
25
- TabbableScope = require('../TabbableScope').default;
25
+ tabbableScopeQuery = require('../TabbableScopeQuery').default;
26
React = require('react');
27
});
28
@@ -43,7 +43,7 @@ describe('FocusTable', () => {
43
44
function createFocusTableComponent() {
45
const [FocusTable, FocusTableRow, FocusTableCell] = createFocusTable(
46
- TabbableScope,
46
+ tabbableScopeQuery,
47
);
48
49
return ({onKeyboardOut, wrapX, wrapY, allowModifiers}) => (
@@ -186,7 +186,7 @@ describe('FocusTable', () => {
186
MainFocusTable,
187
MainFocusTableRow,
188
MainFocusTableCell,
189
- ] = createFocusTable(TabbableScope);
189
+ ] = createFocusTable(tabbableScopeQuery);
190
const SubFocusTable = createFocusTableComponent();
191
const onKeyboardOut = jest.fn((direction, event) =>
192
event.continuePropagation(),
@@ -283,10 +283,12 @@ describe('FocusTable', () => {
283
});
284
285
it('handles nested tables correctly', () => {
286
- const CustomScope = React.unstable_createScope((type, props) => {
286
+ const customScopeQuery = (type, props) => {
287
return type === 'input';
288
- });
289
- const [FocusTable, FocusRow, FocusCell] = createFocusTable(CustomScope);
288
+ };
289
+ const [FocusTable, FocusRow, FocusCell] = createFocusTable(
290
+ customScopeQuery,
291
+ );
292
const firstRef = React.createRef();
293
294
function Test() {
@@ -414,7 +416,9 @@ describe('FocusTable', () => {
416
});
417
418
it('handles keyboard arrow operations mixed with tabbing', () => {
417
- const [FocusTable, FocusRow, FocusCell] = createFocusTable(TabbableScope);
419
+ const [FocusTable, FocusRow, FocusCell] = createFocusTable(
420
+ tabbableScopeQuery,
421
+ );
422
const beforeRef = React.createRef();
423
const afterRef = React.createRef();
424
@@ -422,7 +426,7 @@ describe('FocusTable', () => {
426
return (
427
<>
428
<input placeholder="Before" ref={beforeRef} />
425
- <FocusTable tabScope={TabbableScope}>
429
+ <FocusTable tabScopeQuery={tabbableScopeQuery}>
430
<div>
431
<FocusRow>
432
<FocusCell>
@@ -478,12 +482,14 @@ describe('FocusTable', () => {
482
483
it('handles keyboard arrow operations with colSpan', () => {
484
const firstRef = React.createRef();
481
- const [FocusTable, FocusRow, FocusCell] = createFocusTable(TabbableScope);
485
+ const [FocusTable, FocusRow, FocusCell] = createFocusTable(
486
+ tabbableScopeQuery,
487
+ );
488
489
function Test() {
490
return (
491
<>
486
- <FocusTable tabScope={TabbableScope}>
492
+ <FocusTable tabScopeQuery={tabbableScopeQuery}>
493
<div>
494
<FocusRow>
495
<FocusCell>
packages/react-interactions/accessibility/src/__tests__/TabbableScopeQuery-test.internal.js
renamed
+9
-7
@@ -9,14 +9,14 @@
9
10
let React;
11
let ReactFeatureFlags;
12
-let TabbableScope;
12
+let tabbableScopeQuery;
13
14
-describe('TabbableScope', () => {
14
+describe('TabbableScopeQuery', () => {
15
beforeEach(() => {
16
jest.resetModules();
17
ReactFeatureFlags = require('shared/ReactFeatureFlags');
18
ReactFeatureFlags.enableScopeAPI = true;
19
- TabbableScope = require('../TabbableScope').default;
19
+ tabbableScopeQuery = require('../TabbableScopeQuery').default;
20
React = require('react');
21
});
22
@@ -35,16 +35,18 @@ describe('TabbableScope', () => {
35
container = null;
36
});
37
38
- it('getAllNodes() works as intended', () => {
38
+ it('queryAllNodes() works as intended', () => {
39
const scopeRef = React.createRef();
40
const nodeRefA = React.createRef();
41
const nodeRefB = React.createRef();
42
const nodeRefC = React.createRef();
43
const nodeRefD = React.createRef();
44
45
+ const TestScope = React.unstable_createScope();
46
+
47
function Test() {
48
return (
47
- <TabbableScope ref={scopeRef}>
49
+ <TestScope ref={scopeRef}>
50
<input ref={nodeRefA} />
51
<textarea ref={nodeRefB} />
52
<div tabIndex={0} ref={nodeRefC}>
@@ -53,12 +55,12 @@ describe('TabbableScope', () => {
55
</div>
56
<input disabled={true} />
57
<div tabIndex={-1} />
56
- </TabbableScope>
58
+ </TestScope>
59
);
60
}
61
62
ReactDOM.render(<Test />, container);
61
- let nodes = scopeRef.current.getAllNodes();
63
+ let nodes = scopeRef.current.queryAllNodes(tabbableScopeQuery);
64
expect(nodes).toEqual([
65
nodeRefA.current,
66
nodeRefB.current,
packages/react-interactions/accessibility/src/shared/getTabbableNodes.js
+2
-1
@@ -10,6 +10,7 @@
10
import type {ReactScopeMethods} from 'shared/ReactTypes';
11
12
export default function getTabbableNodes(
13
+ scopeQuery: (type: string | Object, props: Object) => boolean,
14
scope: ReactScopeMethods,
15
): [
16
null | Array<HTMLElement>,
@@ -18,7 +19,7 @@ export default function getTabbableNodes(
19
number,
20
null | HTMLElement,
21
] {
21
- const tabbableNodes = scope.getAllNodes();
22
+ const tabbableNodes = scope.queryAllNodes(scopeQuery);
23
if (tabbableNodes === null || tabbableNodes.length === 0) {
24
return [null, null, null, 0, null];
25
}
packages/react-interactions/accessibility/tabbable-scope-query.js
renamed
+1
-1
@@ -9,4 +9,4 @@
9
10
'use strict';
11
12
-module.exports = require('./src/TabbableScope');
12
+module.exports = require('./src/TabbableScopeQuery');
packages/react-reconciler/src/ReactFiberScope.js
+6
-3
@@ -147,7 +147,6 @@ export function createScopeMethods(
147
scope: ReactScope,
148
instance: ReactScopeInstance,
149
): ReactScopeMethods {
150
- const fn = scope.fn;
150
return {
151
getChildren(): null | Array<ReactScopeMethods> {
152
const currentFiber = ((instance.fiber: any): Fiber);
@@ -189,7 +188,9 @@ export function createScopeMethods(
188
const currentFiber = ((instance.fiber: any): Fiber);
189
return currentFiber.memoizedProps;
190
},
192
- getAllNodes(): null | Array<Object> {
191
+ queryAllNodes(
192
+ fn: (type: string | Object, props: Object) => boolean,
193
+ ): null | Array<Object> {
194
const currentFiber = ((instance.fiber: any): Fiber);
195
const child = currentFiber.child;
196
const scopedNodes = [];
@@ -198,7 +199,9 @@ export function createScopeMethods(
199
}
200
return scopedNodes.length === 0 ? null : scopedNodes;
201
},
201
- getFirstNode(): null | Object {
202
+ queryFirstNode(
203
+ fn: (type: string | Object, props: Object) => boolean,
204
+ ): null | Object {
205
const currentFiber = ((instance.fiber: any): Fiber);
206
const child = currentFiber.child;
207
if (child !== null) {
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+42
-35
@@ -38,8 +38,9 @@ describe('ReactScope', () => {
38
container = null;
39
});
40
41
- it('getAllNodes() works as intended', () => {
42
- const TestScope = React.unstable_createScope((type, props) => true);
41
+ it('queryAllNodes() works as intended', () => {
42
+ const testScopeQuery = (type, props) => true;
43
+ const TestScope = React.unstable_createScope();
44
const scopeRef = React.createRef();
45
const divRef = React.createRef();
46
const spanRef = React.createRef();
@@ -62,17 +63,18 @@ describe('ReactScope', () => {
63
}
64
65
ReactDOM.render(<Test toggle={true} />, container);
65
- let nodes = scopeRef.current.getAllNodes();
66
+ let nodes = scopeRef.current.queryAllNodes(testScopeQuery);
67
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
68
ReactDOM.render(<Test toggle={false} />, container);
68
- nodes = scopeRef.current.getAllNodes();
69
+ nodes = scopeRef.current.queryAllNodes(testScopeQuery);
70
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
71
ReactDOM.render(null, container);
72
expect(scopeRef.current).toBe(null);
73
});
74
74
- it('getFirstNode() works as intended', () => {
75
- const TestScope = React.unstable_createScope((type, props) => true);
75
+ it('queryFirstNode() works as intended', () => {
76
+ const testScopeQuery = (type, props) => true;
77
+ const TestScope = React.unstable_createScope();
78
const scopeRef = React.createRef();
79
const divRef = React.createRef();
80
const spanRef = React.createRef();
@@ -95,17 +97,17 @@ describe('ReactScope', () => {
97
}
98
99
ReactDOM.render(<Test toggle={true} />, container);
98
- let node = scopeRef.current.getFirstNode();
100
+ let node = scopeRef.current.queryFirstNode(testScopeQuery);
101
expect(node).toEqual(divRef.current);
102
ReactDOM.render(<Test toggle={false} />, container);
101
- node = scopeRef.current.getFirstNode();
103
+ node = scopeRef.current.queryFirstNode(testScopeQuery);
104
expect(node).toEqual(aRef.current);
105
ReactDOM.render(null, container);
106
expect(scopeRef.current).toBe(null);
107
});
108
109
it('containsNode() works as intended', () => {
108
- const TestScope = React.unstable_createScope((type, props) => true);
110
+ const TestScope = React.unstable_createScope();
111
const scopeRef = React.createRef();
112
const divRef = React.createRef();
113
const spanRef = React.createRef();
@@ -153,9 +155,10 @@ describe('ReactScope', () => {
155
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
156
});
157
156
- it('mixed getParent() and getAllNodes() works as intended', () => {
157
- const TestScope = React.unstable_createScope((type, props) => true);
158
- const TestScope2 = React.unstable_createScope((type, props) => true);
158
+ it('mixed getParent() and queryAllNodes() works as intended', () => {
159
+ const testScopeQuery = (type, props) => true;
160
+ const TestScope = React.unstable_createScope();
161
+ const TestScope2 = React.unstable_createScope();
162
const refA = React.createRef();
163
const refB = React.createRef();
164
const refC = React.createRef();
@@ -190,14 +193,14 @@ describe('ReactScope', () => {
193
ReactDOM.render(<Test />, container);
194
const dParent = refD.current.getParent();
195
expect(dParent).not.toBe(null);
193
- expect(dParent.getAllNodes()).toEqual([
196
+ expect(dParent.queryAllNodes(testScopeQuery)).toEqual([
197
divA.current,
198
spanB.current,
199
divB.current,
200
]);
201
const cParent = refC.current.getParent();
202
expect(cParent).not.toBe(null);
200
- expect(cParent.getAllNodes()).toEqual([
203
+ expect(cParent.queryAllNodes(testScopeQuery)).toEqual([
204
spanA.current,
205
divA.current,
206
spanB.current,
@@ -208,8 +211,8 @@ describe('ReactScope', () => {
211
});
212
213
it('getChildren() works as intended', () => {
211
- const TestScope = React.unstable_createScope((type, props) => true);
212
- const TestScope2 = React.unstable_createScope((type, props) => true);
214
+ const TestScope = React.unstable_createScope();
215
+ const TestScope2 = React.unstable_createScope();
216
const refA = React.createRef();
217
const refB = React.createRef();
218
const refC = React.createRef();
@@ -253,7 +256,7 @@ describe('ReactScope', () => {
256
});
257
258
it('scopes support server-side rendering and hydration', () => {
256
- const TestScope = React.unstable_createScope((type, props) => true);
259
+ const TestScope = React.unstable_createScope();
260
const ReactDOMServer = require('react-dom/server');
261
const scopeRef = React.createRef();
262
const divRef = React.createRef();
@@ -278,13 +281,14 @@ describe('ReactScope', () => {
281
);
282
container.innerHTML = html;
283
ReactDOM.hydrate(<Test />, container);
281
- const nodes = scopeRef.current.getAllNodes();
284
+ const testScopeQuery = (type, props) => true;
285
+ const nodes = scopeRef.current.queryAllNodes(testScopeQuery);
286
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
287
});
288
289
it('event responders can be attached to scopes', () => {
290
let onKeyDown = jest.fn();
287
- const TestScope = React.unstable_createScope((type, props) => true);
291
+ const TestScope = React.unstable_createScope();
292
const ref = React.createRef();
293
const useKeyboard = require('react-interactions/events/keyboard')
294
.useKeyboard;
@@ -332,8 +336,9 @@ describe('ReactScope', () => {
336
ReactTestRenderer = require('react-test-renderer');
337
});
338
335
- it('getAllNodes() works as intended', () => {
336
- const TestScope = React.unstable_createScope((type, props) => true);
339
+ it('queryAllNodes() works as intended', () => {
340
+ const testScopeQuery = (type, props) => true;
341
+ const TestScope = React.unstable_createScope();
342
const scopeRef = React.createRef();
343
const divRef = React.createRef();
344
const spanRef = React.createRef();
@@ -360,15 +365,16 @@ describe('ReactScope', () => {
365
return element;
366
},
367
});
363
- let nodes = scopeRef.current.getAllNodes();
368
+ let nodes = scopeRef.current.queryAllNodes(testScopeQuery);
369
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
370
renderer.update(<Test toggle={false} />);
366
- nodes = scopeRef.current.getAllNodes();
371
+ nodes = scopeRef.current.queryAllNodes(testScopeQuery);
372
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
373
});
374
370
- it('getFirstNode() works as intended', () => {
371
- const TestScope = React.unstable_createScope((type, props) => true);
375
+ it('queryFirstNode() works as intended', () => {
376
+ const testScopeQuery = (type, props) => true;
377
+ const TestScope = React.unstable_createScope();
378
const scopeRef = React.createRef();
379
const divRef = React.createRef();
380
const spanRef = React.createRef();
@@ -395,15 +401,15 @@ describe('ReactScope', () => {
401
return element;
402
},
403
});
398
- let node = scopeRef.current.getFirstNode();
404
+ let node = scopeRef.current.queryFirstNode(testScopeQuery);
405
expect(node).toEqual(divRef.current);
406
renderer.update(<Test toggle={false} />);
401
- node = scopeRef.current.getFirstNode();
407
+ node = scopeRef.current.queryFirstNode(testScopeQuery);
408
expect(node).toEqual(aRef.current);
409
});
410
411
it('containsNode() works as intended', () => {
406
- const TestScope = React.unstable_createScope((type, props) => true);
412
+ const TestScope = React.unstable_createScope();
413
const scopeRef = React.createRef();
414
const divRef = React.createRef();
415
const spanRef = React.createRef();
@@ -455,9 +461,10 @@ describe('ReactScope', () => {
461
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
462
});
463
458
- it('mixed getParent() and getAllNodes() works as intended', () => {
459
- const TestScope = React.unstable_createScope((type, props) => true);
460
- const TestScope2 = React.unstable_createScope((type, props) => true);
464
+ it('mixed getParent() and queryAllNodes() works as intended', () => {
465
+ const testScopeQuery = (type, props) => true;
466
+ const TestScope = React.unstable_createScope();
467
+ const TestScope2 = React.unstable_createScope();
468
const refA = React.createRef();
469
const refB = React.createRef();
470
const refC = React.createRef();
@@ -496,14 +503,14 @@ describe('ReactScope', () => {
503
});
504
const dParent = refD.current.getParent();
505
expect(dParent).not.toBe(null);
499
- expect(dParent.getAllNodes()).toEqual([
506
+ expect(dParent.queryAllNodes(testScopeQuery)).toEqual([
507
divA.current,
508
spanB.current,
509
divB.current,
510
]);
511
const cParent = refC.current.getParent();
512
expect(cParent).not.toBe(null);
506
- expect(cParent.getAllNodes()).toEqual([
513
+ expect(cParent.queryAllNodes(testScopeQuery)).toEqual([
514
spanA.current,
515
divA.current,
516
spanB.current,
@@ -514,8 +521,8 @@ describe('ReactScope', () => {
521
});
522
523
it('getChildren() works as intended', () => {
517
- const TestScope = React.unstable_createScope((type, props) => true);
518
- const TestScope2 = React.unstable_createScope((type, props) => true);
524
+ const TestScope = React.unstable_createScope();
525
+ const TestScope2 = React.unstable_createScope();
526
const refA = React.createRef();
527
const refB = React.createRef();
528
const refC = React.createRef();
packages/shared/ReactTypes.js
+6
-3
@@ -162,7 +162,6 @@ export type ReactFundamentalComponent<C, H> = {|
162
163
export type ReactScope = {|
164
$$typeof: Symbol | number,
165
- fn: (type: string | Object, props: Object) => boolean,
165
|};
166
167
export type ReactScopeMethods = {|
@@ -170,8 +169,12 @@ export type ReactScopeMethods = {|
169
getChildrenFromRoot(): null | Array<ReactScopeMethods>,
170
getParent(): null | ReactScopeMethods,
171
getProps(): Object,
173
- getAllNodes(): null | Array<Object>,
174
- getFirstNode(): null | Object,
172
+ queryAllNodes(
173
+ (type: string | Object, props: Object) => boolean,
174
+ ): null | Array<Object>,
175
+ queryFirstNode(
176
+ (type: string | Object, props: Object) => boolean,
177
+ ): null | Object,
178
containsNode(Object): boolean,
179
|};
180
packages/shared/createScope.js
+1
-4
@@ -9,12 +9,9 @@
9
import type {ReactScope} from 'shared/ReactTypes';
10
import {REACT_SCOPE_TYPE} from 'shared/ReactSymbols';
11
12
-export default function createScope(
13
- fn: (type: string | Object, props: Object) => boolean,
14
-): ReactScope {
12
+export default function createScope(): ReactScope {
13
const scopeComponent = {
14
$$typeof: REACT_SCOPE_TYPE,
17
- fn,
15
};
16
if (__DEV__) {
17
Object.freeze(scopeComponent);