Event API: normalize event timeStamp property to be in event system (#15598)
Dominic Gannaway committed
May 9, 2019 at 16:05 UTC
e33e32db040f19733d699d4b1bce6c20dcd7711e
8 files changed
+80
-76
packages/react-dom/src/events/DOMEventResponderSystem.js
+21
-6
@@ -64,6 +64,7 @@ type ResponderTimer = {|
64
instance: ReactEventComponentInstance,
65
func: () => void,
66
id: Symbol,
67
+ timeStamp: number,
68
|};
69
70
const activeTimeouts: Map<Symbol, ResponderTimeout> = new Map();
@@ -90,6 +91,7 @@ const responderOwners: Map<
91
> = new Map();
92
let globalOwner = null;
93
94
+let currentTimeStamp = 0;
95
let currentTimers = new Map();
96
let currentInstance: null | ReactEventComponentInstance = null;
97
let currentEventQueue: null | EventQueue = null;
@@ -101,11 +103,11 @@ const eventResponderContext: ReactResponderContext = {
103
{discrete}: ReactResponderDispatchEventOptions,
104
): void {
105
validateResponderContext();
104
- const {target, type} = possibleEventObject;
106
+ const {target, type, timeStamp} = possibleEventObject;
107
106
- if (target == null || type == null) {
108
+ if (target == null || type == null || timeStamp == null) {
109
throw new Error(
108
- 'context.dispatchEvent: "target" and "type" fields on event object are required.',
110
+ 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
111
);
112
}
113
if (__DEV__) {
@@ -313,7 +315,7 @@ const eventResponderContext: ReactResponderContext = {
315
if (timeout === undefined) {
316
const timers = new Map();
317
const id = setTimeout(() => {
316
- processTimers(timers);
318
+ processTimers(timers, delay);
319
}, delay);
320
timeout = {
321
id,
@@ -325,6 +327,7 @@ const eventResponderContext: ReactResponderContext = {
327
instance: ((currentInstance: any): ReactEventComponentInstance),
328
func,
329
id: timerId,
330
+ timeStamp: currentTimeStamp,
331
});
332
activeTimeouts.set(timerId, timeout);
333
return timerId;
@@ -408,6 +411,9 @@ const eventResponderContext: ReactResponderContext = {
411
}
412
return currentTarget;
413
},
414
+ getTimeStamp(): number {
415
+ return currentTimeStamp;
416
+ },
417
};
418
419
function isTargetWithinEventComponent(target: Element | Document): boolean {
@@ -478,13 +484,17 @@ function isFiberHostComponentFocusable(fiber: Fiber): boolean {
484
);
485
}
486
481
-function processTimers(timers: Map<Symbol, ResponderTimer>): void {
487
+function processTimers(
488
+ timers: Map<Symbol, ResponderTimer>,
489
+ delay: number,
490
+): void {
491
const timersArr = Array.from(timers.values());
492
currentEventQueue = createEventQueue();
493
try {
494
for (let i = 0; i < timersArr.length; i++) {
486
- const {instance, func, id} = timersArr[i];
495
+ const {instance, func, id, timeStamp} = timersArr[i];
496
currentInstance = instance;
497
+ currentTimeStamp = timeStamp + delay;
498
try {
499
func();
500
} finally {
@@ -496,6 +506,7 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
506
currentTimers = null;
507
currentInstance = null;
508
currentEventQueue = null;
509
+ currentTimeStamp = 0;
510
}
511
}
512
@@ -861,8 +872,11 @@ export function dispatchEventForResponderEventSystem(
872
const previousEventQueue = currentEventQueue;
873
const previousInstance = currentInstance;
874
const previousTimers = currentTimers;
875
+ const previousTimeStamp = currentTimeStamp;
876
currentTimers = null;
877
currentEventQueue = createEventQueue();
878
+ // We might want to control timeStamp another way here
879
+ currentTimeStamp = (nativeEvent: any).timeStamp;
880
try {
881
traverseAndHandleEventResponderInstances(
882
topLevelType,
@@ -876,6 +890,7 @@ export function dispatchEventForResponderEventSystem(
890
currentTimers = previousTimers;
891
currentInstance = previousInstance;
892
currentEventQueue = previousEventQueue;
893
+ currentTimeStamp = previousTimeStamp;
894
}
895
}
896
}
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+6
@@ -422,6 +422,7 @@ describe('DOMEventResponderSystem', () => {
422
target: event.target,
423
type: 'magicclick',
424
phase: 'bubble',
425
+ timeStamp: context.getTimeStamp(),
426
};
427
context.dispatchEvent(syntheticEvent, props.onMagicClick, {
428
discrete: true,
@@ -434,6 +435,7 @@ describe('DOMEventResponderSystem', () => {
435
target: event.target,
436
type: 'magicclick',
437
phase: 'capture',
438
+ timeStamp: context.getTimeStamp(),
439
};
440
context.dispatchEvent(syntheticEvent, props.onMagicClick, {
441
discrete: true,
@@ -477,6 +479,7 @@ describe('DOMEventResponderSystem', () => {
479
target: event.target,
480
type: 'press',
481
phase,
482
+ timeStamp: context.getTimeStamp(),
483
};
484
context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
485
@@ -486,6 +489,7 @@ describe('DOMEventResponderSystem', () => {
489
target: event.target,
490
type: 'longpress',
491
phase,
492
+ timeStamp: context.getTimeStamp(),
493
};
494
context.dispatchEvent(longPressEvent, props.onLongPress, {
495
discrete: true,
@@ -497,6 +501,7 @@ describe('DOMEventResponderSystem', () => {
501
target: event.target,
502
type: 'longpresschange',
503
phase,
504
+ timeStamp: context.getTimeStamp(),
505
};
506
context.dispatchEvent(longPressChangeEvent, props.onLongPressChange, {
507
discrete: true,
@@ -901,6 +906,7 @@ describe('DOMEventResponderSystem', () => {
906
const syntheticEvent = {
907
target: event.target,
908
type: 'click',
909
+ timeStamp: context.getTimeStamp(),
910
};
911
context.dispatchEvent(syntheticEvent, props.onClick, {
912
discrete: true,
packages/react-events/src/Drag.js
+4
-1
@@ -50,11 +50,13 @@ type DragEventType = 'dragstart' | 'dragend' | 'dragchange' | 'dragmove';
50
type DragEvent = {|
51
target: Element | Document,
52
type: DragEventType,
53
+ timeStamp: number,
54
diffX?: number,
55
diffY?: number,
56
|};
57
58
function createDragEvent(
59
+ context: ReactResponderContext,
60
type: DragEventType,
61
target: Element | Document,
62
eventData?: EventData,
@@ -62,6 +64,7 @@ function createDragEvent(
64
return {
65
target,
66
type,
67
+ timeStamp: context.getTimeStamp(),
68
...eventData,
69
};
70
}
@@ -75,7 +78,7 @@ function dispatchDragEvent(
78
eventData?: EventData,
79
): void {
80
const target = ((state.dragTarget: any): Element | Document);
78
- const syntheticEvent = createDragEvent(name, target, eventData);
81
+ const syntheticEvent = createDragEvent(context, name, target, eventData);
82
context.dispatchEvent(syntheticEvent, listener, {discrete});
83
}
84
packages/react-events/src/Focus.js
+17
-6
@@ -33,6 +33,7 @@ type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
33
type FocusEvent = {|
34
target: Element | Document,
35
type: FocusEventType,
36
+ timeStamp: number,
37
|};
38
39
const targetEventTypes = [
@@ -56,12 +57,14 @@ const rootEventTypes = [
57
];
58
59
function createFocusEvent(
60
+ context: ReactResponderContext,
61
type: FocusEventType,
62
target: Element | Document,
63
): FocusEvent {
64
return {
65
target,
66
type,
67
+ timeStamp: context.getTimeStamp(),
68
};
69
}
70
@@ -72,21 +75,25 @@ function dispatchFocusInEvents(
75
) {
76
const target = ((state.focusTarget: any): Element | Document);
77
if (props.onFocus) {
75
- const syntheticEvent = createFocusEvent('focus', target);
78
+ const syntheticEvent = createFocusEvent(context, 'focus', target);
79
context.dispatchEvent(syntheticEvent, props.onFocus, {discrete: true});
80
}
81
if (props.onFocusChange) {
82
const listener = () => {
83
props.onFocusChange(true);
84
};
82
- const syntheticEvent = createFocusEvent('focuschange', target);
85
+ const syntheticEvent = createFocusEvent(context, 'focuschange', target);
86
context.dispatchEvent(syntheticEvent, listener, {discrete: true});
87
}
88
if (props.onFocusVisibleChange && state.isLocalFocusVisible) {
89
const listener = () => {
90
props.onFocusVisibleChange(true);
91
};
89
- const syntheticEvent = createFocusEvent('focusvisiblechange', target);
92
+ const syntheticEvent = createFocusEvent(
93
+ context,
94
+ 'focusvisiblechange',
95
+ target,
96
+ );
97
context.dispatchEvent(syntheticEvent, listener, {discrete: true});
98
}
99
}
@@ -98,14 +105,14 @@ function dispatchFocusOutEvents(
105
) {
106
const target = ((state.focusTarget: any): Element | Document);
107
if (props.onBlur) {
101
- const syntheticEvent = createFocusEvent('blur', target);
108
+ const syntheticEvent = createFocusEvent(context, 'blur', target);
109
context.dispatchEvent(syntheticEvent, props.onBlur, {discrete: true});
110
}
111
if (props.onFocusChange) {
112
const listener = () => {
113
props.onFocusChange(false);
114
};
108
- const syntheticEvent = createFocusEvent('focuschange', target);
115
+ const syntheticEvent = createFocusEvent(context, 'focuschange', target);
116
context.dispatchEvent(syntheticEvent, listener, {discrete: true});
117
}
118
dispatchFocusVisibleOutEvent(context, props, state);
@@ -121,7 +128,11 @@ function dispatchFocusVisibleOutEvent(
128
const listener = () => {
129
props.onFocusVisibleChange(false);
130
};
124
- const syntheticEvent = createFocusEvent('focusvisiblechange', target);
131
+ const syntheticEvent = createFocusEvent(
132
+ context,
133
+ 'focusvisiblechange',
134
+ target,
135
+ );
136
context.dispatchEvent(syntheticEvent, listener, {discrete: true});
137
state.isLocalFocusVisible = false;
138
}
packages/react-events/src/Hover.js
+7
@@ -42,6 +42,7 @@ type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
42
type HoverEvent = {|
43
target: Element | Document,
44
type: HoverEventType,
45
+ timeStamp: number,
46
|};
47
48
const DEFAULT_HOVER_END_DELAY_MS = 0;
@@ -60,12 +61,14 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
61
}
62
63
function createHoverEvent(
64
+ context: ReactResponderContext,
65
type: HoverEventType,
66
target: Element | Document,
67
): HoverEvent {
68
return {
69
target,
70
type,
71
+ timeStamp: context.getTimeStamp(),
72
};
73
}
74
@@ -79,6 +82,7 @@ function dispatchHoverChangeEvent(
82
props.onHoverChange(bool);
83
};
84
const syntheticEvent = createHoverEvent(
85
+ context,
86
'hoverchange',
87
((state.hoverTarget: any): Element | Document),
88
);
@@ -115,6 +119,7 @@ function dispatchHoverStartEvents(
119
120
if (props.onHoverStart) {
121
const syntheticEvent = createHoverEvent(
122
+ context,
123
'hoverstart',
124
((target: any): Element | Document),
125
);
@@ -174,6 +179,7 @@ function dispatchHoverEndEvents(
179
180
if (props.onHoverEnd) {
181
const syntheticEvent = createHoverEvent(
182
+ context,
183
'hoverend',
184
((target: any): Element | Document),
185
);
@@ -311,6 +317,7 @@ const HoverResponder = {
317
} else {
318
if (props.onHoverMove && state.hoverTarget !== null) {
319
const syntheticEvent = createHoverEvent(
320
+ context,
321
'hovermove',
322
state.hoverTarget,
323
);
packages/react-events/src/Press.js
+20
-62
@@ -84,7 +84,7 @@ type PressEvent = {|
84
target: Element | Document,
85
type: PressEventType,
86
pointerType: PointerType,
87
- timeStamp: null | number,
87
+ timeStamp: number,
88
clientX: null | number,
89
clientY: null | number,
90
pageX: null | number,
@@ -135,23 +135,22 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
135
}
136
137
function createPressEvent(
138
+ context: ReactResponderContext,
139
type: PressEventType,
140
target: Element | Document,
141
pointerType: PointerType,
142
event: ?ReactResponderEvent,
142
- delay: number,
143
): PressEvent {
144
+ const timeStamp = context.getTimeStamp();
145
let clientX = null;
146
let clientY = null;
147
let pageX = null;
148
let pageY = null;
149
let screenX = null;
150
let screenY = null;
150
- let timeStamp = null;
151
152
if (event) {
153
const nativeEvent = (event.nativeEvent: any);
154
- timeStamp = nativeEvent.timeStamp + delay;
154
// Only check for one property, checking for all of them is costly. We can assume
155
// if clientX exists, so do the rest.
156
let eventObject;
@@ -185,16 +184,15 @@ function dispatchEvent(
184
name: PressEventType,
185
listener: (e: Object) => void,
186
discrete: boolean,
188
- delay: number,
187
): void {
188
const target = ((state.pressTarget: any): Element | Document);
189
const pointerType = state.pointerType;
190
const syntheticEvent = createPressEvent(
191
+ context,
192
name,
193
target,
194
pointerType,
195
event,
197
- delay,
196
);
197
context.dispatchEvent(syntheticEvent, listener, {
198
discrete,
@@ -206,13 +204,12 @@ function dispatchPressChangeEvent(
204
context: ReactResponderContext,
205
props: PressProps,
206
state: PressState,
209
- delay: number,
207
): void {
208
const bool = state.isActivePressed;
209
const listener = () => {
210
props.onPressChange(bool);
211
};
215
- dispatchEvent(event, context, state, 'presschange', listener, true, delay);
212
+ dispatchEvent(event, context, state, 'presschange', listener, true);
213
}
214
215
function dispatchLongPressChangeEvent(
@@ -220,30 +217,15 @@ function dispatchLongPressChangeEvent(
217
context: ReactResponderContext,
218
props: PressProps,
219
state: PressState,
223
- delay: number,
220
): void {
221
const bool = state.isLongPressed;
222
const listener = () => {
223
props.onLongPressChange(bool);
224
};
229
- dispatchEvent(
230
- event,
231
- context,
232
- state,
233
- 'longpresschange',
234
- listener,
235
- true,
236
- delay,
237
- );
225
+ dispatchEvent(event, context, state, 'longpresschange', listener, true);
226
}
227
240
-function activate(
241
- event: ReactResponderEvent,
242
- context,
243
- props,
244
- state,
245
- delay: number,
246
-) {
228
+function activate(event: ReactResponderEvent, context, props, state) {
229
const nativeEvent: any = event.nativeEvent;
230
const {x, y} = getEventPageCoords(nativeEvent);
231
const wasActivePressed = state.isActivePressed;
@@ -263,41 +245,26 @@ function activate(
245
'pressstart',
246
props.onPressStart,
247
true,
266
- delay,
248
);
249
}
250
if (!wasActivePressed && props.onPressChange) {
270
- dispatchPressChangeEvent(event, context, props, state, delay);
251
+ dispatchPressChangeEvent(event, context, props, state);
252
}
253
}
254
274
-function deactivate(
275
- event: ?ReactResponderEvent,
276
- context,
277
- props,
278
- state,
279
- delay: number,
280
-) {
255
+function deactivate(event: ?ReactResponderEvent, context, props, state) {
256
const wasLongPressed = state.isLongPressed;
257
state.isActivePressed = false;
258
state.isLongPressed = false;
259
260
if (props.onPressEnd) {
286
- dispatchEvent(
287
- event,
288
- context,
289
- state,
290
- 'pressend',
291
- props.onPressEnd,
292
- true,
293
- delay,
294
- );
261
+ dispatchEvent(event, context, state, 'pressend', props.onPressEnd, true);
262
}
263
if (props.onPressChange) {
297
- dispatchPressChangeEvent(event, context, props, state, delay);
264
+ dispatchPressChangeEvent(event, context, props, state);
265
}
266
if (wasLongPressed && props.onLongPressChange) {
300
- dispatchLongPressChangeEvent(event, context, props, state, delay);
267
+ dispatchLongPressChangeEvent(event, context, props, state);
268
}
269
}
270
@@ -314,9 +281,9 @@ function dispatchPressStartEvents(
281
state.pressEndTimeout = null;
282
}
283
317
- const dispatch = (delay: number) => {
284
+ const dispatch = () => {
285
state.isActivePressStart = true;
319
- activate(event, context, props, state, delay);
286
+ activate(event, context, props, state);
287
288
if (
289
(props.onLongPress || props.onLongPressChange) &&
@@ -338,17 +305,10 @@ function dispatchPressStartEvents(
305
'longpress',
306
props.onLongPress,
307
true,
341
- delayLongPress + delay,
308
);
309
}
310
if (props.onLongPressChange) {
345
- dispatchLongPressChangeEvent(
346
- event,
347
- context,
348
- props,
349
- state,
350
- delayLongPress + delay,
351
- );
311
+ dispatchLongPressChangeEvent(event, context, props, state);
312
}
313
}, delayLongPress);
314
}
@@ -363,10 +323,10 @@ function dispatchPressStartEvents(
323
if (delayPressStart > 0) {
324
state.pressStartTimeout = context.setTimeout(() => {
325
state.pressStartTimeout = null;
366
- dispatch(delayPressStart);
326
+ dispatch();
327
}, delayPressStart);
328
} else {
369
- dispatch(0);
329
+ dispatch();
330
}
331
}
332
}
@@ -394,7 +354,7 @@ function dispatchPressEndEvents(
354
// don't activate if a press has moved beyond the responder region
355
if (state.isPressWithinResponderRegion && event != null) {
356
// if we haven't yet activated (due to delays), activate now
397
- activate(event, context, props, state, 0);
357
+ activate(event, context, props, state);
358
activationWasForced = true;
359
}
360
}
@@ -411,10 +371,10 @@ function dispatchPressEndEvents(
371
if (delayPressEnd > 0) {
372
state.pressEndTimeout = context.setTimeout(() => {
373
state.pressEndTimeout = null;
414
- deactivate(event, context, props, state, delayPressEnd);
374
+ deactivate(event, context, props, state);
375
}, delayPressEnd);
376
} else {
417
- deactivate(event, context, props, state, 0);
377
+ deactivate(event, context, props, state);
378
}
379
}
380
}
@@ -758,7 +718,6 @@ const PressResponder = {
718
'pressmove',
719
props.onPressMove,
720
false,
761
- 0,
721
);
722
}
723
if (
@@ -843,7 +802,6 @@ const PressResponder = {
802
'press',
803
props.onPress,
804
true,
846
- 0,
805
);
806
}
807
}
packages/react-events/src/Swipe.js
+4
-1
@@ -40,11 +40,13 @@ type SwipeEventType = 'swipeleft' | 'swiperight' | 'swipeend' | 'swipemove';
40
type SwipeEvent = {|
41
target: Element | Document,
42
type: SwipeEventType,
43
+ timeStamp: number,
44
diffX?: number,
45
diffY?: number,
46
|};
47
48
function createSwipeEvent(
49
+ context: ReactResponderContext,
50
type: SwipeEventType,
51
target: Element | Document,
52
eventData?: EventData,
@@ -52,6 +54,7 @@ function createSwipeEvent(
54
return {
55
target,
56
type,
57
+ timeStamp: context.getTimeStamp(),
58
...eventData,
59
};
60
}
@@ -65,7 +68,7 @@ function dispatchSwipeEvent(
68
eventData?: EventData,
69
) {
70
const target = ((state.swipeTarget: any): Element | Document);
68
- const syntheticEvent = createSwipeEvent(name, target, eventData);
71
+ const syntheticEvent = createSwipeEvent(context, name, target, eventData);
72
context.dispatchEvent(syntheticEvent, listener, {discrete});
73
}
74
packages/shared/ReactTypes.js
+1
@@ -193,4 +193,5 @@ export type ReactResponderContext = {
193
event: ReactResponderEvent,
194
): '' | 'mouse' | 'keyboard' | 'pen' | 'touch',
195
getEventCurrentTarget(event: ReactResponderEvent): Element,
196
+ getTimeStamp: () => number,
197
};