Skip to content

[Android] Properly handle requestDisallowInterceptTouchEvent for v3 - #4367

Merged
j-piasecki merged 3 commits into
mainfrom
jpiasecki/handle-request-disallow
Aug 3, 2026
Merged

[Android] Properly handle requestDisallowInterceptTouchEvent for v3#4367
j-piasecki merged 3 commits into
mainfrom
jpiasecki/handle-request-disallow

Conversation

@j-piasecki

@j-piasecki j-piasecki commented Jul 30, 2026

Copy link
Copy Markdown
Member

Description

On Android, when a native view calls requestDisallowInterceptTouchEvent (e.g. a pager starting a swipe), RNGestureHandlerRootView reacted by cancelling all handlers registered on the root. react-native-pager-view calls it eagerly on touch down, so any v3 gesture rendered inside a pager (e.g. inside material top tabs) was cancelled before it could activate — a long press nested in top tabs never activated at all.

This PR makes the cancellation targeted:

  • The root view now cancels only legacy handlers (v1/v2 action types) via the new GestureHandlerOrchestrator.cancelAllLegacyHandlers, instead of the old trick of activating the internal RootViewGestureHandler. The root handler is now attached with ACTION_TYPE_NONE so it's excluded from that sweep (and no longer sends dead events to JS).
  • v3 handlers are cancelled by new requestDisallowInterceptTouchEvent overrides on RNGestureHandlerDetectorView and ButtonViewGroup. Since the request only bubbles upward from the requesting view, only handlers attached to its ancestors are cancelled — handlers below the requester (like the long press under the pager) keep working.
  • Cancellation is skipped while the orchestrator is delivering events (isHandlingTouch), mirroring the existing passingTouch guard, so disallow requests caused by RNGH's own event delivery don't cancel gestures.
  • findGestureHandlerRootView now returns the nearest enabled root view so the checks above consult the orchestrator that actually manages the subtree.

Test plan

Tested on reproducer from #2383

Screen.Recording.2026-07-31.at.11.38.38.mov

Copilot AI review requested due to automatic review settings July 30, 2026 13:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts Android’s requestDisallowInterceptTouchEvent handling to avoid canceling v3 gestures too broadly (notably inside pagers), by making cancellation more targeted across root/orchestrator and v3 detector/button views.

Changes:

  • Add GestureHandlerOrchestrator.cancelAllLegacyHandlers() and use it from the root helper instead of triggering cancellation via the internal root handler.
  • Override requestDisallowInterceptTouchEvent in v3 host detector and button view to cancel only relevant handlers (and skip cancellation while the orchestrator is handling touch).
  • Update root view lookup to prefer the nearest enabled RNGestureHandlerRootView and expose the orchestrator via the root view/root helper.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt Exposes orchestrator and returns nearest enabled GH root view when searching ancestors.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt Switches root-level cancellation to cancelAllLegacyHandlers and attaches root handler with ACTION_TYPE_NONE.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt Adds requestDisallowInterceptTouchEvent override to cancel v3 handlers attached via the host detector.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt Adds requestDisallowInterceptTouchEvent override to cancel the button’s managed v3 handler.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt Introduces cancelAllLegacyHandlers() to selectively cancel v1/v2 action types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@j-piasecki
j-piasecki force-pushed the jpiasecki/handle-request-disallow branch 3 times, most recently from 934af71 to c30a19c Compare July 31, 2026 09:25
@j-piasecki
j-piasecki marked this pull request as ready for review July 31, 2026 11:34
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: beec65eb-e680-492b-8610-5310e0a4cb44

📥 Commits

Reviewing files that changed from the base of the PR and between dd2e6bc and dc49bcc.

📒 Files selected for processing (5)
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved gesture cancellation when native views request disallowing touch interception.
    • Prevented stale gesture handlers from remaining active during touch transitions.
    • Improved gesture-handler root detection, including handling disabled roots.
    • Updated legacy gesture handling to cancel applicable handlers and clean them up reliably.
    • Improved touch behavior when switching between native views and gesture handlers.

Walkthrough

The Android gesture-handler implementation now exposes orchestrator state, centralizes legacy-handler cancellation, and cancels attached handlers during disallowed touch interception. Root lookup now prefers enabled gesture-handler roots.

Changes

Native interception cancellation

