Add Press event prop types and fix a check in Safari (#15288)
* Add PressProps type to event module * Move default Press event delays to constants * Fix right-click press check for Safari * Prettier and Linter * Use event.key in press responder event.keyCode is a deprecated API * Remove unused props from Press event module
Nicolas Gallagher committed
Apr 2, 2019 at 06:42 UTC
296c4393da6f8f64b44b4b574e68127389768c4c
2 files changed
+55
-20
packages/react-events/src/Press.js
+54
-18
@@ -10,6 +10,11 @@
10
import type {EventResponderContext} from 'events/EventTypes';
11
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12
13
+// const DEFAULT_PRESS_DELAY_MS = 0;
14
+// const DEFAULT_PRESS_END_DELAY_MS = 0;
15
+// const DEFAULT_PRESS_START_DELAY_MS = 0;
16
+const DEFAULT_LONG_PRESS_DELAY_MS = 1000;
17
+
18
const targetEventTypes = [
19
{name: 'click', passive: false},
20
{name: 'keydown', passive: false},
@@ -26,6 +31,21 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
31
rootEventTypes.push({name: 'mouseup', passive: false});
32
}
33
34
+type PressProps = {
35
+ disabled: boolean,
36
+ delayLongPress: number,
37
+ delayPressEnd: number,
38
+ delayPressStart: number,
39
+ onLongPress: (e: Object) => void,
40
+ onLongPressChange: boolean => void,
41
+ onLongPressShouldCancelPress: () => boolean,
42
+ onPress: (e: Object) => void,
43
+ onPressChange: boolean => void,
44
+ onPressEnd: (e: Object) => void,
45
+ onPressStart: (e: Object) => void,
46
+ pressRententionOffset: Object,
47
+};
48
+
49
type PressState = {
50
defaultPrevented: boolean,
51
isAnchorTouched: boolean,
@@ -47,7 +67,7 @@ function dispatchPressEvent(
67
68
function dispatchPressStartEvents(
69
context: EventResponderContext,
50
- props: Object,
70
+ props: PressProps,
71
state: PressState,
72
): void {
73
function dispatchPressChangeEvent(bool) {
@@ -64,7 +84,11 @@ function dispatchPressStartEvents(
84
dispatchPressChangeEvent(true);
85
}
86
if ((props.onLongPress || props.onLongPressChange) && !state.isLongPressed) {
67
- const delayLongPress = calculateDelayMS(props.delayLongPress, 0, 1000);
87
+ const delayLongPress = calculateDelayMS(
88
+ props.delayLongPress,
89
+ 0,
90
+ DEFAULT_LONG_PRESS_DELAY_MS,
91
+ );
92
93
state.longPressTimeout = setTimeout(() => {
94
state.isLongPressed = true;
@@ -105,7 +129,7 @@ function dispatchPressStartEvents(
129
130
function dispatchPressEndEvents(
131
context: EventResponderContext,
108
- props: Object,
132
+ props: PressProps,
133
state: PressState,
134
): void {
135
if (state.longPressTimeout !== null) {
@@ -138,6 +162,11 @@ function isAnchorTagElement(eventTarget: EventTarget): boolean {
162
return (eventTarget: any).nodeName === 'A';
163
}
164
165
+function isValidKeyPress(key: string): boolean {
166
+ // Accessibility for keyboards. Space and Enter only.
167
+ return key === ' ' || key === 'Enter';
168
+}
169
+
170
function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
171
const maybeNumber = delay == null ? null : delay;
172
return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
@@ -158,22 +187,18 @@ const PressResponder = {
187
},
188
handleEvent(
189
context: EventResponderContext,
161
- props: Object,
190
+ props: PressProps,
191
state: PressState,
192
): void {
193
const {eventTarget, eventType, event} = context;
194
195
switch (eventType) {
196
case 'keydown': {
168
- if (!props.onPress || context.isTargetOwned(eventTarget)) {
169
- return;
170
- }
171
- const isValidKeyPress =
172
- (event: any).which === 13 ||
173
- (event: any).which === 32 ||
174
- (event: any).keyCode === 13;
175
-
176
- if (!isValidKeyPress) {
197
+ if (
198
+ !props.onPress ||
199
+ context.isTargetOwned(eventTarget) ||
200
+ !isValidKeyPress((event: any).key)
201
+ ) {
202
return;
203
}
204
let keyPressEventListener = props.onPress;
@@ -196,8 +221,12 @@ const PressResponder = {
221
dispatchPressEvent(context, state, 'press', keyPressEventListener);
222
break;
223
}
224
+
225
+ /**
226
+ * Touch event implementations are only needed for Safari, which lacks
227
+ * support for pointer events.
228
+ */
229
case 'touchstart':
200
- // Touch events are for Safari, which lack pointer event support.
230
if (!state.isPressed && !context.isTargetOwned(eventTarget)) {
231
// We bail out of polyfilling anchor tags, given the same heuristics
232
// explained above in regards to needing to use click events.
@@ -213,7 +242,6 @@ const PressResponder = {
242
243
break;
244
case 'touchend': {
216
- // Touch events are for Safari, which lack pointer event support
245
if (state.isAnchorTouched) {
246
return;
247
}
@@ -253,6 +281,10 @@ const PressResponder = {
281
}
282
break;
283
}
284
+
285
+ /**
286
+ * Respond to pointer events and fall back to mouse.
287
+ */
288
case 'pointerdown':
289
case 'mousedown': {
290
if (
@@ -260,7 +292,10 @@ const PressResponder = {
292
!context.isTargetOwned(eventTarget) &&
293
!state.shouldSkipMouseAfterTouch
294
) {
263
- if ((event: any).pointerType === 'mouse') {
295
+ if (
296
+ (event: any).pointerType === 'mouse' ||
297
+ eventType === 'mousedown'
298
+ ) {
299
// Ignore if we are pressing on hit slop area with mouse
300
if (
301
context.isPositionWithinTouchHitTarget(
@@ -282,8 +317,8 @@ const PressResponder = {
317
}
318
break;
319
}
285
- case 'mouseup':
286
- case 'pointerup': {
320
+ case 'pointerup':
321
+ case 'mouseup': {
322
if (state.isPressed) {
323
if (state.shouldSkipMouseAfterTouch) {
324
state.shouldSkipMouseAfterTouch = false;
@@ -320,6 +355,7 @@ const PressResponder = {
355
state.isAnchorTouched = false;
356
break;
357
}
358
+
359
case 'scroll':
360
case 'touchcancel':
361
case 'contextmenu':
packages/react-events/src/__tests__/Press-test.internal.js
+1
-2
@@ -83,8 +83,7 @@ describe('Press event responder', () => {
83
84
events = [];
85
const keyDownEvent = new KeyboardEvent('keydown', {
86
- which: 13,
87
- keyCode: 13,
86
+ key: 'Enter',
87
bubbles: true,
88
cancelable: true,
89
});