Skip to content

fix(pointer): label a touch/pen contact as the primary button so JS press handling fires#16338

Open
FaithfulAudio wants to merge 1 commit into
microsoft:mainfrom
FacilitronWorks:fix/touch-primary-button-main
Open

fix(pointer): label a touch/pen contact as the primary button so JS press handling fires#16338
FaithfulAudio wants to merge 1 commit into
microsoft:mainfrom
FacilitronWorks:fix/touch-primary-button-main

Conversation

@FaithfulAudio

@FaithfulAudio FaithfulAudio commented Jul 26, 2026

Copy link
Copy Markdown

fix(pointer): label a touch/pen contact as the primary button so JS press handling fires

Tracks #16332. This is the main counterpart of the primary-button labeling change in #16333 (which targets 0.83-stable).

Problem

On real touch hardware, finger taps on Pressable / TouchableOpacity are 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, onPointerPressed maps ActiveTouch.button exclusively from PointerUpdateKind — a mouse-only concept:

default:
  activeTouch.button = -1;
  break;

A touch or pen contact has no mouse PointerUpdateKind, so it falls through to default: and gets button = -1; the derived W3C buttons bitmask then becomes 0. The dispatched pointerdown tells 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.button fell through to a negative value, set activeTouch.button = 0 (yielding buttons = 1 while the contact is down). Mouse behaviour is unchanged, and the < 0 guard 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

main already addresses the other problems #16333 covers, by different means, so this PR ports only the button-labeling change:

  • The tag == -1 release leak and the stale-pointer-reuse leak were fixed on main by Fix touch event handling, improve reliability, and optimize performance #16048 (Fix touch event handling, improve reliability, and optimize performance, merged 2026-04-24). Note main dispatches a synthesized touch Cancel for these, not a touch End.
  • main also cancels capture loss per-pointer and has onPointerRoutedAway.

#16333 additionally contains a cancel-all-active-touches loop in onPointerCaptureLost, an IsPrimary self-heal purge, and a stale-touch time backstop. None of those exist on main, and I have not assessed whether they are still needed theremain'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:: alias main already uses in this function — is running in production as part of #16333 on RNW 0.83.2: compiled into Microsoft.ReactNative from 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 real PT_TOUCH injection (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:

  • No CI has run on this branch. Azure Pipelines here is gated behind a maintainer comment, so I cannot start it; a /azp run would be appreciated.
  • I have not built main with this change, nor exercised it on a device on main. The behavioural evidence above comes from the 0.83.2 app.
  • Lint/format gates are unrun locally. The added block was written to match the file's existing style (the Composition::Input:: alias used elsewhere in the function; the condition line is 117 columns, within this repo's ColumnLimit: 120) — by inspection, not by running the tools.
  • That main's surrounding code is materially identical to the 0.83 code this was validated on rests on reading main's CompositionEventHandler.cpp at the baseline commit (same switch, same default: button = -1), not on differential testing on main.

Caveats for reviewers:

Microsoft Reviewers: Open in CodeFlow

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.
@FaithfulAudio
FaithfulAudio requested a review from a team as a code owner July 26, 2026 07:51
@azure-pipelines

Copy link
Copy Markdown
Contributor
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

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