Skip to content

Commit bd79be9

Browse files
authored
[react-core] Add experimental React Scope component API (#16587)
1 parent 996acf9 commit bd79be9

22 files changed

+459
-94
lines changed

packages/react-dom/src/events/DOMEventResponderSystem.js

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
PASSIVE_NOT_SUPPORTED,
1313
} from 'legacy-events/EventSystemFlags';
1414
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
15-
import {HostComponent, SuspenseComponent} from 'shared/ReactWorkTags';
15+
import {HostComponent} from 'shared/ReactWorkTags';
1616
import type {EventPriority} from 'shared/ReactTypes';
1717
import type {
1818
ReactDOMEventResponder,
@@ -238,28 +238,6 @@ const eventResponderContext: ReactDOMResponderContext = {
238238
}
239239
}
240240
},
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-
},
263241
getActiveDocument,
264242
objectAssign: Object.assign,
265243
getTimeStamp(): number {
@@ -335,33 +313,6 @@ function validateEventValue(eventValue: any): void {
335313
}
336314
}
337315

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-
365316
function doesFiberHaveResponder(
366317
fiber: Fiber,
367318
responder: ReactDOMEventResponder,
@@ -382,33 +333,6 @@ function getActiveDocument(): Document {
382333
return ((currentDocument: any): Document);
383334
}
384335

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-
412336
function processTimers(
413337
timers: Map<number, ResponderTimer>,
414338
delay: number,
@@ -626,14 +550,6 @@ function validateResponderContext(): void {
626550
);
627551
}
628552

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-
637553
export function dispatchEventForResponderEventSystem(
638554
topLevelType: string,
639555
targetFiber: null | Fiber,

packages/react-events/README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,6 @@ const event = { type: 'press', target, pointerType, x, y };
9292
context.dispatchEvent('onPress', event, DiscreteEvent);
9393
```
9494

95-
### getFocusableElementsInScope(): Array<Element>
96-
97-
Returns every DOM element that can be focused within the scope of the Event
98-
Responder instance.
99-
10095
### isTargetWithinNode(target: Element, element: Element): boolean
10196

10297
Returns `true` if `target` is a child of `element`.

packages/react-reconciler/src/ReactFiber.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
ReactEventResponder,
1616
ReactEventResponderInstance,
1717
ReactFundamentalComponent,
18+
ReactScope,
1819
} from 'shared/ReactTypes';
1920
import type {RootTag} from 'shared/ReactRootTags';
2021
import type {WorkTag} from 'shared/ReactWorkTags';
@@ -32,6 +33,7 @@ import {
3233
enableProfilerTimer,
3334
enableFundamentalAPI,
3435
enableUserTimingAPI,
36+
enableScopeAPI,
3537
} from 'shared/ReactFeatureFlags';
3638
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
3739
import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
@@ -56,6 +58,7 @@ import {
5658
SimpleMemoComponent,
5759
LazyComponent,
5860
FundamentalComponent,
61+
ScopeComponent,
5962
} from 'shared/ReactWorkTags';
6063
import getComponentName from 'shared/getComponentName';
6164

@@ -86,6 +89,7 @@ import {
8689
REACT_MEMO_TYPE,
8790
REACT_LAZY_TYPE,
8891
REACT_FUNDAMENTAL_TYPE,
92+
REACT_SCOPE_TYPE,
8993
} from 'shared/ReactSymbols';
9094

9195
let hasBadMapPolyfill;
@@ -674,6 +678,16 @@ export function createFiberFromTypeAndProps(
674678
);
675679
}
676680
break;
681+
case REACT_SCOPE_TYPE:
682+
if (enableScopeAPI) {
683+
return createFiberFromScope(
684+
type,
685+
pendingProps,
686+
mode,
687+
expirationTime,
688+
key,
689+
);
690+
}
677691
}
678692
}
679693
let info = '';
@@ -766,6 +780,19 @@ export function createFiberFromFundamental(
766780
return fiber;
767781
}
768782

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+
769796
function createFiberFromProfiler(
770797
pendingProps: any,
771798
mode: TypeOfMode,

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
LazyComponent,
4242
IncompleteClassComponent,
4343
FundamentalComponent,
44+
ScopeComponent,
4445
} from 'shared/ReactWorkTags';
4546
import {
4647
NoEffect,
@@ -63,6 +64,7 @@ import {
6364
enableSuspenseServerRenderer,
6465
enableFundamentalAPI,
6566
warnAboutDefaultPropsOnFunctionComponents,
67+
enableScopeAPI,
6668
} from 'shared/ReactFeatureFlags';
6769
import invariant from 'shared/invariant';
6870
import shallowEqual from 'shared/shallowEqual';
@@ -2672,6 +2674,19 @@ function updateFundamentalComponent(
26722674
return workInProgress.child;
26732675
}
26742676

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+
26752690
export function markWorkInProgressReceivedUpdate() {
26762691
didReceiveUpdate = true;
26772692
}
@@ -3158,6 +3173,16 @@ function beginWork(
31583173
}
31593174
break;
31603175
}
3176+
case ScopeComponent: {
3177+
if (enableScopeAPI) {
3178+
return updateScopeComponent(
3179+
current,
3180+
workInProgress,
3181+
renderExpirationTime,
3182+
);
3183+
}
3184+
break;
3185+
}
31613186
}
31623187
invariant(
31633188
false,

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
enableFlareAPI,
3333
enableFundamentalAPI,
3434
enableSuspenseCallback,
35+
enableScopeAPI,
3536
} from 'shared/ReactFeatureFlags';
3637
import {
3738
FunctionComponent,
@@ -49,6 +50,7 @@ import {
4950
SimpleMemoComponent,
5051
SuspenseListComponent,
5152
FundamentalComponent,
53+
ScopeComponent,
5254
} from 'shared/ReactWorkTags';
5355
import {
5456
invokeGuardedCallback,
@@ -617,6 +619,7 @@ function commitLifeCycles(
617619
case SuspenseListComponent:
618620
case IncompleteClassComponent:
619621
case FundamentalComponent:
622+
case ScopeComponent:
620623
return;
621624
default: {
622625
invariant(
@@ -691,6 +694,10 @@ function commitAttachRef(finishedWork: Fiber) {
691694
default:
692695
instanceToUse = instance;
693696
}
697+
// Moved outside to ensure DCE works with this flag
698+
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
699+
instanceToUse = instance.methods;
700+
}
694701
if (typeof ref === 'function') {
695702
ref(instanceToUse);
696703
} else {
@@ -1383,6 +1390,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
13831390
}
13841391
return;
13851392
}
1393+
case ScopeComponent: {
1394+
if (enableScopeAPI) {
1395+
const scopeInstance = finishedWork.stateNode;
1396+
scopeInstance.fiber = finishedWork;
1397+
}
1398+
return;
1399+
}
13861400
default: {
13871401
invariant(
13881402
false,

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99

1010
import type {Fiber} from './ReactFiber';
1111
import type {ExpirationTime} from './ReactFiberExpirationTime';
12-
import type {ReactFundamentalComponentInstance} from 'shared/ReactTypes';
12+
import type {
13+
ReactFundamentalComponentInstance,
14+
ReactScopeInstance,
15+
} from 'shared/ReactTypes';
1316
import type {FiberRoot} from './ReactFiberRoot';
1417
import type {
1518
Instance,
@@ -47,6 +50,7 @@ import {
4750
LazyComponent,
4851
IncompleteClassComponent,
4952
FundamentalComponent,
53+
ScopeComponent,
5054
} from 'shared/ReactWorkTags';
5155
import {NoMode, BatchedMode} from './ReactTypeOfMode';
5256
import {
@@ -112,6 +116,7 @@ import {
112116
enableSuspenseServerRenderer,
113117
enableFlareAPI,
114118
enableFundamentalAPI,
119+
enableScopeAPI,
115120
} from 'shared/ReactFeatureFlags';
116121
import {
117122
markSpawnedWork,
@@ -123,6 +128,7 @@ import {createFundamentalStateInstance} from './ReactFiberFundamental';
123128
import {Never} from './ReactFiberExpirationTime';
124129
import {resetChildFibers} from './ReactChildFiber';
125130
import {updateEventListeners} from './ReactFiberEvents';
131+
import {createScopeMethods} from './ReactFiberScope';
126132

127133
function markUpdate(workInProgress: Fiber) {
128134
// Tag the fiber with an update effect. This turns a Placement into
@@ -1213,6 +1219,31 @@ function completeWork(
12131219
}
12141220
break;
12151221
}
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+
}
12161247
default:
12171248
invariant(
12181249
false,

0 commit comments

Comments
 (0)