Skip to content

fix: resolve grab points in the touch's window space on Android - #13

Merged
V3RON merged 1 commit into
mainfrom
fix/android-owner-origin
Aug 31, 2026
Merged

fix: resolve grab points in the touch's window space on Android#13
V3RON merged 1 commit into
mainfrom
fix/android-owner-origin

Conversation

@V3RON

@V3RON V3RON commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #12.

The problem

Fabric measures in the coordinate space of the surface root, while touches report pageX/pageY in the coordinate space of the native window hosting that surface. The two coincide only when the surface root sits at the window origin — which is true on iOS, and false for Android's main window, where the surface starts a status bar below the window.

Hit testing rebased page coordinates onto the owner's Fabric origin as if both were the same space, so on Android every selection in the main window landed one status bar height below the finger. Tapping the centre of "Open auto sheet" (y=1057px) selected "Open modal" (y=1199px) — 142px off, exactly StatusBar.currentHeight at that density. It reproduced with ReactNativeGrabRoot alone, so it was never about native headers or ReactNativeGrabScreen.

The fix

Nothing is hardcoded and nothing is platform-conditional: the gap is read off the touch itself. A touch carries both coordinate spaces at once — pageX/pageY in window space, plus locationX/locationY relative to target, which measures in Fabric space — so getFabricWindowOffset computes targetOrigin + location - page at runtime. On iOS that resolves to [0, 0], leaving the arithmetic identical to what shipped in #11. If the target tag cannot be resolved it degrades to [0, 0] as well.

It is read once, when the gesture starts. The offset belongs to the window rather than to the touched view, and Android only guarantees that location is consistent with target on the initial touch, so recomputing it on every move would risk stale coordinates mid-drag.

Because the offset is per-window rather than per-platform, natively presented surfaces — which get their own window, where the two spaces already agree — keep working. A global StatusBar.currentHeight correction was tried first and rejected for exactly that reason: it fixed the main window and broke every sheet by the same amount.

Verification

npm run lint, npm run format:check, and 18 unit tests including three new ones covering getFabricWindowOffset (aligned origins, offset origins, and the unresolvable-target fallback).

Driven on device with agent-device.

Android (Pixel 8 emulator, API 35) — every case selects the element under the finger:

tap selected
"Screen target", main window under the native header Text (in ScreenTarget) — previously 142px off
auto sheet target Text (in AutoSheetTarget)
stacked sheet target Text (in StackedSheetTarget)
RN Modal target Text (in ModalTarget)

iOS (iPhone 17 Pro simulator) — regression pass over the same flows, each with the highlight box drawn exactly on the tapped text, all unchanged from before this fix: screen target, auto sheet, stacked sheet, dismissing the top sheet falling back to the sheet below, and the RN Modal.

@V3RON
V3RON merged commit 5722d55 into main Aug 31, 2026
4 checks passed
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.

Android: selection resolves one status-bar height below the touch

1 participant