W3CPointerEvents: emit click events based on pointerDown/pointerUp (#37541)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/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
This commit is contained in:
Alex Danoff
2023-05-25 16:38:53 -07:00
committed by Facebook GitHub Bot
parent 2f1c94efb1
commit 76d41864a2
4 changed files with 74 additions and 5 deletions
@@ -700,6 +700,16 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
setPointerEventsFlag(view, PointerEventHelper.EVENT.MOVE_CAPTURE, value);
}
@ReactProp(name = "onClick")
public void setClick(@NonNull T view, boolean value) {
setPointerEventsFlag(view, PointerEventHelper.EVENT.CLICK, value);
}
@ReactProp(name = "onClickCapture")
public void setClickCapture(@NonNull T view, boolean value) {
setPointerEventsFlag(view, PointerEventHelper.EVENT.CLICK_CAPTURE, value);
}
/* Experimental W3C Pointer events end */
@ReactProp(name = "onMoveShouldSetResponder")
@@ -41,6 +41,7 @@ public class JSPointerDispatcher {
private Map<Integer, List<ViewTarget>> mLastHitPathByPointerId;
private Map<Integer, float[]> mLastEventCoordinatesByPointerId;
private Map<Integer, List<ViewTarget>> mCurrentlyDownPointerIdsToHitPath;
private Set<Integer> mHoveringPointerIds = new HashSet<>();
private int mChildHandlingNativeGesture = -1;
@@ -53,6 +54,7 @@ public class JSPointerDispatcher {
public JSPointerDispatcher(ViewGroup viewGroup) {
mRootViewGroup = viewGroup;
mCurrentlyDownPointerIdsToHitPath = new HashMap<>();
}
public void onChildStartedNativeGesture(
@@ -87,6 +89,30 @@ public class JSPointerDispatcher {
mChildHandlingNativeGesture = -1;
}
// returns the section of the hit path shared by both lists, or an empty list if there's no such
// section
private static List<ViewTarget> findHitPathIntersection(
final List<ViewTarget> hitsA, final List<ViewTarget> hitsB) {
if (hitsA.isEmpty()) {
return new ArrayList<>();
}
if (hitsB.isEmpty()) {
return new ArrayList<>();
}
Set<ViewTarget> inA = new HashSet<>(hitsA);
List<ViewTarget> intersection = new ArrayList<>();
for (final ViewTarget vt : hitsB) {
if (inA.contains(vt)) {
intersection.add(vt);
}
}
return intersection;
}
private void onUp(
int activeTargetTag,
PointerEventState eventState,
@@ -127,6 +153,18 @@ public class JSPointerDispatcher {
eventDispatcher);
}
List<ViewTarget> hitPathDown = mCurrentlyDownPointerIdsToHitPath.remove(activePointerId);
if (hitPathDown != null
&& isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.CLICK, EVENT.CLICK_CAPTURE)) {
List<ViewTarget> hitPathForClick = findHitPathIntersection(hitPathDown, activeHitPath);
if (!hitPathForClick.isEmpty()) {
final ViewTarget clickTarget = hitPathForClick.get(0);
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.CLICK, clickTarget.getViewId(), eventState, motionEvent));
}
}
if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
mPrimaryPointerId = UNSET_POINTER_ID;
}
@@ -175,6 +213,12 @@ public class JSPointerDispatcher {
eventDispatcher);
}
// store some information if we might need to emit a click later on
if (isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.CLICK, EVENT.CLICK_CAPTURE)) {
mCurrentlyDownPointerIdsToHitPath.put(
eventState.getActivePointerId(), new ArrayList<>(activeHitPath));
}
boolean listeningForDown =
isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
if (listeningForDown) {
@@ -91,6 +91,10 @@ public class PointerEvent extends Event<PointerEvent> {
return mEventName;
}
private boolean isClickEvent() {
return mEventName.equals(PointerEventHelper.CLICK);
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
if (mMotionEvent == null) {
@@ -190,7 +194,8 @@ public class PointerEvent extends Event<PointerEvent> {
pointerEvent.putString("pointerType", pointerType);
boolean isPrimary =
mEventState.supportsHover(pointerId) || pointerId == mEventState.mPrimaryPointerId;
!isClickEvent() // compatibility click events should not be considered primary
&& (mEventState.supportsHover(pointerId) || pointerId == mEventState.mPrimaryPointerId);
pointerEvent.putBoolean("isPrimary", isPrimary);
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
@@ -223,8 +228,8 @@ public class PointerEvent extends Event<PointerEvent> {
pointerEvent.putDouble("tiltY", 0);
pointerEvent.putInt("twist", 0);
if (pointerType.equals(PointerEventHelper.POINTER_TYPE_MOUSE)) {
// note: click events should have width = height = 1
if (pointerType.equals(PointerEventHelper.POINTER_TYPE_MOUSE) || isClickEvent()) {
pointerEvent.putDouble("width", 1);
pointerEvent.putDouble("height", 1);
} else {
@@ -241,8 +246,12 @@ public class PointerEvent extends Event<PointerEvent> {
pointerEvent.putInt(
"buttons", PointerEventHelper.getButtons(mEventName, pointerType, buttonState));
pointerEvent.putDouble(
"pressure", PointerEventHelper.getPressure(pointerEvent.getInt("buttons"), mEventName));
final double pressure =
isClickEvent() // click events need pressure=0
? 0
: PointerEventHelper.getPressure(pointerEvent.getInt("buttons"), mEventName);
pointerEvent.putDouble("pressure", pressure);
pointerEvent.putDouble("tangentialPressure", 0.0);
return pointerEvent;
@@ -264,6 +273,7 @@ public class PointerEvent extends Event<PointerEvent> {
case PointerEventHelper.POINTER_LEAVE:
case PointerEventHelper.POINTER_OUT:
case PointerEventHelper.POINTER_OVER:
case PointerEventHelper.CLICK:
pointersEventData = Arrays.asList(createW3CPointerEvent(activePointerIndex));
break;
}
@@ -25,6 +25,8 @@ public class PointerEventHelper {
public static enum EVENT {
CANCEL,
CANCEL_CAPTURE,
CLICK,
CLICK_CAPTURE,
DOWN,
DOWN_CAPTURE,
ENTER,
@@ -49,6 +51,7 @@ public class PointerEventHelper {
public static final String POINTER_UP = "topPointerUp";
public static final String POINTER_OVER = "topPointerOver";
public static final String POINTER_OUT = "topPointerOut";
public static final String CLICK = "topClick";
// https://w3c.github.io/pointerevents/#the-buttons-property
public static int getButtons(String eventName, String pointerType, int buttonState) {
@@ -117,6 +120,8 @@ public class PointerEventHelper {
case UP_CAPTURE:
case CANCEL:
case CANCEL_CAPTURE:
case CLICK:
case CLICK_CAPTURE:
return true;
}