Experimental event API: rework the propagation system for event components (#15462)
Dominic Gannaway committed
Apr 23, 2019 at 11:50 UTC
784ebd8fa919bbca41c2a2f5a3eda5be6961f32c
9 files changed
+342
-434
packages/react-dom/src/events/DOMEventResponderSystem.js
+48
-64
@@ -18,6 +18,7 @@ import {
18
HostComponent,
19
} from 'shared/ReactWorkTags';
20
import type {
21
+ ReactEventResponder,
22
ReactEventResponderEventType,
23
ReactEventComponentInstance,
24
ReactResponderContext,
@@ -61,13 +62,10 @@ type ResponderTimeout = {|
62
63
type ResponderTimer = {|
64
instance: ReactEventComponentInstance,
64
- func: () => boolean,
65
+ func: () => void,
66
id: Symbol,
67
|};
68
68
-const ROOT_PHASE = 0;
69
-const BUBBLE_PHASE = 1;
70
-const CAPTURE_PHASE = 2;
69
const activeTimeouts: Map<Symbol, ResponderTimeout> = new Map();
70
const rootEventTypesToEventComponentInstances: Map<
71
DOMTopLevelEventType | string,
@@ -253,7 +251,7 @@ const eventResponderContext: ReactResponderContext = {
251
triggerOwnershipListeners();
252
return false;
253
},
256
- setTimeout(func: () => boolean, delay): Symbol {
254
+ setTimeout(func: () => void, delay): Symbol {
255
validateResponderContext();
256
if (currentTimers === null) {
257
currentTimers = new Map();
@@ -349,16 +347,13 @@ const eventResponderContext: ReactResponderContext = {
347
348
function processTimers(timers: Map<Symbol, ResponderTimer>): void {
349
const timersArr = Array.from(timers.values());
352
- let shouldStopPropagation = false;
350
currentEventQueue = createEventQueue();
351
try {
352
for (let i = 0; i < timersArr.length; i++) {
353
const {instance, func, id} = timersArr[i];
354
currentInstance = instance;
355
try {
359
- if (!shouldStopPropagation) {
360
- shouldStopPropagation = func();
361
- }
356
+ func();
357
} finally {
358
activeTimeouts.delete(id);
359
}
@@ -390,7 +385,6 @@ function createResponderEvent(
385
nativeEvent: AnyNativeEvent,
386
nativeEventTarget: Element | Document,
387
eventSystemFlags: EventSystemFlags,
393
- phase: 0 | 1 | 2,
388
): ReactResponderEvent {
389
const responderEvent = {
390
nativeEvent: nativeEvent,
@@ -398,7 +392,6 @@ function createResponderEvent(
392
type: topLevelType,
393
passive: (eventSystemFlags & IS_PASSIVE) !== 0,
394
passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0,
401
- phase,
395
};
396
if (__DEV__) {
397
Object.freeze(responderEvent);
@@ -510,16 +503,7 @@ function getRootEventResponderInstances(
503
return eventResponderInstances;
504
}
505
513
-function triggerEventResponderEventListener(
514
- responderEvent: ReactResponderEvent,
515
- eventComponentInstance: ReactEventComponentInstance,
516
-): boolean {
517
- const {responder, props, state} = eventComponentInstance;
518
- currentInstance = eventComponentInstance;
519
- return responder.onEvent(responderEvent, eventResponderContext, props, state);
520
-}
521
-
522
-function traverseAndTriggerEventResponderInstances(
506
+function traverseAndHandleEventResponderInstances(
507
topLevelType: DOMTopLevelEventType,
508
targetFiber: null | Fiber,
509
nativeEvent: AnyNativeEvent,
@@ -535,46 +519,54 @@ function traverseAndTriggerEventResponderInstances(
519
topLevelType,
520
targetFiber,
521
);
522
+ const responderEvent = createResponderEvent(
523
+ ((topLevelType: any): string),
524
+ nativeEvent,
525
+ ((nativeEventTarget: any): Element | Document),
526
+ eventSystemFlags,
527
+ );
528
+ const propagatedEventResponders: Set<ReactEventResponder> = new Set();
529
let length = targetEventResponderInstances.length;
530
let i;
540
- let shouldStopPropagation = false;
541
- let responderEvent;
531
532
+ // Captured and bubbled event phases have the notion of local propagation.
533
+ // This means that the propgation chain can be stopped part of the the way
534
+ // through processing event component instances. The major difference to other
535
+ // events systems is that the stopping of propgation is localized to a single
536
+ // phase, rather than both phases.
537
if (length > 0) {
538
// Capture target phase
545
- responderEvent = createResponderEvent(
546
- ((topLevelType: any): string),
547
- nativeEvent,
548
- ((nativeEventTarget: any): Element | Document),
549
- eventSystemFlags,
550
- CAPTURE_PHASE,
551
- );
539
for (i = length; i-- > 0; ) {
540
const targetEventResponderInstance = targetEventResponderInstances[i];
554
- shouldStopPropagation = triggerEventResponderEventListener(
555
- responderEvent,
556
- targetEventResponderInstance,
557
- );
558
- if (shouldStopPropagation) {
559
- return;
541
+ const {responder, props, state} = targetEventResponderInstance;
542
+ if (responder.stopLocalPropagation) {
543
+ if (propagatedEventResponders.has(responder)) {
544
+ continue;
545
+ }
546
+ propagatedEventResponders.add(responder);
547
+ }
548
+ const eventListener = responder.onEventCapture;
549
+ if (eventListener !== undefined) {
550
+ currentInstance = targetEventResponderInstance;
551
+ eventListener(responderEvent, eventResponderContext, props, state);
552
}
553
}
554
+ // We clean propagated event responders between phases.
555
+ propagatedEventResponders.clear();
556
// Bubble target phase
563
- responderEvent = createResponderEvent(
564
- ((topLevelType: any): string),
565
- nativeEvent,
566
- ((nativeEventTarget: any): Element | Document),
567
- eventSystemFlags,
568
- BUBBLE_PHASE,
569
- );
557
for (i = 0; i < length; i++) {
558
const targetEventResponderInstance = targetEventResponderInstances[i];
572
- shouldStopPropagation = triggerEventResponderEventListener(
573
- responderEvent,
574
- targetEventResponderInstance,
575
- );
576
- if (shouldStopPropagation) {
577
- return;
559
+ const {responder, props, state} = targetEventResponderInstance;
560
+ if (responder.stopLocalPropagation) {
561
+ if (propagatedEventResponders.has(responder)) {
562
+ continue;
563
+ }
564
+ propagatedEventResponders.add(responder);
565
+ }
566
+ const eventListener = responder.onEvent;
567
+ if (eventListener !== undefined) {
568
+ currentInstance = targetEventResponderInstance;
569
+ eventListener(responderEvent, eventResponderContext, props, state);
570
}
571
}
572
}
@@ -584,21 +576,13 @@ function traverseAndTriggerEventResponderInstances(
576
);
577
length = rootEventResponderInstances.length;
578
if (length > 0) {
587
- responderEvent = createResponderEvent(
588
- ((topLevelType: any): string),
589
- nativeEvent,
590
- ((nativeEventTarget: any): Element | Document),
591
- eventSystemFlags,
592
- ROOT_PHASE,
593
- );
579
for (i = 0; i < length; i++) {
595
- const targetEventResponderInstance = rootEventResponderInstances[i];
596
- shouldStopPropagation = triggerEventResponderEventListener(
597
- responderEvent,
598
- targetEventResponderInstance,
599
- );
600
- if (shouldStopPropagation) {
601
- return;
580
+ const rootEventResponderInstance = rootEventResponderInstances[i];
581
+ const {responder, props, state} = rootEventResponderInstance;
582
+ const eventListener = responder.onRootEvent;
583
+ if (eventListener !== undefined) {
584
+ currentInstance = rootEventResponderInstance;
585
+ eventListener(responderEvent, eventResponderContext, props, state);
586
}
587
}
588
}
@@ -672,7 +656,7 @@ export function dispatchEventForResponderEventSystem(
656
if (enableEventAPI) {
657
currentEventQueue = createEventQueue();
658
try {
675
- traverseAndTriggerEventResponderInstances(
659
+ traverseAndHandleEventResponderInstances(
660
topLevelType,
661
targetFiber,
662
nativeEvent,
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+154
-166
@@ -18,15 +18,21 @@ function createReactEventComponent(
18
targetEventTypes,
19
createInitialState,
20
onEvent,
21
+ onEventCapture,
22
+ onRootEvent,
23
onUnmount,
24
onOwnershipChange,
25
+ stopLocalPropagation,
26
) {
27
const testEventResponder = {
28
targetEventTypes,
29
createInitialState,
30
onEvent,
31
+ onEventCapture,
32
+ onRootEvent,
33
onUnmount,
34
onOwnershipChange,
35
+ stopLocalPropagation: stopLocalPropagation || false,
36
};
37
38
return {
@@ -37,18 +43,6 @@ function createReactEventComponent(
43
};
44
}
45
40
-const ROOT_PHASE = 0;
41
-const BUBBLE_PHASE = 1;
42
-const CAPTURE_PHASE = 2;
43
-
44
-function phaseToString(phase) {
45
- return phase === ROOT_PHASE
46
- ? 'root'
47
- : phase === BUBBLE_PHASE
48
- ? 'bubble'
49
- : 'capture';
50
-}
51
-
46
function dispatchEvent(element, type) {
47
const event = document.createEvent('Event');
48
event.initEvent(type, true, true);
@@ -88,7 +82,7 @@ describe('DOMEventResponderSystem', () => {
82
container = null;
83
});
84
91
- it('the event responder onEvent() function should fire on click event', () => {
85
+ it('the event responder event listeners should fire on click event', () => {
86
let eventResponderFiredCount = 0;
87
let eventLog = [];
88
const buttonRef = React.createRef();
@@ -102,7 +96,16 @@ describe('DOMEventResponderSystem', () => {
96
name: event.type,
97
passive: event.passive,
98
passiveSupported: event.passiveSupported,
105
- phase: event.phase,
99
+ phase: 'bubble',
100
+ });
101
+ },
102
+ (event, context, props) => {
103
+ eventResponderFiredCount++;
104
+ eventLog.push({
105
+ name: event.type,
106
+ passive: event.passive,
107
+ passiveSupported: event.passiveSupported,
108
+ phase: 'capture',
109
});
110
},
111
);
@@ -127,13 +130,13 @@ describe('DOMEventResponderSystem', () => {
130
name: 'click',
131
passive: false,
132
passiveSupported: false,
130
- phase: CAPTURE_PHASE,
133
+ phase: 'capture',
134
},
135
{
136
name: 'click',
137
passive: false,
138
passiveSupported: false,
136
- phase: BUBBLE_PHASE,
139
+ phase: 'bubble',
140
},
141
]);
142
@@ -149,7 +152,7 @@ describe('DOMEventResponderSystem', () => {
152
expect(eventResponderFiredCount).toBe(4);
153
});
154
152
- it('the event responder onEvent() function should fire on click event (passive events forced)', () => {
155
+ it('the event responder event listeners should fire on click event (passive events forced)', () => {
156
// JSDOM does not support passive events, so this manually overrides the value to be true
157
const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
158
checkPassiveEvents.passiveBrowserEventsSupported = true;
@@ -165,7 +168,15 @@ describe('DOMEventResponderSystem', () => {
168
name: event.type,
169
passive: event.passive,
170
passiveSupported: event.passiveSupported,
168
- phase: event.phase,
171
+ phase: 'bubble',
172
+ });
173
+ },
174
+ (event, context, props) => {
175
+ eventLog.push({
176
+ name: event.type,
177
+ passive: event.passive,
178
+ passiveSupported: event.passiveSupported,
179
+ phase: 'capture',
180
});
181
},
182
);
@@ -187,18 +198,18 @@ describe('DOMEventResponderSystem', () => {
198
name: 'click',
199
passive: true,
200
passiveSupported: true,
190
- phase: CAPTURE_PHASE,
201
+ phase: 'capture',
202
},
203
{
204
name: 'click',
205
passive: true,
206
passiveSupported: true,
196
- phase: BUBBLE_PHASE,
207
+ phase: 'bubble',
208
},
209
]);
210
});
211
201
- it('nested event responders and their onEvent() function should fire multiple times', () => {
212
+ it('nested event responders and their event listeners should fire multiple times', () => {
213
let eventResponderFiredCount = 0;
214
let eventLog = [];
215
const buttonRef = React.createRef();
@@ -212,7 +223,16 @@ describe('DOMEventResponderSystem', () => {
223
name: event.type,
224
passive: event.passive,
225
passiveSupported: event.passiveSupported,
215
- phase: event.phase,
226
+ phase: 'bubble',
227
+ });
228
+ },
229
+ (event, context, props) => {
230
+ eventResponderFiredCount++;
231
+ eventLog.push({
232
+ name: event.type,
233
+ passive: event.passive,
234
+ passiveSupported: event.passiveSupported,
235
+ phase: 'capture',
236
});
237
},
238
);
@@ -238,30 +258,30 @@ describe('DOMEventResponderSystem', () => {
258
name: 'click',
259
passive: false,
260
passiveSupported: false,
241
- phase: CAPTURE_PHASE,
261
+ phase: 'capture',
262
},
263
{
264
name: 'click',
265
passive: false,
266
passiveSupported: false,
247
- phase: CAPTURE_PHASE,
267
+ phase: 'capture',
268
},
269
{
270
name: 'click',
271
passive: false,
272
passiveSupported: false,
253
- phase: BUBBLE_PHASE,
273
+ phase: 'bubble',
274
},
275
{
276
name: 'click',
277
passive: false,
278
passiveSupported: false,
259
- phase: BUBBLE_PHASE,
279
+ phase: 'bubble',
280
},
281
]);
282
});
283
264
- it('nested event responders and their onEvent() should fire in the correct order', () => {
284
+ it('nested event responders and their event listeners should fire in the correct order', () => {
285
let eventLog = [];
286
const buttonRef = React.createRef();
287
@@ -269,7 +289,10 @@ describe('DOMEventResponderSystem', () => {
289
['click'],
290
undefined,
291
(event, context, props) => {
272
- eventLog.push(`A [${phaseToString(event.phase)}]`);
292
+ eventLog.push(`A [bubble]`);
293
+ },
294
+ (event, context, props) => {
295
+ eventLog.push(`A [capture]`);
296
},
297
);
298
@@ -277,7 +300,10 @@ describe('DOMEventResponderSystem', () => {
300
['click'],
301
undefined,
302
(event, context, props) => {
280
- eventLog.push(`B [${phaseToString(event.phase)}]`);
303
+ eventLog.push(`B [bubble]`);
304
+ },
305
+ (event, context, props) => {
306
+ eventLog.push(`B [capture]`);
307
},
308
);
309
@@ -303,140 +329,81 @@ describe('DOMEventResponderSystem', () => {
329
]);
330
});
331
306
- it('nested event responders and their onEvent() should fire in the correct order with stopPropagation', () => {
307
- let eventLog;
308
- let stopPropagationOnPhase;
332
+ it('nested event responders should fire in the correct order without stopLocalPropagation', () => {
333
+ let eventLog = [];
334
const buttonRef = React.createRef();
335
311
- const ClickEventComponentA = createReactEventComponent(
336
+ const ClickEventComponent = createReactEventComponent(
337
['click'],
338
undefined,
339
(event, context, props) => {
315
- context.addRootEventTypes(event.target.ownerDocument, [
316
- 'click',
317
- 'pointermove',
318
- ]);
319
- eventLog.push(`A [${event.type}, ${phaseToString(event.phase)}]`);
340
+ eventLog.push(`${props.name} [bubble]`);
341
},
321
- );
322
-
323
- const ClickEventComponentB = createReactEventComponent(
324
- ['click'],
325
- undefined,
342
(event, context, props) => {
327
- context.addRootEventTypes(event.target.ownerDocument, [
328
- 'click',
329
- 'pointermove',
330
- ]);
331
- eventLog.push(`B [${event.type}, ${phaseToString(event.phase)}]`);
332
- if (event.phase === stopPropagationOnPhase) {
333
- return true;
334
- }
343
+ eventLog.push(`${props.name} [capture]`);
344
},
345
+ undefined,
346
+ undefined,
347
+ undefined,
348
+ false,
349
);
350
351
const Test = () => (
339
- <ClickEventComponentA>
340
- <ClickEventComponentB>
352
+ <ClickEventComponent name="A">
353
+ <ClickEventComponent name="B">
354
<button ref={buttonRef}>Click me!</button>
342
- </ClickEventComponentB>
343
- </ClickEventComponentA>
355
+ </ClickEventComponent>
356
+ </ClickEventComponent>
357
);
358
346
- function runTestWithPhase(phase) {
347
- eventLog = [];
348
- stopPropagationOnPhase = phase;
349
- ReactDOM.render(<Test />, container);
350
- let buttonElement = buttonRef.current;
351
- dispatchClickEvent(buttonElement);
352
- dispatchEvent(buttonElement, 'pointermove');
353
- }
359
+ ReactDOM.render(<Test />, container);
360
+
361
+ // Clicking the button should trigger the event responder onEvent()
362
+ let buttonElement = buttonRef.current;
363
+ dispatchClickEvent(buttonElement);
364
355
- runTestWithPhase(BUBBLE_PHASE);
356
- // Root phase should not be skipped for different event type
357
- expect(eventLog).toEqual([
358
- 'A [click, capture]',
359
- 'B [click, capture]',
360
- 'B [click, bubble]',
361
- 'A [pointermove, root]',
362
- 'B [pointermove, root]',
363
- ]);
364
- runTestWithPhase(CAPTURE_PHASE);
365
- // Root phase should not be skipped for different event type
365
expect(eventLog).toEqual([
367
- 'A [click, capture]',
368
- 'B [click, capture]',
369
- 'A [pointermove, root]',
370
- 'B [pointermove, root]',
366
+ 'A [capture]',
367
+ 'B [capture]',
368
+ 'B [bubble]',
369
+ 'A [bubble]',
370
]);
371
});
372
374
- it('nested event responders and their onEvent() should fire in the correct order with stopPropagation #2', () => {
375
- let eventLog;
376
- let stopPropagationOnPhase;
373
+ it('nested event responders should fire in the correct order with stopLocalPropagation', () => {
374
+ let eventLog = [];
375
const buttonRef = React.createRef();
376
379
- const ClickEventComponentA = createReactEventComponent(
377
+ const ClickEventComponent = createReactEventComponent(
378
['click'],
379
undefined,
380
(event, context, props) => {
383
- context.addRootEventTypes(event.target.ownerDocument, [
384
- 'click',
385
- 'pointermove',
386
- ]);
387
- eventLog.push(`A [${event.type}, ${phaseToString(event.phase)}]`);
388
- if (event.phase === stopPropagationOnPhase) {
389
- return true;
390
- }
381
+ eventLog.push(`${props.name} [bubble]`);
382
},
392
- );
393
-
394
- const ClickEventComponentB = createReactEventComponent(
395
- ['click'],
396
- undefined,
383
(event, context, props) => {
398
- context.addRootEventTypes(event.target.ownerDocument, [
399
- 'click',
400
- 'pointermove',
401
- ]);
402
- eventLog.push(`B [${event.type}, ${phaseToString(event.phase)}]`);
384
+ eventLog.push(`${props.name} [capture]`);
385
},
386
+ undefined,
387
+ undefined,
388
+ undefined,
389
+ true,
390
);
391
392
const Test = () => (
407
- <ClickEventComponentA>
408
- <ClickEventComponentB>
393
+ <ClickEventComponent name="A">
394
+ <ClickEventComponent name="B">
395
<button ref={buttonRef}>Click me!</button>
410
- </ClickEventComponentB>
411
- </ClickEventComponentA>
396
+ </ClickEventComponent>
397
+ </ClickEventComponent>
398
);
399
414
- function runTestWithPhase(phase) {
415
- eventLog = [];
416
- stopPropagationOnPhase = phase;
417
- ReactDOM.render(<Test />, container);
418
- let buttonElement = buttonRef.current;
419
- dispatchClickEvent(buttonElement);
420
- dispatchEvent(buttonElement, 'pointermove');
421
- }
400
+ ReactDOM.render(<Test />, container);
401
423
- runTestWithPhase(BUBBLE_PHASE);
424
- // Root phase should not be skipped for different event type
425
- expect(eventLog).toEqual([
426
- 'A [click, capture]',
427
- 'B [click, capture]',
428
- 'B [click, bubble]',
429
- 'A [click, bubble]',
430
- 'A [pointermove, root]',
431
- 'B [pointermove, root]',
432
- ]);
433
- runTestWithPhase(CAPTURE_PHASE);
434
- // Root phase should not be skipped for different event type
435
- expect(eventLog).toEqual([
436
- 'A [click, capture]',
437
- 'A [pointermove, root]',
438
- 'B [pointermove, root]',
439
- ]);
402
+ // Clicking the button should trigger the event responder onEvent()
403
+ let buttonElement = buttonRef.current;
404
+ dispatchClickEvent(buttonElement);
405
+
406
+ expect(eventLog).toEqual(['A [capture]', 'B [bubble]']);
407
});
408
409
it('custom event dispatching for click -> magicClick works', () => {
@@ -451,7 +418,19 @@ describe('DOMEventResponderSystem', () => {
418
const syntheticEvent = {
419
target: event.target,
420
type: 'magicclick',
454
- phase: phaseToString(event.phase),
421
+ phase: 'bubble',
422
+ };
423
+ context.dispatchEvent(syntheticEvent, props.onMagicClick, {
424
+ discrete: true,
425
+ });
426
+ }
427
+ },
428
+ (event, context, props) => {
429
+ if (props.onMagicClick) {
430
+ const syntheticEvent = {
431
+ target: event.target,
432
+ type: 'magicclick',
433
+ phase: 'capture',
434
};
435
context.dispatchEvent(syntheticEvent, props.onMagicClick, {
436
discrete: true,
@@ -490,42 +469,47 @@ describe('DOMEventResponderSystem', () => {
469
let eventLog = [];
470
const buttonRef = React.createRef();
471
472
+ function handleEvent(event, context, props, phase) {
473
+ const pressEvent = {
474
+ target: event.target,
475
+ type: 'press',
476
+ phase,
477
+ };
478
+ context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
479
+
480
+ context.setTimeout(() => {
481
+ if (props.onLongPress) {
482
+ const longPressEvent = {
483
+ target: event.target,
484
+ type: 'longpress',
485
+ phase,
486
+ };
487
+ context.dispatchEvent(longPressEvent, props.onLongPress, {
488
+ discrete: true,
489
+ });
490
+ }
491
+
492
+ if (props.onLongPressChange) {
493
+ const longPressChangeEvent = {
494
+ target: event.target,
495
+ type: 'longpresschange',
496
+ phase,
497
+ };
498
+ context.dispatchEvent(longPressChangeEvent, props.onLongPressChange, {
499
+ discrete: true,
500
+ });
501
+ }
502
+ }, 500);
503
+ }
504
+
505
const LongPressEventComponent = createReactEventComponent(
506
['click'],
507
undefined,
508
(event, context, props) => {
497
- const pressEvent = {
498
- target: event.target,
499
- type: 'press',
500
- phase: phaseToString(event.phase),
501
- };
502
- context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
503
-
504
- context.setTimeout(() => {
505
- if (props.onLongPress) {
506
- const longPressEvent = {
507
- target: event.target,
508
- type: 'longpress',
509
- phase: phaseToString(event.phase),
510
- };
511
- context.dispatchEvent(longPressEvent, props.onLongPress, {
512
- discrete: true,
513
- });
514
- }
515
-
516
- if (props.onLongPressChange) {
517
- const longPressChangeEvent = {
518
- target: event.target,
519
- type: 'longpresschange',
520
- phase: phaseToString(event.phase),
521
- };
522
- context.dispatchEvent(
523
- longPressChangeEvent,
524
- props.onLongPressChange,
525
- {discrete: true},
526
- );
527
- }
528
- }, 500);
509
+ handleEvent(event, context, props, 'bubble');
510
+ },
511
+ (event, context, props) => {
512
+ handleEvent(event, context, props, 'capture');
513
},
514
);
515
@@ -565,6 +549,8 @@ describe('DOMEventResponderSystem', () => {
549
const EventComponent = createReactEventComponent(
550
[],
551
undefined,
552
+ undefined,
553
+ undefined,
554
(event, context, props, state) => {},
555
() => {
556
onUnmountFired++;
@@ -590,7 +576,9 @@ describe('DOMEventResponderSystem', () => {
576
() => ({
577
incrementAmount: 5,
578
}),
593
- (event, context, props, state) => {},
579
+ undefined,
580
+ undefined,
581
+ undefined,
582
(context, props, state) => {
583
counter += state.incrementAmount;
584
},
@@ -616,11 +604,11 @@ describe('DOMEventResponderSystem', () => {
604
['click'],
605
undefined,
606
(event, context, props, state) => {
619
- if (event.phase === BUBBLE_PHASE) {
620
- ownershipGained = context.requestOwnership();
621
- }
607
+ ownershipGained = context.requestOwnership();
608
},
609
undefined,
610
+ undefined,
611
+ undefined,
612
() => {
613
onOwnershipChangeFired++;
614
},
packages/react-events/src/Drag.js
+23
-13
@@ -13,9 +13,12 @@ import type {
13
} from 'shared/ReactTypes';
14
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
15
16
-const CAPTURE_PHASE = 2;
17
-const targetEventTypes = ['pointerdown', 'pointercancel'];
18
-const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
16
+const targetEventTypes = ['pointerdown'];
17
+const rootEventTypes = [
18
+ 'pointerup',
19
+ 'pointercancel',
20
+ {name: 'pointermove', passive: false},
21
+];
22
23
type DragState = {
24
dragTarget: null | Element | Document,
@@ -30,8 +33,8 @@ type DragState = {
33
// In the case we don't have PointerEvents (Safari), we listen to touch events
34
// too
35
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
33
- targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
34
- rootEventTypes.push('mouseup', 'mousemove', {
36
+ targetEventTypes.push('touchstart', 'mousedown');
37
+ rootEventTypes.push('mouseup', 'mousemove', 'touchend', 'touchcancel', {
38
name: 'touchmove',
39
passive: false,
40
});
@@ -88,18 +91,15 @@ const DragResponder = {
91
y: 0,
92
};
93
},
94
+ stopLocalPropagation: true,
95
onEvent(
96
event: ReactResponderEvent,
97
context: ReactResponderContext,
98
props: Object,
99
state: DragState,
96
- ): boolean {
97
- const {target, phase, type, nativeEvent} = event;
100
+ ): void {
101
+ const {target, type, nativeEvent} = event;
102
99
- // Drag doesn't handle capture target events at this point
100
- if (phase === CAPTURE_PHASE) {
101
- return false;
102
- }
103
switch (type) {
104
case 'touchstart':
105
case 'mousedown':
@@ -133,11 +133,22 @@ const DragResponder = {
133
}
134
break;
135
}
136
+ }
137
+ },
138
+ onRootEvent(
139
+ event: ReactResponderEvent,
140
+ context: ReactResponderContext,
141
+ props: Object,
142
+ state: DragState,
143
+ ): void {
144
+ const {type, nativeEvent} = event;
145
+
146
+ switch (type) {
147
case 'touchmove':
148
case 'mousemove':
149
case 'pointermove': {
150
if (event.passive) {
140
- return false;
151
+ return;
152
}
153
if (state.isPointerDown) {
154
const obj =
@@ -230,7 +241,6 @@ const DragResponder = {
241
break;
242
}
243
}
233
- return false;
244
},
245
};
246
packages/react-events/src/Focus.js
+3
-13
@@ -14,8 +14,6 @@ import type {
14
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
15
import {getEventCurrentTarget} from './utils.js';
16
17
-const CAPTURE_PHASE = 2;
18
-
17
type FocusProps = {
18
disabled: boolean,
19
onBlur: (e: FocusEvent) => void,
@@ -112,10 +110,8 @@ const FocusResponder = {
110
context: ReactResponderContext,
111
props: Object,
112
state: FocusState,
115
- ): boolean {
116
- const {type, phase, target} = event;
117
- const shouldStopPropagation =
118
- props.stopPropagation === undefined ? true : props.stopPropagation;
113
+ ): void {
114
+ const {type, target} = event;
115
116
if (props.disabled) {
117
if (state.isFocused) {
@@ -123,12 +119,7 @@ const FocusResponder = {
119
state.isFocused = false;
120
state.focusTarget = null;
121
}
126
- return false;
127
- }
128
-
129
- // Focus doesn't handle capture target events at this point
130
- if (phase === CAPTURE_PHASE) {
131
- return false;
122
+ return;
123
}
124
125
switch (type) {
@@ -153,7 +144,6 @@ const FocusResponder = {
144
break;
145
}
146
}
156
- return shouldStopPropagation;
147
},
148
onUnmount(
149
context: ReactResponderContext,
packages/react-events/src/Hover.js
+9
-19
@@ -18,8 +18,6 @@ import {
18
isEventPositionWithinTouchHitTarget,
19
} from './utils';
20
21
-const CAPTURE_PHASE = 2;
22
-
21
type HoverProps = {
22
disabled: boolean,
23
delayHoverEnd: number,
@@ -141,7 +139,6 @@ function dispatchHoverStartEvents(
139
state.hoverStartTimeout = context.setTimeout(() => {
140
state.hoverStartTimeout = null;
141
activate();
144
- return false;
142
}, delayHoverStart);
143
} else {
144
activate();
@@ -201,7 +198,6 @@ function dispatchHoverEndEvents(
198
if (delayHoverEnd > 0) {
199
state.hoverEndTimeout = context.setTimeout(() => {
200
deactivate();
204
- return false;
201
}, delayHoverEnd);
202
} else {
203
deactivate();
@@ -245,12 +241,13 @@ const HoverResponder = {
241
ignoreEmulatedMouseEvents: false,
242
};
243
},
244
+ stopLocalPropagation: true,
245
onEvent(
246
event: ReactResponderEvent,
247
context: ReactResponderContext,
248
props: HoverProps,
249
state: HoverState,
253
- ): boolean {
250
+ ): void {
251
const {type} = event;
252
253
if (props.disabled) {
@@ -261,14 +258,8 @@ const HoverResponder = {
258
if (state.isTouched) {
259
state.isTouched = false;
260
}
264
- return false;
265
- }
266
-
267
- // Hover doesn't handle capture target events at this point
268
- if (event.phase === CAPTURE_PHASE) {
269
- return false;
261
+ return;
262
}
271
-
263
const pointerType = getEventPointerType(event);
264
265
switch (type) {
@@ -280,23 +271,23 @@ const HoverResponder = {
271
// Prevent hover events for touch
272
if (state.isTouched || pointerType === 'touch') {
273
state.isTouched = true;
283
- return false;
274
+ return;
275
}
276
277
// Prevent hover events for emulated events
278
if (isEmulatedMouseEvent(event, state)) {
288
- return false;
279
+ return;
280
}
281
282
if (isEventPositionWithinTouchHitTarget(event, context)) {
283
state.isOverTouchHitTarget = true;
293
- return false;
284
+ return;
285
}
286
state.hoverTarget = getEventCurrentTarget(event, context);
287
state.ignoreEmulatedMouseEvents = true;
288
dispatchHoverStartEvents(event, context, props, state);
289
}
299
- return false;
290
+ return;
291
}
292
293
// MOVE
@@ -331,7 +322,7 @@ const HoverResponder = {
322
}
323
}
324
}
334
- return false;
325
+ return;
326
}
327
328
// END
@@ -347,10 +338,9 @@ const HoverResponder = {
338
if (state.isTouched) {
339
state.isTouched = false;
340
}
350
- return false;
341
+ return;
342
}
343
}
353
- return false;
344
},
345
onUnmount(
346
context: ReactResponderContext,
packages/react-events/src/Press.js
+64
-59
@@ -19,8 +19,6 @@ import {
19
isEventPositionWithinTouchHitTarget,
20
} from './utils';
21
22
-const CAPTURE_PHASE = 2;
23
-
22
type PressProps = {
23
disabled: boolean,
24
delayLongPress: number,
@@ -103,10 +101,11 @@ const rootEventTypes = ['keyup', 'pointerup', 'pointermove', 'scroll'];
101
102
// If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
103
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
106
- targetEventTypes.push('touchstart', 'touchend', 'touchcancel', 'mousedown');
104
+ targetEventTypes.push('touchstart', 'touchcancel', 'mousedown');
105
rootEventTypes.push(
106
{name: 'mouseup', passive: false},
107
'touchmove',
108
+ 'touchend',
109
'mousemove',
110
);
111
}
@@ -199,8 +198,6 @@ function dispatchPressStartEvents(
198
props: PressProps,
199
state: PressState,
200
): void {
202
- const shouldStopPropagation =
203
- props.stopPropagation === undefined ? true : props.stopPropagation;
201
state.isPressed = true;
202
203
if (state.pressEndTimeout !== null) {
@@ -230,7 +227,6 @@ function dispatchPressStartEvents(
227
if (props.onLongPressChange) {
228
dispatchLongPressChangeEvent(context, props, state);
229
}
233
- return shouldStopPropagation;
230
}, delayLongPress);
231
}
232
};
@@ -245,7 +241,6 @@ function dispatchPressStartEvents(
241
state.pressStartTimeout = context.setTimeout(() => {
242
state.pressStartTimeout = null;
243
dispatch();
248
- return shouldStopPropagation;
244
}, delayPressStart);
245
} else {
246
dispatch();
@@ -258,8 +253,6 @@ function dispatchPressEndEvents(
253
props: PressProps,
254
state: PressState,
255
): void {
261
- const shouldStopPropagation =
262
- props.stopPropagation === undefined ? true : props.stopPropagation;
256
const wasActivePressStart = state.isActivePressStart;
257
let activationWasForced = false;
258
@@ -295,7 +288,6 @@ function dispatchPressEndEvents(
288
state.pressEndTimeout = context.setTimeout(() => {
289
state.pressEndTimeout = null;
290
deactivate(context, props, state);
298
- return shouldStopPropagation;
291
}, delayPressEnd);
292
} else {
293
deactivate(context, props, state);
@@ -381,6 +373,24 @@ function unmountResponder(
373
}
374
}
375
376
+function dispatchCancel(
377
+ type: string,
378
+ nativeEvent: $PropertyType<ReactResponderEvent, 'nativeEvent'>,
379
+ context: ReactResponderContext,
380
+ props: PressProps,
381
+ state: PressState,
382
+): void {
383
+ if (state.isPressed) {
384
+ if (type === 'contextmenu' && props.preventDefault !== false) {
385
+ (nativeEvent: any).preventDefault();
386
+ } else {
387
+ state.ignoreEmulatedMouseEvents = false;
388
+ dispatchPressEndEvents(context, props, state);
389
+ context.removeRootEventTypes(rootEventTypes);
390
+ }
391
+ }
392
+}
393
+
394
const PressResponder = {
395
targetEventTypes,
396
createInitialState(): PressState {
@@ -400,30 +410,23 @@ const PressResponder = {
410
ignoreEmulatedMouseEvents: false,
411
};
412
},
413
+ stopLocalPropagation: true,
414
onEvent(
415
event: ReactResponderEvent,
416
context: ReactResponderContext,
417
props: PressProps,
418
state: PressState,
408
- ): boolean {
409
- const {phase, target, type} = event;
419
+ ): void {
420
+ const {target, type} = event;
421
422
if (props.disabled) {
423
dispatchPressEndEvents(context, props, state);
424
context.removeRootEventTypes(rootEventTypes);
425
state.ignoreEmulatedMouseEvents = false;
415
- return false;
426
+ return;
427
}
417
-
418
- // Press doesn't handle capture target events at this point
419
- if (phase === CAPTURE_PHASE) {
420
- return false;
421
- }
422
-
428
const nativeEvent: any = event.nativeEvent;
429
const pointerType = getEventPointerType(event);
425
- const shouldStopPropagation =
426
- props.stopPropagation === undefined ? true : props.stopPropagation;
430
431
switch (type) {
432
// START
@@ -440,7 +443,7 @@ const PressResponder = {
443
// Ignore unrelated key events
444
if (pointerType === 'keyboard') {
445
if (!isValidKeyPress(nativeEvent.key)) {
443
- return shouldStopPropagation;
446
+ return;
447
}
448
}
449
@@ -451,13 +454,13 @@ const PressResponder = {
454
state.ignoreEmulatedMouseEvents ||
455
isEventPositionWithinTouchHitTarget(event, context)
456
) {
454
- return shouldStopPropagation;
457
+ return;
458
}
459
}
460
461
// Ignore any device buttons except left-mouse and touch/pen contact
462
if (nativeEvent.button > 0) {
460
- return shouldStopPropagation;
463
+ return;
464
}
465
466
state.pointerType = pointerType;
@@ -465,17 +468,46 @@ const PressResponder = {
468
state.isPressWithinResponderRegion = true;
469
dispatchPressStartEvents(context, props, state);
470
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
468
- return shouldStopPropagation;
471
} else {
472
// Prevent spacebar press from scrolling the window
473
if (isValidKeyPress(nativeEvent.key) && nativeEvent.key === ' ') {
474
nativeEvent.preventDefault();
473
- return shouldStopPropagation;
475
}
476
}
476
- return shouldStopPropagation;
477
+ break;
478
+ }
479
+
480
+ // CANCEL
481
+ case 'contextmenu': {
482
+ dispatchCancel(type, nativeEvent, context, props, state);
483
+ break;
484
}
485
486
+ case 'click': {
487
+ if (isAnchorTagElement(target)) {
488
+ const {ctrlKey, metaKey, shiftKey} = (nativeEvent: MouseEvent);
489
+ // Check "open in new window/tab" and "open context menu" key modifiers
490
+ const preventDefault = props.preventDefault;
491
+ if (preventDefault !== false && !shiftKey && !metaKey && !ctrlKey) {
492
+ nativeEvent.preventDefault();
493
+ }
494
+ }
495
+ break;
496
+ }
497
+ }
498
+ },
499
+ onRootEvent(
500
+ event: ReactResponderEvent,
501
+ context: ReactResponderContext,
502
+ props: PressProps,
503
+ state: PressState,
504
+ ): void {
505
+ const {target, type} = event;
506
+
507
+ const nativeEvent: any = event.nativeEvent;
508
+ const pointerType = getEventPointerType(event);
509
+
510
+ switch (type) {
511
// MOVE
512
case 'pointermove':
513
case 'mousemove':
@@ -484,7 +516,7 @@ const PressResponder = {
516
// Ignore emulated events (pointermove will dispatch touch and mouse events)
517
// Ignore pointermove events during a keyboard press
518
if (state.pointerType !== pointerType) {
487
- return shouldStopPropagation;
519
+ return;
520
}
521
522
if (state.responderRegion == null) {
@@ -504,9 +536,8 @@ const PressResponder = {
536
state.isPressWithinResponderRegion = false;
537
dispatchPressEndEvents(context, props, state);
538
}
507
- return shouldStopPropagation;
539
}
509
- return false;
540
+ break;
541
}
542
543
// END
@@ -518,7 +549,7 @@ const PressResponder = {
549
// Ignore unrelated keyboard events
550
if (pointerType === 'keyboard') {
551
if (!isValidKeyPress(nativeEvent.key)) {
521
- return false;
552
+ return;
553
}
554
}
555
@@ -539,45 +570,19 @@ const PressResponder = {
570
}
571
}
572
context.removeRootEventTypes(rootEventTypes);
542
- return shouldStopPropagation;
573
} else if (type === 'mouseup' && state.ignoreEmulatedMouseEvents) {
574
state.ignoreEmulatedMouseEvents = false;
575
}
546
- return false;
576
+ break;
577
}
578
579
// CANCEL
550
- case 'contextmenu':
580
case 'pointercancel':
581
case 'scroll':
582
case 'touchcancel': {
554
- if (state.isPressed) {
555
- if (type === 'contextmenu' && props.preventDefault !== false) {
556
- nativeEvent.preventDefault();
557
- } else {
558
- state.ignoreEmulatedMouseEvents = false;
559
- dispatchPressEndEvents(context, props, state);
560
- context.removeRootEventTypes(rootEventTypes);
561
- }
562
- return shouldStopPropagation;
563
- }
564
- return false;
565
- }
566
-
567
- case 'click': {
568
- if (isAnchorTagElement(target)) {
569
- const {ctrlKey, metaKey, shiftKey} = (nativeEvent: MouseEvent);
570
- // Check "open in new window/tab" and "open context menu" key modifiers
571
- const preventDefault = props.preventDefault;
572
- if (preventDefault !== false && !shiftKey && !metaKey && !ctrlKey) {
573
- nativeEvent.preventDefault();
574
- }
575
- return shouldStopPropagation;
576
- }
577
- return false;
583
+ dispatchCancel(type, nativeEvent, context, props, state);
584
}
585
}
580
- return false;
586
},
587
onUnmount(
588
context: ReactResponderContext,
packages/react-events/src/Swipe.js
+25
-15
@@ -13,15 +13,18 @@ import type {
13
} from 'shared/ReactTypes';
14
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
15
16
-const CAPTURE_PHASE = 2;
17
-const targetEventTypes = ['pointerdown', 'pointercancel'];
18
-const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
16
+const targetEventTypes = ['pointerdown'];
17
+const rootEventTypes = [
18
+ 'pointerup',
19
+ 'pointercancel',
20
+ {name: 'pointermove', passive: false},
21
+];
22
23
// In the case we don't have PointerEvents (Safari), we listen to touch events
24
// too
25
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
23
- targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
24
- rootEventTypes.push('mouseup', 'mousemove', {
26
+ targetEventTypes.push('touchstart', 'mousedown');
27
+ rootEventTypes.push('mouseup', 'mousemove', 'touchend', 'touchcancel', {
28
name: 'touchmove',
29
passive: false,
30
});
@@ -92,18 +95,15 @@ const SwipeResponder = {
95
y: 0,
96
};
97
},
98
+ stopLocalPropagation: true,
99
onEvent(
100
event: ReactResponderEvent,
101
context: ReactResponderContext,
102
props: Object,
103
state: SwipeState,
100
- ): boolean {
101
- const {target, phase, type, nativeEvent} = event;
104
+ ): void {
105
+ const {target, type, nativeEvent} = event;
106
103
- // Swipe doesn't handle capture target events at this point
104
- if (phase === CAPTURE_PHASE) {
105
- return false;
106
- }
107
switch (type) {
108
case 'touchstart':
109
case 'mousedown':
@@ -136,11 +136,22 @@ const SwipeResponder = {
136
}
137
break;
138
}
139
+ }
140
+ },
141
+ onRootEvent(
142
+ event: ReactResponderEvent,
143
+ context: ReactResponderContext,
144
+ props: Object,
145
+ state: SwipeState,
146
+ ): void {
147
+ const {type, nativeEvent} = event;
148
+
149
+ switch (type) {
150
case 'touchmove':
151
case 'mousemove':
152
case 'pointermove': {
153
if (event.passive) {
143
- return false;
154
+ return;
155
}
156
if (state.isSwiping) {
157
let obj = null;
@@ -160,7 +171,7 @@ const SwipeResponder = {
171
state.swipeTarget = null;
172
state.touchId = null;
173
context.removeRootEventTypes(rootEventTypes);
163
- return false;
174
+ return;
175
}
176
const x = (obj: any).screenX;
177
const y = (obj: any).screenY;
@@ -196,7 +207,7 @@ const SwipeResponder = {
207
case 'pointerup': {
208
if (state.isSwiping) {
209
if (state.x === state.startX && state.y === state.startY) {
199
- return false;
210
+ return;
211
}
212
if (props.onShouldClaimOwnership) {
213
context.releaseOwnership();
@@ -240,7 +251,6 @@ const SwipeResponder = {
251
break;
252
}
253
}
243
- return false;
254
},
255
};
256
packages/react-events/src/__tests__/Press-test.internal.js
-81
@@ -1127,15 +1127,10 @@ describe('Event responder: Press', () => {
1127
'pointerdown',
1128
'inner: onPressStart',
1129
'inner: onPressChange',
1130
- 'outer: onPressStart',
1131
- 'outer: onPressChange',
1130
'pointerup',
1131
'inner: onPressEnd',
1132
'inner: onPressChange',
1133
'inner: onPress',
1136
- 'outer: onPressEnd',
1137
- 'outer: onPressChange',
1138
- 'outer: onPress',
1134
]);
1135
});
1136
@@ -1214,82 +1209,6 @@ describe('Event responder: Press', () => {
1209
expect(fn).toHaveBeenCalledTimes(2);
1210
});
1211
});
1217
-
1218
- describe('correctly bubble to other event responders when stopPropagation is set to false', () => {
1219
- it('for onPress', () => {
1220
- const ref = React.createRef();
1221
- const fn = jest.fn();
1222
- const element = (
1223
- <Press onPress={fn}>
1224
- <Press onPress={fn} stopPropagation={false}>
1225
- <div ref={ref} />
1226
- </Press>
1227
- </Press>
1228
- );
1229
- ReactDOM.render(element, container);
1230
-
1231
- ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1232
- ref.current.dispatchEvent(createPointerEvent('pointerup'));
1233
- expect(fn).toHaveBeenCalledTimes(2);
1234
- });
1235
-
1236
- it('for onLongPress', () => {
1237
- const ref = React.createRef();
1238
- const fn = jest.fn();
1239
- const element = (
1240
- <Press onLongPress={fn}>
1241
- <Press onLongPress={fn} stopPropagation={false}>
1242
- <div ref={ref} />
1243
- </Press>
1244
- </Press>
1245
- );
1246
- ReactDOM.render(element, container);
1247
-
1248
- ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1249
- jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
1250
- ref.current.dispatchEvent(createPointerEvent('pointerup'));
1251
- expect(fn).toHaveBeenCalledTimes(2);
1252
- });
1253
-
1254
- it('for onPressStart/onPressEnd', () => {
1255
- const ref = React.createRef();
1256
- const fn = jest.fn();
1257
- const fn2 = jest.fn();
1258
- const element = (
1259
- <Press onPressStart={fn} onPressEnd={fn2}>
1260
- <Press onPressStart={fn} onPressEnd={fn2} stopPropagation={false}>
1261
- <div ref={ref} />
1262
- </Press>
1263
- </Press>
1264
- );
1265
- ReactDOM.render(element, container);
1266
-
1267
- ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1268
- expect(fn).toHaveBeenCalledTimes(2);
1269
- expect(fn2).toHaveBeenCalledTimes(0);
1270
- ref.current.dispatchEvent(createPointerEvent('pointerup'));
1271
- expect(fn).toHaveBeenCalledTimes(2);
1272
- expect(fn2).toHaveBeenCalledTimes(2);
1273
- });
1274
-
1275
- it('for onPressChange', () => {
1276
- const ref = React.createRef();
1277
- const fn = jest.fn();
1278
- const element = (
1279
- <Press onPressChange={fn}>
1280
- <Press onPressChange={fn} stopPropagation={false}>
1281
- <div ref={ref} />
1282
- </Press>
1283
- </Press>
1284
- );
1285
- ReactDOM.render(element, container);
1286
-
1287
- ref.current.dispatchEvent(createPointerEvent('pointerdown'));
1288
- expect(fn).toHaveBeenCalledTimes(2);
1289
- ref.current.dispatchEvent(createPointerEvent('pointerup'));
1290
- expect(fn).toHaveBeenCalledTimes(4);
1291
- });
1292
- });
1212
});
1213
1214
describe('link components', () => {
packages/shared/ReactTypes.js
+16
-4
@@ -88,12 +88,25 @@ export type ReactEventResponderEventType =
88
export type ReactEventResponder = {
89
targetEventTypes: Array<ReactEventResponderEventType>,
90
createInitialState?: (props: null | Object) => Object,
91
- onEvent: (
91
+ stopLocalPropagation: boolean,
92
+ onEvent?: (
93
event: ReactResponderEvent,
94
context: ReactResponderContext,
95
props: null | Object,
96
state: null | Object,
96
- ) => boolean,
97
+ ) => void,
98
+ onEventCapture?: (
99
+ event: ReactResponderEvent,
100
+ context: ReactResponderContext,
101
+ props: null | Object,
102
+ state: null | Object,
103
+ ) => void,
104
+ onRootEvent?: (
105
+ event: ReactResponderEvent,
106
+ context: ReactResponderContext,
107
+ props: null | Object,
108
+ state: null | Object,
109
+ ) => void,
110
onUnmount: (
111
context: ReactResponderContext,
112
props: null | Object,
@@ -135,7 +148,6 @@ export type ReactResponderEvent = {
148
type: string,
149
passive: boolean,
150
passiveSupported: boolean,
138
- phase: 0 | 1 | 2,
151
};
152
153
export type ReactResponderDispatchEventOptions = {
@@ -168,7 +180,7 @@ export type ReactResponderContext = {
180
hasOwnership: () => boolean,
181
requestOwnership: () => boolean,
182
releaseOwnership: () => boolean,
171
- setTimeout: (func: () => boolean, timeout: number) => Symbol,
183
+ setTimeout: (func: () => void, timeout: number) => Symbol,
184
clearTimeout: (timerId: Symbol) => void,
185
getEventTargetsFromTarget: (
186
target: Element | Document,