Skip to content

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

Description

@V3RON

Summary

On Android, grab selection resolves an element roughly one status-bar height below the finger. It affects every selection owner — ReactNativeGrabRoot included — so it is not specific to ReactNativeGrabScreen, native headers, or native navigators.

iOS is unaffected.

Cause

ReactNativeGrabOverlay feeds touch page coordinates to findNodeAtPoint, rebased onto the owner's origin as reported by nativeFabricUIManager.measureInWindow:

const ownerRect = measureInWindow(owner.shadowNode);
findNodeAtPoint(owner.shadowNode, pageX - ownerRect[0], pageY - ownerRect[1]);

On Android those two are in different coordinate spaces. Touch page coordinates are window-relative (they include the status bar), while measureInWindow reports positions relative to the root view, which sits below it. The difference is the top window inset, so the resolved point lands that much too low.

On iOS both spaces share an origin, so the rebase alone is correct.

Evidence

Instrumented on a Pixel 8 emulator (API 35, density 2.625), tapping the centre of a control at screen y=1057px (402.67dp):

screen owner:  page.y = 402.67   measureInWindow.y = 56    (owner's real top in page space is 104.8)
surface owner: page.y = 96.38    measureInWindow.y = 0     (its own window - the two spaces agree)
StatusBar.currentHeight = 48.76

The selection landed on the next control down, 142px (48.8dp) away — exactly StatusBar.currentHeight.

Reproduced with the owner being ReactNativeGrabRoot alone, with no ReactNativeGrabScreen and no header in the tree, which rules out owner nesting as the cause.

Content inside a natively presented surface is correct today, because a sheet or dialog gets its own window whose root starts at the window top, so the two spaces coincide there.

What does not work

  • A global Platform.OS === "android" ? StatusBar.currentHeight : 0 correction. It fixes owners in the main window and breaks surfaces by the same amount, because the offset is a property of the window an owner lives in, not of the platform.
  • nativeEvent.locationX/locationY. They are relative to the touched child, not to the responder: the run above reported location.y = 14.86 for the same touch.

Suggested direction

Measure the owner in the same space the touch arrives in — the View ref's own measureInWindow, which reports true window coordinates on both platforms — and keep the Fabric measureInWindow for the highlight rect, which is already consistent with getBoundingClientRect. That means retaining the owner's ref alongside its shadow node in containers.ts.

Notes

Pre-existing, and unrelated to #11. It predates selection owners entirely; it was simply never visible on iOS, where the spaces agree.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions