Skip to content

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

Description

@LeCiel

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions