fix(android): touch interceptor treats box-none dev overlay as obscuring → sandbox untappable in debug#39
Conversation
`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 callstackincubator#33 (the interceptor introduced there).
|
Change LGTM as follow up to the introduction of the SandboxTouchInterceptor to fix the touch bleed events. That said, I think a similar issue could manifest in the hasDescendantAt method: It only checks visibility, never pointerEvents. So for a structure like: ReactViewGroup (pointerEvents="box-none", full-screen) ← dev overlay The fix correctly skips the parent's own bounds. Then hasDescendantAt walks into the child, sees it's VISIBLE, sees its bounds contain the point, and returns true — sandbox is reported obscured, touches are stolen again. Potential fix: Haven't tested this myself, and think such a scenario would be unlikely, so reasonable to defer to separate follow up if needed IMO |
|
Did some quick testing on my end, summarizing findings:
|
Problem
Touches that land inside a
SandboxReactNativeVieware silently dropped in debug builds — the sandbox content renders but is completely untappable (taps and scrolls). The exact same build works in release, which makes it easy to dismiss as flaky or environment-specific.Root cause
SandboxTouchInterceptor.isObscuredAtdecides whether a sandbox is covered by an overlay using only the sibling's visibility + screen bounds — it never checkspointerEvents.In dev, React Native's
AppContainermounts a full-screenpointerEvents="box-none"overlay (for LogBox and the element inspector), drawn on top of the whole app. Visually it's transparent to touch, butisObscuredAtsees a visible, full-screen sibling drawn after the sandbox and reports it as obscuring. The interceptor then takes theredispatchWithHiddenSandboxpath: it hides the sandbox surface and re-dispatches through the normal pipeline, so the event lands on the (empty) host behind it instead of the sandbox. Result: every touch in the sandbox is stolen.Release has no such overlay, so it only reproduces in debug — which is why it's easy to miss.
Repro
SandboxReactNativeViewwith any tappable content in a debug build (New Arch / bridgeless, RN 0.85).(Confirmed by logging
isObscuredAt: it returns the AppContainer'sReactViewGroup— a full-screenbox-noneview drawn after the sandbox.)Fix
In
isObscuredAt, respectpointerEvents:NONE→ fully transparent to touch; skip the sibling entirely.BOX_NONE→ its own box passes touch through (only its children can catch); skip the own-bounds test but still check descendants.Three lines, no behavior change for real overlays (Modals etc. keep
pointerEventsauto). Follow-up to #33, which introduced the interceptor.🤖 Generated with Claude Code