@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
|
|
334
|
-
|
|
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