Skip to content

Commit 76d4186

Browse files
Alex Danofffacebook-github-bot
authored andcommitted
W3CPointerEvents: emit click events based on pointerDown/pointerUp (#37541)
Summary: Pull Request resolved: #37541 This diff makes changes to emit bubbling click events (i.e. [these](https://www.w3.org/TR/uievents/#click)) based on the existing pointer-down/pointer-up events. As per the [pointer events spec](https://www.w3.org/TR/pointerevents3/#the-click-auxclick-and-contextmenu-events), click events are still dispatched as PointerEvents (i.e. conform to the PointerEvent interface). Changelog: [Internal] [Added] - W3CPointerEvents: emit click events based on pointerDown/pointerUp Reviewed By: NickGerleman Differential Revision: D45666344 fbshipit-source-id: 08829d3a24fef6851c1c3c29d4fa51b31ec68931
1 parent 2f1c94e commit 76d4186

File tree

4 files changed

+74
-5
lines changed

4 files changed

+74
-5
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,16 @@ public void setPointerMoveCapture(@NonNull T view, boolean value) {
700700
setPointerEventsFlag(view, PointerEventHelper.EVENT.MOVE_CAPTURE, value);
701701
}
702702

703+
@ReactProp(name = "onClick")
704+
public void setClick(@NonNull T view, boolean value) {
705+
setPointerEventsFlag(view, PointerEventHelper.EVENT.CLICK, value);
706+
}
707+
708+
@ReactProp(name = "onClickCapture")
709+
public void setClickCapture(@NonNull T view, boolean value) {
710+
setPointerEventsFlag(view, PointerEventHelper.EVENT.CLICK_CAPTURE, value);
711+
}
712+
703713
/* Experimental W3C Pointer events end */
704714

705715
@ReactProp(name = "onMoveShouldSetResponder")

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class JSPointerDispatcher {
4141

4242
private Map<Integer, List<ViewTarget>> mLastHitPathByPointerId;
4343
private Map<Integer, float[]> mLastEventCoordinatesByPointerId;
44+
private Map<Integer, List<ViewTarget>> mCurrentlyDownPointerIdsToHitPath;
4445
private Set<Integer> mHoveringPointerIds = new HashSet<>();
4546

4647
private int mChildHandlingNativeGesture = -1;
@@ -53,6 +54,7 @@ public class JSPointerDispatcher {
5354

5455
public JSPointerDispatcher(ViewGroup viewGroup) {
5556
mRootViewGroup = viewGroup;
57+
mCurrentlyDownPointerIdsToHitPath = new HashMap<>();
5658
}
5759

5860
public void onChildStartedNativeGesture(
@@ -87,6 +89,30 @@ public void onChildEndedNativeGesture() {
8789
mChildHandlingNativeGesture = -1;
8890
}
8991

92+
// returns the section of the hit path shared by both lists, or an empty list if there's no such
93+
// section
94+
private static List<ViewTarget> findHitPathIntersection(
95+
final List<ViewTarget> hitsA, final List<ViewTarget> hitsB) {
96+
if (hitsA.isEmpty()) {
97+
return new ArrayList<>();
98+
}
99+
if (hitsB.isEmpty()) {
100+
return new ArrayList<>();
101+
}
102+
103+
Set<ViewTarget> inA = new HashSet<>(hitsA);
104+
105+
List<ViewTarget> intersection = new ArrayList<>();
106+
107+
for (final ViewTarget vt : hitsB) {
108+
if (inA.contains(vt)) {
109+
intersection.add(vt);
110+
}
111+
}
112+
113+
return intersection;
114+
}
115+
90116
private void onUp(
91117
int activeTargetTag,
92118
PointerEventState eventState,
@@ -127,6 +153,18 @@ private void onUp(
127153
eventDispatcher);
128154
}
129155

156+
List<ViewTarget> hitPathDown = mCurrentlyDownPointerIdsToHitPath.remove(activePointerId);
157+
if (hitPathDown != null
158+
&& isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.CLICK, EVENT.CLICK_CAPTURE)) {
159+
List<ViewTarget> hitPathForClick = findHitPathIntersection(hitPathDown, activeHitPath);
160+
if (!hitPathForClick.isEmpty()) {
161+
final ViewTarget clickTarget = hitPathForClick.get(0);
162+
eventDispatcher.dispatchEvent(
163+
PointerEvent.obtain(
164+
PointerEventHelper.CLICK, clickTarget.getViewId(), eventState, motionEvent));
165+
}
166+
}
167+
130168
if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
131169
mPrimaryPointerId = UNSET_POINTER_ID;
132170
}
@@ -175,6 +213,12 @@ private void onDown(
175213
eventDispatcher);
176214
}
177215

216+
// store some information if we might need to emit a click later on
217+
if (isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.CLICK, EVENT.CLICK_CAPTURE)) {
218+
mCurrentlyDownPointerIdsToHitPath.put(
219+
eventState.getActivePointerId(), new ArrayList<>(activeHitPath));
220+
}
221+
178222
boolean listeningForDown =
179223
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
180224
if (listeningForDown) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PointerEvent.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ public String getEventName() {
9191
return mEventName;
9292
}
9393

