Skip to content

[fix] touch issues#16009

Open
gmacmaster wants to merge 2 commits intomicrosoft:0.82-stablefrom
Virtual-Fulfillment-Technologies-Inc:0.82-stable
Open

[fix] touch issues#16009
gmacmaster wants to merge 2 commits intomicrosoft:0.82-stablefrom
Virtual-Fulfillment-Technologies-Inc:0.82-stable

Conversation

@gmacmaster
Copy link
Copy Markdown

@gmacmaster gmacmaster commented Apr 16, 2026

Description

This PR fixes touch input handling for Pressable and TextInput components on Windows by: (1) detecting and cancelling stale active touches in onPointerPressed when a pointer ID is reused without a prior release, (2) synthesizing a touch-cancel when a pointer is released outside any view (tag == -1), and (3) emitting the onPressIn event from WindowsTextInputComponentView. The new DispatchSynthesizedTouchCancelForActiveTouch helper correctly scopes cancels to a single emitter (avoiding the previous spurious broadcast to all emitters), but its touches list is built per-emitter rather than from all active touches, which is inconsistent with DispatchTouchEvent and the W3C spec.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Why

Touch screens cannot focus on text input and have flaky pressable areas.

What

Improved ouch event handling

Changelog

Should this change be included in the release notes: yes

Fixed touch screen events not properly captured for pressable and text inputs

flowchart TD
    A[Pointer Event] --> B{PointerDeviceType?}
    B -->|Mouse| C[Switch on PointerUpdateKind]
    B -->|Touch / Pen| D[IsDoubleClick?]
    C --> E[WM_LBUTTONDOWN / DBLCLK / UP / RBUTTONUP etc.]
    D -->|Yes| F[WM_LBUTTONDBLCLK]
    D -->|No| G[WM_LBUTTONDOWN or WM_LBUTTONUP]
    E --> H[TxSendMessage to RichEdit]
    F --> H
    G --> H
    H --> I[args.Handled]
    A --> J[Emit GestureResponderEvent]
    J -->|Pressed| K[onPressIn]
    J -->|Released| L[onPressOut]
Loading
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[onPointerPressed] --> B{Stale touch\nfor same pointer ID?}
    B -- Yes --> C[DispatchSynthesizedTouchCancelForActiveTouch\nstale touch]
    C --> D[Erase stale touch\nfrom m_activeTouches]
    D --> E[Register new touch &\nDispatchTouchEvent Start]
    B -- No --> E

    F[onPointerReleased] --> G{tag == -1?\nPointer outside view}
    G -- Yes --> H[DispatchSynthesizedTouchCancelForActiveTouch\nactive touch]
    H --> I[Erase touch & return]
    G -- No --> J[DispatchTouchEvent End\nErase touch]

    subgraph DispatchSynthesizedTouchCancelForActiveTouch
        K[Fire onPointerCancel\nvia HandleIncomingPointerEvent] --> L[Build TouchEvent:\nchangedTouches = cancelled touch\ntouches = same-emitter touches only ⚠️\ntargetTouches = same-emitter touches]
        L --> M[Fire onTouchCancel\non cancelled emitter only]
    end
Loading
Microsoft Reviewers: Open in CodeFlow

This PR fixes touch input handling for Pressable and TextInput components on Windows by: (1) detecting and cancelling stale active touches in onPointerPressed when a pointer ID is reused without a prior release, (2) synthesizing a touch-cancel when a pointer is released outside any view (tag == -1), and (3) emitting the onPressIn event from WindowsTextInputComponentView. The new DispatchSynthesizedTouchCancelForActiveTouch helper correctly scopes cancels to a single emitter (avoiding the previous spurious broadcast to all emitters), but its touches list is built per-emitter rather than from all active touches, which is inconsistent with DispatchTouchEvent and the W3C spec.
@gmacmaster gmacmaster requested a review from a team as a code owner April 16, 2026 18:22
@gmacmaster
Copy link
Copy Markdown
Author

@gmacmaster please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree company="Virtual Fulfillment Technologies Inc."

@vineethkuttan
Copy link
Copy Markdown
Contributor

/azp run PR

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes several touch / pointer input edge cases in the Fabric Composition event pipeline on Windows, improving reliability for Pressable and TextInput interactions (e.g., pointer-id reuse, releases outside any view, and TextInput press-in behavior).

Changes:

  • Cancel and clean up “stale” active touches when a pointer ID is reused without a prior release.
  • Synthesize a touch-cancel when a pointer is released outside any view (tag == -1), and scope the cancel to the correct emitter.
  • Update WindowsTextInput’s pointer message translation (mouse-style messages for non-mouse pointers) and fix submit-key modifier checks.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp Adjusts pointer message dispatch for TextInput (including touch/pen), emits onPressIn, and fixes modifier handling for submit behavior.
vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h Declares the new helper for synthesized touch-cancel dispatch.
vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp Adds pointer-exited handling and introduces synthesized touch-cancel logic for stale touches / releases outside a view.
change/react-native-windows-83b6df1e-ce5d-4de3-9cf1-2a5b8f9b74f5.json Adds a patch change entry documenting the touch input fix.

Comment on lines 822 to 828
if (pp.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse) {
msg = WM_MOUSEMOVE;
wParam = PointerRoutedEventArgsToMouseWParam(args);
} else {
msg = WM_POINTERUPDATE;
wParam = PointerPointToPointerWParam(pp);
msg = WM_MOUSEMOVE;
wParam = PointerRoutedEventArgsToMouseWParam(args);
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

OnPointerMoved now sets msg = WM_MOUSEMOVE and wParam = PointerRoutedEventArgsToMouseWParam(args) in both branches of the PointerDeviceType check. The conditional is redundant as written and makes future edits risk diverging behavior unintentionally; consider collapsing to a single assignment (or reintroducing device-specific handling if still needed).

Copilot uses AI. Check for mistakes.
Comment on lines +1491 to +1510
facebook::react::TouchEvent touchEvent;
touchEvent.changedTouches.insert(cancelledTouch.touch);

for (const auto &pair : m_activeTouches) {
if (!pair.second.eventEmitter || pair.second.eventEmitter != cancelledTouch.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);
}
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

DispatchSynthesizedTouchCancelForActiveTouch builds touchEvent.touches by filtering to touches that share the same eventEmitter as the cancelled touch. This makes synthesized onTouchCancel payloads inconsistent with DispatchTouchEvent, which populates touches from all active touches (minus the changed touch for end-ish events). It can break JS code that relies on touches representing all current screen touches per the TouchEvent contract. Consider constructing touchEvent.touches the same way as DispatchTouchEvent (across m_activeTouches, excluding the cancelled touch), while still dispatching only to cancelledTouch.eventEmitter and keeping targetTouches emitter-scoped.

Copilot uses AI. Check for mistakes.
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.

3 participants