software-mansion-labs / software-mansion-labs/react-native-bottom-sheet

No way to nominate a drag region when the content reaches the sheet's top edge (scrollableNegotiation: 'none')

Open
#85 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
432
Forks
21
Avg merge
7d 7h
Merged PRs (30d)
1

Description

Summary

With scrollableNegotiation: 'none', a sheet whose scrollable content reaches the sheet's own top edge has no drag region at all. There is no way to nominate one from JS, and the usual workarounds (overlaying a handle, reordering it, making it the topmost sibling, blocking touches) cannot work, because the decision never looks at touch delivery.

Why the usual workarounds do not apply

Both platforms answer "may this drag begin" by searching for a vertically-scrollable view whose bounds contain the touch point:

  • iOS: BottomSheetHostingView.scrollView(containing:in:), reached from scrollableAncestorChain(containing:) in gestureRecognizerShouldBegin
  • Android: BottomSheetHostView.findScrollableAtPoint, reached from findScrollableAtTouch

Both walk the subtree by geometry. Neither stops at the topmost sibling, and neither consults hit testing, pointerEvents or zIndex. So if the scrollable's frame covers a point, that point cannot start a sheet drag, whatever is painted on top of it and in whatever order.

That is the correct default. It only becomes a dead end when the content is meant to reach the sheet's top edge.

The case

A sheet with a full-bleed header (an image, a map preview) over a list, where:

  1. scrolling the list must never move the sheet, so 'none' is the right mode, and
  2. the header must reach the sheet's own rounded top edge rather than start below a band of bare surface, so the list's frame starts at y = 0.

Together those leave nowhere to drag from. Pulling the header out of the scrollable works but pins it, which costs a screenful on a list-first sheet.

Reproduce with any 'none' sheet whose first child is a FlatList filling the sheet: the grabber is inert no matter where it is drawn.

Proposal

A prop that names the drag region directly, replacing the geometric search rather than adding to it:

/**
 * When > 0, a drag may only BEGIN within this many points of the sheet's own
 * top edge. Only meaningful with `scrollableNegotiation: 'none'`; 0 (default)
 * leaves current behaviour untouched.
 */
dragRegionTopInset?: number;

Inside the existing branch:

if dragRegionTopInset > 0 {
  if locationInContainer.y > dragRegionTopInset { return false }
} else if scrollableChain is not empty {
  // current behaviour
}

A single number rather than a rect, because the handle is always a full-width band at the top: no measurement from JS, nothing to re-measure on rotation or a detent change.

It is also stricter than today in that mode. The current rule makes any incidental non-scrollable area a drag handle; naming the band means only the band is.

Notes
  • Happy to open a PR if the shape looks right. I have this working as a local patch on 0.15.3 against the older disableScrollableNegotiation boolean, on both platforms, but the negotiation code has been refactored since, so I would rather agree the API before porting it.
  • An alternative shape is nominating a view instead of an inset (a ref or a tag the native side can compare against). That is more flexible and more code; the inset covers the handle case, which I suspect is most of it.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the existing negotiation paths: iOS BottomSheetHostingView.scrollView(containing:in:), scrollableAncestorChain(containing:), and gestureRecognizerShouldBegin, plus Android BottomSheetHostView.findScrollableAtPoint and findScrollableAtTouch. Trace the scrollableNegotiation: 'none' branch on both platforms before deciding how the proposed inset enters the search. Done means the top band can begin a drag while points below it remain blocked, with default behavior unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin, react-native, swift, typescript
Domain
mobile-dev
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.