fix(pointer): label a touch/pen contact as the primary button so JS press handling fires - #16338
Merged
Andrew Coates (acoates-ms) merged 1 commit intoAug 10, 2026
Conversation
onPointerPressed maps ActiveTouch.button exclusively from PointerUpdateKind, a mouse-only concept. A touch or pen contact matches no case, falls through to default: button = -1, and the derived W3C buttons bitmask becomes 0. The pointerdown delivered to JS therefore claims no button is pressed, so pointer-event-driven press handling discards finger contacts while identical mouse clicks work. Per W3C pointer-events a touch/pen contact IS the primary button: button 0, buttons 1. Set that after the switch when the mouse mapping left it negative. main counterpart of the button-labeling change in microsoft#16333 (0.83-stable), tracking microsoft#16332. Ports only that change: main already covers the tag == -1 release leak and the stale-pointer-reuse leak via microsoft#16048 (dispatching a synthesized touch Cancel), and cancels capture loss per pointer. microsoft#16333's cancel-all loop, IsPrimary purge and stale-touch backstop are deliberately not ported - they do not exist on main and their necessity there has not been assessed.
Contributor
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Andrew Coates (acoates-ms)
approved these changes
Aug 4, 2026
Contributor
|
/azp run |
Contributor
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Andrew Coates (acoates-ms)
merged commit Aug 10, 2026
5411eac
into
microsoft:main
22 of 25 checks passed
Vladimir Morozov (vmoroz)
pushed a commit
that referenced
this pull request
Aug 21, 2026
* fix(scrollview): honor programmatic scrollTo when scrollEnabled={false} (#16336)
scrollEnabled={false} must only disable user scroll gestures, matching iOS and
Android where setContentOffset / scrollToOffset still work when scrolling is
disabled. The scrollTo command (and scrollToIndex / scrollToOffset, which route
through it) previously hit a scrollEnabled early-return and was silently
dropped. User-gesture input is gated separately via m_scrollVisual.ScrollEnabled
(set from scrollEnabled in updateProps), so honoring a programmatic scroll here
does not re-enable user scrolling.
main-branch twin of #16304 (0.83-stable).
* fix(textinput): correct placeholder layout constraints (px vs DIP) and no-op NaN fontSize guard (#16317)
* fix(textinput): correct placeholder layout constraints (px vs DIP) and no-op NaN fontSize guard
Forward-port of #16303 (0.83-stable) to main.
CreatePlaceholderLayout fed m_imgWidth/m_imgHeight - which are physical
pixels (frame * pointScaleFactor) - into LayoutConstraints, which are
expressed in DIPs. The placeholder was laid out in a box pointScaleFactor
times too large, so it measured and positioned at a different height than
the typed text. Divide by pointScaleFactor.
The NaN fontSize guard was also a no-op: it evaluated
defaultTextAttributes().fontSize as a discarded expression statement
instead of assigning it, so a placeholder with no fontSize never picked
up the default.
* add beachball change file
* Update release type to prerelease
Change type from 'patch' to 'prerelease' for react-native-windows.
---------
Co-authored-by: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
* fix(pointer): label a touch/pen contact as the primary button (#16338)
onPointerPressed maps ActiveTouch.button exclusively from PointerUpdateKind, a
mouse-only concept. A touch or pen contact matches no case, falls through to
default: button = -1, and the derived W3C buttons bitmask becomes 0. The
pointerdown delivered to JS therefore claims no button is pressed, so
pointer-event-driven press handling discards finger contacts while identical
mouse clicks work.
Per W3C pointer-events a touch/pen contact IS the primary button: button 0,
buttons 1. Set that after the switch when the mouse mapping left it negative.
main counterpart of the button-labeling change in #16333 (0.83-stable), tracking
#16332. Ports only that change: main already covers the tag == -1 release leak
and the stale-pointer-reuse leak via #16048 (dispatching a synthesized touch
Cancel), and cancels capture loss per pointer. #16333's cancel-all loop,
IsPrimary purge and stale-touch backstop are deliberately not ported - they do
not exist on main and their necessity there has not been assessed.
* fix(pointer): null-check the capturing component view before notifying OnPointerCaptureLost (#16337)
CapturePointer and releasePointerCapture look the capturing component up by its
cached m_pointerCapturingComponentTag and dereference the result unguarded. That
tag can outlive the component it names: when list/ScrollView virtualization
recycles the capturing row mid-pan, componentViewDescriptorWithTag returns a
descriptor whose .view is null, so winrt::get_self(...)->OnPointerCaptureLost()
dereferences null and terminates the process with 0xc0000005.
Null-check targetComponentView at both sites. Skipping the notify loses no state
transition: CapturePointer overwrites the stale tag immediately below, and
releasePointerCapture clears it via the existing m_capturedPointers.empty()
branch.
main twin of #16334 (0.83-stable).
* Fix modifying outline property
* delete createRnwApp tests in 0.81 branch
* Change files
---------
Co-authored-by: Collin Schneide <27441618+FaithfulAudio@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(pointer): label a touch/pen contact as the primary button so JS press handling fires
Tracks #16332. This is the
maincounterpart of the primary-button labeling change in #16333 (which targets0.83-stable).Problem
On real touch hardware, finger taps on
Pressable/TouchableOpacityare ignored by pointer-event-driven press machinery, while identical mouse clicks on the same element work.Root cause
In
vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp,onPointerPressedmapsActiveTouch.buttonexclusively fromPointerUpdateKind— a mouse-only concept:A touch or pen contact has no mouse
PointerUpdateKind, so it falls through todefault:and getsbutton = -1; the derived W3Cbuttonsbitmask then becomes0. The dispatchedpointerdowntells JS "no button is pressed", so press logic driven by pointer events discards finger contacts. Per W3C pointer-events, a touch/pen contact is the primary button:button 0,buttons 1.Fix
After the existing switch: if the device is not a mouse and
activeTouch.buttonfell through to a negative value, setactiveTouch.button = 0(yieldingbuttons = 1while the contact is down). Mouse behaviour is unchanged, and the< 0guard leaves untouched any non-mouse contact that somehow carried a real button mapping.Baseline:
main@c69cf55f67f9b03f467502dac1007ac2d9ebe209. One hunk, +10 lines, no header or API change.Scope — what this PR deliberately does not port
mainalready addresses the other problems #16333 covers, by different means, so this PR ports only the button-labeling change:tag == -1release leak and the stale-pointer-reuse leak were fixed onmainby Fix touch event handling, improve reliability, and optimize performance #16048 (Fix touch event handling, improve reliability, and optimize performance, merged 2026-04-24). Notemaindispatches a synthesized touch Cancel for these, not a touch End.mainalso cancels capture loss per-pointer and hasonPointerRoutedAway.#16333 additionally contains a cancel-all-active-touches loop in
onPointerCaptureLost, anIsPrimaryself-heal purge, and a stale-touch time backstop. None of those exist onmain, and I have not assessed whether they are still needed there —main's at-source cleanups may subsume them. I have left them out rather than port hardening whose necessity I cannot demonstrate on this branch. Happy to do that assessment as a follow-up if it would be useful.Validation
This exact logic — identical condition and assignment, the only textual difference being that this PR spells the enum via the
Composition::Input::aliasmainalready uses in this function — is running in production as part of #16333 on RNW 0.83.2: compiled intoMicrosoft.ReactNativefrom source (UseExperimentalNuget=false), x64 Release, new-arch app (Facilitron FIT), on a physical Surface-class touch tablet. There, finger taps on press targets went from ignored to firing, matching mouse clicks. Validated with realPT_TOUCHinjection (InitializeTouchInjection/InjectTouchInput), not simulated mouse input — the mouse path does not exercise the touch pipeline at all.Diff verified against the pinned base: exactly one hunk, +10 lines, LF endings preserved, and the file's current tip is byte-identical to that base (no drift).
Not verified — please weigh before merging:
/azp runwould be appreciated.mainwith this change, nor exercised it on a device onmain. The behavioural evidence above comes from the 0.83.2 app.Composition::Input::alias used elsewhere in the function; the condition line is 117 columns, within this repo'sColumnLimit: 120) — by inspection, not by running the tools.main's surrounding code is materially identical to the 0.83 code this was validated on rests on readingmain'sCompositionEventHandler.cppat the baseline commit (same switch, samedefault: button = -1), not on differential testing onmain.Caveats for reviewers:
type: prereleaseto match the convention inmain's pending change files; happy to adjust.Microsoft Reviewers: Open in CodeFlow