Skip to content

[Feature request] First-class support for nested draggable / long-press pan gestures (e.g. react-native-draggable-flatlist reorder) #64

Description

@LeCiel

Summary

There is no way to run a nested gesture-handler pan (as used by react-native-draggable-flatlist, or hand-rolled reorder handles) inside a sheet on iOS without the sheet's pan hijacking the drag mid-gesture. Android works fine — this is iOS-only.

Filing this as a feature request rather than a bug because the existing negotiation is scoped to scrollables (disableScrollableNegotiation / the new scrollableNegotiation) and doesn't have a hook for "some other gesture layer owns this touch." A distinct concern from #57 (scroll handoff) and #63 (post-threshold hit-test), though the fix in #63 may partly cover this — see below.

Concrete symptom

Setup: a full-height sheet whose content is a DraggableFlatList. Each row has a short (~36×64 pt) drag handle on the left; long-pressing the handle calls the library's drag().

  • Long-press succeeds — drag() fires and the row is picked up (ShadowDecorator overlay renders).
  • The moment the finger moves vertically, the sheet's pan claims the gesture and the sheet slides down. The overlay row stays put, the outer list gets no onUpdate, and the reorder is impossible to complete.

Android runs the same code path end-to-end.

Why the existing knobs don't cover it

  • disableScrollableNegotiation / scrollableNegotiation: 'none' only inspect for a UIScrollView ancestor at the touch point. The DraggableFlatList row is inside a FlatList (which is a scroll view), so the ancestor exists — but after drag() fires, the library re-parents the pickup into an overlay outside the list's subtree, and the hit-test walks up from there, sees no scroll view, and the sheet's pan begins. [iOS] Scrollable hit-test uses the post-threshold pan location, so drags starting on a short handle strip are refused (Android uses the touch-down point) #63 addresses part of this by capturing the touch-down location — that fix likely resolves the specific pickup case, but doesn't give the sheet a general way to defer to arbitrary nested gestures.
  • shouldBeRequiredToFailBy: other is UIPanGestureRecognizer (BottomSheetHostingView.swift:997-1001) is meant to make the sheet wait for other pans, but iOS only routes the delegate between recognizers UIKit treats as related. A gesture-handler pan attached inside a nested subtree — especially one that becomes an overlay — often does not qualify, so the sheet doesn't actually defer to it.
  • shouldRecognizeSimultaneouslyWith returns false, so there's no way to run the sheet pan and the nested draggable pan concurrently either.

Prior art

  • @gorhom/bottom-sheet — exposes the sheet's animated pan ref via useBottomSheetGestureHandlers so nested gestures can hook .simultaneousWithExternalGesture(sheetGestureRef). Reorder libraries can be wired up explicitly.
  • iOS FloatingPaneltrack(scrollView:) accepts an explicit registration.

Possible implementation directions

Rough shopping list — hoping at least one is compatible with the current design:

  1. Expose the sheet's pan (or a stable coordinator ref) for nested gestures to compose with. Gesture-handler consumers could then do .simultaneousWithExternalGesture(sheetPanRef) or .requireExternalGestureToFail(sheetPanRef) explicitly — same shape as @gorhom/bottom-sheet.

  2. Marker prop for a subtree that owns its own gestures — e.g. <View bottomSheetOwnsGestures={false}> (or an accessibilityIdentifier sentinel) that gestureRecognizerShouldBegin walks up from the touch point; if any ancestor carries it, the sheet pan defers.

  3. enableContentPanningGesture: boolean prop — nuclear escape hatch. Disables the sheet's pan entirely for the sheet instance, relying on scrim tap / a close button / the handle for dismissal. Coarse but predictable, and covers the case where a whole screen is a reorder surface.

  4. Delegate composition — call a user-provided shouldSheetPanBegin(touch) from gestureRecognizerShouldBegin so the app can vote no on gesture-handler-owned regions.

Any of the four would unblock DraggableFlatList / reorder-handle use cases without giving up sheet-drag on the rest of the surface. (1) mirrors what people already know from @gorhom/bottom-sheet and is probably the most flexible.

Environment

  • @swmansion/react-native-bottom-sheet 0.15.3 (also reproduced on 0.16.x from reading source; no functional divergence in the paths involved)
  • react-native-draggable-flatlist 4.x
  • react-native-reanimated 4.5.2, react-native-gesture-handler 2.31.x
  • RN 0.85.3 (Fabric), Hermes
  • iOS 26 device
  • Repros in every published version I checked.

Happy to test a proposal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions