Skip to content

Commit 9ce8711

Browse files
authored
[react-events] Tap responder (#16628)
This is a partial replacement for the 'Press' responder: 1. `useTap` is scoped to pointers (no keyboard support). Our current thinking is that "responders" should be limited to working with pointers, and that they can be combined with 'useKeyboard' in user-space. For example, we might create a 'usePress' hook in user-space that combines 'useTap' with 'useKeyboard' to react to both pointers and keyboard interactions. 2. `useTap` cancels the gesture once the pointer moves over an element that is not within the responder target's subtree. This differs from `usePress` (and React Native), where the gesture remains active after the pointer exits the target's subtree and is restarted once the pointer reenters. One of the drawbacks with the `usePress` behavior is that it requires repeatedly measuring DOM elements (which can cause jank) to perform hit region tests. `useTap` avoids doing this and relies on `document.elementFromPoint` only to support the TouchEvent fallbacks. 3. `useTap` calls `onTapUpdate` when the active gesture's state changes, `onTapEnd` when the gesture successfully completes. and `onTapCancel` when it fails. There is no `onTap` callback. `usePress` did not explicitly report back when the gesture failed, and product developers were confused about the difference between `onPress` and `onPressEnd`. 4. `useTap` explicitly separates the PointerEvent implementation from the MouseEvent/TouchEvent fallback. 5. `useTap` has better unit test coverage . All pointer types and the fallback environment are tested. The shape of the gesture state object is also defined and tested.
1 parent e86146e commit 9ce8711

File tree

14 files changed

+1470
-42
lines changed

14 files changed

+1470
-42
lines changed

packages/react-dom/src/events/DOMEventResponderSystem.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ const eventResponderContext: ReactDOMResponderContext = {
119119
}
120120
}
121121
},
122-
isTargetWithinResponder(target: Element | Document): boolean {
122+
isTargetWithinResponder(target: null | Element | Document): boolean {
123123
validateResponderContext();
124124
if (target != null) {
125125
let fiber = getClosestInstanceFromNode(target);
@@ -135,7 +135,7 @@ const eventResponderContext: ReactDOMResponderContext = {
135135
}
136136
return false;
137137
},
138-
isTargetWithinResponderScope(target: Element | Document): boolean {
138+
isTargetWithinResponderScope(target: null | Element | Document): boolean {
139139
validateResponderContext();
140140
const componentInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
141141
const responder = componentInstance.responder;
@@ -158,7 +158,7 @@ const eventResponderContext: ReactDOMResponderContext = {
158158
return false;
159159
},
160160
isTargetWithinNode(
161-
childTarget: Element | Document,
161+
childTarget: null | Element | Document,
162162
parentTarget: Element | Document,
163163
): boolean {
164164
validateResponderContext();
@@ -390,11 +390,9 @@ function createDOMResponderEvent(
390390
): ReactDOMResponderEvent {
391391
const {buttons, pointerType} = (nativeEvent: any);
392392
let eventPointerType = '';
393-
let pointerId = null;
394393

395394
if (pointerType !== undefined) {
396395
eventPointerType = pointerType;
397-
pointerId = (nativeEvent: any).pointerId;
398396
} else if (nativeEvent.key !== undefined) {
399397
eventPointerType = 'keyboard';
400398
} else if (buttons !== undefined) {
@@ -407,7 +405,6 @@ function createDOMResponderEvent(
407405
nativeEvent: nativeEvent,
408406
passive,
409407
passiveSupported,
410-
pointerId,
411408
pointerType: eventPointerType,
412409
target: nativeEventTarget,
413410
type: topLevelType,

packages/react-events/npm/tap.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('./cjs/react-events-tap.production.min.js');
5+
} else {
6+
module.exports = require('./cjs/react-events-tap.development.js');
7+
}

packages/react-events/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"press.js",
2222
"scroll.js",
2323
"swipe.js",
24+
"tap.js",
2425
"build-info.json",
2526
"cjs/",
2627
"umd/"

packages/react-events/src/dom/Press.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ const pressResponderImpl = {
521521
props: PressProps,
522522
state: PressState,
523523
): void {
524-
const {pointerId, pointerType, type} = event;
524+
const {pointerType, type} = event;
525525

526526
if (props.disabled) {
527527
removeRootEventTypes(context, state);
@@ -584,7 +584,7 @@ const pressResponderImpl = {
584584
state.pointerType = pointerType;
585585
const pressTarget = (state.pressTarget = context.getResponderNode());
586586
if (isPointerEvent) {
587-
state.activePointerId = pointerId;
587+
state.activePointerId = nativeEvent.pointerId;
588588
} else if (isTouchEvent) {
589589
const touchEvent = getTouchFromPressEvent(nativeEvent);
590590
if (touchEvent === null) {
@@ -652,7 +652,7 @@ const pressResponderImpl = {
652652
props: PressProps,
653653
state: PressState,
654654
): void {
655-
let {pointerId, pointerType, target, type} = event;
655+
let {pointerType, target, type} = event;
656656

657657
const nativeEvent: any = event.nativeEvent;
658658
const isPressed = state.isPressed;
@@ -672,7 +672,10 @@ const pressResponderImpl = {
672672
if (previousPointerType !== pointerType) {
673673
return;
674674
}
675-
if (type === 'pointermove' && activePointerId !== pointerId) {
675+
if (
676+
type === 'pointermove' &&
677+
activePointerId !== nativeEvent.pointerId
678+
) {
676679
return;
677680
} else if (type === 'touchmove') {
678681
touchEvent = getTouchById(nativeEvent, activePointerId);
@@ -733,7 +736,10 @@ const pressResponderImpl = {
733736
const buttons = state.buttons;
734737
let isKeyboardEvent = false;
735738
let touchEvent;
736-
if (type === 'pointerup' && activePointerId !== pointerId) {
739+
if (
740+
type === 'pointerup' &&
741+
activePointerId !== nativeEvent.pointerId
742+
) {
737743
return;
738744
} else if (type === 'touchend') {
739745
touchEvent = getTouchById(nativeEvent, activePointerId);

0 commit comments

Comments
 (0)