Skip to content

fix: share a single touch listener across press pseudo-selectors#9729

Merged
MatiPl01 merged 1 commit into
mainfrom
@matipl01/share-press-touch-listener
Jun 22, 2026
Merged

fix: share a single touch listener across press pseudo-selectors#9729
MatiPl01 merged 1 commit into
mainfrom
@matipl01/share-press-touch-listener

Conversation

@MatiPl01

Copy link
Copy Markdown
Member

Summary

On Android a View exposes a single OnTouchListener slot. :active and :active-deepest each installed their own listener, so registering both on one view left only whichever attached last working.

This routes both selectors through one shared listener (ensureTouchListener), keyed by activeCallbacks and deepestCallbacks, so they coexist. The :active-deepest arbitration (hasDeepestDescendantAt) is unchanged.

Android only: iOS already attaches a separate gesture recognizer per selector, so there is no collision there.

Test plan

On the Android emulator, on one view with both :active and :active-deepest, and on nested :active-deepest views: both selectors now fire, and the deeper view still wins the arbitration.

A View exposes a single OnTouchListener slot, so attaching both :active and
:active-deepest to one view made whichever selector registered second overwrite
the first. Funnel both through one shared listener keyed by activeCallbacks and
deepestCallbacks so they coexist.
@MatiPl01
MatiPl01 force-pushed the @matipl01/share-press-touch-listener branch from e72ae7e to e9e76cd Compare June 22, 2026 01:31
@MatiPl01
MatiPl01 requested a review from wisniewskij June 22, 2026 01:51
@MatiPl01
MatiPl01 marked this pull request as ready for review June 22, 2026 01:51
@MatiPl01
MatiPl01 added this pull request to the merge queue Jun 22, 2026
Merged via the queue into main with commit f8c8180 Jun 22, 2026
9 checks passed
@MatiPl01
MatiPl01 deleted the @matipl01/share-press-touch-listener branch June 22, 2026 11:08
pull Bot pushed a commit to esinanturan/react-native-reanimated that referenced this pull request Jun 25, 2026
> Stacked on software-mansion#9729 (shared press touch listener). The diff here is just
the `:hover` additions on top of it; review/merge software-mansion#9729 first.

## Summary

`:hover` was a no-op on touchscreens (the pointer recognizers never fire
for a finger). This makes it respond to touch with the **sticky** model
the major browser engines use: a tapped view gains `:hover` and keeps it
after the finger lifts, clearing only when a later touch lands elsewhere
or on scroll. `:active` stays press-only and distinct; real-pointer
hover (trackpad/mouse/stylus) is unchanged.

Native-only change (the pseudo-state flow is selector-agnostic). On each
touch-down a `:hover` view is hovered when its on-screen bounds contain
the point. iOS uses a shared coordinator watching the key window through
a passive, non-recognizing gesture recognizer (so it never competes with
the `:active` recognizers); Android recomputes per view plus a
`Window.Callback` observer to catch taps on blank space.

> A `:hover` style needs a non-zero `transitionDuration` to apply
(zero-duration is a pre-existing no-op).

## Demo

Tap the box: it turns green and stays green after you lift; tap
elsewhere to clear.

<!-- drag hover-touch-android.gif here -->
<!-- drag hover-touch-ios.gif here -->

---------

Co-authored-by: Jakub Wiśniewski <wisniewskij514@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jakub Wiśniewski <119816982+wisniewskij@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants