@samitouri / QOS-React-2 / commits / 05d08500b2

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 });