[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> = {|