@samitouri / QOS-React / commits / 296c4393da

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