Experimental Event API: Press event properties (#15586)
Dominic Gannaway committed
May 8, 2019 at 20:29 UTC
05d08500b2d3bf7dbb68702068bade4f3dbcf8e4
2 files changed
+296
-34
packages/react-events/src/Press.js
+143
-34
@@ -10,11 +10,9 @@
10
import type {
11
ReactResponderEvent,
12
ReactResponderContext,
13
- ReactResponderDispatchEventOptions,
13
} from 'shared/ReactTypes';
14
15
import React from 'react';
17
-
16
import {isEventPositionWithinTouchHitTarget} from './utils';
17
18
type PressProps = {
@@ -86,6 +84,13 @@ type PressEvent = {|
84
target: Element | Document,
85
type: PressEventType,
86
pointerType: PointerType,
87
+ timeStamp: null | number,
88
+ clientX: null | number,
89
+ clientY: null | number,
90
+ pageX: null | number,
91
+ pageY: null | number,
92
+ screenX: null | number,
93
+ screenY: null | number,
94
|};
95
96
const DEFAULT_PRESS_END_DELAY_MS = 0;
@@ -133,58 +138,112 @@ function createPressEvent(
138
type: PressEventType,
139
target: Element | Document,
140
pointerType: PointerType,
141
+ event: ?ReactResponderEvent,
142
+ delay: number,
143
): PressEvent {
144
+ let clientX = null;
145
+ let clientY = null;
146
+ let pageX = null;
147
+ let pageY = null;
148
+ let screenX = null;
149
+ let screenY = null;
150
+ let timeStamp = null;
151
+
152
+ if (event) {
153
+ const nativeEvent = (event.nativeEvent: any);
154
+ timeStamp = nativeEvent.timeStamp + delay;
155
+ // Only check for one property, checking for all of them is costly. We can assume
156
+ // if clientX exists, so do the rest.
157
+ let eventObject;
158
+ if (nativeEvent.clientX !== undefined) {
159
+ eventObject = (nativeEvent: any);
160
+ } else if (isTouchEvent(nativeEvent)) {
161
+ eventObject = getTouchFromPressEvent(nativeEvent);
162
+ }
163
+ if (eventObject) {
164
+ ({clientX, clientY, pageX, pageY, screenX, screenY} = eventObject);
165
+ }
166
+ }
167
return {
168
target,
169
type,
170
pointerType,
171
+ timeStamp,
172
+ clientX,
173
+ clientY,
174
+ pageX,
175
+ pageY,
176
+ screenX,
177
+ screenY,
178
};
179
}
180
181
function dispatchEvent(
182
+ event: ?ReactResponderEvent,
183
context: ReactResponderContext,
184
state: PressState,
185
name: PressEventType,
186
listener: (e: Object) => void,
149
- options?: ReactResponderDispatchEventOptions,
187
+ discrete: boolean,
188
+ delay: number,
189
): void {
190
const target = ((state.pressTarget: any): Element | Document);
191
const pointerType = state.pointerType;
153
- const syntheticEvent = createPressEvent(name, target, pointerType);
154
- context.dispatchEvent(
155
- syntheticEvent,
156
- listener,
157
- options || {
158
- discrete: true,
159
- },
192
+ const syntheticEvent = createPressEvent(
193
+ name,
194
+ target,
195
+ pointerType,
196
+ event,
197
+ delay,
198
);
199
+ context.dispatchEvent(syntheticEvent, listener, {
200
+ discrete,
201
+ });
202
}
203
204
function dispatchPressChangeEvent(
205
+ event: ?ReactResponderEvent,
206
context: ReactResponderContext,
207
props: PressProps,
208
state: PressState,
209
+ delay: number,
210
): void {
211
const bool = state.isActivePressed;
212
const listener = () => {
213
props.onPressChange(bool);
214
};
172
- dispatchEvent(context, state, 'presschange', listener);
215
+ dispatchEvent(event, context, state, 'presschange', listener, true, delay);
216
}
217
218
function dispatchLongPressChangeEvent(
219
+ event: ?ReactResponderEvent,
220
context: ReactResponderContext,
221
props: PressProps,
222
state: PressState,
223
+ delay: number,
224
): void {
225
const bool = state.isLongPressed;
226
const listener = () => {
227
props.onLongPressChange(bool);
228
};
184
- dispatchEvent(context, state, 'longpresschange', listener);
229
+ dispatchEvent(
230
+ event,
231
+ context,
232
+ state,
233
+ 'longpresschange',
234
+ listener,
235
+ true,
236
+ delay,
237
+ );
238
}
239
187
-function activate(event, context, props, state) {
240
+function activate(
241
+ event: ReactResponderEvent,
242
+ context,
243
+ props,
244
+ state,
245
+ delay: number,
246
+) {
247
const nativeEvent: any = event.nativeEvent;
248
const {x, y} = getEventPageCoords(nativeEvent);
249
const wasActivePressed = state.isActivePressed;
@@ -197,26 +256,48 @@ function activate(event, context, props, state) {
256
}
257
258
if (props.onPressStart) {
200
- dispatchEvent(context, state, 'pressstart', props.onPressStart);
259
+ dispatchEvent(
260
+ event,
261
+ context,
262
+ state,
263
+ 'pressstart',
264
+ props.onPressStart,
265
+ true,
266
+ delay,
267
+ );
268
}
269
if (!wasActivePressed && props.onPressChange) {
203
- dispatchPressChangeEvent(context, props, state);
270
+ dispatchPressChangeEvent(event, context, props, state, delay);
271
}
272
}
273
207
-function deactivate(context, props, state) {
274
+function deactivate(
275
+ event: ?ReactResponderEvent,
276
+ context,
277
+ props,
278
+ state,
279
+ delay: number,
280
+) {
281
const wasLongPressed = state.isLongPressed;
282
state.isActivePressed = false;
283
state.isLongPressed = false;
284
285
if (props.onPressEnd) {
213
- dispatchEvent(context, state, 'pressend', props.onPressEnd);
286
+ dispatchEvent(
287
+ event,
288
+ context,
289
+ state,
290
+ 'pressend',
291
+ props.onPressEnd,
292
+ true,
293
+ delay,
294
+ );
295
}
296
if (props.onPressChange) {
216
- dispatchPressChangeEvent(context, props, state);
297
+ dispatchPressChangeEvent(event, context, props, state, delay);
298
}
299
if (wasLongPressed && props.onLongPressChange) {
219
- dispatchLongPressChangeEvent(context, props, state);
300
+ dispatchLongPressChangeEvent(event, context, props, state, delay);
301
}
302
}
303
@@ -233,9 +314,9 @@ function dispatchPressStartEvents(
314
state.pressEndTimeout = null;
315
}
316
236
- const dispatch = () => {
317
+ const dispatch = (delay: number) => {
318
state.isActivePressStart = true;
238
- activate(event, context, props, state);
319
+ activate(event, context, props, state, delay);
320
321
if (
322
(props.onLongPress || props.onLongPressChange) &&
@@ -250,10 +331,24 @@ function dispatchPressStartEvents(
331
state.isLongPressed = true;
332
state.longPressTimeout = null;
333
if (props.onLongPress) {
253
- dispatchEvent(context, state, 'longpress', props.onLongPress);
334
+ dispatchEvent(
335
+ event,
336
+ context,
337
+ state,
338
+ 'longpress',
339
+ props.onLongPress,
340
+ true,
341
+ delayLongPress + delay,
342
+ );
343
}
344
if (props.onLongPressChange) {
256
- dispatchLongPressChangeEvent(context, props, state);
345
+ dispatchLongPressChangeEvent(
346
+ event,
347
+ context,
348
+ props,
349
+ state,
350
+ delayLongPress + delay,
351
+ );
352
}
353
}, delayLongPress);
354
}
@@ -268,10 +363,10 @@ function dispatchPressStartEvents(
363
if (delayPressStart > 0) {
364
state.pressStartTimeout = context.setTimeout(() => {
365
state.pressStartTimeout = null;
271
- dispatch();
366
+ dispatch(delayPressStart);
367
}, delayPressStart);
368
} else {
274
- dispatch();
369
+ dispatch(0);
370
}
371
}
372
}
@@ -299,7 +394,7 @@ function dispatchPressEndEvents(
394
// don't activate if a press has moved beyond the responder region
395
if (state.isPressWithinResponderRegion && event != null) {
396
// if we haven't yet activated (due to delays), activate now
302
- activate(event, context, props, state);
397
+ activate(event, context, props, state, 0);
398
activationWasForced = true;
399
}
400
}
@@ -316,10 +411,10 @@ function dispatchPressEndEvents(
411
if (delayPressEnd > 0) {
412
state.pressEndTimeout = context.setTimeout(() => {
413
state.pressEndTimeout = null;
319
- deactivate(context, props, state);
414
+ deactivate(event, context, props, state, delayPressEnd);
415
}, delayPressEnd);
416
} else {
322
- deactivate(context, props, state);
417
+ deactivate(event, context, props, state, 0);
418
}
419
}
420
}
@@ -368,8 +463,8 @@ function calculateResponderRegion(
463
) {
464
const pressRetentionOffset = context.objectAssign(
465
{},
371
- ...DEFAULT_PRESS_RETENTION_OFFSET,
372
- ...props.pressRetentionOffset,
466
+ DEFAULT_PRESS_RETENTION_OFFSET,
467
+ props.pressRetentionOffset,
468
);
469
470
const clientRect = target.getBoundingClientRect();
@@ -656,9 +751,15 @@ const PressResponder = {
751
if (state.isPressWithinResponderRegion) {
752
if (state.isPressed) {
753
if (props.onPressMove) {
659
- dispatchEvent(context, state, 'pressmove', props.onPressMove, {
660
- discrete: false,
661
- });
754
+ dispatchEvent(
755
+ event,
756
+ context,
757
+ state,
758
+ 'pressmove',
759
+ props.onPressMove,
760
+ false,
761
+ 0,
762
+ );
763
}
764
if (
765
state.activationPosition != null &&
@@ -735,7 +836,15 @@ const PressResponder = {
836
props.onLongPressShouldCancelPress()
837
)
838
) {
738
- dispatchEvent(context, state, 'press', props.onPress);
839
+ dispatchEvent(
840
+ event,
841
+ context,
842
+ state,
843
+ 'press',
844
+ props.onPress,
845
+ true,
846
+ 0,
847
+ );
848
}
849
}
850
}
packages/react-events/src/__tests__/Press-test.internal.js
+153
@@ -2053,6 +2053,159 @@ describe('Event responder: Press', () => {
2053
ref.current.dispatchEvent(createEvent('pointermove'));
2054
ref.current.dispatchEvent(createEvent('pointerup'));
2055
ref.current.dispatchEvent(createEvent('pointerdown'));
2056
+ });
2057
+
2058
+ it('should correctly pass through event properties', () => {
2059
+ const timeStamps = [];
2060
+ const ref = React.createRef();
2061
+ const eventLog = [];
2062
+ const logEvent = event => {
2063
+ const propertiesWeCareAbout = {
2064
+ pageX: event.pageX,
2065
+ pageY: event.pageY,
2066
+ screenX: event.screenX,
2067
+ screenY: event.screenY,
2068
+ clientX: event.clientX,
2069
+ clientY: event.clientY,
2070
+ pointerType: event.pointerType,
2071
+ target: event.target,
2072
+ timeStamp: event.timeStamp,
2073
+ type: event.type,
2074
+ };
2075
+ timeStamps.push(event.timeStamp);
2076
+ eventLog.push(propertiesWeCareAbout);
2077
+ };
2078
+ const element = (
2079
+ <Press
2080
+ onPressStart={logEvent}
2081
+ onPressEnd={logEvent}
2082
+ onPressMove={logEvent}
2083
+ onLongPress={logEvent}
2084
+ onPress={logEvent}>
2085
+ <button ref={ref} />
2086
+ </Press>
2087
+ );
2088
ReactDOM.render(element, container);
2089
+
2090
+ ref.current.dispatchEvent(
2091
+ createEvent('pointerdown', {
2092
+ pointerType: 'mouse',
2093
+ pageX: 15,
2094
+ pageY: 16,
2095
+ screenX: 20,
2096
+ screenY: 21,
2097
+ clientX: 30,
2098
+ clientY: 31,
2099
+ }),
2100
+ );
2101
+ jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
2102
+ ref.current.dispatchEvent(
2103
+ createEvent('pointermove', {
2104
+ pointerType: 'mouse',
2105
+ pageX: 16,
2106
+ pageY: 17,
2107
+ screenX: 21,
2108
+ screenY: 22,
2109
+ clientX: 31,
2110
+ clientY: 32,
2111
+ }),
2112
+ );
2113
+ ref.current.dispatchEvent(
2114
+ createEvent('pointerup', {
2115
+ pointerType: 'mouse',
2116
+ pageX: 17,
2117
+ pageY: 18,
2118
+ screenX: 22,
2119
+ screenY: 23,
2120
+ clientX: 32,
2121
+ clientY: 33,
2122
+ }),
2123
+ );
2124
+ ref.current.dispatchEvent(
2125
+ createEvent('pointerdown', {
2126
+ pointerType: 'mouse',
2127
+ pageX: 18,
2128
+ pageY: 19,
2129
+ screenX: 23,
2130
+ screenY: 24,
2131
+ clientX: 33,
2132
+ clientY: 34,
2133
+ }),
2134
+ );
2135
+ expect(typeof timeStamps[0] === 'number').toBe(true);
2136
+ expect(eventLog).toEqual([
2137
+ {
2138
+ pointerType: 'mouse',
2139
+ pageX: 15,
2140
+ pageY: 16,
2141
+ screenX: 20,
2142
+ screenY: 21,
2143
+ clientX: 30,
2144
+ clientY: 31,
2145
+ target: ref.current,
2146
+ timeStamp: timeStamps[0],
2147
+ type: 'pressstart',
2148
+ },
2149
+ {
2150
+ pointerType: 'mouse',
2151
+ pageX: 15,
2152
+ pageY: 16,
2153
+ screenX: 20,
2154
+ screenY: 21,
2155
+ clientX: 30,
2156
+ clientY: 31,
2157
+ target: ref.current,
2158
+ timeStamp: timeStamps[0] + DEFAULT_LONG_PRESS_DELAY,
2159
+ type: 'longpress',
2160
+ },
2161
+ {
2162
+ pointerType: 'mouse',
2163
+ pageX: 16,
2164
+ pageY: 17,
2165
+ screenX: 21,
2166
+ screenY: 22,
2167
+ clientX: 31,
2168
+ clientY: 32,
2169
+ target: ref.current,
2170
+ timeStamp: timeStamps[2],
2171
+ type: 'pressmove',
2172
+ },
2173
+ {
2174
+ pointerType: 'mouse',
2175
+ pageX: 17,
2176
+ pageY: 18,
2177
+ screenX: 22,
2178
+ screenY: 23,
2179
+ clientX: 32,
2180
+ clientY: 33,
2181
+ target: ref.current,
2182
+ timeStamp: timeStamps[3],
2183
+ type: 'pressend',
2184
+ },
2185
+ {
2186
+ pointerType: 'mouse',
2187
+ pageX: 17,
2188
+ pageY: 18,
2189
+ screenX: 22,
2190
+ screenY: 23,
2191
+ clientX: 32,
2192
+ clientY: 33,
2193
+ target: ref.current,
2194
+ timeStamp: timeStamps[3],
2195
+ type: 'press',
2196
+ },
2197
+ {
2198
+ pointerType: 'mouse',
2199
+ pageX: 18,
2200
+ pageY: 19,
2201
+ screenX: 23,
2202
+ screenY: 24,
2203
+ clientX: 33,
2204
+ clientY: 34,
2205
+ target: ref.current,
2206
+ timeStamp: timeStamps[5],
2207
+ type: 'pressstart',
2208
+ },
2209
+ ]);
2210
});
2211
});