Layer / File(s) Summary
Orchestrator cancellation API
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
isHandlingTouch is publicly readable. cancelAllLegacyHandlers() cancels legacy handlers and schedules cleanup.
Root helper integration
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
The root helper exposes its orchestrator, registers the JS handler with ACTION_TYPE_NONE, uses the new cancellation method, and removes the obsolete local helper.
Interception entry points and root lookup
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt, packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt, packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
Views propagate active disallow-intercept requests and cancel attached handlers when the orchestrator is not handling touch events. Root lookup prefers enabled roots.

Sequence Diagram(s)

sequenceDiagram
  participant ButtonViewGroup
  participant RNGestureHandlerDetectorView
  participant RNGestureHandlerRootView
  participant GestureHandlerOrchestrator
  ButtonViewGroup->>RNGestureHandlerDetectorView: requestDisallowInterceptTouchEvent(true)
  RNGestureHandlerDetectorView->>RNGestureHandlerRootView: find gesture-handler root
  RNGestureHandlerRootView->>GestureHandlerOrchestrator: read isHandlingTouch
  RNGestureHandlerDetectorView->>GestureHandlerOrchestrator: cancel attached handlers
Loading
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Android v3 changes to requestDisallowInterceptTouchEvent, which is the primary focus of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

j-piasecki and others added 3 commits August 3, 2026 08:32
…m/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt

Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com>
@j-piasecki
j-piasecki force-pushed the jpiasecki/handle-request-disallow branch from dd2e6bc to dc49bcc Compare August 3, 2026 06:36
@j-piasecki
j-piasecki merged commit f0c98c7 into main Aug 3, 2026
4 checks passed
@j-piasecki
j-piasecki deleted the jpiasecki/handle-request-disallow branch August 3, 2026 07:30
RonenMars added a commit to RonenMars/threadbase-mobile that referenced this pull request Aug 26, 2026
…867)

