@samitouri / QOS-React-2 / commits / 9e075d16b2

[react-interactions] Remove deprecated Scope APIs + update Focus components/docs (#17859)

Dominic Gannaway committed Jan 17, 2020 at 12:16 UTC 9e075d16b250de8f65680c45c3686eb3faec8153
25 files changed +15 -2710
packages/react-interactions/accessibility/README.md deleted
-25
@@ -1,25 +0,0 @@
1 -# `react-interactions/accessibility`
2 -
3 -*This package is experimental. It is intended for use with the experimental React
4 -Scope API that is not available in open source builds.*
5 -
6 -Scopes allow for querying of the internal React sub-tree to collect handles to
7 -host nodes. Scopes also have their own separate tree structure that allows
8 -traversal of scopes of the same type.
9 -
10 -The core API is documented below. Documentation for individual Accessibility Components
11 -can be found [here](./docs).
12 -
13 -## React Scopes
14 -
15 -Note: React Scopes require the internal React flag `enableScopeAPI`.
16 -
17 -## React Scope Interface
18 -
19 -Scopes require a `ref` to access the internal interface of a particular scope.
20 -The internal interface (`ReactScopeInterface`) exposes the following scope API:
21 -
22 -### containsNode: (node: HTMLElement) => boolean
23 -
24 -Returns `true` or `false` depending on if the given `HTMLElement` is a descendant
25 -of the scope's sub-tree.
\ No newline at end of file
packages/react-interactions/accessibility/docs/FocusContain.md deleted
-27
@@ -1,27 +0,0 @@
1 -# FocusContain
2 -
3 -`FocusContain` is a component that contains user-focusability to only that
4 -of the children of the component. This means focus control will not escape
5 -unless the componoent is disabled (using the `disabled` prop) or unmounted.
6 -Additionally, `FocusContain` can contain tab focus when passed a `ReactScope`
7 -using the `tabFocus` prop.
8 -
9 -## Usage
10 -
11 -```jsx
12 -import FocusContain from 'react-interactions/accessibility/focus-contain';
13 -import tabbableScopeQuery from 'react-interactions/accessibility/tabbable-scope-query';
14 -
15 -function MyDialog(props) {
16 - return (
17 - <FocusContain scopeQuery={tabbableScopeQuery} disabled={false}>
18 - <div>
19 - <h2>{props.title}<h2>
20 - <p>{props.text}</p>
21 - <Button onPress={...}>Accept</Button>
22 - <Button onPress={...}>Close</Button>
23 - </div>
24 - </FocusContain>
25 - )
26 -}
27 -```
packages/react-interactions/accessibility/docs/FocusManager.md deleted
-63
@@ -1,63 +0,0 @@
1 -# FocusManager
2 -
3 -`FocusManager` is a module that exports a selection of helpful utility functions to be used
4 -in conjunction with the `ref` from a React Scope, such as `TabbableScope`.
5 -
6 -## Example
7 -
8 -```jsx
9 -import {
10 - focusFirst,
11 - focusNext,
12 - focusPrevious,
13 - getNextScope,
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 -
24 - useEffect(() => {
25 - const scope = scopeRef.current;
26 -
27 - if (scope) {
28 - // Focus the first tabbable DOM node in my children
29 - focusFirst(scopeQuery, scope);
30 - // Then focus the next chilkd
31 - focusNext(scopeQuery, scope);
32 - }
33 - });
34 -
35 - return (
36 - <TabbableScope ref={scopeRef}>
37 - {props.children}
38 - </TabbableScope>
39 - );
40 -}
41 -```
42 -
43 -## FocusManager API
44 -
45 -### `focusFirst`
46 -
47 -Focus the first node that matches the given scope.
48 -
49 -### `focusNext`
50 -
51 -Focus the next sequential node that matches the given scope.
52 -
53 -### `focusPrevious`
54 -
55 -Focus the previous sequential node that matches the given scope.
56 -
57 -### `getNextScope`
58 -
59 -Focus the first node that matches the next sibling scope from the given scope.
60 -
61 -### `getPreviousScope`
62 -
63 -Focus the first node that matches the previous sibling scope from the given scope.
packages/react-interactions/accessibility/docs/TabbableScopeQuery.md deleted
-31
@@ -1,31 +0,0 @@
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.DO_NOT_USE_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/focus-contain.js deleted
-12
@@ -1,12 +0,0 @@
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/FocusContain');
packages/react-interactions/accessibility/focus-group.js deleted
-12
@@ -1,12 +0,0 @@
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/FocusGroup');
packages/react-interactions/accessibility/focus-manager.js deleted
-12
@@ -1,12 +0,0 @@
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/FocusManager');
packages/react-interactions/accessibility/focus-table.js deleted
-12
@@ -1,12 +0,0 @@
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/FocusTable');
packages/react-interactions/accessibility/src/FocusContain.js deleted
-88
@@ -1,88 +0,0 @@
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 {KeyboardEvent} from 'react-interactions/events/keyboard';
11 -
12 -import React from 'react';
13 -import {useFocusWithin} from 'react-interactions/events/focus';
14 -import {useKeyboard} from 'react-interactions/events/keyboard';
15 -import {
16 - focusPrevious,
17 - focusNext,
18 -} from 'react-interactions/accessibility/focus-manager';
19 -
20 -type FocusContainProps = {|
21 - children: React.Node,
22 - disabled?: boolean,
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,
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)
37 - const keyboard = useKeyboard({
38 - onKeyDown(event: KeyboardEvent): void {
39 - if (disabled === true || event.key !== 'Tab') {
40 - event.continuePropagation();
41 - return;
42 - }
43 - const scope = scopeRef.current;
44 - if (scope !== null) {
45 - if (event.shiftKey) {
46 - focusPrevious(scopeQuery, scope, event, true);
47 - } else {
48 - focusNext(scopeQuery, scope, event, true);
49 - }
50 - }
51 - },
52 - });
53 - const focusWithin = useFocusWithin({
54 - onBlurWithin: function(event) {
55 - if (disabled === true) {
56 - event.continuePropagation();
57 - return;
58 - }
59 - const lastNode = event.target;
60 - if (lastNode) {
61 - requestAnimationFrame(() => {
62 - (lastNode: any).focus();
63 - });
64 - }
65 - },
66 - });
67 - useLayoutEffect(() => {
68 - const scope = scopeRef.current;
69 - if (
70 - scope !== null &&
71 - disabled !== true &&
72 - !scope.containsNode(document.activeElement)
73 - ) {
74 - const fistElem = scope.DO_NOT_USE_queryFirstNode(scopeQuery);
75 - if (fistElem !== null) {
76 - fistElem.focus();
77 - }
78 - }
79 - }, [disabled]);
80 -
81 - return (
82 - <FocusContainScope
83 - ref={scopeRef}
84 - DEPRECATED_flareListeners={[keyboard, focusWithin]}>
85 - {children}
86 - </FocusContainScope>
87 - );
88 -}
packages/react-interactions/accessibility/src/FocusGroup.js deleted
-233
@@ -1,233 +0,0 @@
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 -import React from 'react';
14 -import {useKeyboard} from 'react-interactions/events/keyboard';
15 -import setElementCanTab from './shared/setElementCanTab';
16 -
17 -type FocusItemProps = {
18 - children?: React.Node,
19 - onKeyDown?: KeyboardEvent => void,
20 - ...
21 -};
22 -
23 -type FocusGroupProps = {|
24 - children: React.Node,
25 - portrait: boolean,
26 - wrap?: boolean,
27 - tabScopeQuery?: (type: string | Object, props: Object) => boolean,
28 - allowModifiers?: boolean,
29 -|};
30 -
31 -const {useRef} = React;
32 -
33 -function focusGroupItem(
34 - scopeQuery: (type: string | Object, props: Object) => boolean,
35 - cell: ReactScopeMethods,
36 - event: KeyboardEvent,
37 -): void {
38 - const firstScopedNode = cell.DO_NOT_USE_queryFirstNode(scopeQuery);
39 - if (firstScopedNode !== null) {
40 - firstScopedNode.focus();
41 - event.preventDefault();
42 - }
43 -}
44 -
45 -function getPreviousGroupItem(
46 - group: ReactScopeMethods,
47 - currentItem: ReactScopeMethods,
48 -): null | ReactScopeMethods {
49 - const items = group.DO_NOT_USE_getChildren();
50 - if (items !== null) {
51 - const currentItemIndex = items.indexOf(currentItem);
52 - const wrap = getGroupProps(currentItem).wrap;
53 - if (currentItemIndex === 0 && wrap) {
54 - return items[items.length - 1] || null;
55 - } else if (currentItemIndex > 0) {
56 - return items[currentItemIndex - 1] || null;
57 - }
58 - }
59 - return null;
60 -}
61 -
62 -function getNextGroupItem(
63 - group: ReactScopeMethods,
64 - currentItem: ReactScopeMethods,
65 -): null | ReactScopeMethods {
66 - const items = group.DO_NOT_USE_getChildren();
67 - if (items !== null) {
68 - const currentItemIndex = items.indexOf(currentItem);
69 - const wrap = getGroupProps(currentItem).wrap;
70 - const end = currentItemIndex === items.length - 1;
71 - if (end && wrap) {
72 - return items[0] || null;
73 - } else if (currentItemIndex !== -1 && !end) {
74 - return items[currentItemIndex + 1] || null;
75 - }
76 - }
77 - return null;
78 -}
79 -
80 -function getGroupProps(currentCell: ReactScopeMethods): Object {
81 - const group = currentCell.DO_NOT_USE_getParent();
82 - if (group !== null) {
83 - const groupProps = group.DO_NOT_USE_getProps();
84 - if (groupProps && groupProps.type === 'group') {
85 - return groupProps;
86 - }
87 - }
88 - return {};
89 -}
90 -
91 -function hasModifierKey(event: KeyboardEvent): boolean {
92 - const {altKey, ctrlKey, metaKey, shiftKey} = event;
93 - return (
94 - altKey === true || ctrlKey === true || metaKey === true || shiftKey === true
95 - );
96 -}
97 -
98 -export function createFocusGroup(
99 - scopeQuery: (type: string | Object, props: Object) => boolean,
100 -): [(FocusGroupProps) => React.Node, (FocusItemProps) => React.Node] {
101 - const TableScope = React.unstable_createScope();
102 -
103 - function Group({
104 - children,
105 - portrait,
106 - wrap,
107 - tabScopeQuery,
108 - allowModifiers,
109 - }: FocusGroupProps): React.Node {
110 - return (
111 - <TableScope
112 - type="group"
113 - portrait={portrait}
114 - wrap={wrap}
115 - tabScopeQuery={tabScopeQuery}
116 - allowModifiers={allowModifiers}>
117 - {children}
118 - </TableScope>
119 - );
120 - }
121 -
122 - function Item({children, onKeyDown}: FocusItemProps): React.Node {
123 - const scopeRef = useRef(null);
124 - const keyboard = useKeyboard({
125 - onKeyDown(event: KeyboardEvent): void {
126 - const currentItem = scopeRef.current;
127 - if (currentItem !== null) {
128 - const group = currentItem.DO_NOT_USE_getParent();
129 - const groupProps = group && group.DO_NOT_USE_getProps();
130 - if (group !== null && groupProps.type === 'group') {
131 - const portrait = groupProps.portrait;
132 - const key = event.key;
133 -
134 - if (key === 'Tab') {
135 - const tabScopeQuery = getGroupProps(currentItem).tabScopeQuery;
136 - if (tabScopeQuery) {
137 - const groupScope = currentItem.DO_NOT_USE_getParent();
138 - if (groupScope) {
139 - const activeNode = document.activeElement;
140 - const nodes = groupScope.DO_NOT_USE_queryAllNodes(
141 - tabScopeQuery,
142 - );
143 - for (let i = 0; i < nodes.length; i++) {
144 - const node = nodes[i];
145 - if (node !== activeNode) {
146 - setElementCanTab(node, false);
147 - } else {
148 - setElementCanTab(node, true);
149 - }
150 - }
151 - return;
152 - }
153 - }
154 - event.continuePropagation();
155 - return;
156 - }
157 - // Using modifier keys with keyboard arrow events should be no-ops
158 - // unless an explicit allowModifiers flag is set on the FocusGroup.
159 - if (hasModifierKey(event)) {
160 - const allowModifiers = getGroupProps(currentItem).allowModifiers;
161 - if (!allowModifiers) {
162 - event.continuePropagation();
163 - return;
164 - }
165 - }
166 - switch (key) {
167 - case 'ArrowUp': {
168 - if (portrait) {
169 - const previousGroupItem = getPreviousGroupItem(
170 - group,
171 - currentItem,
172 - );
173 - if (previousGroupItem) {
174 - focusGroupItem(scopeQuery, previousGroupItem, event);
175 - return;
176 - }
177 - }
178 - break;
179 - }
180 - case 'ArrowDown': {
181 - if (portrait) {
182 - const nextGroupItem = getNextGroupItem(group, currentItem);
183 - if (nextGroupItem) {
184 - focusGroupItem(scopeQuery, nextGroupItem, event);
185 - return;
186 - }
187 - }
188 - break;
189 - }
190 - case 'ArrowLeft': {
191 - if (!portrait) {
192 - const previousGroupItem = getPreviousGroupItem(
193 - group,
194 - currentItem,
195 - );
196 - if (previousGroupItem) {
197 - focusGroupItem(scopeQuery, previousGroupItem, event);
198 - return;
199 - }
200 - }
201 - break;
202 - }
203 - case 'ArrowRight': {
204 - if (!portrait) {
205 - const nextGroupItem = getNextGroupItem(group, currentItem);
206 - if (nextGroupItem) {
207 - focusGroupItem(scopeQuery, nextGroupItem, event);
208 - return;
209 - }
210 - }
211 - break;
212 - }
213 - }
214 - }
215 - }
216 - if (onKeyDown) {
217 - onKeyDown(event);
218 - }
219 - event.continuePropagation();
220 - },
221 - });
222 - return (
223 - <TableScope
224 - DEPRECATED_flareListeners={keyboard}
225 - ref={scopeRef}
226 - type="item">
227 - {children}
228 - </TableScope>
229 - );
230 - }
231 -
232 - return [Group, Item];
233 -}
packages/react-interactions/accessibility/src/FocusManager.js deleted
-127
@@ -1,127 +0,0 @@
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 -import getTabbableNodes from './shared/getTabbableNodes';
14 -
15 -export function focusFirst(
16 - scopeQuery: (type: string | Object, props: Object) => boolean,
17 - scope: ReactScopeMethods,
18 -): void {
19 - const firstNode = scope.DO_NOT_USE_queryFirstNode(scopeQuery);
20 - if (firstNode) {
21 - focusElem(firstNode);
22 - }
23 -}
24 -
25 -function focusElem(elem: null | HTMLElement): void {
26 - if (elem !== null) {
27 - elem.focus();
28 - }
29 -}
30 -
31 -export function focusNext(
32 - scopeQuery: (type: string | Object, props: Object) => boolean,
33 - scope: ReactScopeMethods,
34 - event?: KeyboardEvent,
35 - contain?: boolean,
36 -): void {
37 - const [
38 - tabbableNodes,
39 - firstTabbableElem,
40 - lastTabbableElem,
41 - currentIndex,
42 - focusedElement,
43 - ] = getTabbableNodes(scopeQuery, scope);
44 -
45 - if (focusedElement === null) {
46 - if (event) {
47 - event.continuePropagation();
48 - }
49 - } else if (focusedElement === lastTabbableElem) {
50 - if (contain === true) {
51 - focusElem(firstTabbableElem);
52 - if (event) {
53 - event.preventDefault();
54 - }
55 - } else if (event) {
56 - event.continuePropagation();
57 - }
58 - } else if (tabbableNodes) {
59 - focusElem(tabbableNodes[currentIndex + 1]);
60 - if (event) {
61 - event.preventDefault();
62 - }
63 - }
64 -}
65 -
66 -export function focusPrevious(
67 - scopeQuery: (type: string | Object, props: Object) => boolean,
68 - scope: ReactScopeMethods,
69 - event?: KeyboardEvent,
70 - contain?: boolean,
71 -): void {
72 - const [
73 - tabbableNodes,
74 - firstTabbableElem,
75 - lastTabbableElem,
76 - currentIndex,
77 - focusedElement,
78 - ] = getTabbableNodes(scopeQuery, scope);
79 -
80 - if (focusedElement === null) {
81 - if (event) {
82 - event.continuePropagation();
83 - }
84 - } else if (focusedElement === firstTabbableElem) {
85 - if (contain === true) {
86 - focusElem(lastTabbableElem);
87 - if (event) {
88 - event.preventDefault();
89 - }
90 - } else if (event) {
91 - event.continuePropagation();
92 - }
93 - } else if (tabbableNodes) {
94 - focusElem(tabbableNodes[currentIndex - 1]);
95 - if (event) {
96 - event.preventDefault();
97 - }
98 - }
99 -}
100 -
101 -export function getNextScope(
102 - scope: ReactScopeMethods,
103 -): null | ReactScopeMethods {
104 - const allScopes = scope.DO_NOT_USE_getChildrenFromRoot();
105 - if (allScopes === null) {
106 - return null;
107 - }
108 - const currentScopeIndex = allScopes.indexOf(scope);
109 - if (currentScopeIndex === -1 || currentScopeIndex === allScopes.length - 1) {
110 - return null;
111 - }
112 - return allScopes[currentScopeIndex + 1];
113 -}
114 -
115 -export function getPreviousScope(
116 - scope: ReactScopeMethods,
117 -): null | ReactScopeMethods {
118 - const allScopes = scope.DO_NOT_USE_getChildrenFromRoot();
119 - if (allScopes === null) {
120 - return null;
121 - }
122 - const currentScopeIndex = allScopes.indexOf(scope);
123 - if (currentScopeIndex <= 0) {
124 - return null;
125 - }
126 - return allScopes[currentScopeIndex - 1];
127 -}
packages/react-interactions/accessibility/src/FocusTable.js deleted
-360
@@ -1,360 +0,0 @@
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 -import React from 'react';
14 -import {useKeyboard} from 'react-interactions/events/keyboard';
15 -import setElementCanTab from './shared/setElementCanTab';
16 -
17 -type FocusCellProps = {
18 - children?: React.Node,
19 - onKeyDown?: KeyboardEvent => void,
20 - colSpan?: number,
21 - ...
22 -};
23 -
24 -type FocusRowProps = {children: React.Node, ...};
25 -
26 -type FocusTableProps = {|
27 - children: React.Node,
28 - onKeyboardOut?: (
29 - direction: 'left' | 'right' | 'up' | 'down',
30 - event: KeyboardEvent,
31 - ) => void,
32 - wrapX?: boolean,
33 - wrapY?: boolean,
34 - tabScopeQuery?: (type: string | Object, props: Object) => boolean,
35 - allowModifiers?: boolean,
36 -|};
37 -
38 -const {useRef} = React;
39 -
40 -function focusScope(
41 - scopeQuery: (type: string | Object, props: Object) => boolean,
42 - cell: ReactScopeMethods,
43 - event?: KeyboardEvent,
44 -): void {
45 - const firstScopedNode = cell.DO_NOT_USE_queryFirstNode(scopeQuery);
46 - if (firstScopedNode !== null) {
47 - firstScopedNode.focus();
48 - if (event) {
49 - event.preventDefault();
50 - }
51 - }
52 -}
53 -
54 -// This takes into account colSpan
55 -function focusCellByColumnIndex(
56 - scopeQuery: (type: string | Object, props: Object) => boolean,
57 - row: ReactScopeMethods,
58 - columnIndex: number,
59 - event?: KeyboardEvent,
60 -): void {
61 - const cells = row.DO_NOT_USE_getChildren();
62 - if (cells !== null) {
63 - let colSize = 0;
64 - for (let i = 0; i < cells.length; i++) {
65 - const cell = cells[i];
66 - if (cell) {
67 - colSize += cell.DO_NOT_USE_getProps().colSpan || 1;
68 - if (colSize > columnIndex) {
69 - focusScope(scopeQuery, cell, event);
70 - return;
71 - }
72 - }
73 - }
74 - }
75 -}
76 -
77 -function getCellIndexes(
78 - cells: Array<ReactScopeMethods>,
79 - currentCell: ReactScopeMethods,
80 -): [number, number] {
81 - let totalColSpan = 0;
82 - for (let i = 0; i < cells.length; i++) {
83 - const cell = cells[i];
84 - if (cell === currentCell) {
85 - return [i, i + totalColSpan];
86 - }
87 - const colSpan = cell.DO_NOT_USE_getProps().colSpan;
88 - if (colSpan) {
89 - totalColSpan += colSpan - 1;
90 - }
91 - }
92 - return [-1, -1];
93 -}
94 -
95 -function getRowCells(currentCell: ReactScopeMethods) {
96 - const row = currentCell.DO_NOT_USE_getParent();
97 - if (row !== null && row.DO_NOT_USE_getProps().type === 'row') {
98 - const cells = row.DO_NOT_USE_getChildren();
99 - if (cells !== null) {
100 - const [rowIndex, rowIndexWithColSpan] = getCellIndexes(
101 - cells,
102 - currentCell,
103 - );
104 - return [cells, rowIndex, rowIndexWithColSpan];
105 - }
106 - }
107 - return [null, -1, -1];
108 -}
109 -
110 -function getRows(currentCell: ReactScopeMethods) {
111 - const row = currentCell.DO_NOT_USE_getParent();
112 - if (row !== null && row.DO_NOT_USE_getProps().type === 'row') {
113 - const table = row.DO_NOT_USE_getParent();
114 - if (table !== null && table.DO_NOT_USE_getProps().type === 'table') {
115 - const rows = table.DO_NOT_USE_getChildren();
116 - if (rows !== null) {
117 - const columnIndex = rows.indexOf(row);
118 - return [rows, columnIndex];
119 - }
120 - }
121 - }
122 - return [null, -1, -1];
123 -}
124 -
125 -function triggerNavigateOut(
126 - currentCell: ReactScopeMethods,
127 - direction: 'left' | 'right' | 'up' | 'down',
128 - event,
129 -): void {
130 - const row = currentCell.DO_NOT_USE_getParent();
131 - if (row !== null && row.DO_NOT_USE_getProps().type === 'row') {
132 - const table = row.DO_NOT_USE_getParent();
133 - if (table !== null) {
134 - const props = table.DO_NOT_USE_getProps();
135 - const onKeyboardOut = props.onKeyboardOut;
136 - if (props.type === 'table' && typeof onKeyboardOut === 'function') {
137 - onKeyboardOut(direction, event);
138 - return;
139 - }
140 - }
141 - }
142 - event.continuePropagation();
143 -}
144 -
145 -function getTableProps(currentCell: ReactScopeMethods): Object {
146 - const row = currentCell.DO_NOT_USE_getParent();
147 - if (row !== null && row.DO_NOT_USE_getProps().type === 'row') {
148 - const table = row.DO_NOT_USE_getParent();
149 - if (table !== null) {
150 - return table.DO_NOT_USE_getProps();
151 - }
152 - }
153 - return {};
154 -}
155 -
156 -function hasModifierKey(event: KeyboardEvent): boolean {
157 - const {altKey, ctrlKey, metaKey, shiftKey} = event;
158 - return (
159 - altKey === true || ctrlKey === true || metaKey === true || shiftKey === true
160 - );
161 -}
162 -
163 -export function createFocusTable(
164 - scopeQuery: (type: string | Object, props: Object) => boolean,
165 -): [
166 - (FocusTableProps) => React.Node,
167 - (FocusRowProps) => React.Node,
168 - (FocusCellProps) => React.Node,
169 -] {
170 - const TableScope = React.unstable_createScope();
171 -
172 - function Table({
173 - children,
174 - onKeyboardOut,
175 - wrapX,
176 - wrapY,
177 - tabScopeQuery,
178 - allowModifiers,
179 - }: FocusTableProps): React.Node {
180 - return (
181 - <TableScope
182 - type="table"
183 - onKeyboardOut={onKeyboardOut}
184 - wrapX={wrapX}
185 - wrapY={wrapY}
186 - tabScopeQuery={tabScopeQuery}
187 - allowModifiers={allowModifiers}>
188 - {children}
189 - </TableScope>
190 - );
191 - }
192 -
193 - function Row({children}: FocusRowProps): React.Node {
194 - return <TableScope type="row">{children}</TableScope>;
195 - }
196 -
197 - function Cell({children, onKeyDown, colSpan}: FocusCellProps): React.Node {
198 - const scopeRef = useRef(null);
199 - const keyboard = useKeyboard({
200 - onKeyDown(event: KeyboardEvent): void {
201 - const currentCell = scopeRef.current;
202 - if (currentCell === null) {
203 - event.continuePropagation();
204 - return;
205 - }
206 - const key = event.key;
207 - if (key === 'Tab') {
208 - const tabScopeQuery = getTableProps(currentCell).tabScopeQuery;
209 - if (tabScopeQuery) {
210 - const rowScope = currentCell.DO_NOT_USE_getParent();
211 - if (rowScope) {
212 - const tableScope = rowScope.DO_NOT_USE_getParent();
213 - if (tableScope) {
214 - const activeNode = document.activeElement;
215 - const nodes = tableScope.DO_NOT_USE_queryAllNodes(
216 - tabScopeQuery,
217 - );
218 - for (let i = 0; i < nodes.length; i++) {
219 - const node = nodes[i];
220 - if (node !== activeNode) {
221 - setElementCanTab(node, false);
222 - } else {
223 - setElementCanTab(node, true);
224 - }
225 - }
226 - return;
227 - }
228 - }
229 - }
230 - event.continuePropagation();
231 - return;
232 - }
233 - // Using modifier keys with keyboard arrow events should be no-ops
234 - // unless an explicit allowModifiers flag is set on the FocusTable.
235 - if (hasModifierKey(event)) {
236 - const allowModifiers = getTableProps(currentCell).allowModifiers;
237 - if (!allowModifiers) {
238 - event.continuePropagation();
239 - return;
240 - }
241 - }
242 - switch (key) {
243 - case 'ArrowUp': {
244 - const [cells, , cellIndexWithColSpan] = getRowCells(currentCell);
245 - if (cells !== null) {
246 - const [rows, rowIndex] = getRows(currentCell);
247 - if (rows !== null) {
248 - if (rowIndex > 0) {
249 - const row = rows[rowIndex - 1];
250 - focusCellByColumnIndex(
251 - scopeQuery,
252 - row,
253 - cellIndexWithColSpan,
254 - event,
255 - );
256 - } else if (rowIndex === 0) {
257 - const wrapY = getTableProps(currentCell).wrapY;
258 - if (wrapY) {
259 - const row = rows[rows.length - 1];
260 - focusCellByColumnIndex(
261 - scopeQuery,
262 - row,
263 - cellIndexWithColSpan,
264 - event,
265 - );
266 - } else {
267 - triggerNavigateOut(currentCell, 'up', event);
268 - }
269 - }
270 - }
271 - }
272 - return;
273 - }
274 - case 'ArrowDown': {
275 - const [cells, , cellIndexWithColSpan] = getRowCells(currentCell);
276 - if (cells !== null) {
277 - const [rows, rowIndex] = getRows(currentCell);
278 - if (rows !== null) {
279 - if (rowIndex !== -1) {
280 - if (rowIndex === rows.length - 1) {
281 - const wrapY = getTableProps(currentCell).wrapY;
282 - if (wrapY) {
283 - const row = rows[0];
284 - focusCellByColumnIndex(
285 - scopeQuery,
286 - row,
287 - cellIndexWithColSpan,
288 - event,
289 - );
290 - } else {
291 - triggerNavigateOut(currentCell, 'down', event);
292 - }
293 - } else {
294 - const row = rows[rowIndex + 1];
295 - focusCellByColumnIndex(
296 - scopeQuery,
297 - row,
298 - cellIndexWithColSpan,
299 - event,
300 - );
301 - }
302 - }
303 - }
304 - }
305 - return;
306 - }
307 - case 'ArrowLeft': {
308 - const [cells, rowIndex] = getRowCells(currentCell);
309 - if (cells !== null) {
310 - if (rowIndex > 0) {
311 - focusScope(scopeQuery, cells[rowIndex - 1]);
312 - event.preventDefault();
313 - } else if (rowIndex === 0) {
314 - const wrapX = getTableProps(currentCell).wrapX;
315 - if (wrapX) {
316 - focusScope(scopeQuery, cells[cells.length - 1], event);
317 - } else {
318 - triggerNavigateOut(currentCell, 'left', event);
319 - }
320 - }
321 - }
322 - return;
323 - }
324 - case 'ArrowRight': {
325 - const [cells, rowIndex] = getRowCells(currentCell);
326 - if (cells !== null) {
327 - if (rowIndex !== -1) {
328 - if (rowIndex === cells.length - 1) {
329 - const wrapX = getTableProps(currentCell).wrapX;
330 - if (wrapX) {
331 - focusScope(scopeQuery, cells[0], event);
332 - } else {
333 - triggerNavigateOut(currentCell, 'right', event);
334 - }
335 - } else {
336 - focusScope(scopeQuery, cells[rowIndex + 1], event);
337 - }
338 - }
339 - }
340 - return;
341 - }
342 - }
343 - if (onKeyDown) {
344 - onKeyDown(event);
345 - }
346 - },
347 - });
348 - return (
349 - <TableScope
350 - DEPRECATED_flareListeners={keyboard}
351 - ref={scopeRef}
352 - type="cell"
353 - colSpan={colSpan}>
354 - {children}
355 - </TableScope>
356 - );
357 - }
358 -
359 - return [Table, Row, Cell];
360 -}
packages/react-interactions/accessibility/src/TabbableScopeQuery.js deleted
-33
@@ -1,33 +0,0 @@
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 -const tabbableScopeQuery = (type: string, props: Object): boolean => {
11 - if (props.tabIndex === -1 || props.disabled) {
12 - return false;
13 - }
14 - if (props.tabIndex === 0 || props.contentEditable === true) {
15 - return true;
16 - }
17 - if (type === 'a' || type === 'area') {
18 - return !!props.href && props.rel !== 'ignore';
19 - }
20 - if (type === 'input') {
21 - return props.type !== 'hidden' && props.type !== 'file';
22 - }
23 - return (
24 - type === 'button' ||
25 - type === 'textarea' ||
26 - type === 'object' ||
27 - type === 'select' ||
28 - type === 'iframe' ||
29 - type === 'embed'
30 - );
31 -};
32 -
33 -export default tabbableScopeQuery;
packages/react-interactions/accessibility/src/__tests__/FocusContain-test.internal.js deleted
-238
@@ -1,238 +0,0 @@
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 {createEventTarget} from 'dom-event-testing-library';
11 -
12 -let React;
13 -let ReactFeatureFlags;
14 -let FocusContain;
15 -let tabbableScopeQuery;
16 -
17 -function tabNext(target) {
18 - target.keydown({key: 'Tab'});
19 - target.keyup({key: 'Tab'});
20 -}
21 -
22 -function tabPrevious(target) {
23 - target.keydown({key: 'Tab', shiftKey: true});
24 - target.keyup({key: 'Tab', shiftKey: true});
25 -}
26 -
27 -describe('FocusContain', () => {
28 - beforeEach(() => {
29 - jest.resetModules();
30 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
31 - ReactFeatureFlags.enableScopeAPI = true;
32 - ReactFeatureFlags.enableDeprecatedFlareAPI = true;
33 - FocusContain = require('../FocusContain').default;
34 - tabbableScopeQuery = require('../TabbableScopeQuery').default;
35 - React = require('react');
36 - });
37 -
38 - describe('ReactDOM', () => {
39 - let ReactDOM;
40 - let container;
41 -
42 - beforeEach(() => {
43 - ReactDOM = require('react-dom');
44 - container = document.createElement('div');
45 - document.body.appendChild(container);
46 - });
47 -
48 - afterEach(() => {
49 - document.body.removeChild(container);
50 - container = null;
51 - });
52 -
53 - it('handles tab operations by default', () => {
54 - const inputRef = React.createRef();
55 - const input2Ref = React.createRef();
56 - const buttonRef = React.createRef();
57 - const button2Ref = React.createRef();
58 - const divRef = React.createRef();
59 -
60 - const Test = () => (
61 - <FocusContain scopeQuery={tabbableScopeQuery}>
62 - <input ref={inputRef} />
63 - <button ref={buttonRef} />
64 - <div ref={divRef} tabIndex={0} />
65 - <input ref={input2Ref} tabIndex={-1} />
66 - <button ref={button2Ref} />
67 - </FocusContain>
68 - );
69 -
70 - ReactDOM.render(<Test />, container);
71 - expect(document.activeElement).toBe(inputRef.current);
72 - tabNext(createEventTarget(document.activeElement));
73 - expect(document.activeElement).toBe(buttonRef.current);
74 - tabNext(createEventTarget(document.activeElement));
75 - expect(document.activeElement).toBe(divRef.current);
76 - tabNext(createEventTarget(document.activeElement));
77 - expect(document.activeElement).toBe(button2Ref.current);
78 - tabPrevious(createEventTarget(document.activeElement));
79 - expect(document.activeElement).toBe(divRef.current);
80 - });
81 -
82 - it('handles focus containment', () => {
83 - const inputRef = React.createRef();
84 - const input2Ref = React.createRef();
85 - const input3Ref = React.createRef();
86 - const buttonRef = React.createRef();
87 - const button2Ref = React.createRef();
88 -
89 - const Test = () => (
90 - <div>
91 - <FocusContain scopeQuery={tabbableScopeQuery}>
92 - <input ref={inputRef} tabIndex={-1} />
93 - <button ref={buttonRef} id={1} />
94 - <button ref={button2Ref} id={2} />
95 - <input ref={input2Ref} tabIndex={-1} />
96 - </FocusContain>
97 - <input ref={input3Ref} />
98 - </div>
99 - );
100 -
101 - ReactDOM.render(<Test />, container);
102 - buttonRef.current.focus();
103 - tabNext(createEventTarget(document.activeElement));
104 - expect(document.activeElement).toBe(button2Ref.current);
105 - tabNext(createEventTarget(document.activeElement));
106 - expect(document.activeElement).toBe(buttonRef.current);
107 - tabNext(createEventTarget(document.activeElement));
108 - expect(document.activeElement).toBe(button2Ref.current);
109 - tabPrevious(createEventTarget(document.activeElement));
110 - expect(document.activeElement).toBe(buttonRef.current);
111 - tabPrevious(createEventTarget(document.activeElement));
112 - expect(document.activeElement).toBe(button2Ref.current);
113 - // Focus should be restored to the contained area
114 - const rAF = window.requestAnimationFrame;
115 - window.requestAnimationFrame = x => setTimeout(x);
116 - input3Ref.current.focus();
117 - jest.advanceTimersByTime(1);
118 - window.requestAnimationFrame = rAF;
119 - expect(document.activeElement).toBe(button2Ref.current);
120 - });
121 -
122 - it('works with a disabled nested FocusContain', () => {
123 - const inputRef = React.createRef();
124 - const input2Ref = React.createRef();
125 - const buttonRef = React.createRef();
126 - const button2Ref = React.createRef();
127 - const button3Ref = React.createRef();
128 - const button4Ref = React.createRef();
129 -
130 - const Test = () => (
131 - <FocusContain scopeQuery={tabbableScopeQuery}>
132 - <input ref={inputRef} tabIndex={-1} />
133 - <button ref={buttonRef} id={1} />
134 - <FocusContain scopeQuery={tabbableScopeQuery} disabled={true}>
135 - <button ref={button2Ref} id={2} />
136 - <button ref={button3Ref} id={3} />
137 - </FocusContain>
138 - <input ref={input2Ref} tabIndex={-1} />
139 - <button ref={button4Ref} id={4} />
140 - </FocusContain>
141 - );
142 -
143 - ReactDOM.render(<Test />, container);
144 - expect(document.activeElement).toBe(buttonRef.current);
145 - tabNext(createEventTarget(document.activeElement));
146 - expect(document.activeElement).toBe(button2Ref.current);
147 - tabNext(createEventTarget(document.activeElement));
148 - expect(document.activeElement).toBe(button3Ref.current);
149 - tabNext(createEventTarget(document.activeElement));
150 - expect(document.activeElement).toBe(button4Ref.current);
151 - tabPrevious(createEventTarget(document.activeElement));
152 - expect(document.activeElement).toBe(button3Ref.current);
153 - tabPrevious(createEventTarget(document.activeElement));
154 - expect(document.activeElement).toBe(button2Ref.current);
155 - });
156 -
157 - it('works with enabled nested FocusContain', () => {
158 - const inputRef = React.createRef();
159 - const input2Ref = React.createRef();
160 - const buttonRef = React.createRef();
161 - const button2Ref = React.createRef();
162 - const button3Ref = React.createRef();
163 - const button4Ref = React.createRef();
164 -
165 - const Test = () => (
166 - <FocusContain scopeQuery={tabbableScopeQuery}>
167 - <input ref={inputRef} tabIndex={-1} />
168 - <button ref={buttonRef} id={1} />
169 - <FocusContain scopeQuery={tabbableScopeQuery} disabled={false}>
170 - <button ref={button2Ref} id={2} />
171 - <button ref={button3Ref} id={3} />
172 - </FocusContain>
173 - <input ref={input2Ref} tabIndex={-1} />
174 - <button ref={button4Ref} id={4} />
175 - </FocusContain>
176 - );
177 -
178 - ReactDOM.render(<Test />, container);
179 - expect(document.activeElement).toBe(button2Ref.current);
180 - tabNext(createEventTarget(document.activeElement));
181 - expect(document.activeElement).toBe(button3Ref.current);
182 - tabNext(createEventTarget(document.activeElement));
183 - expect(document.activeElement).toBe(button2Ref.current);
184 - tabPrevious(createEventTarget(document.activeElement));
185 - expect(document.activeElement).toBe(button3Ref.current);
186 - tabPrevious(createEventTarget(document.activeElement));
187 - expect(document.activeElement).toBe(button2Ref.current);
188 - });
189 -
190 - it('should work as expected with suspense fallbacks', () => {
191 - const buttonRef = React.createRef();
192 - const button2Ref = React.createRef();
193 - const button3Ref = React.createRef();
194 - const button4Ref = React.createRef();
195 - const button5Ref = React.createRef();
196 -
197 - function SuspendedComponent() {
198 - throw new Promise(() => {
199 - // Never resolve
200 - });
201 - }
202 -
203 - function Component() {
204 - return (
205 - <React.Fragment>
206 - <button ref={button5Ref} id={5} />
207 - <SuspendedComponent />
208 - </React.Fragment>
209 - );
210 - }
211 -
212 - const Test = () => (
213 - <FocusContain scopeQuery={tabbableScopeQuery}>
214 - <button ref={buttonRef} id={1} />
215 - <button ref={button2Ref} id={2} />
216 - <React.Suspense fallback={<button ref={button3Ref} id={3} />}>
217 - <Component />
218 - </React.Suspense>
219 - <button ref={button4Ref} id={4} />
220 - </FocusContain>
221 - );
222 -
223 - ReactDOM.render(<Test />, container);
224 - buttonRef.current.focus();
225 - expect(document.activeElement).toBe(buttonRef.current);
226 - tabNext(createEventTarget(document.activeElement));
227 - expect(document.activeElement).toBe(button2Ref.current);
228 - tabNext(createEventTarget(document.activeElement));
229 - expect(document.activeElement).toBe(button3Ref.current);
230 - tabNext(createEventTarget(document.activeElement));
231 - expect(document.activeElement).toBe(button4Ref.current);
232 - tabPrevious(createEventTarget(document.activeElement));
233 - expect(document.activeElement).toBe(button3Ref.current);
234 - tabPrevious(createEventTarget(document.activeElement));
235 - expect(document.activeElement).toBe(button2Ref.current);
236 - });
237 - });
238 -});
packages/react-interactions/accessibility/src/__tests__/FocusGroup-test.internal.js deleted
-256
@@ -1,256 +0,0 @@
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 {createEventTarget} from 'dom-event-testing-library';
11 -import {emulateBrowserTab} from '../shared/emulateBrowserTab';
12 -
13 -let React;
14 -let ReactFeatureFlags;
15 -let createFocusGroup;
16 -let tabbableScopeQuery;
17 -
18 -describe('FocusGroup', () => {
19 - beforeEach(() => {
20 - jest.resetModules();
21 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
22 - ReactFeatureFlags.enableScopeAPI = true;
23 - ReactFeatureFlags.enableDeprecatedFlareAPI = true;
24 - createFocusGroup = require('../FocusGroup').createFocusGroup;
25 - tabbableScopeQuery = require('../TabbableScopeQuery').default;
26 - React = require('react');
27 - });
28 -
29 - describe('ReactDOM', () => {
30 - let ReactDOM;
31 - let container;
32 -
33 - beforeEach(() => {
34 - ReactDOM = require('react-dom');
35 - container = document.createElement('div');
36 - document.body.appendChild(container);
37 - });
38 -
39 - afterEach(() => {
40 - document.body.removeChild(container);
41 - container = null;
42 - });
43 -
44 - function createFocusGroupComponent() {
45 - const [FocusGroup, FocusItem] = createFocusGroup(tabbableScopeQuery);
46 -
47 - return ({portrait, wrap, allowModifiers}) => (
48 - <FocusGroup
49 - portrait={portrait}
50 - wrap={wrap}
51 - allowModifiers={allowModifiers}>
52 - <ul>
53 - <FocusItem>
54 - <li tabIndex={0}>Item 1</li>
55 - </FocusItem>
56 - <FocusItem>
57 - <li tabIndex={0}>Item 2</li>
58 - </FocusItem>
59 - <FocusItem>
60 - <li tabIndex={0}>Item 3</li>
61 - </FocusItem>
62 - </ul>
63 - </FocusGroup>
64 - );
65 - }
66 -
67 - it('handles keyboard arrow operations (portrait)', () => {
68 - const Test = createFocusGroupComponent();
69 -
70 - ReactDOM.render(<Test portrait={true} />, container);
71 - const groupItems = document.querySelectorAll('li');
72 - const firstGroupItem = createEventTarget(groupItems[0]);
73 - firstGroupItem.focus();
74 - firstGroupItem.keydown({
75 - key: 'ArrowDown',
76 - });
77 - expect(document.activeElement.textContent).toBe('Item 2');
78 -
79 - const secondGroupItem = createEventTarget(document.activeElement);
80 - secondGroupItem.keydown({
81 - key: 'ArrowDown',
82 - });
83 - expect(document.activeElement.textContent).toBe('Item 3');
84 -
85 - const thirdGroupItem = createEventTarget(document.activeElement);
86 - thirdGroupItem.keydown({
87 - key: 'ArrowDown',
88 - });
89 - expect(document.activeElement.textContent).toBe('Item 3');
90 - thirdGroupItem.keydown({
91 - key: 'ArrowRight',
92 - });
93 - expect(document.activeElement.textContent).toBe('Item 3');
94 - thirdGroupItem.keydown({
95 - key: 'ArrowLeft',
96 - });
97 - expect(document.activeElement.textContent).toBe('Item 3');
98 - // Should be a no-op due to modifier
99 - thirdGroupItem.keydown({
100 - key: 'ArrowUp',
101 - altKey: true,
102 - });
103 - expect(document.activeElement.textContent).toBe('Item 3');
104 - });
105 -
106 - it('handles keyboard arrow operations (landscape)', () => {
107 - const Test = createFocusGroupComponent();
108 -
109 - ReactDOM.render(<Test portrait={false} />, container);
110 - const GroupItems = document.querySelectorAll('li');
111 - const firstGroupItem = createEventTarget(GroupItems[0]);
112 - firstGroupItem.focus();
113 - firstGroupItem.keydown({
114 - key: 'ArrowRight',
115 - });
116 - expect(document.activeElement.textContent).toBe('Item 2');
117 -
118 - const secondGroupItem = createEventTarget(document.activeElement);
119 - secondGroupItem.keydown({
120 - key: 'ArrowRight',
121 - });
122 - expect(document.activeElement.textContent).toBe('Item 3');
123 -
124 - const thirdGroupItem = createEventTarget(document.activeElement);
125 - thirdGroupItem.keydown({
126 - key: 'ArrowRight',
127 - });
128 - expect(document.activeElement.textContent).toBe('Item 3');
129 - thirdGroupItem.keydown({
130 - key: 'ArrowUp',
131 - });
132 - expect(document.activeElement.textContent).toBe('Item 3');
133 - thirdGroupItem.keydown({
134 - key: 'ArrowDown',
135 - });
136 - expect(document.activeElement.textContent).toBe('Item 3');
137 - });
138 -
139 - it('handles keyboard arrow operations (portrait) with wrapping enabled', () => {
140 - const Test = createFocusGroupComponent();
141 -
142 - ReactDOM.render(<Test portrait={true} wrap={true} />, container);
143 - const GroupItems = document.querySelectorAll('li');
144 - let firstGroupItem = createEventTarget(GroupItems[0]);
145 - firstGroupItem.focus();
146 - firstGroupItem.keydown({
147 - key: 'ArrowDown',
148 - });
149 - expect(document.activeElement.textContent).toBe('Item 2');
150 -
151 - const secondGroupItem = createEventTarget(document.activeElement);
152 - secondGroupItem.keydown({
153 - key: 'ArrowDown',
154 - });
155 - expect(document.activeElement.textContent).toBe('Item 3');
156 -
157 - const thirdGroupItem = createEventTarget(document.activeElement);
158 - thirdGroupItem.keydown({
159 - key: 'ArrowDown',
160 - });
161 - expect(document.activeElement.textContent).toBe('Item 1');
162 -
163 - firstGroupItem = createEventTarget(document.activeElement);
164 - firstGroupItem.keydown({
165 - key: 'ArrowUp',
166 - });
167 - expect(document.activeElement.textContent).toBe('Item 3');
168 - });
169 -
170 - it('handles keyboard arrow operations (portrait) with allowModifiers', () => {
171 - const Test = createFocusGroupComponent();
172 -
173 - ReactDOM.render(
174 - <Test portrait={true} allowModifiers={true} />,
175 - container,
176 - );
177 - const GroupItems = document.querySelectorAll('li');
178 - let firstGroupItem = createEventTarget(GroupItems[0]);
179 - firstGroupItem.focus();
180 - firstGroupItem.keydown({
181 - key: 'ArrowDown',
182 - altKey: true,
183 - });
184 - expect(document.activeElement.textContent).toBe('Item 2');
185 - });
186 -
187 - it('handles keyboard arrow operations mixed with tabbing', () => {
188 - const [FocusGroup, FocusItem] = createFocusGroup(tabbableScopeQuery);
189 - const beforeRef = React.createRef();
190 - const afterRef = React.createRef();
191 -
192 - function Test() {
193 - return (
194 - <>
195 - <input placeholder="Before" ref={beforeRef} />
196 - <FocusGroup tabScopeQuery={tabbableScopeQuery} portrait={true}>
197 - <ul>
198 - <FocusItem>
199 - <li>
200 - <input placeholder="A" />
201 - </li>
202 - </FocusItem>
203 - <FocusItem>
204 - <li>
205 - <input placeholder="B" />
206 - </li>
207 - </FocusItem>
208 - <FocusItem>
209 - <li>
210 - <input placeholder="C" />
211 - </li>
212 - </FocusItem>
213 - <FocusItem>
214 - <li>
215 - <input placeholder="D" />
216 - </li>
217 - </FocusItem>
218 - <FocusItem>
219 - <li>
220 - <input placeholder="E" />
221 - </li>
222 - </FocusItem>
223 - <FocusItem>
224 - <li>
225 - <input placeholder="F" />
226 - </li>
227 - </FocusItem>
228 - </ul>
229 - </FocusGroup>
230 - <input placeholder="After" ref={afterRef} />
231 - </>
232 - );
233 - }
234 -
235 - ReactDOM.render(<Test />, container);
236 - beforeRef.current.focus();
237 -
238 - expect(document.activeElement.placeholder).toBe('Before');
239 - emulateBrowserTab();
240 - expect(document.activeElement.placeholder).toBe('A');
241 - emulateBrowserTab();
242 - expect(document.activeElement.placeholder).toBe('After');
243 - emulateBrowserTab(true);
244 - expect(document.activeElement.placeholder).toBe('A');
245 - const a = createEventTarget(document.activeElement);
246 - a.keydown({
247 - key: 'ArrowDown',
248 - });
249 - expect(document.activeElement.placeholder).toBe('B');
250 - emulateBrowserTab();
251 - expect(document.activeElement.placeholder).toBe('After');
252 - emulateBrowserTab(true);
253 - expect(document.activeElement.placeholder).toBe('B');
254 - });
255 - });
256 -});
packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js deleted
-86
@@ -1,86 +0,0 @@
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 -let React;
11 -let ReactFeatureFlags;
12 -let FocusManager;
13 -let tabbableScopeQuery;
14 -
15 -describe('FocusManager', () => {
16 - beforeEach(() => {
17 - jest.resetModules();
18 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
19 - ReactFeatureFlags.enableScopeAPI = true;
20 - ReactFeatureFlags.enableDeprecatedFlareAPI = true;
21 - FocusManager = require('../FocusManager');
22 - tabbableScopeQuery = require('../TabbableScopeQuery').default;
23 - React = require('react');
24 - });
25 -
26 - describe('ReactDOM', () => {
27 - let ReactDOM;
28 - let container;
29 -
30 - beforeEach(() => {
31 - ReactDOM = require('react-dom');
32 - container = document.createElement('div');
33 - document.body.appendChild(container);
34 - });
35 -
36 - afterEach(() => {
37 - document.body.removeChild(container);
38 - container = null;
39 - });
40 -
41 - it('allows for imperative tab focus control with a scope', () => {
42 - const firstFocusControllerRef = React.createRef();
43 - const secondFocusControllerRef = React.createRef();
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>
51 - <TestScope ref={firstFocusControllerRef}>
52 - <input tabIndex={-1} />
53 - <button ref={buttonRef} />
54 - <button ref={button2Ref} />
55 - <input tabIndex={-1} />
56 - </TestScope>
57 - <TestScope ref={secondFocusControllerRef}>
58 - <input tabIndex={-1} />
59 - <div ref={divRef} tabIndex={0} />
60 - </TestScope>
61 - </div>
62 - );
63 -
64 - ReactDOM.render(<Test />, container);
65 - const firstFocusController = firstFocusControllerRef.current;
66 - const secondFocusController = secondFocusControllerRef.current;
67 -
68 - FocusManager.focusFirst(tabbableScopeQuery, firstFocusController);
69 - expect(document.activeElement).toBe(buttonRef.current);
70 - FocusManager.focusNext(tabbableScopeQuery, firstFocusController);
71 - expect(document.activeElement).toBe(button2Ref.current);
72 - FocusManager.focusPrevious(tabbableScopeQuery, firstFocusController);
73 - expect(document.activeElement).toBe(buttonRef.current);
74 -
75 - const nextController = FocusManager.getNextScope(firstFocusController);
76 - expect(nextController).toBe(secondFocusController);
77 - FocusManager.focusFirst(tabbableScopeQuery, nextController);
78 - expect(document.activeElement).toBe(divRef.current);
79 -
80 - const previousController = FocusManager.getPreviousScope(nextController);
81 - expect(previousController).toBe(firstFocusController);
82 - FocusManager.focusFirst(tabbableScopeQuery, previousController);
83 - expect(document.activeElement).toBe(buttonRef.current);
84 - });
85 - });
86 -});
packages/react-interactions/accessibility/src/__tests__/FocusTable-test.internal.js deleted
-573
@@ -1,573 +0,0 @@
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 {createEventTarget} from 'dom-event-testing-library';
11 -import {emulateBrowserTab} from '../shared/emulateBrowserTab';
12 -
13 -let React;
14 -let ReactFeatureFlags;
15 -let createFocusTable;
16 -let tabbableScopeQuery;
17 -
18 -describe('FocusTable', () => {
19 - beforeEach(() => {
20 - jest.resetModules();
21 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
22 - ReactFeatureFlags.enableScopeAPI = true;
23 - ReactFeatureFlags.enableDeprecatedFlareAPI = true;
24 - createFocusTable = require('../FocusTable').createFocusTable;
25 - tabbableScopeQuery = require('../TabbableScopeQuery').default;
26 - React = require('react');
27 - });
28 -
29 - describe('ReactDOM', () => {
30 - let ReactDOM;
31 - let container;
32 -
33 - beforeEach(() => {
34 - ReactDOM = require('react-dom');
35 - container = document.createElement('div');
36 - document.body.appendChild(container);
37 - });
38 -
39 - afterEach(() => {
40 - document.body.removeChild(container);
41 - container = null;
42 - });
43 -
44 - function createFocusTableComponent() {
45 - const [FocusTable, FocusTableRow, FocusTableCell] = createFocusTable(
46 - tabbableScopeQuery,
47 - );
48 -
49 - return ({onKeyboardOut, wrapX, wrapY, allowModifiers}) => (
50 - <FocusTable
51 - onKeyboardOut={onKeyboardOut}
52 - wrapX={wrapX}
53 - wrapY={wrapY}
54 - allowModifiers={allowModifiers}>
55 - <table>
56 - <tbody>
57 - <FocusTableRow>
58 - <tr>
59 - <FocusTableCell>
60 - <td>
61 - <button>A1</button>
62 - </td>
63 - </FocusTableCell>
64 - <FocusTableCell>
65 - <td>
66 - <button>A2</button>
67 - </td>
68 - </FocusTableCell>
69 - <FocusTableCell>
70 - <td>
71 - <button>A3</button>
72 - </td>
73 - </FocusTableCell>
74 - </tr>
75 - </FocusTableRow>
76 - <FocusTableRow>
77 - <tr>
78 - <FocusTableCell>
79 - <td>
80 - <button>B1</button>
81 - </td>
82 - </FocusTableCell>
83 - <FocusTableCell>
84 - <td>
85 - <button>B2</button>
86 - </td>
87 - </FocusTableCell>
88 - <FocusTableCell>
89 - <td>
90 - <button>B3</button>
91 - </td>
92 - </FocusTableCell>
93 - </tr>
94 - </FocusTableRow>
95 - <FocusTableRow>
96 - <tr>
97 - <FocusTableCell>
98 - <td>
99 - <button>C1</button>
100 - </td>
101 - </FocusTableCell>
102 - <FocusTableCell>
103 - <td>
104 - <button>C2</button>
105 - </td>
106 - </FocusTableCell>
107 - <FocusTableCell>
108 - <td>
109 - <button>C3</button>
110 - </td>
111 - </FocusTableCell>
112 - </tr>
113 - </FocusTableRow>
114 - </tbody>
115 - </table>
116 - </FocusTable>
117 - );
118 - }
119 -
120 - it('handles keyboard arrow operations with allowModifiers', () => {
121 - const Test = createFocusTableComponent();
122 -
123 - ReactDOM.render(<Test allowModifiers={true} />, container);
124 - const buttons = document.querySelectorAll('button');
125 - const a1 = createEventTarget(buttons[0]);
126 - a1.focus();
127 - a1.keydown({
128 - key: 'ArrowRight',
129 - altKey: true,
130 - });
131 - expect(document.activeElement.textContent).toBe('A2');
132 - });
133 -
134 - it('handles keyboard arrow operations', () => {
135 - const Test = createFocusTableComponent();
136 -
137 - ReactDOM.render(<Test />, container);
138 - const buttons = document.querySelectorAll('button');
139 - const a1 = createEventTarget(buttons[0]);
140 - a1.focus();
141 - a1.keydown({
142 - key: 'ArrowRight',
143 - });
144 - expect(document.activeElement.textContent).toBe('A2');
145 -
146 - const a2 = createEventTarget(document.activeElement);
147 - a2.keydown({
148 - key: 'ArrowDown',
149 - });
150 - expect(document.activeElement.textContent).toBe('B2');
151 -
152 - let b2 = createEventTarget(document.activeElement);
153 - b2.keydown({
154 - key: 'ArrowLeft',
155 - });
156 - expect(document.activeElement.textContent).toBe('B1');
157 -
158 - const b1 = createEventTarget(document.activeElement);
159 - b1.keydown({
160 - key: 'ArrowDown',
161 - });
162 - expect(document.activeElement.textContent).toBe('C1');
163 -
164 - const c1 = createEventTarget(document.activeElement);
165 - c1.keydown({
166 - key: 'ArrowDown',
167 - });
168 - expect(document.activeElement.textContent).toBe('C1');
169 - c1.keydown({
170 - key: 'ArrowUp',
171 - });
172 - expect(document.activeElement.textContent).toBe('B1');
173 - // Should be a no-op due to modifier
174 - b2 = createEventTarget(document.activeElement);
175 - b2.keydown({
176 - key: 'ArrowUp',
177 - altKey: true,
178 - });
179 - b2 = createEventTarget(document.activeElement);
180 - expect(document.activeElement.textContent).toBe('B1');
181 - });
182 -
183 - it('handles keyboard arrow operations between nested tables', () => {
184 - const leftSidebarRef = React.createRef();
185 - const [
186 - MainFocusTable,
187 - MainFocusTableRow,
188 - MainFocusTableCell,
189 - ] = createFocusTable(tabbableScopeQuery);
190 - const SubFocusTable = createFocusTableComponent();
191 - const onKeyboardOut = jest.fn((direction, event) =>
192 - event.continuePropagation(),
193 - );
194 -
195 - function Test() {
196 - return (
197 - <MainFocusTable>
198 - <MainFocusTableRow>
199 - <div>
200 - <h1>Title</h1>
201 - <aside ref={leftSidebarRef}>
202 - <h2>Left Sidebar</h2>
203 - <MainFocusTableCell>
204 - <SubFocusTable onKeyboardOut={onKeyboardOut} />
205 - </MainFocusTableCell>
206 - </aside>
207 - <section>
208 - <h2>Content</h2>
209 - <MainFocusTableCell>
210 - <SubFocusTable onKeyboardOut={onKeyboardOut} />
211 - </MainFocusTableCell>
212 - </section>
213 - <aside>
214 - <h2>Right Sidebar</h2>
215 - <MainFocusTableCell>
216 - <SubFocusTable onKeyboardOut={onKeyboardOut} />
217 - </MainFocusTableCell>
218 - </aside>
219 - </div>
220 - </MainFocusTableRow>
221 - </MainFocusTable>
222 - );
223 - }
224 -
225 - ReactDOM.render(<Test />, container);
226 - const buttons = document.querySelectorAll('button');
227 - let a1 = createEventTarget(buttons[0]);
228 - a1.focus();
229 - a1.keydown({
230 - key: 'ArrowRight',
231 - });
232 - expect(document.activeElement.textContent).toBe('A2');
233 -
234 - let a2 = createEventTarget(document.activeElement);
235 - a2.keydown({
236 - key: 'ArrowRight',
237 - });
238 - expect(document.activeElement.textContent).toBe('A3');
239 -
240 - let a3 = createEventTarget(document.activeElement);
241 - a3.keydown({
242 - key: 'ArrowRight',
243 - });
244 - expect(onKeyboardOut).toHaveBeenCalledTimes(1);
245 - expect(document.activeElement.textContent).toBe('A1');
246 -
247 - a1 = createEventTarget(document.activeElement);
248 - a1.keydown({
249 - key: 'ArrowRight',
250 - });
251 - expect(document.activeElement.textContent).toBe('A2');
252 -
253 - a2 = createEventTarget(document.activeElement);
254 - a2.keydown({
255 - key: 'ArrowRight',
256 - });
257 - expect(document.activeElement.textContent).toBe('A3');
258 -
259 - a3 = createEventTarget(document.activeElement);
260 - a3.keydown({
261 - key: 'ArrowRight',
262 - });
263 - expect(onKeyboardOut).toHaveBeenCalledTimes(2);
264 - expect(document.activeElement.textContent).toBe('A1');
265 -
266 - a1 = createEventTarget(document.activeElement);
267 - a1.keydown({
268 - key: 'ArrowRight',
269 - });
270 - expect(document.activeElement.textContent).toBe('A2');
271 -
272 - a2 = createEventTarget(document.activeElement);
273 - a2.keydown({
274 - key: 'ArrowRight',
275 - });
276 - expect(document.activeElement.textContent).toBe('A3');
277 -
278 - a3 = createEventTarget(document.activeElement);
279 - a3.keydown({
280 - key: 'ArrowRight',
281 - });
282 - expect(document.activeElement.textContent).toBe('A3');
283 - });
284 -
285 - it('handles nested tables correctly', () => {
286 - const customScopeQuery = (type, props) => {
287 - return type === 'input';
288 - };
289 - const [FocusTable, FocusRow, FocusCell] = createFocusTable(
290 - customScopeQuery,
291 - );
292 - const firstRef = React.createRef();
293 -
294 - function Test() {
295 - return (
296 - <FocusTable>
297 - <div>
298 - <FocusRow>
299 - <FocusCell>
300 - <FocusTable>
301 - <FocusRow>
302 - <FocusCell>
303 - <input
304 - placeholder="Nested A1"
305 - tabIndex={0}
306 - ref={firstRef}
307 - />
308 - </FocusCell>
309 - <FocusCell>
310 - <input placeholder="Nested B1" tabIndex={0} />
311 - </FocusCell>
312 - </FocusRow>
313 - </FocusTable>
314 - </FocusCell>
315 - <FocusCell>
316 - <input placeholder="B1" tabIndex={-1} />
317 - </FocusCell>
318 - <FocusCell>
319 - <input placeholder="C1" tabIndex={-1} />
320 - </FocusCell>
321 - </FocusRow>
322 - </div>
323 - <div>
324 - <FocusRow>
325 - <FocusCell>
326 - <input placeholder="A2" tabIndex={-1} />
327 - </FocusCell>
328 - <FocusCell>
329 - <input placeholder="B2" tabIndex={-1} />
330 - </FocusCell>
331 - <FocusCell>
332 - <input placeholder="C1" tabIndex={-1} />
333 - </FocusCell>
334 - </FocusRow>
335 - </div>
336 - </FocusTable>
337 - );
338 - }
339 -
340 - ReactDOM.render(<Test />, container);
341 - firstRef.current.focus();
342 -
343 - const nestedA1 = createEventTarget(document.activeElement);
344 - nestedA1.keydown({
345 - key: 'ArrowRight',
346 - });
347 - expect(document.activeElement.placeholder).toBe('Nested B1');
348 -
349 - const nestedB1 = createEventTarget(document.activeElement);
350 - nestedB1.keydown({
351 - key: 'ArrowRight',
352 - });
353 - expect(document.activeElement.placeholder).toBe('B1');
354 - });
355 -
356 - it('handles keyboard arrow operations with X wrapping enabled', () => {
357 - const Test = createFocusTableComponent();
358 -
359 - ReactDOM.render(<Test wrapX={true} />, container);
360 - const buttons = document.querySelectorAll('button');
361 - let a1 = createEventTarget(buttons[0]);
362 - a1.focus();
363 - a1.keydown({
364 - key: 'ArrowRight',
365 - });
366 - expect(document.activeElement.textContent).toBe('A2');
367 -
368 - const a2 = createEventTarget(document.activeElement);
369 - a2.keydown({
370 - key: 'ArrowRight',
371 - });
372 - expect(document.activeElement.textContent).toBe('A3');
373 -
374 - const a3 = createEventTarget(document.activeElement);
375 - a3.keydown({
376 - key: 'ArrowRight',
377 - });
378 - expect(document.activeElement.textContent).toBe('A1');
379 -
380 - a1 = createEventTarget(document.activeElement);
381 - a1.keydown({
382 - key: 'ArrowLeft',
383 - });
384 - expect(document.activeElement.textContent).toBe('A3');
385 - });
386 -
387 - it('handles keyboard arrow operations with Y wrapping enabled', () => {
388 - const Test = createFocusTableComponent();
389 -
390 - ReactDOM.render(<Test wrapY={true} />, container);
391 - const buttons = document.querySelectorAll('button');
392 - let a1 = createEventTarget(buttons[0]);
393 - a1.focus();
394 - a1.keydown({
395 - key: 'ArrowDown',
396 - });
397 - expect(document.activeElement.textContent).toBe('B1');
398 -
399 - const a2 = createEventTarget(document.activeElement);
400 - a2.keydown({
401 - key: 'ArrowDown',
402 - });
403 - expect(document.activeElement.textContent).toBe('C1');
404 -
405 - const a3 = createEventTarget(document.activeElement);
406 - a3.keydown({
407 - key: 'ArrowDown',
408 - });
409 - expect(document.activeElement.textContent).toBe('A1');
410 -
411 - a1 = createEventTarget(document.activeElement);
412 - a1.keydown({
413 - key: 'ArrowUp',
414 - });
415 - expect(document.activeElement.textContent).toBe('C1');
416 - });
417 -
418 - it('handles keyboard arrow operations mixed with tabbing', () => {
419 - const [FocusTable, FocusRow, FocusCell] = createFocusTable(
420 - tabbableScopeQuery,
421 - );
422 - const beforeRef = React.createRef();
423 - const afterRef = React.createRef();
424 -
425 - function Test() {
426 - return (
427 - <>
428 - <input placeholder="Before" ref={beforeRef} />
429 - <FocusTable tabScopeQuery={tabbableScopeQuery}>
430 - <div>
431 - <FocusRow>
432 - <FocusCell>
433 - <input placeholder="A1" />
434 - </FocusCell>
435 - <FocusCell>
436 - <input placeholder="B1" />
437 - </FocusCell>
438 - <FocusCell>
439 - <input placeholder="C1" />
440 - </FocusCell>
441 - </FocusRow>
442 - </div>
443 - <div>
444 - <FocusRow>
445 - <FocusCell>
446 - <input placeholder="A2" />
447 - </FocusCell>
448 - <FocusCell>
449 - <input placeholder="B2" />
450 - </FocusCell>
451 - <FocusCell>
452 - <input placeholder="C1" />
453 - </FocusCell>
454 - </FocusRow>
455 - </div>
456 - </FocusTable>
457 - <input placeholder="After" ref={afterRef} />
458 - </>
459 - );
460 - }
461 -
462 - ReactDOM.render(<Test />, container);
463 - beforeRef.current.focus();
464 -
465 - expect(document.activeElement.placeholder).toBe('Before');
466 - emulateBrowserTab();
467 - expect(document.activeElement.placeholder).toBe('A1');
468 - emulateBrowserTab();
469 - expect(document.activeElement.placeholder).toBe('After');
470 - emulateBrowserTab(true);
471 - expect(document.activeElement.placeholder).toBe('A1');
472 - const a1 = createEventTarget(document.activeElement);
473 - a1.keydown({
474 - key: 'ArrowRight',
475 - });
476 - expect(document.activeElement.placeholder).toBe('B1');
477 - emulateBrowserTab();
478 - expect(document.activeElement.placeholder).toBe('After');
479 - emulateBrowserTab(true);
480 - expect(document.activeElement.placeholder).toBe('B1');
481 - });
482 -
483 - it('handles keyboard arrow operations with colSpan', () => {
484 - const firstRef = React.createRef();
485 - const [FocusTable, FocusRow, FocusCell] = createFocusTable(
486 - tabbableScopeQuery,
487 - );
488 -
489 - function Test() {
490 - return (
491 - <>
492 - <FocusTable tabScopeQuery={tabbableScopeQuery}>
493 - <div>
494 - <FocusRow>
495 - <FocusCell>
496 - <input placeholder="A1" ref={firstRef} />
497 - </FocusCell>
498 - <FocusCell colSpan={2}>
499 - <input placeholder="B1" />
500 - </FocusCell>
501 - <FocusCell>
502 - <input placeholder="C1" />
503 - </FocusCell>
504 - </FocusRow>
505 - </div>
506 - <div>
507 - <FocusRow>
508 - <FocusCell>
509 - <input placeholder="A2" />
510 - </FocusCell>
511 - <FocusCell>
512 - <input placeholder="B2" />
513 - </FocusCell>
514 - <FocusCell>
515 - <input placeholder="C2" />
516 - </FocusCell>
517 - <FocusCell>
518 - <input placeholder="D2" />
519 - </FocusCell>
520 - </FocusRow>
521 - </div>
522 - </FocusTable>
523 - </>
524 - );
525 - }
526 -
527 - ReactDOM.render(<Test />, container);
528 - firstRef.current.focus();
529 -
530 - expect(document.activeElement.placeholder).toBe('A1');
531 - const a1 = createEventTarget(document.activeElement);
532 - a1.keydown({
533 - key: 'ArrowRight',
534 - });
535 - expect(document.activeElement.placeholder).toBe('B1');
536 - let b1 = createEventTarget(document.activeElement);
537 - b1.keydown({
538 - key: 'ArrowRight',
539 - });
540 - expect(document.activeElement.placeholder).toBe('C1');
541 - let c1 = createEventTarget(document.activeElement);
542 - c1.keydown({
543 - key: 'ArrowDown',
544 - });
545 - expect(document.activeElement.placeholder).toBe('D2');
546 - let d2 = createEventTarget(document.activeElement);
547 - d2.keydown({
548 - key: 'ArrowUp',
549 - });
550 - expect(document.activeElement.placeholder).toBe('C1');
551 - c1 = createEventTarget(document.activeElement);
552 - c1.keydown({
553 - key: 'ArrowLeft',
554 - });
555 - expect(document.activeElement.placeholder).toBe('B1');
556 - b1 = createEventTarget(document.activeElement);
557 - b1.keydown({
558 - key: 'ArrowDown',
559 - });
560 - expect(document.activeElement.placeholder).toBe('B2');
561 - const b2 = createEventTarget(document.activeElement);
562 - b2.keydown({
563 - key: 'ArrowRight',
564 - });
565 - expect(document.activeElement.placeholder).toBe('C2');
566 - const c2 = createEventTarget(document.activeElement);
567 - c2.keydown({
568 - key: 'ArrowUp',
569 - });
570 - expect(document.activeElement.placeholder).toBe('B1');
571 - });
572 - });
573 -});
packages/react-interactions/accessibility/src/__tests__/TabbableScopeQuery-test.internal.js deleted
-75
@@ -1,75 +0,0 @@
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 -let React;
11 -let ReactFeatureFlags;
12 -let tabbableScopeQuery;
13 -
14 -describe('TabbableScopeQuery', () => {
15 - beforeEach(() => {
16 - jest.resetModules();
17 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
18 - ReactFeatureFlags.enableScopeAPI = true;
19 - tabbableScopeQuery = require('../TabbableScopeQuery').default;
20 - React = require('react');
21 - });
22 -
23 - describe('ReactDOM', () => {
24 - let ReactDOM;
25 - let container;
26 -
27 - beforeEach(() => {
28 - ReactDOM = require('react-dom');
29 - container = document.createElement('div');
30 - document.body.appendChild(container);
31 - });
32 -
33 - afterEach(() => {
34 - document.body.removeChild(container);
35 - container = null;
36 - });
37 -
38 - it('DO_NOT_USE_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 (
49 - <TestScope ref={scopeRef}>
50 - <input ref={nodeRefA} />
51 - <textarea ref={nodeRefB} />
52 - <div tabIndex={0} ref={nodeRefC}>
53 - <input tabIndex={-1} />
54 - <div tabIndex={0} ref={nodeRefD} />
55 - </div>
56 - <input disabled={true} />
57 - <div tabIndex={-1} />
58 - </TestScope>
59 - );
60 - }
61 -
62 - ReactDOM.render(<Test />, container);
63 - let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(tabbableScopeQuery);
64 - expect(nodes).toEqual([
65 - nodeRefA.current,
66 - nodeRefB.current,
67 - nodeRefC.current,
68 - nodeRefD.current,
69 - ]);
70 - expect(nodes.length).toBe(4);
71 - ReactDOM.render(null, container);
72 - expect(scopeRef.current).toBe(null);
73 - });
74 - });
75 -});
packages/react-interactions/accessibility/src/shared/emulateBrowserTab.js deleted
-38
@@ -1,38 +0,0 @@
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 - * @emails react-core
8 - */
9 -
10 -'use strict';
11 -
12 -import {createEventTarget} from 'dom-event-testing-library';
13 -
14 -// This function is used by the a11y modules for testing
15 -export function emulateBrowserTab(backwards: boolean): void {
16 - const activeElement = document.activeElement;
17 - const focusedElem = createEventTarget(activeElement);
18 - let defaultPrevented = false;
19 - focusedElem.keydown({
20 - key: 'Tab',
21 - shiftKey: backwards,
22 - preventDefault() {
23 - defaultPrevented = true;
24 - },
25 - });
26 - if (!defaultPrevented) {
27 - // This is not a full spec compliant version, but should be suffice for this test
28 - const focusableElems = Array.from(
29 - document.querySelectorAll(
30 - 'input, button, select, textarea, a[href], [tabindex], [contenteditable], iframe, object, embed',
31 - ),
32 - ).filter(elem => elem.tabIndex > -1 && !elem.disabled);
33 - const idx = focusableElems.indexOf(activeElement);
34 - if (idx !== -1) {
35 - focusableElems[backwards ? idx - 1 : idx + 1].focus();
36 - }
37 - }
38 -}
packages/react-interactions/accessibility/src/shared/getTabbableNodes.js deleted
-40
@@ -1,40 +0,0 @@
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 -
12 -export default function getTabbableNodes(
13 - scopeQuery: (type: string | Object, props: Object) => boolean,
14 - scope: ReactScopeMethods,
15 -): [
16 - null | Array<HTMLElement>,
17 - null | HTMLElement,
18 - null | HTMLElement,
19 - number,
20 - null | HTMLElement,
21 -] {
22 - const tabbableNodes = scope.DO_NOT_USE_queryAllNodes(scopeQuery);
23 - if (tabbableNodes === null || tabbableNodes.length === 0) {
24 - return [null, null, null, 0, null];
25 - }
26 - const firstTabbableElem = tabbableNodes[0];
27 - const lastTabbableElem = tabbableNodes[tabbableNodes.length - 1];
28 - const currentIndex = tabbableNodes.indexOf(document.activeElement);
29 - let focusedElement = null;
30 - if (currentIndex !== -1) {
31 - focusedElement = tabbableNodes[currentIndex];
32 - }
33 - return [
34 - tabbableNodes,
35 - firstTabbableElem,
36 - lastTabbableElem,
37 - currentIndex,
38 - focusedElement,
39 - ];
40 -}
packages/react-interactions/accessibility/src/shared/setElementCanTab.js deleted
-50
@@ -1,50 +0,0 @@
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 -const tabIndexDesc = Object.getOwnPropertyDescriptor(
11 - HTMLElement.prototype,
12 - 'tabIndex',
13 -);
14 -const tabIndexSetter = (tabIndexDesc: any).set;
15 -
16 -export default function setElementCanTab(
17 - elem: HTMLElement,
18 - canTab: boolean,
19 -): void {
20 - let tabIndexState = (elem: any)._tabIndexState;
21 - if (!tabIndexState) {
22 - tabIndexState = {
23 - value: elem.tabIndex,
24 - canTab,
25 - };
26 - (elem: any)._tabIndexState = tabIndexState;
27 - if (!canTab) {
28 - elem.tabIndex = -1;
29 - }
30 - // We track the tabIndex value so we can restore the correct
31 - // tabIndex after we're done with it.
32 - // $FlowFixMe: Flow comoplains that we are missing value?
33 - Object.defineProperty(elem, 'tabIndex', {
34 - enumerable: false,
35 - configurable: true,
36 - get() {
37 - return tabIndexState.canTab ? tabIndexState.value : -1;
38 - },
39 - set(val) {
40 - if (tabIndexState.canTab) {
41 - tabIndexSetter.call(elem, val);
42 - }
43 - tabIndexState.value = val;
44 - },
45 - });
46 - } else if (tabIndexState.canTab !== canTab) {
47 - tabIndexSetter.call(elem, canTab ? tabIndexState.value : -1);
48 - tabIndexState.canTab = canTab;
49 - }
50 -}
packages/react-interactions/accessibility/tabbable-scope-query.js deleted
-12
@@ -1,12 +0,0 @@
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/TabbableScopeQuery');
packages/react-reconciler/src/ReactFiberScope.js
+7 -89
@@ -13,6 +13,7 @@ import type {
13 ReactScopeInstance,
14 ReactScopeMethods,
15 ReactContext,
16 + ReactScopeQuery,
17 } from 'shared/ReactTypes';
18
19 import {getPublicInstance, getInstanceFromNode} from './ReactFiberHostConfig';
@@ -37,7 +38,7 @@ const emptyObject = {};
38
39 function collectScopedNodes(
40 node: Fiber,
40 - fn: (type: string | Object, props: Object, instance: Object) => boolean,
41 + fn: ReactScopeQuery,
42 scopedNodes: Array<any>,
43 ): void {
44 if (enableScopeAPI) {
@@ -64,7 +65,7 @@ function collectScopedNodes(
65
66 function collectFirstScopedNode(
67 node: Fiber,
67 - fn: (type: string | Object, props: Object, instance: Object) => boolean,
68 + fn: ReactScopeQuery,
69 ): null | Object {
70 if (enableScopeAPI) {
71 if (node.tag === HostComponent) {
@@ -88,7 +89,7 @@ function collectFirstScopedNode(
89
90 function collectScopedNodesFromChildren(
91 startingChild: Fiber,
91 - fn: (type: string | Object, props: Object, instance: Object) => boolean,
92 + fn: ReactScopeQuery,
93 scopedNodes: Array<any>,
94 ): void {
95 let child = startingChild;
@@ -100,7 +101,7 @@ function collectScopedNodesFromChildren(
101
102 function collectFirstScopedNodeFromChildren(
103 startingChild: Fiber,
103 - fn: (type: string | Object, props: Object, instance: Object) => boolean,
104 + fn: ReactScopeQuery,
105 ): Object | null {
106 let child = startingChild;
107 while (child !== null) {
@@ -113,37 +114,6 @@ function collectFirstScopedNodeFromChildren(
114 return null;
115 }
116
116 -function collectNearestScopeMethods(
117 - node: Fiber,
118 - scope: ReactScope,
119 - childrenScopes: Array<ReactScopeMethods>,
120 -): void {
121 - if (isValidScopeNode(node, scope)) {
122 - childrenScopes.push(node.stateNode.methods);
123 - } else {
124 - let child = node.child;
125 -
126 - if (isFiberSuspenseAndTimedOut(node)) {
127 - child = getSuspenseFallbackChild(node);
128 - }
129 - if (child !== null) {
130 - collectNearestChildScopeMethods(child, scope, childrenScopes);
131 - }
132 - }
133 -}
134 -
135 -function collectNearestChildScopeMethods(
136 - startingChild: Fiber | null,
137 - scope: ReactScope,
138 - childrenScopes: Array<ReactScopeMethods>,
139 -): void {
140 - let child = startingChild;
141 - while (child !== null) {
142 - collectNearestScopeMethods(child, scope, childrenScopes);
143 - child = child.sibling;
144 - }
145 -}
146 -
117 function collectNearestContextValues<T>(
118 node: Fiber,
119 context: ReactContext<T>,
@@ -176,62 +146,12 @@ function collectNearestChildContextValues<T>(
146 }
147 }
148
179 -function isValidScopeNode(node, scope) {
180 - return (
181 - node.tag === ScopeComponent &&
182 - node.type === scope &&
183 - node.stateNode !== null
184 - );
185 -}
186 -
149 export function createScopeMethods(
150 scope: ReactScope,
151 instance: ReactScopeInstance,
152 ): ReactScopeMethods {
153 return {
192 - DO_NOT_USE_getChildren(): null | Array<ReactScopeMethods> {
193 - const currentFiber = ((instance.fiber: any): Fiber);
194 - const child = currentFiber.child;
195 - const childrenScopes = [];
196 - if (child !== null) {
197 - collectNearestChildScopeMethods(child, scope, childrenScopes);
198 - }
199 - return childrenScopes.length === 0 ? null : childrenScopes;
200 - },
201 - DO_NOT_USE_getChildrenFromRoot(): null | Array<ReactScopeMethods> {
202 - const currentFiber = ((instance.fiber: any): Fiber);
203 - let node = currentFiber;
204 - while (node !== null) {
205 - const parent = node.return;
206 - if (parent === null) {
207 - break;
208 - }
209 - node = parent;
210 - if (node.tag === ScopeComponent && node.type === scope) {
211 - break;
212 - }
213 - }
214 - const childrenScopes = [];
215 - collectNearestChildScopeMethods(node.child, scope, childrenScopes);
216 - return childrenScopes.length === 0 ? null : childrenScopes;
217 - },
218 - DO_NOT_USE_getParent(): null | ReactScopeMethods {
219 - let node = ((instance.fiber: any): Fiber).return;
220 - while (node !== null) {
221 - if (node.tag === ScopeComponent && node.type === scope) {
222 - return node.stateNode.methods;
223 - }
224 - node = node.return;
225 - }
226 - return null;
227 - },
228 - DO_NOT_USE_getProps(): Object {
229 - const currentFiber = ((instance.fiber: any): Fiber);
230 - return currentFiber.memoizedProps;
231 - },
232 - DO_NOT_USE_queryAllNodes(
233 - fn: (type: string | Object, props: Object, instance: Object) => boolean,
234 - ): null | Array<Object> {
154 + DO_NOT_USE_queryAllNodes(fn: ReactScopeQuery): null | Array<Object> {
155 const currentFiber = ((instance.fiber: any): Fiber);
156 const child = currentFiber.child;
157 const scopedNodes = [];
@@ -240,9 +160,7 @@ export function createScopeMethods(
160 }
161 return scopedNodes.length === 0 ? null : scopedNodes;
162 },
243 - DO_NOT_USE_queryFirstNode(
244 - fn: (type: string | Object, props: Object, instance: Object) => boolean,
245 - ): null | Object {
163 + DO_NOT_USE_queryFirstNode(fn: ReactScopeQuery): null | Object {
164 const currentFiber = ((instance.fiber: any): Fiber);
165 const child = currentFiber.child;
166 if (child !== null) {
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
-208
@@ -201,106 +201,6 @@ describe('ReactScope', () => {
201 expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
202 });
203
204 - it('mixed DO_NOT_USE_getParent() and queryAllNodes() works as intended', () => {
205 - const testScopeQuery = (type, props) => true;
206 - const TestScope = React.unstable_createScope();
207 - const TestScope2 = React.unstable_createScope();
208 - const refA = React.createRef();
209 - const refB = React.createRef();
210 - const refC = React.createRef();
211 - const refD = React.createRef();
212 - const spanA = React.createRef();
213 - const spanB = React.createRef();
214 - const divA = React.createRef();
215 - const divB = React.createRef();
216 -
217 - function Test() {
218 - return (
219 - <div>
220 - <TestScope ref={refA}>
221 - <span ref={spanA}>
222 - <TestScope2 ref={refB}>
223 - <div ref={divA}>
224 - <TestScope ref={refC}>
225 - <span ref={spanB}>
226 - <TestScope2 ref={refD}>
227 - <div ref={divB}>>Hello world</div>
228 - </TestScope2>
229 - </span>
230 - </TestScope>
231 - </div>
232 - </TestScope2>
233 - </span>
234 - </TestScope>
235 - </div>
236 - );
237 - }
238 -
239 - ReactDOM.render(<Test />, container);
240 - const dParent = refD.current.DO_NOT_USE_getParent();
241 - expect(dParent).not.toBe(null);
242 - expect(dParent.DO_NOT_USE_queryAllNodes(testScopeQuery)).toEqual([
243 - divA.current,
244 - spanB.current,
245 - divB.current,
246 - ]);
247 - const cParent = refC.current.DO_NOT_USE_getParent();
248 - expect(cParent).not.toBe(null);
249 - expect(cParent.DO_NOT_USE_queryAllNodes(testScopeQuery)).toEqual([
250 - spanA.current,
251 - divA.current,
252 - spanB.current,
253 - divB.current,
254 - ]);
255 - expect(refB.current.DO_NOT_USE_getParent()).toBe(null);
256 - expect(refA.current.DO_NOT_USE_getParent()).toBe(null);
257 - });
258 -
259 - it('DO_NOT_USE_getChildren() works as intended', () => {
260 - const TestScope = React.unstable_createScope();
261 - const TestScope2 = React.unstable_createScope();
262 - const refA = React.createRef();
263 - const refB = React.createRef();
264 - const refC = React.createRef();
265 - const refD = React.createRef();
266 - const spanA = React.createRef();
267 - const spanB = React.createRef();
268 - const divA = React.createRef();
269 - const divB = React.createRef();
270 -
271 - function Test() {
272 - return (
273 - <div>
274 - <TestScope ref={refA}>
275 - <span ref={spanA}>
276 - <TestScope2 ref={refB}>
277 - <div ref={divA}>
278 - <TestScope ref={refC}>
279 - <span ref={spanB}>
280 - <TestScope2 ref={refD}>
281 - <div ref={divB}>>Hello world</div>
282 - </TestScope2>
283 - </span>
284 - </TestScope>
285 - </div>
286 - </TestScope2>
287 - </span>
288 - </TestScope>
289 - </div>
290 - );
291 - }
292 -
293 - ReactDOM.render(<Test />, container);
294 - const dChildren = refD.current.DO_NOT_USE_getChildren();
295 - expect(dChildren).toBe(null);
296 - const cChildren = refC.current.DO_NOT_USE_getChildren();
297 - expect(cChildren).toBe(null);
298 - const bChildren = refB.current.DO_NOT_USE_getChildren();
299 - expect(bChildren).toEqual([refD.current]);
300 - const aChildren = refA.current.DO_NOT_USE_getChildren();
301 - expect(aChildren).toEqual([refC.current]);
302 - });
303 -
204 it('scopes support server-side rendering and hydration', () => {
205 const TestScope = React.unstable_createScope();
206 const ReactDOMServer = require('react-dom/server');
@@ -534,113 +434,5 @@ describe('ReactScope', () => {
434 renderer.update(<Test toggle={true} />);
435 expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
436 });
537 -
538 - it('mixed DO_NOT_USE_getParent() and DO_NOT_USE_queryAllNodes() works as intended', () => {
539 - const testScopeQuery = (type, props) => true;
540 - const TestScope = React.unstable_createScope();
541 - const TestScope2 = React.unstable_createScope();
542 - const refA = React.createRef();
543 - const refB = React.createRef();
544 - const refC = React.createRef();
545 - const refD = React.createRef();
546 - const spanA = React.createRef();
547 - const spanB = React.createRef();
548 - const divA = React.createRef();
549 - const divB = React.createRef();
550 -
551 - function Test() {
552 - return (
553 - <div>
554 - <TestScope ref={refA}>
555 - <span ref={spanA}>
556 - <TestScope2 ref={refB}>
557 - <div ref={divA}>
558 - <TestScope ref={refC}>
559 - <span ref={spanB}>
560 - <TestScope2 ref={refD}>
561 - <div ref={divB}>>Hello world</div>
562 - </TestScope2>
563 - </span>
564 - </TestScope>
565 - </div>
566 - </TestScope2>
567 - </span>
568 - </TestScope>
569 - </div>
570 - );
571 - }
572 -
573 - ReactTestRenderer.create(<Test />, {
574 - createNodeMock: element => {
575 - return element;
576 - },
577 - });
578 - const dParent = refD.current.DO_NOT_USE_getParent();
579 - expect(dParent).not.toBe(null);
580 - expect(dParent.DO_NOT_USE_queryAllNodes(testScopeQuery)).toEqual([
581 - divA.current,
582 - spanB.current,
583 - divB.current,
584 - ]);
585 - const cParent = refC.current.DO_NOT_USE_getParent();
586 - expect(cParent).not.toBe(null);
587 - expect(cParent.DO_NOT_USE_queryAllNodes(testScopeQuery)).toEqual([
588 - spanA.current,
589 - divA.current,
590 - spanB.current,
591 - divB.current,
592 - ]);
593 - expect(refB.current.DO_NOT_USE_getParent()).toBe(null);
594 - expect(refA.current.DO_NOT_USE_getParent()).toBe(null);
595 - });
596 -
597 - it('DO_NOT_USE_getChildren() works as intended', () => {
598 - const TestScope = React.unstable_createScope();
599 - const TestScope2 = React.unstable_createScope();
600 - const refA = React.createRef();
601 - const refB = React.createRef();
602 - const refC = React.createRef();
603 - const refD = React.createRef();
604 - const spanA = React.createRef();
605 - const spanB = React.createRef();
606 - const divA = React.createRef();
607 - const divB = React.createRef();
608 -
609 - function Test() {
610 - return (
611 - <div>
612 - <TestScope ref={refA}>
613 - <span ref={spanA}>
614 - <TestScope2 ref={refB}>
615 - <div ref={divA}>
616 - <TestScope ref={refC}>
617 - <span ref={spanB}>
618 - <TestScope2 ref={refD}>
619 - <div ref={divB}>>Hello world</div>
620 - </TestScope2>
621 - </span>
622 - </TestScope>
623 - </div>
624 - </TestScope2>
625 - </span>
626 - </TestScope>
627 - </div>
628 - );
629 - }
630 -
631 - ReactTestRenderer.create(<Test />, {
632 - createNodeMock: element => {
633 - return element;
634 - },
635 - });
636 - const dChildren = refD.current.DO_NOT_USE_getChildren();
637 - expect(dChildren).toBe(null);
638 - const cChildren = refC.current.DO_NOT_USE_getChildren();
639 - expect(cChildren).toBe(null);
640 - const bChildren = refB.current.DO_NOT_USE_getChildren();
641 - expect(bChildren).toEqual([refD.current]);
642 - const aChildren = refA.current.DO_NOT_USE_getChildren();
643 - expect(aChildren).toEqual([refC.current]);
644 - });
437 });
438 });
packages/shared/ReactTypes.js
+8 -10
@@ -170,17 +170,15 @@ export type ReactScope = {|
170 $$typeof: Symbol | number,
171 |};
172
173 +export type ReactScopeQuery = (
174 + type: string,
175 + props: {[string]: mixed, ...},
176 + instance: mixed,
177 +) => boolean;
178 +
179 export type ReactScopeMethods = {|
174 - DO_NOT_USE_getChildren(): null | Array<ReactScopeMethods>,
175 - DO_NOT_USE_getChildrenFromRoot(): null | Array<ReactScopeMethods>,
176 - DO_NOT_USE_getParent(): null | ReactScopeMethods,
177 - DO_NOT_USE_getProps(): Object,
178 - DO_NOT_USE_queryAllNodes(
179 - (type: string | Object, props: Object, instance: Object) => boolean,
180 - ): null | Array<Object>,
181 - DO_NOT_USE_queryFirstNode(
182 - (type: string | Object, props: Object, instance: Object) => boolean,
183 - ): null | Object,
180 + DO_NOT_USE_queryAllNodes(ReactScopeQuery): null | Array<Object>,
181 + DO_NOT_USE_queryFirstNode(ReactScopeQuery): null | Object,
182 containsNode(Object): boolean,
183 getChildContextValues: <T>(context: ReactContext<T>) => Array<T>,
184 |};