Skip to content

Commit b6ccdc6

Browse files
akelmansonAndré Kelmanson
andauthored
fix(android): touch interceptor must ignore pointerEvents none/box-none (#39)
`SandboxTouchInterceptor.isObscuredAt` decided whether a sandbox is covered by an overlay using only the sibling's visibility + bounds — it never checked `pointerEvents`. React Native's AppContainer adds a full-screen `pointerEvents="box-none"` overlay in dev (LogBox / the element inspector), drawn on top of everything. That overlay was treated as obscuring, so every touch landing in a sandbox was rerouted via `redispatchWithHiddenSandbox` and the sandbox became untappable — but only in debug builds (release has no such overlay), which makes it very easy to miss. Fix: skip siblings whose `pointerEvents` is NONE (fully transparent to touch), and skip the own-bounds test for BOX_NONE (its own box passes touch through; only its children can catch). Descendant checks are unchanged. Follow-up to #33 (the interceptor introduced there). Co-authored-by: André Kelmanson <andre@grana.capital>
1 parent c7cc802 commit b6ccdc6

1 file changed

Lines changed: 27 additions & 10 deletions

File tree

packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxTouchInterceptor.kt

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import android.view.MotionEvent
66
import android.view.View
77
import android.view.ViewGroup
88
import android.view.Window
9+
import com.facebook.react.uimanager.PointerEvents
10+
import com.facebook.react.views.view.ReactViewGroup
911
import java.lang.ref.WeakReference
1012

1113
/**
@@ -338,16 +340,31 @@ object SandboxTouchInterceptor {
338340
if (!isDrawnAfter(group, i, childIndex)) continue
339341
if (sibling.visibility != View.VISIBLE) continue
340342

341-
// Check the sibling's own bounds
342-
sibling.getLocationOnScreen(siblingLoc)
343-
val sibRect =
344-
Rect(
345-
siblingLoc[0],
346-
siblingLoc[1],
347-
siblingLoc[0] + sibling.width,
348-
siblingLoc[1] + sibling.height,
349-
)
350-
if (sibRect.contains(screenX, screenY)) return true
343+
// A view with pointerEvents NONE/BOX_NONE does not consume touch in
344+
// its own box, so it does not obscure. This matters in debug builds:
345+
// React Native's AppContainer adds a full-screen pointerEvents
346+
// "box-none" overlay (for LogBox/the element inspector) drawn on top
347+
// of everything. Without this check it is treated as an overlay and
348+
// steals every touch that lands in a sandbox — the sandbox becomes
349+
// untappable in dev (release has no such overlay, so it only repros
350+
// in debug, which makes it easy to miss).
351+
val pointerEvents = (sibling as? ReactViewGroup)?.pointerEvents
352+
if (pointerEvents == PointerEvents.NONE) continue
353+
354+
// Check the sibling's own bounds — only if the view itself catches
355+
// touch. BOX_NONE lets the touch pass through its own box (only its
356+
// children can catch), so skip the own-bounds test for it.
357+
if (pointerEvents != PointerEvents.BOX_NONE) {
358+
sibling.getLocationOnScreen(siblingLoc)
359+
val sibRect =
360+
Rect(
361+
siblingLoc[0],
362+
siblingLoc[1],
363+
siblingLoc[0] + sibling.width,
364+
siblingLoc[1] + sibling.height,
365+
)
366+
if (sibRect.contains(screenX, screenY)) return true
367+
}
351368

352369
// Check descendants — catches overflow (e.g. a card with
353370
// negative margin extending outside its parent's bounds)

0 commit comments

Comments
 (0)