@samitouri / QOS-React-2 / commits / 539640d89f

[react-events] Various core tweaks for event responder system (#16654)

Dominic Gannaway committed Sep 4, 2019 at 18:05 UTC 539640d89f327743f2872098ec8aad08ab3a4dfd
17 files changed +181 -89
packages/react-dom/src/events/DOMEventResponderSystem.js
+47 -18
@@ -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} from 'shared/ReactWorkTags';
15 +import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags';
16 import type {EventPriority} from 'shared/ReactTypes';
17 import type {
18 ReactDOMEventResponder,
@@ -66,6 +66,7 @@ type ResponderTimer = {|
66 instance: ReactDOMEventResponderInstance,
67 func: () => void,
68 id: number,
69 + targetFiber: Fiber | null,
70 timeStamp: number,
71 |};
72
@@ -80,6 +81,7 @@ let currentTimers = new Map();
81 let currentInstance: null | ReactDOMEventResponderInstance = null;
82 let currentTimerIDCounter = 0;
83 let currentDocument: null | Document = null;
84 +let currentTargetFiber: null | Fiber = null;
85
86 const eventResponderContext: ReactDOMResponderContext = {
87 dispatchEvent(
@@ -158,16 +160,20 @@ const eventResponderContext: ReactDOMResponderContext = {
160 validateResponderContext();
161 const childFiber = getClosestInstanceFromNode(childTarget);
162 const parentFiber = getClosestInstanceFromNode(parentTarget);
161 - const parentAlternateFiber = parentFiber.alternate;
163
163 - let node = childFiber;
164 - while (node !== null) {
165 - if (node === parentFiber || node === parentAlternateFiber) {
166 - return true;
164 + if (childFiber != null && parentFiber != null) {
165 + const parentAlternateFiber = parentFiber.alternate;
166 + let node = childFiber;
167 + while (node !== null) {
168 + if (node === parentFiber || node === parentAlternateFiber) {
169 + return true;
170 + }
171 + node = node.return;
172 }
168 - node = node.return;
173 + return false;
174 }
170 - return false;
175 + // Fallback to DOM APIs
176 + return parentTarget.contains(childTarget);
177 },
178 addRootEventTypes(rootEventTypes: Array<string>): void {
179 validateResponderContext();
@@ -221,6 +227,7 @@ const eventResponderContext: ReactDOMResponderContext = {
227 instance: ((currentInstance: any): ReactDOMEventResponderInstance),
228 func,
229 id: timerId,
230 + targetFiber: currentTargetFiber,
231 timeStamp: currentTimeStamp,
232 });
233 activeTimeouts.set(timerId, timeout);
@@ -260,6 +267,24 @@ const eventResponderContext: ReactDOMResponderContext = {
267 return false;
268 },
269 enqueueStateRestore,
270 + getCurrentTarget(): Element | null {
271 + validateResponderContext();
272 + const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
273 + .fiber;
274 + let fiber = currentTargetFiber;
275 + let currentTarget = null;
276 +
277 + while (fiber !== null) {
278 + if (fiber.tag === HostComponent) {
279 + currentTarget = fiber.stateNode;
280 + }
281 + if (fiber === responderFiber || fiber.alternate === responderFiber) {
282 + break;
283 + }
284 + fiber = fiber.return;
285 + }
286 + return currentTarget;
287 + },
288 };
289
290 function validateEventValue(eventValue: any): void {
@@ -317,7 +342,8 @@ function doesFiberHaveResponder(
342 fiber: Fiber,
343 responder: ReactDOMEventResponder,
344 ): boolean {
320 - if (fiber.tag === HostComponent) {
345 + const tag = fiber.tag;
346 + if (tag === HostComponent || tag === ScopeComponent) {
347 const dependencies = fiber.dependencies;
348 if (dependencies !== null) {
349 const respondersMap = dependencies.responders;
@@ -341,8 +367,9 @@ function processTimers(
367 try {
368 batchedEventUpdates(() => {
369 for (let i = 0; i < timersArr.length; i++) {
344 - const {instance, func, id, timeStamp} = timersArr[i];
370 + const {instance, func, id, timeStamp, targetFiber} = timersArr[i];
371 currentInstance = instance;
372 + currentTargetFiber = targetFiber;
373 currentTimeStamp = timeStamp + delay;
374 try {
375 func();
@@ -355,6 +382,7 @@ function processTimers(
382 currentTimers = null;
383 currentInstance = null;
384 currentTimeStamp = 0;
385 + currentTargetFiber = null;
386 }
387 }
388
@@ -386,7 +414,6 @@ function createDOMResponderEvent(
414 passiveSupported,
415 pointerId,
416 pointerType: eventPointerType,
389 - responderTarget: null,
417 target: nativeEventTarget,
418 type: topLevelType,
419 };
@@ -443,13 +470,16 @@ function traverseAndHandleEventResponderInstances(
470 let node = targetFiber;
471 while (node !== null) {
472 const {dependencies, tag} = node;
446 - if (tag === HostComponent && dependencies !== null) {
473 + if (
474 + (tag === HostComponent || tag === ScopeComponent) &&
475 + dependencies !== null
476 + ) {
477 const respondersMap = dependencies.responders;
478 if (respondersMap !== null) {
479 const responderInstances = Array.from(respondersMap.values());
480 for (let i = 0, length = responderInstances.length; i < length; i++) {
481 const responderInstance = responderInstances[i];
452 - const {props, responder, state, target} = responderInstance;
482 + const {props, responder, state} = responderInstance;
483 if (
484 !visitedResponders.has(responder) &&
485 validateResponderTargetEventTypes(eventType, responder)
@@ -458,9 +488,6 @@ function traverseAndHandleEventResponderInstances(
488 const onEvent = responder.onEvent;
489 if (onEvent !== null) {
490 currentInstance = responderInstance;
461 - responderEvent.responderTarget = ((target: any):
462 - | Element
463 - | Document);
491 onEvent(responderEvent, eventResponderContext, props, state);
492 }
493 }
@@ -478,11 +505,10 @@ function traverseAndHandleEventResponderInstances(
505
506 for (let i = 0; i < responderInstances.length; i++) {
507 const responderInstance = responderInstances[i];
481 - const {props, responder, state, target} = responderInstance;
508 + const {props, responder, state} = responderInstance;
509 const onRootEvent = responder.onRootEvent;
510 if (onRootEvent !== null) {
511 currentInstance = responderInstance;
485 - responderEvent.responderTarget = ((target: any): Element | Document);
512 onRootEvent(responderEvent, eventResponderContext, props, state);
513 }
514 }
@@ -562,7 +588,9 @@ export function dispatchEventForResponderEventSystem(
588 const previousTimers = currentTimers;
589 const previousTimeStamp = currentTimeStamp;
590 const previousDocument = currentDocument;
591 + const previousTargetFiber = currentTargetFiber;
592 currentTimers = null;
593 + currentTargetFiber = targetFiber;
594 // nodeType 9 is DOCUMENT_NODE
595 currentDocument =
596 (nativeEventTarget: any).nodeType === 9
@@ -585,6 +613,7 @@ export function dispatchEventForResponderEventSystem(
613 currentInstance = previousInstance;
614 currentTimeStamp = previousTimeStamp;
615 currentDocument = previousDocument;
616 + currentTargetFiber = previousTargetFiber;
617 }
618 }
619 }
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+1 -1
@@ -1017,7 +1017,7 @@ describe('DOMEventResponderSystem', () => {
1017 const obj = {
1018 counter,
1019 timeStamp: context.getTimeStamp(),
1020 - target: event.responderTarget,
1020 + target: context.getCurrentTarget(),
1021 type: 'click-test',
1022 };
1023 context.dispatchEvent(obj, props.onClick, DiscreteEvent);
packages/react-events/README.md
-1
@@ -22,7 +22,6 @@ type ResponderEventType = string;
22
23 type ResponderEvent = {|
24 nativeEvent: any,
25 - responderTarget: Element | Document,
25 target: Element | Document,
26 pointerType: string,
27 type: string,
packages/react-events/src/dom/Focus.js
+2 -2
@@ -289,7 +289,7 @@ const focusResponderImpl = {
289
290 switch (type) {
291 case 'focus': {
292 - state.focusTarget = event.responderTarget;
292 + state.focusTarget = context.getCurrentTarget();
293 // Limit focus events to the direct child of the event component.
294 // Browser focus is not expected to bubble.
295 if (!state.isFocused && state.focusTarget === target) {
@@ -427,7 +427,7 @@ const focusWithinResponderImpl = {
427
428 switch (type) {
429 case 'focus': {
430 - state.focusTarget = event.responderTarget;
430 + state.focusTarget = context.getCurrentTarget();
431 // Limit focus events to the direct child of the event component.
432 // Browser focus is not expected to bubble.
433 if (!state.isFocused) {
packages/react-events/src/dom/Hover.js
+2 -2
@@ -235,7 +235,7 @@ const hoverResponderImpl = {
235 // START
236 case 'pointerover': {
237 if (!state.isHovered && pointerType !== 'touch') {
238 - state.hoverTarget = event.responderTarget;
238 + state.hoverTarget = context.getCurrentTarget();
239 dispatchHoverStartEvents(event, context, props, state);
240 }
241 break;
@@ -295,7 +295,7 @@ const hoverResponderFallbackImpl = {
295 // START
296 case 'mouseover': {
297 if (!state.isHovered && !state.ignoreEmulatedMouseEvents) {
298 - state.hoverTarget = event.responderTarget;
298 + state.hoverTarget = context.getCurrentTarget();
299 dispatchHoverStartEvents(event, context, props, state);
300 }
301 break;
packages/react-events/src/dom/Input.js
+6 -5
@@ -179,30 +179,31 @@ const inputResponderImpl = {
179 context: ReactDOMResponderContext,
180 props: InputResponderProps,
181 ): void {
182 - const {responderTarget, type, target} = event;
182 + const {type, target} = event;
183
184 if (props.disabled) {
185 return;
186 }
187 - if (target !== responderTarget || responderTarget === null) {
187 + const currentTarget = context.getCurrentTarget();
188 + if (target !== currentTarget || currentTarget === null) {
189 return;
190 }
191 switch (type) {
192 default: {
193 if (shouldUseChangeEvent(target) && type === 'change') {
193 - dispatchBothChangeEvents(event, context, props, responderTarget);
194 + dispatchBothChangeEvents(event, context, props, currentTarget);
195 } else if (
196 isTextInputElement(target) &&
197 (type === 'input' || type === 'change') &&
198 updateValueIfChanged(target)
199 ) {
199 - dispatchBothChangeEvents(event, context, props, responderTarget);
200 + dispatchBothChangeEvents(event, context, props, currentTarget);
201 } else if (
202 isCheckable(target) &&
203 type === 'click' &&
204 updateValueIfChanged(target)
205 ) {
205 - dispatchBothChangeEvents(event, context, props, responderTarget);
206 + dispatchBothChangeEvents(event, context, props, currentTarget);
207 }
208 break;
209 }
packages/react-events/src/dom/Keyboard.js
+2 -6
@@ -131,7 +131,6 @@ function createKeyboardEvent(
131 event: ReactDOMResponderEvent,
132 context: ReactDOMResponderContext,
133 type: KeyboardEventType,
134 - target: Document | Element,
134 defaultPrevented: boolean,
135 ): KeyboardEvent {
136 const nativeEvent = (event: any).nativeEvent;
@@ -144,6 +143,7 @@ function createKeyboardEvent(
143 repeat,
144 shiftKey,
145 } = nativeEvent;
146 + const target = ((context.getCurrentTarget(): any): Element);
147
148 return {
149 altKey,
@@ -166,14 +166,12 @@ function dispatchKeyboardEvent(
166 listener: KeyboardEvent => void,
167 context: ReactDOMResponderContext,
168 type: KeyboardEventType,
169 - target: Element | Document,
169 defaultPrevented: boolean,
170 ): void {
171 const syntheticEvent = createKeyboardEvent(
172 event,
173 context,
174 type,
176 - target,
175 defaultPrevented,
176 );
177 context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
@@ -186,7 +184,7 @@ const keyboardResponderImpl = {
184 context: ReactDOMResponderContext,
185 props: KeyboardProps,
186 ): void {
189 - const {responderTarget, type} = event;
187 + const {type} = event;
188 const nativeEvent: any = event.nativeEvent;
189
190 if (props.disabled) {
@@ -227,7 +225,6 @@ const keyboardResponderImpl = {
225 onKeyDown,
226 context,
227 'keydown',
230 - ((responderTarget: any): Element | Document),
228 defaultPrevented,
229 );
230 }
@@ -239,7 +236,6 @@ const keyboardResponderImpl = {
236 onKeyUp,
237 context,
238 'keyup',
242 - ((responderTarget: any): Element | Document),
239 defaultPrevented,
240 );
241 }
packages/react-events/src/dom/Press.js
+2 -2
@@ -582,7 +582,7 @@ const pressResponderImpl = {
582 // We set these here, before the button check so we have this
583 // data around for handling of the context menu
584 state.pointerType = pointerType;
585 - const pressTarget = (state.pressTarget = event.responderTarget);
585 + const pressTarget = (state.pressTarget = context.getCurrentTarget());
586 if (isPointerEvent) {
587 state.activePointerId = pointerId;
588 } else if (isTouchEvent) {
@@ -634,7 +634,7 @@ const pressResponderImpl = {
634
635 if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
636 state.pointerType = 'keyboard';
637 - state.pressTarget = event.responderTarget;
637 + state.pressTarget = context.getCurrentTarget();
638 const preventDefault = props.preventDefault;
639
640 if (preventDefault !== false) {
packages/react-events/src/rn/Press.js
+1 -1
@@ -412,7 +412,7 @@ const pressResponderImpl = {
412 if (type === 'topTouchStart') {
413 if (!state.isPressed) {
414 state.pointerType = 'touch';
415 - const pressTarget = (state.pressTarget = event.responderTarget);
415 + const pressTarget = (state.pressTarget = context.getCurrentTarget());
416 const touchEvent = getTouchFromPressEvent(nativeEvent);
417 if (touchEvent === null) {
418 return;
packages/react-native-renderer/src/ReactFabricEventResponderSystem.js
+34 -8
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import {HostComponent} from 'shared/ReactWorkTags';
10 +import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags';
11 import type {Fiber} from 'react-reconciler/src/ReactFiber';
12 import {
13 batchedEventUpdates,
@@ -52,6 +52,7 @@ type ResponderTimer = {|
52 instance: ReactNativeEventResponderInstance,
53 func: () => void,
54 id: number,
55 + targetFiber: Fiber | null,
56 timeStamp: number,
57 |};
58
@@ -77,6 +78,7 @@ let currentTimeStamp = 0;
78 let currentTimers = new Map();
79 let currentInstance: null | ReactNativeEventResponderInstance = null;
80 let currentTimerIDCounter = 0;
81 +let currentTargetFiber: Fiber | null = null;
82
83 const eventResponderContext: ReactNativeResponderContext = {
84 dispatchEvent(
@@ -196,6 +198,7 @@ const eventResponderContext: ReactNativeResponderContext = {
198 instance: ((currentInstance: any): ReactNativeEventResponderInstance),
199 func,
200 id: timerId,
201 + targetFiber: currentTargetFiber,
202 timeStamp: currentTimeStamp,
203 });
204 activeTimeouts.set(timerId, timeout);
@@ -217,6 +220,24 @@ const eventResponderContext: ReactNativeResponderContext = {
220 validateResponderContext();
221 return currentTimeStamp;
222 },
223 + getCurrentTarget(): ReactNativeEventTarget | null {
224 + validateResponderContext();
225 + const responderFiber = ((currentInstance: any): ReactNativeEventResponderInstance)
226 + .fiber;
227 + let fiber = currentTargetFiber;
228 + let currentTarget = null;
229 +
230 + while (fiber !== null) {
231 + if (fiber.tag === HostComponent) {
232 + currentTarget = fiber.stateNode;
233 + }
234 + if (fiber === responderFiber || fiber.alternate === responderFiber) {
235 + break;
236 + }
237 + fiber = fiber.return;
238 + }
239 + return currentTarget;
240 + },
241 };
242
243 function validateEventValue(eventValue: any): void {
@@ -287,8 +308,9 @@ function processTimers(
308 try {
309 batchedEventUpdates(() => {
310 for (let i = 0; i < timersArr.length; i++) {
290 - const {instance, func, id, timeStamp} = timersArr[i];
311 + const {instance, func, id, targetFiber, timeStamp} = timersArr[i];
312 currentInstance = instance;
313 + currentTargetFiber = targetFiber;
314 currentTimeStamp = timeStamp + delay;
315 try {
316 func();
@@ -301,6 +323,7 @@ function processTimers(
323 currentTimers = null;
324 currentInstance = null;
325 currentTimeStamp = 0;
326 + currentTargetFiber = null;
327 }
328 }
329
@@ -311,7 +334,6 @@ function createFabricResponderEvent(
334 ): ReactNativeResponderEvent {
335 return {
336 nativeEvent,
314 - responderTarget: target,
337 target,
338 type: topLevelType,
339 };
@@ -373,13 +395,16 @@ function traverseAndHandleEventResponderInstances(
395 let node = targetFiber;
396 while (node !== null) {
397 const {dependencies, tag} = node;
376 - if (tag === HostComponent && dependencies !== null) {
398 + if (
399 + (tag === HostComponent || tag === ScopeComponent) &&
400 + dependencies !== null
401 + ) {
402 const respondersMap = dependencies.responders;
403 if (respondersMap !== null) {
404 const responderInstances = Array.from(respondersMap.values());
405 for (let i = 0, length = responderInstances.length; i < length; i++) {
406 const responderInstance = responderInstances[i];
382 - const {props, responder, state, target} = responderInstance;
407 + const {props, responder, state} = responderInstance;
408 if (
409 !visitedResponders.has(responder) &&
410 validateResponderTargetEventTypes(eventType, responder)
@@ -388,7 +413,6 @@ function traverseAndHandleEventResponderInstances(
413 visitedResponders.add(responder);
414 if (onEvent !== null) {
415 currentInstance = responderInstance;
391 - responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
416 onEvent(responderEvent, eventResponderContext, props, state);
417 }
418 }
@@ -406,11 +430,10 @@ function traverseAndHandleEventResponderInstances(
430
431 for (let i = 0; i < responderInstances.length; i++) {
432 const responderInstance = responderInstances[i];
409 - const {props, responder, state, target} = responderInstance;
433 + const {props, responder, state} = responderInstance;
434 const onRootEvent = responder.onRootEvent;
435 if (onRootEvent !== null) {
436 currentInstance = responderInstance;
413 - responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
437 onRootEvent(responderEvent, eventResponderContext, props, state);
438 }
439 }
@@ -427,7 +450,9 @@ export function dispatchEventForResponderEventSystem(
450 const previousInstance = currentInstance;
451 const previousTimers = currentTimers;
452 const previousTimeStamp = currentTimeStamp;
453 + const previousTargetFiber = currentTargetFiber;
454 currentTimers = null;
455 + currentTargetFiber = targetFiber;
456 // We might want to control timeStamp another way here
457 currentTimeStamp = Date.now();
458 try {
@@ -442,6 +467,7 @@ export function dispatchEventForResponderEventSystem(
467 currentTimers = previousTimers;
468 currentInstance = previousInstance;
469 currentTimeStamp = previousTimeStamp;
470 + currentTargetFiber = previousTargetFiber;
471 }
472 }
473
packages/react-native-renderer/src/ReactNativeTypes.js
+1 -1
@@ -191,7 +191,6 @@ export type ReactFaricEvent = {
191
192 export type ReactNativeResponderEvent = {
193 nativeEvent: ReactFaricEvent,
194 - responderTarget: null | ReactNativeEventTarget,
194 target: null | ReactNativeEventTarget,
195 type: string,
196 };
@@ -220,6 +219,7 @@ export type ReactNativeResponderContext = {
219 setTimeout: (func: () => void, timeout: number) => number,
220 clearTimeout: (timerId: number) => void,
221 getTimeStamp: () => number,
222 + getCurrentTarget(): ReactNativeEventTarget | null,
223 };
224
225 export type PointerType =
packages/react-reconciler/src/ReactFiberCommitWork.js
+10 -1
@@ -1343,7 +1343,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1343 const prevListeners = oldProps.listeners;
1344 const nextListeners = newProps.listeners;
1345 if (prevListeners !== nextListeners) {
1346 - updateEventListeners(nextListeners, instance, finishedWork);
1346 + updateEventListeners(nextListeners, finishedWork);
1347 }
1348 }
1349 }
@@ -1394,6 +1394,15 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1394 if (enableScopeAPI) {
1395 const scopeInstance = finishedWork.stateNode;
1396 scopeInstance.fiber = finishedWork;
1397 + if (enableFlareAPI) {
1398 + const newProps = finishedWork.memoizedProps;
1399 + const oldProps = current !== null ? current.memoizedProps : newProps;
1400 + const prevListeners = oldProps.listeners;
1401 + const nextListeners = newProps.listeners;
1402 + if (prevListeners !== nextListeners) {
1403 + updateEventListeners(nextListeners, finishedWork);
1404 + }
1405 + }
1406 }
1407 return;
1408 }
packages/react-reconciler/src/ReactFiberCompleteWork.js
+25 -7
@@ -722,10 +722,9 @@ function completeWork(
722 markUpdate(workInProgress);
723 }
724 if (enableFlareAPI) {
725 - const instance = workInProgress.stateNode;
725 const listeners = newProps.listeners;
726 if (listeners != null) {
728 - updateEventListeners(listeners, instance, workInProgress);
727 + updateEventListeners(listeners, workInProgress);
728 }
729 }
730 } else {
@@ -739,10 +738,13 @@ function completeWork(
738
739 appendAllChildren(instance, workInProgress, false, false);
740
741 + // This needs to be set before we mount Flare event listeners
742 + workInProgress.stateNode = instance;
743 +
744 if (enableFlareAPI) {
745 const listeners = newProps.listeners;
746 if (listeners != null) {
745 - updateEventListeners(listeners, instance, workInProgress);
747 + updateEventListeners(listeners, workInProgress);
748 }
749 }
750
@@ -760,7 +762,6 @@ function completeWork(
762 ) {
763 markUpdate(workInProgress);
764 }
763 - workInProgress.stateNode = instance;
765 }
766
767 if (workInProgress.ref !== null) {
@@ -1229,17 +1230,34 @@ function completeWork(
1230 };
1231 workInProgress.stateNode = scopeInstance;
1232 scopeInstance.methods = createScopeMethods(type, scopeInstance);
1233 + if (enableFlareAPI) {
1234 + const listeners = newProps.listeners;
1235 + if (listeners != null) {
1236 + updateEventListeners(listeners, workInProgress);
1237 + }
1238 + }
1239 if (workInProgress.ref !== null) {
1240 markRef(workInProgress);
1241 markUpdate(workInProgress);
1242 }
1243 } else {
1244 + if (enableFlareAPI) {
1245 + const prevListeners = current.memoizedProps.listeners;
1246 + const nextListeners = newProps.listeners;
1247 + if (
1248 + prevListeners !== nextListeners ||
1249 + workInProgress.ref !== null
1250 + ) {
1251 + markUpdate(workInProgress);
1252 + }
1253 + } else {
1254 + if (workInProgress.ref !== null) {
1255 + markUpdate(workInProgress);
1256 + }
1257 + }
1258 if (current.ref !== workInProgress.ref) {
1259 markRef(workInProgress);
1260 }
1240 - if (workInProgress.ref !== null) {
1241 - markUpdate(workInProgress);
1242 - }
1261 }
1262 }
1263 break;
packages/react-reconciler/src/ReactFiberEvents.js
+19 -32
@@ -25,6 +25,7 @@ import warning from 'shared/warning';
25 import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
26
27 import invariant from 'shared/invariant';
28 +import {HostComponent, HostRoot} from 'shared/ReactWorkTags';
29
30 const emptyObject = {};
31 const isArray = Array.isArray;
@@ -33,7 +34,6 @@ export function createResponderInstance(
34 responder: ReactEventResponder<any, any>,
35 responderProps: Object,
36 responderState: Object,
36 - target: Instance,
37 fiber: Fiber,
38 ): ReactEventResponderInstance<any, any> {
39 return {
@@ -42,14 +42,12 @@ export function createResponderInstance(
42 responder,
43 rootEventTypes: null,
44 state: responderState,
45 - target,
45 };
46 }
47
48 function mountEventResponder(
49 responder: ReactEventResponder<any, any>,
50 responderProps: Object,
52 - instance: Instance,
51 fiber: Fiber,
52 respondersMap: Map<
53 ReactEventResponder<any, any>,
@@ -65,15 +63,27 @@ function mountEventResponder(
63 responder,
64 responderProps,
65 responderState,
68 - instance,
66 fiber,
67 );
68 + let instance = null;
69 + let node = fiber;
70 + while (node !== null) {
71 + const tag = node.tag;
72 + if (tag === HostComponent) {
73 + instance = node.stateNode;
74 + break;
75 + } else if (tag === HostRoot) {
76 + instance = node.stateNode.containerInfo;
77 + break;
78 + }
79 + node = node.return;
80 + }
81 mountResponderInstance(
82 responder,
83 responderInstance,
84 responderProps,
85 responderState,
76 - instance,
86 + ((instance: any): Instance),
87 );
88 respondersMap.set(responder, responderInstance);
89 }
@@ -86,7 +96,6 @@ function updateEventListener(
96 ReactEventResponder<any, any>,
97 ReactEventResponderInstance<any, any>,
98 >,
89 - instance: Instance,
99 ): void {
100 let responder;
101 let props;
@@ -118,13 +127,7 @@ function updateEventListener(
127
128 if (responderInstance === undefined) {
129 // Mount (happens in either complete or commit phase)
121 - mountEventResponder(
122 - responder,
123 - listenerProps,
124 - instance,
125 - fiber,
126 - respondersMap,
127 - );
130 + mountEventResponder(responder, listenerProps, fiber, respondersMap);
131 } else {
132 // Update (happens during commit phase only)
133 responderInstance.props = listenerProps;
@@ -132,11 +135,7 @@ function updateEventListener(
135 }
136 }
137
135 -export function updateEventListeners(
136 - listeners: any,
137 - instance: Instance,
138 - fiber: Fiber,
139 -): void {
138 +export function updateEventListeners(listeners: any, fiber: Fiber): void {
139 const visistedResponders = new Set();
140 let dependencies = fiber.dependencies;
141 if (listeners != null) {
@@ -154,22 +153,10 @@ export function updateEventListeners(
153 if (isArray(listeners)) {
154 for (let i = 0, length = listeners.length; i < length; i++) {
155 const listener = listeners[i];
157 - updateEventListener(
158 - listener,
159 - fiber,
160 - visistedResponders,
161 - respondersMap,
162 - instance,
163 - );
156 + updateEventListener(listener, fiber, visistedResponders, respondersMap);
157 }
158 } else {
166 - updateEventListener(
167 - listeners,
168 - fiber,
169 - visistedResponders,
170 - respondersMap,
171 - instance,
172 - );
159 + updateEventListener(listeners, fiber, visistedResponders, respondersMap);
160 }
161 }
162 if (dependencies !== null) {
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+28
@@ -9,6 +9,8 @@
9
10 'use strict';
11
12 +import {createEventTarget} from 'react-events/src/dom/testing-library';
13 +
14 let React;
15 let ReactFeatureFlags;
16
@@ -17,6 +19,7 @@ describe('ReactScope', () => {
19 jest.resetModules();
20 ReactFeatureFlags = require('shared/ReactFeatureFlags');
21 ReactFeatureFlags.enableScopeAPI = true;
22 + ReactFeatureFlags.enableFlareAPI = true;
23 React = require('react');
24 });
25
@@ -194,6 +197,31 @@ describe('ReactScope', () => {
197 const nodes = scopeRef.current.getScopedNodes();
198 expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
199 });
200 +
201 + it('event responders can be attached to scopes', () => {
202 + const TestScope = React.unstable_createScope((type, props) => true);
203 + const onKeyDown = jest.fn();
204 + const ref = React.createRef();
205 + const useKeyboard = require('react-events/keyboard').useKeyboard;
206 + const Component = () => {
207 + const listener = useKeyboard({
208 + onKeyDown,
209 + });
210 + return (
211 + <TestScope listeners={listener}>
212 + <div ref={ref} />
213 + </TestScope>
214 + );
215 + };
216 + ReactDOM.render(<Component />, container);
217 +
218 + const target = createEventTarget(ref.current);
219 + target.keydown({key: 'Q'});
220 + expect(onKeyDown).toHaveBeenCalledTimes(1);
221 + expect(onKeyDown).toHaveBeenCalledWith(
222 + expect.objectContaining({key: 'Q', type: 'keydown'}),
223 + );
224 + });
225 });
226
227 describe('ReactTestRenderer', () => {
packages/shared/ReactDOMTypes.js
+1 -1
@@ -30,7 +30,6 @@ export type ReactDOMResponderEvent = {
30 passiveSupported: boolean,
31 pointerId: null | number,
32 pointerType: PointerType,
33 - responderTarget: null | Element | Document,
33 target: Element | Document,
34 type: string,
35 };
@@ -75,4 +74,5 @@ export type ReactDOMResponderContext = {
74 ) => boolean,
75 // Used for controller components
76 enqueueStateRestore(Element | Document): void,
77 + getCurrentTarget(): Element | null,
78 };
packages/shared/ReactTypes.js
-1
@@ -85,7 +85,6 @@ export type ReactEventResponderInstance<E, C> = {|
85 responder: ReactEventResponder<E, C>,
86 rootEventTypes: null | Set<string>,
87 state: Object,
88 - target: mixed,
88 |};
89
90 export type ReactEventResponderListener<E, C> = {|