94+
private boolean isClickEvent() {
95+
return mEventName.equals(PointerEventHelper.CLICK);
96+
}
97+
9498
@Override
9599
public void dispatch(RCTEventEmitter rctEventEmitter) {
96100
if (mMotionEvent == null) {
@@ -190,7 +194,8 @@ private WritableMap createW3CPointerEvent(int index) {
190194
pointerEvent.putString("pointerType", pointerType);
191195

192196
boolean isPrimary =
193-
mEventState.supportsHover(pointerId) || pointerId == mEventState.mPrimaryPointerId;
197+
!isClickEvent() // compatibility click events should not be considered primary
198+
&& (mEventState.supportsHover(pointerId) || pointerId == mEventState.mPrimaryPointerId);
194199
pointerEvent.putBoolean("isPrimary", isPrimary);
195200

196201
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
@@ -223,8 +228,8 @@ private WritableMap createW3CPointerEvent(int index) {
223228
pointerEvent.putDouble("tiltY", 0);
224229

225230
pointerEvent.putInt("twist", 0);
226-
227-
if (pointerType.equals(PointerEventHelper.POINTER_TYPE_MOUSE)) {
231+
// note: click events should have width = height = 1
232+
if (pointerType.equals(PointerEventHelper.POINTER_TYPE_MOUSE) || isClickEvent()) {
228233
pointerEvent.putDouble("width", 1);
229234
pointerEvent.putDouble("height", 1);
230235
} else {
@@ -241,8 +246,12 @@ private WritableMap createW3CPointerEvent(int index) {
241246
pointerEvent.putInt(
242247
"buttons", PointerEventHelper.getButtons(mEventName, pointerType, buttonState));
243248

244-
pointerEvent.putDouble(
245-
"pressure", PointerEventHelper.getPressure(pointerEvent.getInt("buttons"), mEventName));
249+
final double pressure =
250+
isClickEvent() // click events need pressure=0
251+
? 0
252+
: PointerEventHelper.getPressure(pointerEvent.getInt("buttons"), mEventName);
253+
254+
pointerEvent.putDouble("pressure", pressure);
246255
pointerEvent.putDouble("tangentialPressure", 0.0);
247256

248257
return pointerEvent;
@@ -264,6 +273,7 @@ private List<WritableMap> createPointersEventData() {
264273
case PointerEventHelper.POINTER_LEAVE:
265274
case PointerEventHelper.POINTER_OUT:
266275
case PointerEventHelper.POINTER_OVER:
276+
case PointerEventHelper.CLICK:
267277
pointersEventData = Arrays.asList(createW3CPointerEvent(activePointerIndex));
268278
break;
269279
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PointerEventHelper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class PointerEventHelper {
2525
public static enum EVENT {
2626
CANCEL,
2727
CANCEL_CAPTURE,
28+
CLICK,
29+
CLICK_CAPTURE,
2830
DOWN,
2931
DOWN_CAPTURE,
3032
ENTER,
@@ -49,6 +51,7 @@ public static enum EVENT {
4951
public static final String POINTER_UP = "topPointerUp";
5052
public static final String POINTER_OVER = "topPointerOver";
5153
public static final String POINTER_OUT = "topPointerOut";
54+
public static final String CLICK = "topClick";
5255

5356
// https://w3c.github.io/pointerevents/#the-buttons-property
5457
public static int getButtons(String eventName, String pointerType, int buttonState) {
@@ -117,6 +120,8 @@ public static boolean isListening(@Nullable View view, EVENT event) {
117120
case UP_CAPTURE:
118121
case CANCEL:
119122
case CANCEL_CAPTURE:
123+
case CLICK:
124+
case CLICK_CAPTURE:
120125
return true;
121126
}
122127

0 commit comments

Comments
 (0)