Bumps
\[react-native-gesture-handler\](https://github.com/software-mansion/react-native-gesture-handler)
from 2.32.0 to 3.2.1. Release notes

_Sourced from [react-native-gesture-handler's
releases](https://github.com/software-mansion/react-native-gesture-handler/releases)._

> v3.2.1
> ------
> 
> 🐛 Bug fixes
> ------------
> 
> * Forward press handlers as `testOnly_*` in `PressableWithTouchable`
by [`@​huextrat`](https://github.com/huextrat) in
[software-mansion/react-native-gesture-handler#4416](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4416)
> 
> 🔢 Miscellaneous
> ----------------
> 
> * Update `Pressable` props by [`@​m-bert`](https://github.com/m-bert)
in
[software-mansion/react-native-gesture-handler#4421](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4421)
> 
> **Full Changelog**:
[https://github.com/software-mansion/react-native-gesture-handler/compare/v3.2.0...v3.2.1](https://github.com/software-mansion/react-native-gesture-handler/compare/v3.2.0...v3.2.1)
> 
> v3.2.0
> ------
> 
> ❗ Important changes
> -------------------
> 
> * feat: Adopt AGP v9 by [`@​hurali97`](https://github.com/hurali97) in
[software-mansion/react-native-gesture-handler#4263](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4263)
> * Implement `Pressable` based on `Touchable` by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4411](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4411)
> * \[Android\] Add hover callbacks to Touchable by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4396](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4396)
> * \[iOS\] Add hover callbacks to Touchable by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4397](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4397)
> * \[Web\] Add hover callbacks to Touchable by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4398](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4398)
> * \[Web\] Refactor `Touchable` not to rely on `GestureDetector` by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4344](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4344)
> * \[iOS\] Refactor `Touchable` not to rely on `GestureDetector` by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4343](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4343)
> * \[Android\] Refactor `Touchable` not to rely on `GestureDetector` by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4342](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4342)
> * Fix fatal crash `Cannot read property 'translationX' of undefined`
when a touch event is serialized without `allTouches` by
[`@​huextrat`](https://github.com/huextrat) in
[software-mansion/react-native-gesture-handler#4316](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4316)
> 
> 👍 Improvements
> ---------------
> 
> * \[Android\] Skip the underlay drawable when it can never be visible
by [`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4359](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4359)
> * \[Android\] Apply the button's managed handler config once per prop
transaction by [`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4357](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4357)
> * \[Android\] Configure the button's handler directly instead of
through a `ReadableMap` by
[`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4358](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4358)
> 
> 🐛 Bug fixes
> ------------
> 
> * \[Android\] Guard update events to only be dispatched in ACTIVE
state by [`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4332](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4332)
> * Pass empty callbacks to UI when `runOnJS` is `true` by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4326](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4326)
> * \[Web\] Fix incorrectly calculated `timeDelta` by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4329](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4329)
> * \[Web\] Fix incorrect `Tap` offset by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4330](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4330)
> * Fix `minVelocity` props behavior by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4327](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4327)
> * \[Android\] Fix `minDistance` being reset by partial config updates
by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4347](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4347)
> * Move Interceptor on `ScrollView`, not its content by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4331](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4331)
> * \[iOS\] Re-sync layer opacity and transform from retained props when
recycling buttons by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4360](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4360)
> * \[Android\] Properly handle `requestDisallowInterceptTouchEvent` for
v3 by [`@​j-piasecki`](https://github.com/j-piasecki) in
[software-mansion/react-native-gesture-handler#4367](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4367)
> * Fix `Touchable` not respecting `keyboardShouldPersistTaps="handled"`
by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4372](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4372)
> * \[macOS\] Fix touch events never being delivered by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4390](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4390)
> * fix: crash when mount listener fires after GestureDetector unmount
by [`@​kosmydel`](https://github.com/kosmydel) in
[software-mansion/react-native-gesture-handler#4268](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4268)
> * \[macOS\] Fix `Pan` activation criteria being ignored by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4387](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4387)
> * \[macOS\] Fix `manualActivation` never blocking gesture activation
by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4389](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4389)
> * \[iOS\] Fix touch events never being delivered to `VirtualDetector`
handlers by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4392](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4392)
> * \[iOS\] Fix gestures attached via `VirtualGestureDetector` never
recognizing continuous gestures by
[`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4393](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4393)
> * \[macOS\] Fix `Fling` not sending touch events and begin/end states
consistently by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4395](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4395)
> * \[Android\] Fix handlers cancelled while awaiting leaking in the
orchestrator by [`@​m-bert`](https://github.com/m-bert) in
[software-mansion/react-native-gesture-handler#4402](https://redirect.github.com/software-mansion/react-native-gesture-handler/pull/4402)

... (truncated)

Commits

*
[`62f0f7d`](software-mansion/react-native-gesture-handler@62f0f7d)
Release v3.2.1
*
[`4716425`](software-mansion/react-native-gesture-handler@4716425)
Merge branch '3.2-stable' of
github.com:software-mansion/react-native-gesture...
*
[`f0ae48c`](software-mansion/react-native-gesture-handler@f0ae48c)
Update `Pressable` props
([#4421](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4421))
*
[`5f0f0d8`](software-mansion/react-native-gesture-handler@5f0f0d8)
Forward press handlers as `testOnly_*` in `PressableWithTouchable`
([#4416](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4416))
*
[`0a91db7`](software-mansion/react-native-gesture-handler@0a91db7)
Release v3.2.0
*
[`44046a6`](software-mansion/react-native-gesture-handler@44046a6)
\[Android\] Resolve the button event dispatcher by react tag
([#4415](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4415))
*
[`2469c1d`](software-mansion/react-native-gesture-handler@2469c1d)
Derive `Pressable` pressed state from `testOnly_pressed`
([#4414](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4414))
*
[`3f1bf74`](software-mansion/react-native-gesture-handler@3f1bf74)
Clear pending timers on unmount in StatefulPressable
([#4413](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4413))
*
[`50ae6a1`](software-mansion/react-native-gesture-handler@50ae6a1)
\[General\] Default GestureDetector moduleId to -1
([#4412](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4412))
*
[`8b661c9`](software-mansion/react-native-gesture-handler@8b661c9)
Implement `Pressable` based on `Touchable`
([#4411](https://redirect.github.com/software-mansion/react-native-gesture-handler/issues/4411))
* Additional commits viewable in [compare
view](software-mansion/react-native-gesture-handler@v2.32.0...v3.2.1)
m-bert added a commit that referenced this pull request Aug 26, 2026
…e touch (#4441)

## Description

`Pressable` without relation props presses natively through
`ButtonViewGroup`, whose managed `NativeViewGestureHandler` is attached
with `ACTION_TYPE_NONE`. RNGH delivers touches through the orchestrator
regardless of what happens in the native dispatch, so when a native
`ScrollView` takes the gesture over, nothing stops the handler - it
reaches `STATE_END` on lift and fires a press. This shows up in three
ways:

- fling catch: the `ScrollView` intercepts `DOWN` while decelerating,
the button never sees any native event, yet `onPress` fires on lift
(#4432)
- drag: the `ScrollView` intercepts on `MOVE` when the finger starts
scrolling from a row, and `onPress` still fires on lift
([comment](#4432 (comment)))
- long press while scrolling: the content moves with the finger, so the
pointer never leaves the row and the long-press timer posted on `BEGAN`
fires mid-scroll (same comment)

In all three the `ScrollView` calls
`requestDisallowInterceptTouchEvent(true)`, but the existing sweep
(`cancelAllLegacyHandlers`) only cancels action-driven handlers, and the
`ButtonViewGroup` override from #4367 never runs since the request only
bubbles up from the `ScrollView`.

Cancelling button handlers directly at request time (the #4433 approach)
is not valid either: an eager disallow-intercept
(`react-native-pager-view`'s `NestedScrollableHost` requests it on
`DOWN` whenever it's nested inside another `ViewPager`, without
intercepting anything) is indistinguishable from a real interception at
that moment, so every `Pressable` inside nested pagers (e.g. material
top tabs in a pager) would go dead - the regression class #4367 fixed.

The two can be told apart by when the grab happened and whether the
native dispatch still reached the button:

- `ButtonViewGroup` tracks `receivedNativeDown` - set in
`dispatchTouchEvent` (handler delivery bypasses it), reset on `BEGAN`,
which the orchestrator dispatches before the native dispatch of the same
`DOWN`.
- `RNGestureHandlerRootHelper` records the disallow request, and once
the root view finishes `super.dispatchTouchEvent` runs
`cancelHandlersOnNativeTouchGrab`, cancelling handlers whose hook opts
in.
- The hook decides via `shouldCancelOnNativeTouchGrab(grabbedMidGesture)
= grabbedMidGesture || !receivedNativeDown`: a grab on any pass after
`DOWN` means actual dragging (cancel, matching what the legacy
`Pressable` and RN's `Pressable` do), while a grab during the `DOWN`
pass spares a button that received that `DOWN` (a defensive disallow
lets the event through).

Only `ButtonViewGroup` opts into the hook, so handlers attached to
detectors, scrollables and text inputs are unaffected. The cost on
passes without a disallow request is a single boolean check.

Fixes #4432
Supersedes #4433

## Test plan

Repro below: a `SectionList` with `Pressable` rows (`onPress` +
`onLongPress`), a `Pressable` and a long-press `GestureDetector` inside
nested `PagerView`s (the eager-disallow setup from #2383), and an engine
toggle (v3 / `LegacyPressable` / RN `Pressable`). All runs on the same
emulator, main vs this PR:

| scenario | main | this PR |
| --- | --- | --- |
| fling the list, touch a row to stop it, lift | phantom `onPress` |
nothing |
| put a finger on a row and drag-scroll, lift | phantom `onPress` |
nothing |
| hold a row while drag-scrolling past 500 ms | phantom `onLongPress` |
nothing |
| tap a row on a settled list | `onPress` | `onPress` |
| stationary long press on a row | `onLongPress` | `onLongPress` |
| tap the `Pressable` inside nested pagers | `onPress` | `onPress` |
| long press the detector box inside nested pagers (#2383) | activates |
activates |

`LegacyPressable` behaves the same in the list scenarios; inside nested
pagers it doesn't fire on main either - its handlers are cancelled on
any disallow-intercept request, which is the pre-existing legacy
behavior this PR doesn't change. RN's `Pressable` doesn't go through
RNGH and is clean everywhere.

<details>
<summary>Repro</summary>

```tsx
import React, { useState } from 'react';
import {
  Pressable as RNPressable,
  SectionList,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import PagerView from 'react-native-pager-view';
import {
  GestureDetector,
  LegacyPressable,
  Pressable,
  useLongPressGesture,
} from 'react-native-gesture-handler';

const SECTIONS = Array.from({ length: 8 }, (_, section) => ({
  title: `Section ${section}`,
  data: Array.from({ length: 10 }, (_, index) => `Item ${section}-${index}`),
}));

const ENGINES = ['Pressable (v3)', 'LegacyPressable', 'RN Pressable'] as const;
const COMPONENTS = [Pressable, LegacyPressable, RNPressable] as const;

function LongPressBox({ onLongPress }: { onLongPress: () => void }) {
  const longPress = useLongPressGesture({
    runOnJS: true,
    onActivate: onLongPress,
  });

  return (
    <GestureDetector gesture={longPress}>
      <View style={styles.gestureBox} />
    </GestureDetector>
  );
}

export default function EmptyExample() {
  const [engine, setEngine] = useState(0);
  const [lastEvent, setLastEvent] = useState('none');
  const [eventCount, setEventCount] = useState(0);

  const Row = COMPONENTS[engine] as typeof Pressable;

  const report = (kind: string, item: string) => {
    setLastEvent(`${kind} ${item}`);
    setEventCount((count) => count + 1);
  };

  return (
    <View style={styles.root}>
      <View style={styles.banner}>
        <Text style={styles.bannerText}>engine: {ENGINES[engine]}</Text>
        <Text style={styles.bannerText}>
          last: {lastEvent} (count: {eventCount})
        </Text>
        <Pressable
          style={styles.toggle}
          onPress={() => {
            setEngine((current) => (current + 1) % ENGINES.length);
            setLastEvent('none');
            setEventCount(0);
          }}>
          <Text style={styles.toggleText}>Toggle engine</Text>
        </Pressable>
      </View>
      {/* Nested pagers: the inner pager's NestedScrollableHost calls
          requestDisallowInterceptTouchEvent(true) on ACTION_DOWN only when it
          sits inside another ViewPager2 — the eager-disallow case from #4367. */}
      <PagerView style={styles.pager} initialPage={0}>
        <View key="outer-a" style={styles.page}>
          <PagerView style={styles.innerPager} initialPage={0}>
            <View key="a" style={[styles.page, styles.pageRow]}>
              <Row
                style={styles.pagerButton}
                onPress={() => report('press', 'pager-button')}>
                <Text style={styles.toggleText}>Pager button</Text>
              </Row>
              {/* The #2383 setup: a long-press gesture inside nested pagers
                  (material top tabs are pager-view underneath). */}
              <LongPressBox onLongPress={() => report('gesture', 'pager-box')} />
            </View>
            <View key="b" style={styles.page}>
              <Text>Page B</Text>
            </View>
          </PagerView>
        </View>
        <View key="outer-b" style={styles.page}>
          <Text>Outer page B</Text>
        </View>
      </PagerView>
      <SectionList
        sections={SECTIONS}
        keyExtractor={(item) => item}
        renderSectionHeader={({ section }) => (
          <Text style={styles.sectionHeader}>{section.title}</Text>
        )}
        renderItem={({ item }) => (
          <Row
            style={styles.row}
            onPress={() => report('press', item)}
            onLongPress={() => report('longPress', item)}>
            <Text>{item}</Text>
          </Row>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
  banner: {
    padding: 16,
    gap: 8,
    backgroundColor: '#eee',
  },
  bannerText: {
    fontWeight: 'bold',
  },
  toggle: {
    alignSelf: 'flex-start',
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderRadius: 8,
    backgroundColor: 'steelblue',
  },
  toggleText: {
    color: 'white',
  },
  sectionHeader: {
    paddingHorizontal: 24,
    paddingVertical: 8,
    fontWeight: 'bold',
    backgroundColor: '#ddd',
  },
  row: {
    padding: 24,
    borderBottomWidth: 1,
    borderBottomColor: '#ddd',
  },
  pager: {
    height: 110,
    borderBottomWidth: 2,
    borderBottomColor: '#bbb',
  },
  page: {
    alignItems: 'center',
    justifyContent: 'center',
  },
  pageRow: {
    flexDirection: 'row',
    gap: 16,
  },
  gestureBox: {
    width: 64,
    height: 44,
    borderRadius: 8,
    backgroundColor: 'crimson',
  },
  innerPager: {
    alignSelf: 'stretch',
    flex: 1,
  },
  pagerButton: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    backgroundColor: 'darkorange',
  },
});
```

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants