fix: keep grab controls out of the selection owner's subtree - #19
Merged
Conversation
The controls moved inside the owner view so they would render in the resolved owner's window. On iOS that puts `FullWindowOverlay` - a full-screen native node with no `pointerEvents` prop - into the subtree that `findNodeAtPoint` walks, and since it skips a node only when `pointerEvents` makes it untargetable, the overlay swallowed every hit test: each grab resolved to the owner itself, so nothing but the root, screen or surface could be selected. Render the controls as a sibling of the registered view instead. They still mount inside the owner component, so a natively presented surface keeps its bar in its own window, but the owner's shadow subtree stays clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Grabbing broke on iOS after #15: every grab resolved to the root, screen or surface itself instead of the touched element, with a full-screen highlight and the menu anchored off the bottom edge.
#15 moved the control bar from a sibling of the registered owner view into it:
On iOS
GrabOwnerControlsis hosted by react-native-screens'FullWindowOverlay, which renders a native node sized to the whole window and accepts onlystyleandaccessibilityContainerViewIsModal- there is no way to give itpointerEvents.findNodeAtPointwalks the owner's shadow subtree in reverse child order and skips a node only when itspointerEventsmakes it untargetable, which is what keepsReactNativeGrabOverlay(box-none, withnonechildren) out of the way. The controls have no such protection: the overlay node is last, full-screen and targetable, so it won every hit test, and with nothing targetable beneath itfindNodeAtPointreturned the overlay itself - whose component stack readsGrabOwnerControls -> GrabSelectionOwnerView -> ReactNativeGrabRoot/Screen/Surface, hence the owner's name in the menu.Android was unaffected:
FullScreenOverlayis aFragmentthere, so no extra node exists and thebox-noneanchor is skipped as intended.The controls are now a sibling of the registered view rather than a child of it. They still mount inside the owner component, so #15's behaviour is preserved - a natively presented surface keeps its bar in its own window - but the subtree
findNodeAtPointwalks no longer contains grab's own UI.Testing
Diagnosis and mechanism verified on an iOS simulator (iPhone 17 Pro, example app): on
maina tap on the title highlighted the entire screen; withFullWindowOverlayswapped for aFragmentin the built output - the only change - the same tap correctly selectedText (in ThemedText)with a tight highlight, which isolates the overlay node as the cause.Not yet re-run on device against this branch's build, and not yet checked on the Android emulator (Android is unaffected by construction, but the sibling placement changes which container the control bar measures itself against, so it is worth a look).