@swmansion/react-native-bottom-sheet 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,6 +12,7 @@ React Native.
12
12
  - Native implementation for optimal performance.
13
13
  - Bring your own sheet surface.
14
14
  - Dynamic, content‍-‍based sizing out of the box.
15
+ - Automatic handling of vertically scrollable children.
15
16
  - Position tracking for driving UI tied to sheets.
16
17
  - Programmatic‍-‍only detents for snap points unreachable
17
18
  by dragging.
@@ -178,7 +179,7 @@ const position = useSharedValue(0);
178
179
  }}
179
180
  >
180
181
  {/* ... */}
181
- </BottomSheet>;
182
+ </BottomSheet>
182
183
  ```
183
184
 
184
185
  ## By [Software Mansion](https://swmansion.com)
@@ -59,6 +59,10 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
59
59
  init {
60
60
  clipChildren = false
61
61
  clipToPadding = false
62
+ // Set directly rather than via the JSX prop because Fabric doesn't forward
63
+ // pointerEvents to the native view on Android. Without BOX_NONE the view
64
+ // itself becomes a touch target and its onTouchEvent would claim gestures
65
+ // that should go to children.
62
66
  pointerEvents = PointerEvents.BOX_NONE
63
67
  sheetContainer.clipChildren = false
64
68
  sheetContainer.clipToPadding = false
@@ -327,6 +331,10 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
327
331
  if (!isAtMaxDraggable) {
328
332
  lastTouchY = y
329
333
  requestDisallowInterceptTouchEvent(false)
334
+ // Cancel in-flight JS touches. React Native's JSTouchDispatcher
335
+ // processes events at the root view level before onInterceptTouchEvent
336
+ // runs, so without this the JS side never sees a cancel and Pressable
337
+ // would still fire onPress.
330
338
  NativeGestureUtil.notifyNativeGestureStarted(this, ev)
331
339
  return true
332
340
  }
@@ -408,12 +416,32 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
408
416
  }
409
417
 
410
418
  // MARK: - Scroll view helpers
419
+ //
420
+ // Explicit scroll-view detection is required because Android's touch dispatch
421
+ // doesn't support mid-gesture handoff. Once a ScrollView claims a gesture it
422
+ // keeps it for the entire sequence, and it always claims (returns true from
423
+ // onTouchEvent) even when at the scroll boundary. Without this check the sheet
424
+ // could never collapse by dragging down when a ScrollView is at the top.
411
425
 
412
426
  private fun isScrollViewAtTop(): Boolean {
413
427
  val scrollView = findScrollView(sheetContainer) ?: return true
428
+ if (!isTouchInsideView(scrollView)) return true
414
429
  return !scrollView.canScrollVertically(-1)
415
430
  }
416
431
 
432
+ private fun isTouchInsideView(target: View): Boolean {
433
+ val targetLocation = IntArray(2)
434
+ target.getLocationOnScreen(targetLocation)
435
+ val myLocation = IntArray(2)
436
+ getLocationOnScreen(myLocation)
437
+ val touchScreenX = myLocation[0] + initialTouchX
438
+ val touchScreenY = myLocation[1] + initialTouchY
439
+ return touchScreenX >= targetLocation[0] &&
440
+ touchScreenX < targetLocation[0] + target.width &&
441
+ touchScreenY >= targetLocation[1] &&
442
+ touchScreenY < targetLocation[1] + target.height
443
+ }
444
+
417
445
  private fun findScrollView(view: View): View? {
418
446
  if (view.canScrollVertically(1) || view.canScrollVertically(-1)) return view
419
447
  if (view is ViewGroup) {
@@ -42,6 +42,8 @@ public final class RNSBottomSheetHostingView: UIView {
42
42
  panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
43
43
  panGesture.delegate = self
44
44
  panGesture.cancelsTouchesInView = true
45
+ // Delay touch delivery to views so that Pressable doesn't flash its pressed
46
+ // state while the pan gesture is still being disambiguated.
45
47
  panGesture.delaysTouchesBegan = true
46
48
  panGesture.delaysTouchesEnded = false
47
49
  sheetContainer.addGestureRecognizer(panGesture)
@@ -51,6 +53,30 @@ public final class RNSBottomSheetHostingView: UIView {
51
53
  fatalError("init(coder:) has not been implemented")
52
54
  }
53
55
 
56
+ // RCTSurfaceTouchHandler dispatches touch events to JS independently of the
57
+ // pan gesture (it fires in touchesBegan: regardless of its recognizer state).
58
+ // We cache it here and toggle isEnabled in handlePan(.began) to force a
59
+ // touchesCancelled dispatch to JS, preventing Pressable from firing onPress
60
+ // during a sheet drag. This is the iOS equivalent of Android's
61
+ // NativeGestureUtil.notifyNativeGestureStarted.
62
+ private weak var surfaceTouchHandler: UIGestureRecognizer?
63
+
64
+ public override func didMoveToWindow() {
65
+ super.didMoveToWindow()
66
+ surfaceTouchHandler = nil
67
+ guard window != nil else { return }
68
+ var current: UIView? = superview
69
+ while let view = current {
70
+ for gr in view.gestureRecognizers ?? [] {
71
+ if NSStringFromClass(type(of: gr)).contains("TouchHandler") {
72
+ surfaceTouchHandler = gr
73
+ return
74
+ }
75
+ }
76
+ current = view.superview
77
+ }
78
+ }
79
+
54
80
  public override func layoutSubviews() {
55
81
  super.layoutSubviews()
56
82
  guard bounds.width > 0, bounds.height > 0 else { return }
@@ -240,6 +266,10 @@ public final class RNSBottomSheetHostingView: UIView {
240
266
  case .began:
241
267
  isPanning = true
242
268
  setContentInteractionEnabled(false)
269
+ if let handler = surfaceTouchHandler {
270
+ handler.isEnabled = false
271
+ handler.isEnabled = true
272
+ }
243
273
  gesture.setTranslation(.zero, in: self)
244
274
  if let animator = activeAnimator {
245
275
  stopDisplayLink()
@@ -324,14 +354,18 @@ public final class RNSBottomSheetHostingView: UIView {
324
354
  guard draggable.count > 1 else { return false }
325
355
 
326
356
  let maxDraggableIndex = draggable.last?.offset ?? 0
357
+ // Below max: allow drag in either direction to reach other detents.
327
358
  guard targetIndex >= maxDraggableIndex else { return true }
328
-
359
+ // At max: only allow downward drag, and only when the scroll view (if any)
360
+ // is at its top edge — otherwise the scroll view should handle the gesture.
329
361
  if velocity.y < 0 {
330
362
  return false
331
363
  }
332
364
 
333
- let scrollAtTop = (firstScrollView(in: sheetContainer)?.contentOffset.y ?? 0) <= 0
334
- return scrollAtTop
365
+ guard let scrollView = firstScrollView(in: sheetContainer) else { return true }
366
+ let locationInScroll = panGesture.location(in: scrollView)
367
+ guard scrollView.bounds.contains(locationInScroll) else { return true }
368
+ return scrollView.contentOffset.y <= 0
335
369
  }
336
370
  }
337
371
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/react-native-bottom-sheet",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Provides bottom-sheet components for React Native.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",