Skip to content

[Fix] touch event handling#3

Open
gmacmaster wants to merge 4 commits into
mainfrom
fix/touch-event-handling
Open

[Fix] touch event handling#3
gmacmaster wants to merge 4 commits into
mainfrom
fix/touch-event-handling

Conversation

@gmacmaster
Copy link
Copy Markdown

No description provided.

gmacmaster and others added 2 commits April 17, 2026 22:49
…ssified as mouse

In onPointerPressed(), activeTouch.touchType was unconditionally set to
UITouchType::Mouse. Now reads pointerPoint.PointerDeviceType() to correctly
classify touch, pen, and mouse inputs, matching the pattern already used
in WindowsTextInputComponentView.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…local

screenPoint was incorrectly set to ptLocal (element-local coordinates),
same as offsetPoint. Changed to ptScaled (document-relative coordinates)
which is consistent with how other React Native platforms report
screenX/screenY in touch events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 18, 2026

Greptile Summary

This PR fixes two clear bugs: screenPoint was being assigned local (element-relative) coordinates instead of scaled page coordinates, and touchType was hardcoded to Mouse regardless of the actual pointer device. It also introduces DispatchSynthesizedTouchCancelForActiveTouch for synthesizing cancel events, but this function has no call sites — the scenario it is meant to handle is not yet wired up.

  • Dead code: DispatchSynthesizedTouchCancelForActiveTouch is declared and defined but never invoked anywhere in the codebase; the feature it supports appears incomplete.
  • targetTouches spec deviation: the new cancel helper includes the cancelled touch in targetTouches, which violates the W3C Touch Events spec (though this mirrors an existing issue in DispatchTouchEvent).

Confidence Score: 4/5

Safe to merge the two bug fixes, but the new cancel helper is dead code indicating an incomplete feature.

The screenPoint and touchType fixes are clearly correct. However, DispatchSynthesizedTouchCancelForActiveTouch has no callers, suggesting the cancel-synthesizing feature it enables is not yet connected — shipping dead code that emits events could mask a missing behavior or confuse future contributors.

CompositionEventHandler.cpp — specifically the missing call site for DispatchSynthesizedTouchCancelForActiveTouch

Important Files Changed

Filename Overview
vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp Fixes screenPoint to use ptScaled (was ptLocal), properly dispatches UITouchType per device, and adds DispatchSynthesizedTouchCancelForActiveTouch — but the new function has no call sites (dead code) and targetTouches incorrectly includes the cancelled touch.
vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h Adds declaration for DispatchSynthesizedTouchCancelForActiveTouch; straightforward header change, no issues beyond the corresponding .cpp concerns.

Sequence Diagram

sequenceDiagram
    participant W as Windows Input
    participant CEH as CompositionEventHandler
    participant EE as TouchEventEmitter

    W->>CEH: onPointerPressed(args)
    CEH->>CEH: detect PointerDeviceType (Touch/Pen/Mouse)
    CEH->>CEH: UpdateActiveTouch(ptScaled, ptLocal)
    note right of CEH: pagePoint=ptScaled, screenPoint=ptScaled (fixed), offsetPoint=ptLocal
    CEH->>EE: DispatchTouchEvent(Start, ...)

    W->>CEH: onPointerMoved / onPointerReleased
    CEH->>CEH: UpdateActiveTouch
    CEH->>EE: DispatchTouchEvent(Move/End, ...)

    note over CEH,EE: New function — no call site yet
    CEH-->>EE: DispatchSynthesizedTouchCancelForActiveTouch
    note right of EE: onPointerCancel + onTouchCancel
Loading

Reviews (1): Last reviewed commit: "Fix touch cancel to include all active t..." | Re-trigger Greptile

Comment on lines +1491 to +1530
void CompositionEventHandler::DispatchSynthesizedTouchCancelForActiveTouch(
const ActiveTouch &cancelledTouch,
const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
winrt::Windows::System::VirtualKeyModifiers keyModifiers) {
if (!cancelledTouch.eventEmitter) {
return;
}

facebook::react::PointerEvent pointerEvent =
CreatePointerEventFromActiveTouch(cancelledTouch, TouchEventType::Cancel);
winrt::Microsoft::ReactNative::ComponentView targetView{nullptr};
facebook::react::SharedTouchEventEmitter emitter = cancelledTouch.eventEmitter;
auto pointerHandler = [emitter, pointerEvent](std::vector<winrt::Microsoft::ReactNative::ComponentView> &) {
emitter->onPointerCancel(pointerEvent);
};
HandleIncomingPointerEvent(pointerEvent, targetView, pointerPoint, keyModifiers, pointerHandler);

facebook::react::TouchEvent touchEvent;
touchEvent.changedTouches.insert(cancelledTouch.touch);

for (const auto &pair : m_activeTouches) {
if (!pair.second.eventEmitter) {
continue;
}

if (touchEvent.changedTouches.find(pair.second.touch) != touchEvent.changedTouches.end()) {
continue;
}

touchEvent.touches.insert(pair.second.touch);
}

for (const auto &pair : m_activeTouches) {
if (pair.second.eventEmitter == cancelledTouch.eventEmitter) {
touchEvent.targetTouches.insert(pair.second.touch);
}
}

cancelledTouch.eventEmitter->onTouchCancel(touchEvent);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 DispatchSynthesizedTouchCancelForActiveTouch is never called

This new function is defined and declared in the header, but there are no call sites anywhere in the codebase. If it was intended to synthesize cancel events for a specific scenario (e.g. pointer capture loss or view detachment), the call site was not added. Dead-code that emits events is unlikely to be intentional — please either wire it up where needed or remove it if it's reserved for a follow-up.

Comment on lines +1523 to +1527
for (const auto &pair : m_activeTouches) {
if (pair.second.eventEmitter == cancelledTouch.eventEmitter) {
touchEvent.targetTouches.insert(pair.second.touch);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 targetTouches includes the cancelled touch itself

Per the W3C Touch Events spec, targetTouches for a touchcancel should only list touches that are still active on the same target — the cancelled touch should be excluded (it belongs only in changedTouches). The loop here doesn't filter out cancelledTouch.touch, so targetTouches will incorrectly contain the touch that was just cancelled.

Note that DispatchTouchEvent has the same pattern (lines ~1621-1624), so this is a pre-existing issue being replicated; however, correcting it in the new function would be straightforward:

  for (const auto &pair : m_activeTouches) {
    if (pair.second.eventEmitter == cancelledTouch.eventEmitter &&
        touchEvent.changedTouches.find(pair.second.touch) == touchEvent.changedTouches.end()) {
      touchEvent.targetTouches.insert(pair.second.touch);
    }
  }

@gmacmaster gmacmaster force-pushed the fix/touch-event-handling branch from be8c697 to e316584 Compare April 18, 2026 21:04
…spec compliance)

DispatchSynthesizedTouchCancelForActiveTouch() was filtering the touches
array to only include touches from the same eventEmitter. Per W3C spec,
touches should contain all active touch points on the surface, matching
the behavior in DispatchTouchEvent(). targetTouches remains correctly
scoped to the target element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gmacmaster gmacmaster force-pushed the fix/touch-event-handling branch from e316584 to 1926ab9 Compare April 18, 2026 21:07
@gmacmaster
Copy link
Copy Markdown
Author

@grepile review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant