Skip to content

fix: hit-test the topmost view for touch :hover#9731

Draft
MatiPl01 wants to merge 2 commits into
@matipl01/css-touch-hoverfrom
@matipl01/css-hover-hit-test-topmost
Draft

fix: hit-test the topmost view for touch :hover#9731
MatiPl01 wants to merge 2 commits into
@matipl01/css-touch-hoverfrom
@matipl01/css-hover-hit-test-topmost

Conversation

@MatiPl01

Copy link
Copy Markdown
Member

Stacked on #9720 (touch :hover) — it modifies files introduced there. Merge #9720 first.

Summary

Touch :hover recomputed which views are hovered with a flat on-screen bounds test, so two views that overlap on screen but are not in an ancestor relationship both got :hover. CSS hit-tests the topmost element and applies :hover to it and its ancestors only.

This hit-tests the topmost view and hovers it plus its registered ancestors, on each touch-down:

  • Android: builds the hit path via RN's TouchTargetHelper, rooted on the touched view's own window, so :hover also works inside a Modal/Dialog (a separate window).
  • iOS: [window hitTest:] + a superview walk — the idiom already shipped for :active.

The pointer path (OnHoverListener / UIHoverGestureRecognizer) was already hit-tested correctly; only the touch recompute changes.

Test plan

Validated on the Android emulator: overlapping sibling :hover boxes → only the topmost hovers; :hover inside a Modal works; ancestor propagation, sticky persistence, recompute-on-new-tap, and blank-space clear all hold.

Known limitations (low severity)

  • A view displaced outside its parent's bounds by a transform may not hover, since hitTest skips it (now consistent with :active).
  • Multi-touch :hover is undefined; a second finger recomputes from its own point.

Touch :hover recomputed which views are hovered with a flat on-screen bounds
test, so two views that overlap without an ancestor relationship both hovered.
Hit-test the topmost view instead and hover it plus its ancestors, matching CSS
and the pointer path. Android builds the hit path via TouchTargetHelper rooted on
the touched view's own window, so :hover also works inside a Modal/Dialog; iOS
uses UIView hitTest, mirroring the :active idiom.
@MatiPl01 MatiPl01 self-assigned this Jun 22, 2026
@wisniewskij wisniewskij self-assigned this Jun 23, 2026
@wisniewskij wisniewskij force-pushed the @matipl01/css-touch-hover branch 2 times, most recently from ddc6205 to e0690e9 Compare June 23, 2026 11:17
The window touch observer set its state to .failed on touch end/cancel. Because
it never sets .began it never claims a touch, so failing was pointless and made
UIKit stop delivering the rest of a multi-touch sequence to it (losing the
scroll-clear slop and the new-finger recompute). Drop the .failed writes so it
stays .possible.

Also drop the window<->screen round-trip: observeTouchBegan converted the touch
to screen space only for recompute to convert it straight back for hitTest, so
track and hit-test in window space directly.
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