[react-core] Add experimental React Scope component API (#16587)
Dominic Gannaway committed
Aug 29, 2019 at 12:06 UTC
bd79be9b687156067416ffe5219e49a11bd0f1e7
22 files changed
+459
-94
packages/react-dom/src/events/DOMEventResponderSystem.js
+1
-85
@@ -12,7 +12,7 @@ import {
12
PASSIVE_NOT_SUPPORTED,
13
} from 'legacy-events/EventSystemFlags';
14
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
15
-import {HostComponent, SuspenseComponent} from 'shared/ReactWorkTags';
15
+import {HostComponent} from 'shared/ReactWorkTags';
16
import type {EventPriority} from 'shared/ReactTypes';
17
import type {
18
ReactDOMEventResponder,
@@ -238,28 +238,6 @@ const eventResponderContext: ReactDOMResponderContext = {
238
}
239
}
240
},
241
- getFocusableElementsInScope(deep: boolean): Array<HTMLElement> {
242
- validateResponderContext();
243
- const focusableElements = [];
244
- const eventResponderInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
245
- const currentResponder = eventResponderInstance.responder;
246
- let focusScopeFiber = eventResponderInstance.fiber;
247
- if (deep) {
248
- let deepNode = focusScopeFiber.return;
249
- while (deepNode !== null) {
250
- if (doesFiberHaveResponder(deepNode, currentResponder)) {
251
- focusScopeFiber = deepNode;
252
- }
253
- deepNode = deepNode.return;
254
- }
255
- }
256
- const child = focusScopeFiber.child;
257
-
258
- if (child !== null) {
259
- collectFocusableElements(child, focusableElements);
260
- }
261
- return focusableElements;
262
- },
241
getActiveDocument,
242
objectAssign: Object.assign,
243
getTimeStamp(): number {
@@ -335,33 +313,6 @@ function validateEventValue(eventValue: any): void {
313
}
314
}
315
338
-function collectFocusableElements(
339
- node: Fiber,
340
- focusableElements: Array<HTMLElement>,
341
-): void {
342
- if (isFiberSuspenseAndTimedOut(node)) {
343
- const fallbackChild = getSuspenseFallbackChild(node);
344
- if (fallbackChild !== null) {
345
- collectFocusableElements(fallbackChild, focusableElements);
346
- }
347
- } else {
348
- if (isFiberHostComponentFocusable(node)) {
349
- focusableElements.push(node.stateNode);
350
- } else {
351
- const child = node.child;
352
-
353
- if (child !== null) {
354
- collectFocusableElements(child, focusableElements);
355
- }
356
- }
357
- }
358
- const sibling = node.sibling;
359
-
360
- if (sibling !== null) {
361
- collectFocusableElements(sibling, focusableElements);
362
- }
363
-}
364
-
316
function doesFiberHaveResponder(
317
fiber: Fiber,
318
responder: ReactDOMEventResponder,
@@ -382,33 +333,6 @@ function getActiveDocument(): Document {
333
return ((currentDocument: any): Document);
334
}
335
385
-function isFiberHostComponentFocusable(fiber: Fiber): boolean {
386
- if (fiber.tag !== HostComponent) {
387
- return false;
388
- }
389
- const {type, memoizedProps} = fiber;
390
- if (memoizedProps.tabIndex === -1 || memoizedProps.disabled) {
391
- return false;
392
- }
393
- if (memoizedProps.tabIndex === 0 || memoizedProps.contentEditable === true) {
394
- return true;
395
- }
396
- if (type === 'a' || type === 'area') {
397
- return !!memoizedProps.href && memoizedProps.rel !== 'ignore';
398
- }
399
- if (type === 'input') {
400
- return memoizedProps.type !== 'hidden' && memoizedProps.type !== 'file';
401
- }
402
- return (
403
- type === 'button' ||
404
- type === 'textarea' ||
405
- type === 'object' ||
406
- type === 'select' ||
407
- type === 'iframe' ||
408
- type === 'embed'
409
- );
410
-}
411
-
336
function processTimers(
337
timers: Map<number, ResponderTimer>,
338
delay: number,
@@ -626,14 +550,6 @@ function validateResponderContext(): void {
550
);
551
}
552
629
-function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
630
- return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
631
-}
632
-
633
-function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
634
- return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
635
-}
636
-
553
export function dispatchEventForResponderEventSystem(
554
topLevelType: string,
555
targetFiber: null | Fiber,
packages/react-events/README.md
-5
@@ -92,11 +92,6 @@ const event = { type: 'press', target, pointerType, x, y };
92
context.dispatchEvent('onPress', event, DiscreteEvent);
93
```
94
95
-### getFocusableElementsInScope(): Array<Element>
96
-
97
-Returns every DOM element that can be focused within the scope of the Event
98
-Responder instance.
99
-
95
### isTargetWithinNode(target: Element, element: Element): boolean
96
97
Returns `true` if `target` is a child of `element`.
packages/react-reconciler/src/ReactFiber.js
+27
@@ -15,6 +15,7 @@ import type {
15
ReactEventResponder,
16
ReactEventResponderInstance,
17
ReactFundamentalComponent,
18
+ ReactScope,
19
} from 'shared/ReactTypes';
20
import type {RootTag} from 'shared/ReactRootTags';
21
import type {WorkTag} from 'shared/ReactWorkTags';
@@ -32,6 +33,7 @@ import {
33
enableProfilerTimer,
34
enableFundamentalAPI,
35
enableUserTimingAPI,
36
+ enableScopeAPI,
37
} from 'shared/ReactFeatureFlags';
38
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
39
import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
@@ -56,6 +58,7 @@ import {
58
SimpleMemoComponent,
59
LazyComponent,
60
FundamentalComponent,
61
+ ScopeComponent,
62
} from 'shared/ReactWorkTags';
63
import getComponentName from 'shared/getComponentName';
64
@@ -86,6 +89,7 @@ import {
89
REACT_MEMO_TYPE,
90
REACT_LAZY_TYPE,
91
REACT_FUNDAMENTAL_TYPE,
92
+ REACT_SCOPE_TYPE,
93
} from 'shared/ReactSymbols';
94
95
let hasBadMapPolyfill;
@@ -674,6 +678,16 @@ export function createFiberFromTypeAndProps(
678
);
679
}
680
break;
681
+ case REACT_SCOPE_TYPE:
682
+ if (enableScopeAPI) {
683
+ return createFiberFromScope(
684
+ type,
685
+ pendingProps,
686
+ mode,
687
+ expirationTime,
688
+ key,
689
+ );
690
+ }
691
}
692
}
693
let info = '';
@@ -766,6 +780,19 @@ export function createFiberFromFundamental(
780
return fiber;
781
}
782
783
+function createFiberFromScope(
784
+ scope: ReactScope,
785
+ pendingProps: any,
786
+ mode: TypeOfMode,
787
+ expirationTime: ExpirationTime,
788
+ key: null | string,
789
+) {
790
+ const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
791
+ fiber.type = scope;
792
+ fiber.expirationTime = expirationTime;
793
+ return fiber;
794
+}
795
+
796
function createFiberFromProfiler(
797
pendingProps: any,
798
mode: TypeOfMode,
packages/react-reconciler/src/ReactFiberBeginWork.js
+25
@@ -41,6 +41,7 @@ import {
41
LazyComponent,
42
IncompleteClassComponent,
43
FundamentalComponent,
44
+ ScopeComponent,
45
} from 'shared/ReactWorkTags';
46
import {
47
NoEffect,
@@ -63,6 +64,7 @@ import {
64
enableSuspenseServerRenderer,
65
enableFundamentalAPI,
66
warnAboutDefaultPropsOnFunctionComponents,
67
+ enableScopeAPI,
68
} from 'shared/ReactFeatureFlags';
69
import invariant from 'shared/invariant';
70
import shallowEqual from 'shared/shallowEqual';
@@ -2672,6 +2674,19 @@ function updateFundamentalComponent(
2674
return workInProgress.child;
2675
}
2676
2677
+function updateScopeComponent(current, workInProgress, renderExpirationTime) {
2678
+ const nextProps = workInProgress.pendingProps;
2679
+ const nextChildren = nextProps.children;
2680
+
2681
+ reconcileChildren(
2682
+ current,
2683
+ workInProgress,
2684
+ nextChildren,
2685
+ renderExpirationTime,
2686
+ );
2687
+ return workInProgress.child;
2688
+}
2689
+
2690
export function markWorkInProgressReceivedUpdate() {
2691
didReceiveUpdate = true;
2692
}
@@ -3158,6 +3173,16 @@ function beginWork(
3173
}
3174
break;
3175
}
3176
+ case ScopeComponent: {
3177
+ if (enableScopeAPI) {
3178
+ return updateScopeComponent(
3179
+ current,
3180
+ workInProgress,
3181
+ renderExpirationTime,
3182
+ );
3183
+ }
3184
+ break;
3185
+ }
3186
}
3187
invariant(
3188
false,
packages/react-reconciler/src/ReactFiberCommitWork.js
+14
@@ -32,6 +32,7 @@ import {
32
enableFlareAPI,
33
enableFundamentalAPI,
34
enableSuspenseCallback,
35
+ enableScopeAPI,
36
} from 'shared/ReactFeatureFlags';
37
import {
38
FunctionComponent,
@@ -49,6 +50,7 @@ import {
50
SimpleMemoComponent,
51
SuspenseListComponent,
52
FundamentalComponent,
53
+ ScopeComponent,
54
} from 'shared/ReactWorkTags';
55
import {
56
invokeGuardedCallback,
@@ -617,6 +619,7 @@ function commitLifeCycles(
619
case SuspenseListComponent:
620
case IncompleteClassComponent:
621
case FundamentalComponent:
622
+ case ScopeComponent:
623
return;
624
default: {
625
invariant(
@@ -691,6 +694,10 @@ function commitAttachRef(finishedWork: Fiber) {
694
default:
695
instanceToUse = instance;
696
}
697
+ // Moved outside to ensure DCE works with this flag
698
+ if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
699
+ instanceToUse = instance.methods;
700
+ }
701
if (typeof ref === 'function') {
702
ref(instanceToUse);
703
} else {
@@ -1383,6 +1390,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1390
}
1391
return;
1392
}
1393
+ case ScopeComponent: {
1394
+ if (enableScopeAPI) {
1395
+ const scopeInstance = finishedWork.stateNode;
1396
+ scopeInstance.fiber = finishedWork;
1397
+ }
1398
+ return;
1399
+ }
1400
default: {
1401
invariant(
1402
false,
packages/react-reconciler/src/ReactFiberCompleteWork.js
+32
-1
@@ -9,7 +9,10 @@
9
10
import type {Fiber} from './ReactFiber';
11
import type {ExpirationTime} from './ReactFiberExpirationTime';
12
-import type {ReactFundamentalComponentInstance} from 'shared/ReactTypes';
12
+import type {
13
+ ReactFundamentalComponentInstance,
14
+ ReactScopeInstance,
15
+} from 'shared/ReactTypes';
16
import type {FiberRoot} from './ReactFiberRoot';
17
import type {
18
Instance,
@@ -47,6 +50,7 @@ import {
50
LazyComponent,
51
IncompleteClassComponent,
52
FundamentalComponent,
53
+ ScopeComponent,
54
} from 'shared/ReactWorkTags';
55
import {NoMode, BatchedMode} from './ReactTypeOfMode';
56
import {
@@ -112,6 +116,7 @@ import {
116
enableSuspenseServerRenderer,
117
enableFlareAPI,
118
enableFundamentalAPI,
119
+ enableScopeAPI,
120
} from 'shared/ReactFeatureFlags';
121
import {
122
markSpawnedWork,
@@ -123,6 +128,7 @@ import {createFundamentalStateInstance} from './ReactFiberFundamental';
128
import {Never} from './ReactFiberExpirationTime';
129
import {resetChildFibers} from './ReactChildFiber';
130
import {updateEventListeners} from './ReactFiberEvents';
131
+import {createScopeMethods} from './ReactFiberScope';
132
133
function markUpdate(workInProgress: Fiber) {
134
// Tag the fiber with an update effect. This turns a Placement into
@@ -1213,6 +1219,31 @@ function completeWork(
1219
}
1220
break;
1221
}
1222
+ case ScopeComponent: {
1223
+ if (enableScopeAPI) {
1224
+ if (current === null) {
1225
+ const type = workInProgress.type;
1226
+ const scopeInstance: ReactScopeInstance = {
1227
+ fiber: workInProgress,
1228
+ methods: null,
1229
+ };
1230
+ workInProgress.stateNode = scopeInstance;
1231
+ scopeInstance.methods = createScopeMethods(type, scopeInstance);
1232
+ if (workInProgress.ref !== null) {
1233
+ markRef(workInProgress);
1234
+ markUpdate(workInProgress);
1235
+ }
1236
+ } else {
1237
+ if (current.ref !== workInProgress.ref) {
1238
+ markRef(workInProgress);
1239
+ }
1240
+ if (workInProgress.ref !== null) {
1241
+ markUpdate(workInProgress);
1242
+ }
1243
+ }
1244
+ }
1245
+ break;
1246
+ }
1247
default:
1248
invariant(
1249
false,
packages/react-reconciler/src/ReactFiberScope.js
new
+130
@@ -0,0 +1,130 @@
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 {Fiber} from './ReactFiber';
11
+import type {
12
+ ReactScope,
13
+ ReactScopeInstance,
14
+ ReactScopeMethods,
15
+} from 'shared/ReactTypes';
16
+
17
+import {
18
+ HostComponent,
19
+ SuspenseComponent,
20
+ ScopeComponent,
21
+} from 'shared/ReactWorkTags';
22
+
23
+function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
24
+ return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
25
+}
26
+
27
+function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
28
+ return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
29
+}
30
+
31
+function collectScopedNodes(
32
+ node: Fiber,
33
+ fn: (type: string | Object, props: Object) => boolean,
34
+ scopedNodes: Array<any>,
35
+): void {
36
+ if (node.tag === HostComponent) {
37
+ const {type, memoizedProps} = node;
38
+ if (fn(type, memoizedProps) === true) {
39
+ scopedNodes.push(node.stateNode);
40
+ }
41
+ }
42
+ let child = node.child;
43
+
44
+ if (isFiberSuspenseAndTimedOut(node)) {
45
+ child = getSuspenseFallbackChild(node);
46
+ }
47
+ if (child !== null) {
48
+ collectScopedNodesFromChildren(child, fn, scopedNodes);
49
+ }
50
+}
51
+
52
+function collectScopedNodesFromChildren(
53
+ startingChild: Fiber,
54
+ fn: (type: string | Object, props: Object) => boolean,
55
+ scopedNodes: Array<any>,
56
+): void {
57
+ let child = startingChild;
58
+ while (child !== null) {
59
+ collectScopedNodes(child, fn, scopedNodes);
60
+ child = child.sibling;
61
+ }
62
+}
63
+
64
+function collectNearestScopeMethods(
65
+ node: Fiber,
66
+ scope: ReactScope,
67
+ childrenScopes: Array<ReactScopeMethods>,
68
+): void {
69
+ if (node.tag === ScopeComponent && node.type === scope) {
70
+ childrenScopes.push(node.stateNode.methods);
71
+ } else {
72
+ let child = node.child;
73
+
74
+ if (isFiberSuspenseAndTimedOut(node)) {
75
+ child = getSuspenseFallbackChild(node);
76
+ }
77
+ if (child !== null) {
78
+ collectNearestChildScopeMethods(child, scope, childrenScopes);
79
+ }
80
+ }
81
+}
82
+
83
+function collectNearestChildScopeMethods(
84
+ startingChild: Fiber,
85
+ scope: ReactScope,
86
+ childrenScopes: Array<ReactScopeMethods>,
87
+): void {
88
+ let child = startingChild;
89
+ while (child !== null) {
90
+ collectNearestScopeMethods(child, scope, childrenScopes);
91
+ child = child.sibling;
92
+ }
93
+}
94
+
95
+export function createScopeMethods(
96
+ scope: ReactScope,
97
+ instance: ReactScopeInstance,
98
+): ReactScopeMethods {
99
+ const fn = scope.fn;
100
+ return {
101
+ getChildren(): null | Array<ReactScopeMethods> {
102
+ const currentFiber = ((instance.fiber: any): Fiber);
103
+ const child = currentFiber.child;
104
+ const childrenScopes = [];
105
+ if (child !== null) {
106
+ collectNearestChildScopeMethods(child, scope, childrenScopes);
107
+ }
108
+ return childrenScopes.length === 0 ? null : childrenScopes;
109
+ },
110
+ getParent(): null | ReactScopeMethods {
111
+ let node = ((instance.fiber: any): Fiber).return;
112
+ while (node !== null) {
113
+ if (node.tag === ScopeComponent && node.type === scope) {
114
+ return node.stateNode.methods;
115
+ }
116
+ node = node.return;
117
+ }
118
+ return null;
119
+ },
120
+ getScopedNodes(): null | Array<Object> {
121
+ const currentFiber = ((instance.fiber: any): Fiber);
122
+ const child = currentFiber.child;
123
+ const scopedNodes = [];
124
+ if (child !== null) {
125
+ collectScopedNodesFromChildren(child, fn, scopedNodes);
126
+ }
127
+ return scopedNodes.length === 0 ? null : scopedNodes;
128
+ },
129
+ };
130
+}
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
new
+168
@@ -0,0 +1,168 @@
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
+let React;
13
+let ReactFeatureFlags;
14
+
15
+describe('ReactScope', () => {
16
+ beforeEach(() => {
17
+ jest.resetModules();
18
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
19
+ ReactFeatureFlags.enableScopeAPI = true;
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('getScopedNodes() works as intended', () => {
39
+ const TestScope = React.unstable_createScope((type, props) => true);
40
+ const scopeRef = React.createRef();
41
+ const divRef = React.createRef();
42
+ const spanRef = React.createRef();
43
+ const aRef = React.createRef();
44
+
45
+ function Test({toggle}) {
46
+ return toggle ? (
47
+ <TestScope ref={scopeRef}>
48
+ <div ref={divRef}>DIV</div>
49
+ <span ref={spanRef}>SPAN</span>
50
+ <a ref={aRef}>A</a>
51
+ </TestScope>
52
+ ) : (
53
+ <TestScope ref={scopeRef}>
54
+ <a ref={aRef}>A</a>
55
+ <div ref={divRef}>DIV</div>
56
+ <span ref={spanRef}>SPAN</span>
57
+ </TestScope>
58
+ );
59
+ }
60
+
61
+ ReactDOM.render(<Test toggle={true} />, container);
62
+ let nodes = scopeRef.current.getScopedNodes();
63
+ expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
64
+ ReactDOM.render(<Test toggle={false} />, container);
65
+ nodes = scopeRef.current.getScopedNodes();
66
+ expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
67
+ });
68
+
69
+ it('mixed getParent() and getScopedNodes() works as intended', () => {
70
+ const TestScope = React.unstable_createScope((type, props) => true);
71
+ const TestScope2 = React.unstable_createScope((type, props) => true);
72
+ const refA = React.createRef();
73
+ const refB = React.createRef();
74
+ const refC = React.createRef();
75
+ const refD = React.createRef();
76
+ const spanA = React.createRef();
77
+ const spanB = React.createRef();
78
+ const divA = React.createRef();
79
+ const divB = React.createRef();
80
+
81
+ function Test() {
82
+ return (
83
+ <div>
84
+ <TestScope ref={refA}>
85
+ <span ref={spanA}>
86
+ <TestScope2 ref={refB}>
87
+ <div ref={divA}>
88
+ <TestScope ref={refC}>
89
+ <span ref={spanB}>
90
+ <TestScope2 ref={refD}>
91
+ <div ref={divB}>>Hello world</div>
92
+ </TestScope2>
93
+ </span>
94
+ </TestScope>
95
+ </div>
96
+ </TestScope2>
97
+ </span>
98
+ </TestScope>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ ReactDOM.render(<Test />, container);
104
+ const dParent = refD.current.getParent();
105
+ expect(dParent).not.toBe(null);
106
+ expect(dParent.getScopedNodes()).toEqual([
107
+ divA.current,
108
+ spanB.current,
109
+ divB.current,
110
+ ]);
111
+ const cParent = refC.current.getParent();
112
+ expect(cParent).not.toBe(null);
113
+ expect(cParent.getScopedNodes()).toEqual([
114
+ spanA.current,
115
+ divA.current,
116
+ spanB.current,
117
+ divB.current,
118
+ ]);
119
+ expect(refB.current.getParent()).toBe(null);
120
+ expect(refA.current.getParent()).toBe(null);
121
+ });
122
+
123
+ it('getChildren() works as intended', () => {
124
+ const TestScope = React.unstable_createScope((type, props) => true);
125
+ const TestScope2 = React.unstable_createScope((type, props) => true);
126
+ const refA = React.createRef();
127
+ const refB = React.createRef();
128
+ const refC = React.createRef();
129
+ const refD = React.createRef();
130
+ const spanA = React.createRef();
131
+ const spanB = React.createRef();
132
+ const divA = React.createRef();
133
+ const divB = React.createRef();
134
+
135
+ function Test() {
136
+ return (
137
+ <div>
138
+ <TestScope ref={refA}>
139
+ <span ref={spanA}>
140
+ <TestScope2 ref={refB}>
141
+ <div ref={divA}>
142
+ <TestScope ref={refC}>
143
+ <span ref={spanB}>
144
+ <TestScope2 ref={refD}>
145
+ <div ref={divB}>>Hello world</div>
146
+ </TestScope2>
147
+ </span>
148
+ </TestScope>
149
+ </div>
150
+ </TestScope2>
151
+ </span>
152
+ </TestScope>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ ReactDOM.render(<Test />, container);
158
+ const dChildren = refD.current.getChildren();
159
+ expect(dChildren).toBe(null);
160
+ const cChildren = refC.current.getChildren();
161
+ expect(cChildren).toBe(null);
162
+ const bChildren = refB.current.getChildren();
163
+ expect(bChildren).toEqual([refD.current]);
164
+ const aChildren = refA.current.getChildren();
165
+ expect(aChildren).toEqual([refC.current]);
166
+ });
167
+ });
168
+});
packages/react/src/React.js
+6
@@ -53,10 +53,12 @@ import {
53
import ReactSharedInternals from './ReactSharedInternals';
54
import createFundamental from 'shared/createFundamentalComponent';
55
import createResponder from 'shared/createEventResponder';
56
+import createScope from 'shared/createScope';
57
import {
58
enableJSXTransformAPI,
59
enableFlareAPI,
60
enableFundamentalAPI,
61
+ enableScopeAPI,
62
} from 'shared/ReactFeatureFlags';
63
const React = {
64
Children: {
@@ -114,6 +116,10 @@ if (enableFundamentalAPI) {
116
React.unstable_createFundamental = createFundamental;
117
}
118
119
+if (enableScopeAPI) {
120
+ React.unstable_createScope = createScope;
121
+}
122
+
123
// Note: some APIs are added with feature flags.
124
// Make sure that stable builds for open source
125
// don't modify the React object to avoid deopts.
packages/shared/ReactDOMTypes.js
-1
@@ -66,7 +66,6 @@ export type ReactDOMResponderContext = {
66
removeRootEventTypes: (rootEventTypes: Array<string>) => void,
67
setTimeout: (func: () => void, timeout: number) => number,
68
clearTimeout: (timerId: number) => void,
69
- getFocusableElementsInScope(deep: boolean): Array<HTMLElement>,
69
getActiveDocument(): Document,
70
objectAssign: Function,
71
getTimeStamp: () => number,
packages/shared/ReactFeatureFlags.js
+3
@@ -65,6 +65,9 @@ export const enableFlareAPI = false;
65
// Experimental Host Component support.
66
export const enableFundamentalAPI = false;
67
68
+// Experimental Scope support.
69
+export const enableScopeAPI = false;
70
+
71
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
72
export const enableJSXTransformAPI = false;
73
packages/shared/ReactSymbols.js
+1
@@ -57,6 +57,7 @@ export const REACT_FUNDAMENTAL_TYPE = hasSymbol
57
export const REACT_RESPONDER_TYPE = hasSymbol
58
? Symbol.for('react.responder')
59
: 0xead6;
60
+export const REACT_SCOPE_TYPE = hasSymbol ? Symbol.for('react.scope') : 0xead7;
61
62
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
63
const FAUX_ITERATOR_SYMBOL = '@@iterator';
packages/shared/ReactTypes.js
+16
@@ -159,3 +159,19 @@ export type ReactFundamentalComponent<C, H> = {|
159
$$typeof: Symbol | number,
160
impl: ReactFundamentalImpl<C, H>,
161
|};
162
+
163
+export type ReactScope = {|
164
+ $$typeof: Symbol | number,
165
+ fn: (type: string | Object, props: Object) => boolean,
166
+|};
167
+
168
+export type ReactScopeMethods = {|
169
+ getChildren(): null | Array<ReactScopeMethods>,
170
+ getParent(): null | ReactScopeMethods,
171
+ getScopedNodes(): null | Array<Object>,
172
+|};
173
+
174
+export type ReactScopeInstance = {|
175
+ fiber: Object,
176
+ methods: null | ReactScopeMethods,
177
+|};
packages/shared/ReactWorkTags.js
+3
-1
@@ -28,7 +28,8 @@ export type WorkTag =
28
| 17
29
| 18
30
| 19
31
- | 20;
31
+ | 20
32
+ | 21;
33
34
export const FunctionComponent = 0;
35
export const ClassComponent = 1;
@@ -51,3 +52,4 @@ export const IncompleteClassComponent = 17;
52
export const DehydratedFragment = 18;
53
export const SuspenseListComponent = 19;
54
export const FundamentalComponent = 20;
55
+export const ScopeComponent = 21;
packages/shared/createScope.js
new
+23
@@ -0,0 +1,23 @@
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
+ * @flow
7
+ */
8
+
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 {
15
+ const scopeComponent = {
16
+ $$typeof: REACT_SCOPE_TYPE,
17
+ fn,
18
+ };
19
+ if (__DEV__) {
20
+ Object.freeze(scopeComponent);
21
+ }
22
+ return scopeComponent;
23
+}
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -33,6 +33,7 @@ export const warnAboutDeprecatedLifecycles = true;
33
export const warnAboutDeprecatedSetNativeProps = true;
34
export const enableFlareAPI = false;
35
export const enableFundamentalAPI = false;
36
+export const enableScopeAPI = false;
37
export const enableJSXTransformAPI = false;
38
export const warnAboutUnmockedScheduler = true;
39
export const flushSuspenseFallbacksInTests = true;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const enableFlareAPI = false;
30
export const enableFundamentalAPI = false;
31
+export const enableScopeAPI = false;
32
export const enableJSXTransformAPI = false;
33
export const warnAboutUnmockedScheduler = false;
34
export const flushSuspenseFallbacksInTests = true;
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const enableFlareAPI = false;
30
export const enableFundamentalAPI = false;
31
+export const enableScopeAPI = false;
32
export const enableJSXTransformAPI = false;
33
export const warnAboutUnmockedScheduler = true;
34
export const flushSuspenseFallbacksInTests = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const enableFlareAPI = false;
30
export const enableFundamentalAPI = false;
31
+export const enableScopeAPI = false;
32
export const enableJSXTransformAPI = false;
33
export const warnAboutUnmockedScheduler = false;
34
export const flushSuspenseFallbacksInTests = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -26,6 +26,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
26
export const disableJavaScriptURLs = false;
27
export const enableFlareAPI = true;
28
export const enableFundamentalAPI = false;
29
+export const enableScopeAPI = false;
30
export const enableJSXTransformAPI = true;
31
export const warnAboutUnmockedScheduler = true;
32
export const flushSuspenseFallbacksInTests = true;
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -73,6 +73,8 @@ export const enableFlareAPI = true;
73
74
export const enableFundamentalAPI = false;
75
76
+export const enableScopeAPI = false;
77
+
78
export const enableJSXTransformAPI = true;
79
80
export const warnAboutUnmockedScheduler = true;
packages/shared/isValidElementType.js
+3
-1
@@ -21,6 +21,7 @@ import {
21
REACT_LAZY_TYPE,
22
REACT_FUNDAMENTAL_TYPE,
23
REACT_RESPONDER_TYPE,
24
+ REACT_SCOPE_TYPE,
25
} from 'shared/ReactSymbols';
26
27
export default function isValidElementType(type: mixed) {
@@ -42,6 +43,7 @@ export default function isValidElementType(type: mixed) {
43
type.$$typeof === REACT_CONTEXT_TYPE ||
44
type.$$typeof === REACT_FORWARD_REF_TYPE ||
45
type.$$typeof === REACT_FUNDAMENTAL_TYPE ||
45
- type.$$typeof === REACT_RESPONDER_TYPE))
46
+ type.$$typeof === REACT_RESPONDER_TYPE ||
47
+ type.$$typeof === REACT_SCOPE_TYPE))
48
);
49
}