Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-hoops-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-grab": patch
---

Keep the grab controls out of the selection owner's shadow subtree, so elements can be grabbed again on iOS. The controls moved inside the owner view when they started rendering in the resolved owner's window, and on iOS they are hosted by `FullWindowOverlay` - a full-screen native node that takes no `pointerEvents` prop. `findNodeAtPoint` walks the owner's subtree and skips a node only when its `pointerEvents` makes it untargetable, so that overlay swallowed every hit test and resolved each grab to the owner itself. The controls are now a sibling of the registered view, which keeps them in the owner's window without shadowing its content.
7 changes: 4 additions & 3 deletions src/react-native/grab-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ export const GrabOwnerControls = () => {
);

return (
// The anchor is absolutely positioned so that mounting the controls inside an
// owner cannot disturb its layout: an owner is free to be a `gap`-spaced flex
// container, and absolutely positioned children are not flex items.
// The anchor is absolutely positioned so that mounting the controls alongside
// an owner cannot disturb the surrounding layout: that parent is free to be a
// `gap`-spaced flex container, and absolutely positioned children are not flex
// items.
<View pointerEvents="box-none" style={styles.overlayAnchor}>
<FullScreenOverlay>
{/* Measured rather than the anchor, because `FullScreenOverlay` lifts this
Expand Down
19 changes: 15 additions & 4 deletions src/react-native/grab-selection-owner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,21 @@ export const GrabSelectionOwnerView = ({
const isResolvedSelectionOwner = useIsResolvedGrabSelectionOwner(ownerId);

return (
<View {...props} {...(panHandlers ?? {})} collapsable={false} ref={ownerRef}>
{children}
<ReactNativeGrabOverlay ownerId={ownerId} onPanHandlersChange={setPanHandlers} />
<>
<View {...props} {...(panHandlers ?? {})} collapsable={false} ref={ownerRef}>
{children}
<ReactNativeGrabOverlay ownerId={ownerId} onPanHandlersChange={setPanHandlers} />
</View>

{/*
A sibling of the registered view rather than a child of it: `findNodeAtPoint`
walks the owner's shadow subtree and skips a node only when its `pointerEvents`
makes it untargetable. The controls cannot rely on that - on iOS they are hosted
by `FullWindowOverlay`, a full-screen native node that takes no `pointerEvents`
prop - so a controls subtree inside the owner swallows every hit test and
resolves each grab to the owner itself.
*/}
{isResolvedSelectionOwner && <GrabOwnerControls />}
</View>
</>
);
};
Loading