@samitouri / QOS-React-2 / commits / 4cb399a433

[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);