@swmansion/react-native-bottom-sheet 0.7.2 → 0.8.0-next.1
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/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetView.kt +129 -10
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetViewManager.kt +10 -0
- package/ios/BottomSheetComponentView.mm +8 -0
- package/ios/BottomSheetContentView.h +2 -0
- package/ios/BottomSheetContentView.mm +15 -0
- package/ios/RNSBottomSheetHostingView.swift +110 -3
- package/lib/module/BottomSheet.js +14 -66
- package/lib/module/BottomSheet.js.map +1 -1
- package/lib/module/BottomSheetNativeComponent.ts +3 -0
- package/lib/module/bottomSheetUtils.js +1 -1
- package/lib/module/bottomSheetUtils.js.map +1 -1
- package/lib/typescript/src/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetNativeComponent.d.ts +3 -1
- package/lib/typescript/src/BottomSheetNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/bottomSheetUtils.d.ts +1 -1
- package/lib/typescript/src/bottomSheetUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/BottomSheet.tsx +11 -100
- package/src/BottomSheetNativeComponent.ts +3 -0
- package/src/bottomSheetUtils.ts +2 -2
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
package com.swmansion.reactnativebottomsheet
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Paint
|
|
4
7
|
import android.view.Choreographer
|
|
5
8
|
import android.view.MotionEvent
|
|
6
9
|
import android.view.VelocityTracker
|
|
@@ -37,6 +40,12 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
37
40
|
private var detentSpecs: List<DetentSpec> = emptyList()
|
|
38
41
|
private var targetIndex: Int = 0
|
|
39
42
|
var animateIn: Boolean = true
|
|
43
|
+
var modal: Boolean = false
|
|
44
|
+
set(value) {
|
|
45
|
+
field = value
|
|
46
|
+
updateInteractionState()
|
|
47
|
+
updateScrim()
|
|
48
|
+
}
|
|
40
49
|
private var pendingIndex: Int? = null
|
|
41
50
|
private var hasLaidOut = false
|
|
42
51
|
private var isPanning = false
|
|
@@ -44,6 +53,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
44
53
|
// MARK: - Internal
|
|
45
54
|
|
|
46
55
|
private val sheetContainer = FrameLayout(context)
|
|
56
|
+
private val scrimPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
47
57
|
private var activeAnimation: SpringAnimation? = null
|
|
48
58
|
private var velocityTracker: VelocityTracker? = null
|
|
49
59
|
private var choreographerCallback: Choreographer.FrameCallback? = null
|
|
@@ -55,6 +65,9 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
55
65
|
private var initialTouchX = 0f
|
|
56
66
|
private var lastTouchY = 0f
|
|
57
67
|
private var activePointerId = MotionEvent.INVALID_POINTER_ID
|
|
68
|
+
private var scrimPressed = false
|
|
69
|
+
private var scrimColor = Color.argb(128, 0, 0, 0)
|
|
70
|
+
private var scrimProgress = 0f
|
|
58
71
|
|
|
59
72
|
init {
|
|
60
73
|
clipChildren = false
|
|
@@ -141,6 +154,11 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
141
154
|
updateShadowState(sheetContainer.translationY)
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
override fun dispatchDraw(canvas: Canvas) {
|
|
158
|
+
drawScrim(canvas)
|
|
159
|
+
super.dispatchDraw(canvas)
|
|
160
|
+
}
|
|
161
|
+
|
|
144
162
|
private fun layoutSheetChildren() {
|
|
145
163
|
for (i in 0 until sheetContainer.childCount) {
|
|
146
164
|
val child = sheetContainer.getChildAt(i)
|
|
@@ -175,6 +193,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
requestLayout()
|
|
196
|
+
updateScrim()
|
|
178
197
|
}
|
|
179
198
|
|
|
180
199
|
fun setIndex(newIndex: Int) {
|
|
@@ -190,6 +209,11 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
190
209
|
snapToIndex(newIndex, 0f)
|
|
191
210
|
}
|
|
192
211
|
|
|
212
|
+
fun setScrimColor(color: Int?) {
|
|
213
|
+
scrimColor = color ?: Color.argb(128, 0, 0, 0)
|
|
214
|
+
invalidate()
|
|
215
|
+
}
|
|
216
|
+
|
|
193
217
|
// MARK: - Snap logic
|
|
194
218
|
|
|
195
219
|
private fun translationY(index: Int): Float {
|
|
@@ -198,6 +222,12 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
198
222
|
return maxHeight - snapHeight
|
|
199
223
|
}
|
|
200
224
|
|
|
225
|
+
private val closedIndex: Int?
|
|
226
|
+
get() = detentSpecs.indexOfFirst { it.height == 0f }.takeIf { it >= 0 }
|
|
227
|
+
|
|
228
|
+
private val firstNonZeroDetentHeight: Float
|
|
229
|
+
get() = detentSpecs.firstOrNull { it.height > 0f }?.height ?: 0f
|
|
230
|
+
|
|
201
231
|
private val draggableMinTy: Float
|
|
202
232
|
get() {
|
|
203
233
|
val highestIndex = detentSpecs.indices.lastOrNull { !detentSpecs[it].programmatic } ?: 0
|
|
@@ -216,7 +246,10 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
216
246
|
private fun emitPosition() {
|
|
217
247
|
val maxHeight = detentSpecs.lastOrNull()?.height ?: height.toFloat()
|
|
218
248
|
val ty = sheetContainer.translationY
|
|
219
|
-
|
|
249
|
+
val position = maxHeight - ty
|
|
250
|
+
updateScrim(position)
|
|
251
|
+
updateInteractionState()
|
|
252
|
+
listener?.onPositionChange((position / density).toDouble())
|
|
220
253
|
updateShadowState(ty)
|
|
221
254
|
}
|
|
222
255
|
|
|
@@ -276,6 +309,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
276
309
|
stopChoreographer()
|
|
277
310
|
emitPosition()
|
|
278
311
|
activeAnimation = null
|
|
312
|
+
updateInteractionState()
|
|
279
313
|
listener?.onIndexChange(index)
|
|
280
314
|
}
|
|
281
315
|
}
|
|
@@ -308,6 +342,13 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
308
342
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
|
309
343
|
val sheetTop = sheetContainer.top + sheetContainer.translationY
|
|
310
344
|
if (ev.actionMasked == MotionEvent.ACTION_DOWN && ev.y < sheetTop) {
|
|
345
|
+
if (isScrimVisible()) {
|
|
346
|
+
initialTouchX = ev.x
|
|
347
|
+
initialTouchY = ev.y
|
|
348
|
+
lastTouchY = ev.y
|
|
349
|
+
scrimPressed = true
|
|
350
|
+
return true
|
|
351
|
+
}
|
|
311
352
|
return false
|
|
312
353
|
}
|
|
313
354
|
|
|
@@ -350,12 +391,37 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
350
391
|
initialTouchX = 0f
|
|
351
392
|
initialTouchY = 0f
|
|
352
393
|
activePointerId = MotionEvent.INVALID_POINTER_ID
|
|
394
|
+
scrimPressed = false
|
|
353
395
|
}
|
|
354
396
|
}
|
|
355
397
|
return false
|
|
356
398
|
}
|
|
357
399
|
|
|
358
400
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
401
|
+
if (scrimPressed) {
|
|
402
|
+
when (event.actionMasked) {
|
|
403
|
+
MotionEvent.ACTION_MOVE -> {
|
|
404
|
+
val sheetTop = sheetContainer.top + sheetContainer.translationY
|
|
405
|
+
if (event.y >= sheetTop || abs(event.y - initialTouchY) > touchSlop) {
|
|
406
|
+
scrimPressed = false
|
|
407
|
+
}
|
|
408
|
+
return true
|
|
409
|
+
}
|
|
410
|
+
MotionEvent.ACTION_UP -> {
|
|
411
|
+
val closeIndex = closedIndex
|
|
412
|
+
scrimPressed = false
|
|
413
|
+
if (closeIndex != null && isScrimVisible()) {
|
|
414
|
+
snapToIndex(closeIndex, 0f)
|
|
415
|
+
}
|
|
416
|
+
return true
|
|
417
|
+
}
|
|
418
|
+
MotionEvent.ACTION_CANCEL -> {
|
|
419
|
+
scrimPressed = false
|
|
420
|
+
return true
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
359
425
|
when (event.actionMasked) {
|
|
360
426
|
MotionEvent.ACTION_MOVE -> {
|
|
361
427
|
if (!isPanning) beginPan(event)
|
|
@@ -426,20 +492,18 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
426
492
|
private fun isScrollViewAtTop(): Boolean {
|
|
427
493
|
val scrollView = findScrollView(sheetContainer) ?: return true
|
|
428
494
|
if (!isTouchInsideView(scrollView)) return true
|
|
429
|
-
|
|
495
|
+
val inverted = isViewInverted(scrollView)
|
|
496
|
+
return if (inverted) !scrollView.canScrollVertically(1) else !scrollView.canScrollVertically(-1)
|
|
430
497
|
}
|
|
431
498
|
|
|
432
499
|
private fun isTouchInsideView(target: View): Boolean {
|
|
433
|
-
val
|
|
434
|
-
target.
|
|
500
|
+
val rect = android.graphics.Rect()
|
|
501
|
+
if (!target.getGlobalVisibleRect(rect)) return false
|
|
435
502
|
val myLocation = IntArray(2)
|
|
436
503
|
getLocationOnScreen(myLocation)
|
|
437
|
-
val touchScreenX = myLocation[0] + initialTouchX
|
|
438
|
-
val touchScreenY = myLocation[1] + initialTouchY
|
|
439
|
-
return touchScreenX
|
|
440
|
-
touchScreenX < targetLocation[0] + target.width &&
|
|
441
|
-
touchScreenY >= targetLocation[1] &&
|
|
442
|
-
touchScreenY < targetLocation[1] + target.height
|
|
504
|
+
val touchScreenX = (myLocation[0] + initialTouchX).toInt()
|
|
505
|
+
val touchScreenY = (myLocation[1] + initialTouchY).toInt()
|
|
506
|
+
return rect.contains(touchScreenX, touchScreenY)
|
|
443
507
|
}
|
|
444
508
|
|
|
445
509
|
private fun findScrollView(view: View): View? {
|
|
@@ -452,6 +516,19 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
452
516
|
return null
|
|
453
517
|
}
|
|
454
518
|
|
|
519
|
+
private fun isViewInverted(view: View): Boolean {
|
|
520
|
+
val values = FloatArray(9)
|
|
521
|
+
var current: View? = view
|
|
522
|
+
while (current != null && current !== sheetContainer) {
|
|
523
|
+
if (!current.matrix.isIdentity) {
|
|
524
|
+
current.matrix.getValues(values)
|
|
525
|
+
if (values[android.graphics.Matrix.MSCALE_Y] < 0) return true
|
|
526
|
+
}
|
|
527
|
+
current = current.parent as? View
|
|
528
|
+
}
|
|
529
|
+
return false
|
|
530
|
+
}
|
|
531
|
+
|
|
455
532
|
// MARK: - Cleanup
|
|
456
533
|
|
|
457
534
|
fun destroy() {
|
|
@@ -469,9 +546,51 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
469
546
|
initialTouchX = 0f
|
|
470
547
|
lastTouchY = 0f
|
|
471
548
|
activePointerId = MotionEvent.INVALID_POINTER_ID
|
|
549
|
+
scrimPressed = false
|
|
472
550
|
sheetContainer.translationY = 0f
|
|
551
|
+
scrimProgress = 0f
|
|
473
552
|
sheetContainer.removeAllViews()
|
|
474
553
|
stateWrapper = null
|
|
475
554
|
lastShadowOffsetY = Float.NaN
|
|
476
555
|
}
|
|
556
|
+
|
|
557
|
+
private fun updateScrim(position: Float = currentSheetHeight()) {
|
|
558
|
+
if (!modal) {
|
|
559
|
+
scrimProgress = 0f
|
|
560
|
+
invalidate()
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
val threshold = firstNonZeroDetentHeight
|
|
565
|
+
scrimProgress =
|
|
566
|
+
if (threshold <= 0f) 0f else (position / threshold).coerceIn(0f, 1f)
|
|
567
|
+
invalidate()
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private fun updateInteractionState() {
|
|
571
|
+
pointerEvents =
|
|
572
|
+
if (modal && (activeAnimation != null || isPanning || isScrimVisible())) {
|
|
573
|
+
PointerEvents.AUTO
|
|
574
|
+
} else {
|
|
575
|
+
PointerEvents.BOX_NONE
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private fun currentSheetHeight(): Float {
|
|
580
|
+
val maxHeight = detentSpecs.lastOrNull()?.height ?: height.toFloat()
|
|
581
|
+
return maxHeight - sheetContainer.translationY
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private fun isScrimVisible(): Boolean = modal && currentSheetHeight() > 1f
|
|
585
|
+
|
|
586
|
+
private fun drawScrim(canvas: Canvas) {
|
|
587
|
+
if (!modal || scrimProgress <= 0.001f) {
|
|
588
|
+
return
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
val alpha = (Color.alpha(scrimColor) * scrimProgress).toInt().coerceIn(0, 255)
|
|
592
|
+
scrimPaint.color =
|
|
593
|
+
Color.argb(alpha, Color.red(scrimColor), Color.green(scrimColor), Color.blue(scrimColor))
|
|
594
|
+
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), scrimPaint)
|
|
595
|
+
}
|
|
477
596
|
}
|
package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetViewManager.kt
CHANGED
|
@@ -109,6 +109,16 @@ class BottomSheetViewManager :
|
|
|
109
109
|
view.animateIn = animateIn
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
@ReactProp(name = "modal")
|
|
113
|
+
override fun setModal(view: BottomSheetView, modal: Boolean) {
|
|
114
|
+
view.modal = modal
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@ReactProp(name = "scrimColor", customType = "Color")
|
|
118
|
+
override fun setScrimColor(view: BottomSheetView, scrimColor: Int?) {
|
|
119
|
+
view.setScrimColor(scrimColor)
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
override fun onDropViewInstance(view: BottomSheetView) {
|
|
113
123
|
super.onDropViewInstance(view)
|
|
114
124
|
view.destroy()
|
|
@@ -64,6 +64,14 @@ using namespace facebook::react;
|
|
|
64
64
|
_sheetView.animateIn = newViewProps.animateIn;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
if (newViewProps.modal != oldViewProps.modal) {
|
|
68
|
+
_sheetView.modal = newViewProps.modal;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (newViewProps.scrimColor != oldViewProps.scrimColor) {
|
|
72
|
+
[_sheetView setScrimColor:RCTUIColorFromSharedColor(newViewProps.scrimColor)];
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
[super updateProps:props oldProps:oldProps];
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -13,10 +13,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
13
13
|
|
|
14
14
|
@property (nonatomic, weak, nullable) id<BottomSheetContentViewDelegate> delegate;
|
|
15
15
|
@property (nonatomic) BOOL animateIn;
|
|
16
|
+
@property (nonatomic) BOOL modal;
|
|
16
17
|
@property (nonatomic, readonly) UIView *sheetContainer;
|
|
17
18
|
|
|
18
19
|
- (void)setDetents:(NSArray<NSDictionary *> *)raw;
|
|
19
20
|
- (void)setDetentIndex:(NSInteger)newIndex;
|
|
21
|
+
- (void)setScrimColor:(UIColor *_Nullable)color;
|
|
20
22
|
- (void)mountChildComponentView:(UIView *)childView atIndex:(NSInteger)index;
|
|
21
23
|
- (void)unmountChildComponentView:(UIView *)childView;
|
|
22
24
|
- (void)resetSheetState;
|
|
@@ -40,6 +40,16 @@
|
|
|
40
40
|
return _impl.sheetContainer;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
- (BOOL)modal
|
|
44
|
+
{
|
|
45
|
+
return _impl.modal;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)setModal:(BOOL)modal
|
|
49
|
+
{
|
|
50
|
+
_impl.modal = modal;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
- (void)setDetents:(NSArray<NSDictionary *> *)raw
|
|
44
54
|
{
|
|
45
55
|
[_impl setDetents:raw];
|
|
@@ -50,6 +60,11 @@
|
|
|
50
60
|
[_impl setDetentIndex:newIndex];
|
|
51
61
|
}
|
|
52
62
|
|
|
63
|
+
- (void)setScrimColor:(UIColor *)color
|
|
64
|
+
{
|
|
65
|
+
_impl.scrimColor = color;
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
- (void)mountChildComponentView:(UIView *)childView atIndex:(NSInteger)index
|
|
54
69
|
{
|
|
55
70
|
[_impl mountChildComponentView:childView atIndex:index];
|
|
@@ -13,15 +13,25 @@ private struct DetentSpec {
|
|
|
13
13
|
@objcMembers
|
|
14
14
|
public final class RNSBottomSheetHostingView: UIView {
|
|
15
15
|
public weak var eventDelegate: RNSBottomSheetHostingViewDelegate?
|
|
16
|
+
public var modal: Bool = false {
|
|
17
|
+
didSet { updateScrim() }
|
|
18
|
+
}
|
|
19
|
+
public var scrimColor: UIColor? = UIColor.black.withAlphaComponent(0.5) {
|
|
20
|
+
didSet { scrimView.backgroundColor = scrimColor }
|
|
21
|
+
}
|
|
16
22
|
|
|
17
23
|
private var detentSpecs: [DetentSpec] = [] {
|
|
18
|
-
didSet {
|
|
24
|
+
didSet {
|
|
25
|
+
setNeedsLayout()
|
|
26
|
+
updateScrim()
|
|
27
|
+
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
private var targetIndex: Int = 0
|
|
22
31
|
public var animateIn: Bool = true
|
|
23
32
|
|
|
24
33
|
public let sheetContainer = UIView()
|
|
34
|
+
private let scrimView = UIControl()
|
|
25
35
|
private var panGesture: UIPanGestureRecognizer!
|
|
26
36
|
private var activeAnimator: UIViewPropertyAnimator?
|
|
27
37
|
private var displayLink: CADisplayLink?
|
|
@@ -35,6 +45,12 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
35
45
|
backgroundColor = .clear
|
|
36
46
|
clipsToBounds = false
|
|
37
47
|
|
|
48
|
+
scrimView.backgroundColor = scrimColor
|
|
49
|
+
scrimView.alpha = 0
|
|
50
|
+
scrimView.isHidden = true
|
|
51
|
+
scrimView.addTarget(self, action: #selector(handleScrimPress), for: .touchUpInside)
|
|
52
|
+
addSubview(scrimView)
|
|
53
|
+
|
|
38
54
|
sheetContainer.backgroundColor = .clear
|
|
39
55
|
sheetContainer.clipsToBounds = false
|
|
40
56
|
addSubview(sheetContainer)
|
|
@@ -81,6 +97,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
81
97
|
super.layoutSubviews()
|
|
82
98
|
guard bounds.width > 0, bounds.height > 0 else { return }
|
|
83
99
|
|
|
100
|
+
scrimView.frame = bounds
|
|
84
101
|
let maxHeight = detentSpecs.last?.height ?? bounds.height
|
|
85
102
|
sheetContainer.bounds = CGRect(x: 0, y: 0, width: bounds.width, height: maxHeight)
|
|
86
103
|
sheetContainer.center = CGPoint(x: bounds.width / 2, y: bounds.height - maxHeight / 2)
|
|
@@ -106,6 +123,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
106
123
|
|
|
107
124
|
if activeAnimator != nil || isPanning { return }
|
|
108
125
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: translationY(for: targetIndex))
|
|
126
|
+
updateScrim()
|
|
109
127
|
}
|
|
110
128
|
|
|
111
129
|
private var presentedSheetFrame: CGRect {
|
|
@@ -116,12 +134,21 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
116
134
|
}
|
|
117
135
|
|
|
118
136
|
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
119
|
-
presentedSheetFrame.contains(point)
|
|
137
|
+
if presentedSheetFrame.contains(point) {
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return isScrimVisible && bounds.contains(point)
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
123
145
|
guard self.point(inside: point, with: event) else { return nil }
|
|
124
146
|
|
|
147
|
+
if isScrimVisible && !presentedSheetFrame.contains(point) {
|
|
148
|
+
let scrimPoint = convert(point, to: scrimView)
|
|
149
|
+
return scrimView.hitTest(scrimPoint, with: event)
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
let containerPoint = convert(point, to: sheetContainer)
|
|
126
153
|
guard sheetContainer.bounds.contains(containerPoint) else { return nil }
|
|
127
154
|
return sheetContainer.hitTest(containerPoint, with: event)
|
|
@@ -169,6 +196,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
169
196
|
isPanning = false
|
|
170
197
|
setContentInteractionEnabled(true)
|
|
171
198
|
sheetContainer.transform = .identity
|
|
199
|
+
scrimView.alpha = 0
|
|
200
|
+
scrimView.isHidden = true
|
|
172
201
|
for subview in sheetContainer.subviews {
|
|
173
202
|
subview.removeFromSuperview()
|
|
174
203
|
}
|
|
@@ -194,10 +223,31 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
194
223
|
return (minTy: translationY(for: highestIndex), maxTy: translationY(for: lowestIndex))
|
|
195
224
|
}
|
|
196
225
|
|
|
226
|
+
private var closedIndex: Int? {
|
|
227
|
+
detentSpecs.firstIndex(where: { $0.height == 0 })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private var firstNonZeroDetentHeight: CGFloat {
|
|
231
|
+
detentSpecs.first(where: { $0.height > 0 })?.height ?? 0
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private var currentSheetHeight: CGFloat {
|
|
235
|
+
let maxHeight = detentSpecs.last?.height ?? bounds.height
|
|
236
|
+
let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.ty
|
|
237
|
+
return maxHeight - ty
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private var isScrimVisible: Bool {
|
|
241
|
+
modal && currentSheetHeight > 0.5
|
|
242
|
+
}
|
|
243
|
+
|
|
197
244
|
private func emitPosition() {
|
|
198
245
|
let maxHeight = detentSpecs.last?.height ?? bounds.height
|
|
199
246
|
let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.ty
|
|
200
|
-
|
|
247
|
+
let position = maxHeight - ty
|
|
248
|
+
updateScrim(forPosition: position)
|
|
249
|
+
updateInteractionState()
|
|
250
|
+
eventDelegate?.bottomSheetHostingView(self, didChangePosition: position)
|
|
201
251
|
}
|
|
202
252
|
|
|
203
253
|
private func startDisplayLink() {
|
|
@@ -227,6 +277,19 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
227
277
|
emitPosition()
|
|
228
278
|
}
|
|
229
279
|
|
|
280
|
+
@objc private func handleScrimPress() {
|
|
281
|
+
guard
|
|
282
|
+
modal,
|
|
283
|
+
let closedIndex,
|
|
284
|
+
targetIndex != closedIndex,
|
|
285
|
+
activeAnimator == nil || currentSheetHeight > 0.5
|
|
286
|
+
else {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
snapToIndex(closedIndex, velocity: 0)
|
|
291
|
+
}
|
|
292
|
+
|
|
230
293
|
private func snapToIndex(_ index: Int, velocity: CGFloat) {
|
|
231
294
|
guard index >= 0, index < detentSpecs.count else { return }
|
|
232
295
|
targetIndex = index
|
|
@@ -252,6 +315,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
252
315
|
self.emitPosition()
|
|
253
316
|
self.activeAnimator = nil
|
|
254
317
|
self.setContentInteractionEnabled(true)
|
|
318
|
+
self.updateInteractionState()
|
|
255
319
|
self.eventDelegate?.bottomSheetHostingView(self, didChangeIndex: index)
|
|
256
320
|
}
|
|
257
321
|
animator.startAnimation()
|
|
@@ -344,6 +408,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
344
408
|
return nil
|
|
345
409
|
}
|
|
346
410
|
|
|
411
|
+
private func isViewInverted(_ view: UIView) -> Bool {
|
|
412
|
+
var current: UIView? = view
|
|
413
|
+
while let v = current, v !== sheetContainer {
|
|
414
|
+
if v.transform.d < 0 { return true }
|
|
415
|
+
current = v.superview
|
|
416
|
+
}
|
|
417
|
+
return false
|
|
418
|
+
}
|
|
419
|
+
|
|
347
420
|
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
348
421
|
guard gestureRecognizer === panGesture else { return true }
|
|
349
422
|
|
|
@@ -365,6 +438,11 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
365
438
|
guard let scrollView = firstScrollView(in: sheetContainer) else { return true }
|
|
366
439
|
let locationInScroll = panGesture.location(in: scrollView)
|
|
367
440
|
guard scrollView.bounds.contains(locationInScroll) else { return true }
|
|
441
|
+
let inverted = isViewInverted(scrollView)
|
|
442
|
+
if inverted {
|
|
443
|
+
let maxOffsetY = scrollView.contentSize.height - scrollView.bounds.height + scrollView.adjustedContentInset.bottom
|
|
444
|
+
return scrollView.contentOffset.y >= maxOffsetY
|
|
445
|
+
}
|
|
368
446
|
return scrollView.contentOffset.y <= 0
|
|
369
447
|
}
|
|
370
448
|
}
|
|
@@ -385,3 +463,32 @@ extension RNSBottomSheetHostingView: UIGestureRecognizerDelegate {
|
|
|
385
463
|
return false
|
|
386
464
|
}
|
|
387
465
|
}
|
|
466
|
+
|
|
467
|
+
private extension RNSBottomSheetHostingView {
|
|
468
|
+
func updateScrim() {
|
|
469
|
+
updateScrim(forPosition: currentSheetHeight)
|
|
470
|
+
updateInteractionState()
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
func updateScrim(forPosition position: CGFloat) {
|
|
474
|
+
guard modal else {
|
|
475
|
+
scrimView.alpha = 0
|
|
476
|
+
scrimView.isHidden = true
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let threshold = firstNonZeroDetentHeight
|
|
481
|
+
let progress: CGFloat
|
|
482
|
+
if threshold <= 0 {
|
|
483
|
+
progress = 0
|
|
484
|
+
} else {
|
|
485
|
+
progress = min(1, max(0, position / threshold))
|
|
486
|
+
}
|
|
487
|
+
scrimView.alpha = progress
|
|
488
|
+
scrimView.isHidden = progress <= 0.001
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
func updateInteractionState() {
|
|
492
|
+
scrimView.isUserInteractionEnabled = modal && (closedIndex != nil) && !scrimView.isHidden
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { Animated,
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { Animated, StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
5
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
6
|
import BottomSheetNativeComponent from './BottomSheetNativeComponent';
|
|
7
7
|
import { Portal } from "./BottomSheetProvider.js";
|
|
8
8
|
import { resolveDetent } from "./bottomSheetUtils.js";
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
export { programmatic } from "./bottomSheetUtils.js";
|
|
11
|
-
const DefaultScrim = ({
|
|
12
|
-
progress,
|
|
13
|
-
color
|
|
14
|
-
}) => {
|
|
15
|
-
return /*#__PURE__*/_jsx(Animated.View, {
|
|
16
|
-
style: [StyleSheet.absoluteFill, {
|
|
17
|
-
flex: 1,
|
|
18
|
-
backgroundColor: color,
|
|
19
|
-
opacity: progress
|
|
20
|
-
}]
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
11
|
export const BottomSheet = ({
|
|
24
12
|
children,
|
|
25
13
|
style,
|
|
26
|
-
detents = [0, '
|
|
14
|
+
detents = [0, 'content'],
|
|
27
15
|
index,
|
|
28
16
|
animateIn = true,
|
|
29
17
|
onIndexChange,
|
|
@@ -32,13 +20,11 @@ export const BottomSheet = ({
|
|
|
32
20
|
scrimColor = 'rgba(0, 0, 0, 0.5)'
|
|
33
21
|
}) => {
|
|
34
22
|
const {
|
|
35
|
-
height:
|
|
23
|
+
height: windowHeight
|
|
36
24
|
} = useWindowDimensions();
|
|
37
25
|
const insets = useSafeAreaInsets();
|
|
38
|
-
const maxHeight =
|
|
26
|
+
const maxHeight = windowHeight - insets.top;
|
|
39
27
|
const [contentHeight, setContentHeight] = useState(0);
|
|
40
|
-
const currentPositionRef = useRef(0);
|
|
41
|
-
const scrimProgress = useRef(new Animated.Value(0)).current;
|
|
42
28
|
const sheetOpacity = useRef(new Animated.Value(0)).current;
|
|
43
29
|
const resolvedDetents = detents.map(detent => {
|
|
44
30
|
const value = resolveDetent(detent, contentHeight, maxHeight);
|
|
@@ -52,61 +38,18 @@ export const BottomSheet = ({
|
|
|
52
38
|
};
|
|
53
39
|
const clampedIndex = Math.max(0, Math.min(index, resolvedDetents.length - 1));
|
|
54
40
|
const isCollapsed = (resolvedDetents[clampedIndex]?.height ?? 0) === 0;
|
|
55
|
-
const scrimPressEnabledRef = useRef(!modal || isCollapsed);
|
|
56
|
-
const previousIsCollapsedRef = useRef(isCollapsed);
|
|
57
|
-
const firstNonzeroDetent = resolvedDetents.find(detent => detent.height > 0)?.height ?? 0;
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const progress = firstNonzeroDetent <= 0 ? 0 : Math.min(1, Math.max(0, currentPositionRef.current / firstNonzeroDetent));
|
|
60
|
-
scrimProgress.setValue(progress);
|
|
61
|
-
}, [firstNonzeroDetent, scrimProgress]);
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (!modal) {
|
|
64
|
-
scrimPressEnabledRef.current = true;
|
|
65
|
-
previousIsCollapsedRef.current = isCollapsed;
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
if (previousIsCollapsedRef.current && !isCollapsed) {
|
|
69
|
-
scrimPressEnabledRef.current = false;
|
|
70
|
-
previousIsCollapsedRef.current = isCollapsed;
|
|
71
|
-
const frame = requestAnimationFrame(() => {
|
|
72
|
-
scrimPressEnabledRef.current = true;
|
|
73
|
-
});
|
|
74
|
-
return () => cancelAnimationFrame(frame);
|
|
75
|
-
}
|
|
76
|
-
scrimPressEnabledRef.current = !isCollapsed;
|
|
77
|
-
previousIsCollapsedRef.current = isCollapsed;
|
|
78
|
-
return undefined;
|
|
79
|
-
}, [isCollapsed, modal]);
|
|
80
41
|
const handleIndexChange = event => {
|
|
81
42
|
onIndexChange?.(event.nativeEvent.index);
|
|
82
43
|
};
|
|
83
44
|
const handlePositionChange = event => {
|
|
84
45
|
const height = event.nativeEvent.position;
|
|
85
|
-
currentPositionRef.current = height;
|
|
86
|
-
const progress = firstNonzeroDetent <= 0 ? 0 : Math.min(1, Math.max(0, height / firstNonzeroDetent));
|
|
87
|
-
scrimProgress.setValue(progress);
|
|
88
46
|
sheetOpacity.setValue(height === 0 ? 0 : 1);
|
|
89
47
|
onPositionChange?.(height);
|
|
90
48
|
};
|
|
91
|
-
const
|
|
92
|
-
const handleScrimPress = () => {
|
|
93
|
-
if (closedIndex === -1 || clampedIndex === closedIndex || !scrimPressEnabledRef.current) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
onIndexChange?.(closedIndex);
|
|
97
|
-
};
|
|
98
|
-
const scrimElement = modal ? /*#__PURE__*/_jsx(DefaultScrim, {
|
|
99
|
-
progress: scrimProgress,
|
|
100
|
-
color: scrimColor
|
|
101
|
-
}) : null;
|
|
102
|
-
const sheet = /*#__PURE__*/_jsxs(Animated.View, {
|
|
49
|
+
const sheet = /*#__PURE__*/_jsx(Animated.View, {
|
|
103
50
|
style: StyleSheet.absoluteFill,
|
|
104
51
|
pointerEvents: modal ? isCollapsed ? 'none' : 'auto' : 'box-none',
|
|
105
|
-
children:
|
|
106
|
-
style: StyleSheet.absoluteFill,
|
|
107
|
-
onPress: handleScrimPress,
|
|
108
|
-
children: scrimElement
|
|
109
|
-
}) : null, /*#__PURE__*/_jsx(Animated.View, {
|
|
52
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
110
53
|
pointerEvents: "box-none",
|
|
111
54
|
style: [StyleSheet.absoluteFill, {
|
|
112
55
|
opacity: sheetOpacity
|
|
@@ -118,11 +61,16 @@ export const BottomSheet = ({
|
|
|
118
61
|
left: 0,
|
|
119
62
|
right: 0,
|
|
120
63
|
bottom: 0,
|
|
121
|
-
height
|
|
64
|
+
// The native host always spans the full height of its container.
|
|
65
|
+
// Detents are still capped to `maxHeight`, so the sheet itself
|
|
66
|
+
// never extends under the status bar.
|
|
67
|
+
height: windowHeight
|
|
122
68
|
}, style],
|
|
123
69
|
detents: resolvedDetents,
|
|
124
70
|
index: index,
|
|
125
71
|
animateIn: animateIn,
|
|
72
|
+
modal: modal,
|
|
73
|
+
scrimColor: scrimColor,
|
|
126
74
|
onIndexChange: handleIndexChange,
|
|
127
75
|
onPositionChange: handlePositionChange,
|
|
128
76
|
children: /*#__PURE__*/_jsxs(View, {
|
|
@@ -136,7 +84,7 @@ export const BottomSheet = ({
|
|
|
136
84
|
})]
|
|
137
85
|
})
|
|
138
86
|
})
|
|
139
|
-
})
|
|
87
|
+
})
|
|
140
88
|
});
|
|
141
89
|
if (modal) {
|
|
142
90
|
return /*#__PURE__*/_jsx(Portal, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["useRef","useState","Animated","StyleSheet","View","useWindowDimensions","useSafeAreaInsets","BottomSheetNativeComponent","Portal","resolveDetent","jsx","_jsx","jsxs","_jsxs","programmatic","BottomSheet","children","style","detents","index","animateIn","onIndexChange","onPositionChange","modal","scrimColor","height","windowHeight","insets","maxHeight","top","contentHeight","setContentHeight","sheetOpacity","Value","current","resolvedDetents","map","detent","value","Math","max","min","isDetentProgrammatic","handleSentinelLayout","event","nativeEvent","layout","y","clampedIndex","length","isCollapsed","handleIndexChange","handlePositionChange","position","setValue","sheet","absoluteFill","pointerEvents","opacity","left","right","bottom","collapsable","flex","onLayout"],"sourceRoot":"../../src","sources":["BottomSheet.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
codegenNativeComponent,
|
|
3
3
|
type CodegenTypes,
|
|
4
|
+
type ColorValue,
|
|
4
5
|
type ViewProps,
|
|
5
6
|
} from 'react-native';
|
|
6
7
|
|
|
@@ -13,6 +14,8 @@ export interface NativeProps extends ViewProps {
|
|
|
13
14
|
detents: ReadonlyArray<NativeDetent>;
|
|
14
15
|
index: CodegenTypes.Int32;
|
|
15
16
|
animateIn: boolean;
|
|
17
|
+
modal: boolean;
|
|
18
|
+
scrimColor: ColorValue;
|
|
16
19
|
onIndexChange?: CodegenTypes.DirectEventHandler<
|
|
17
20
|
Readonly<{ index: CodegenTypes.Int32 }>
|
|
18
21
|
>;
|
|
@@ -41,7 +41,7 @@ export const findSnapTarget = (currentTranslate, velocityY, currentIndex, allPos
|
|
|
41
41
|
export const resolveDetent = (detent, contentHeight, maxHeight) => {
|
|
42
42
|
const detentValueInput = detentValue(detent);
|
|
43
43
|
if (typeof detentValueInput === 'number') return detentValueInput;
|
|
44
|
-
if (detentValueInput === '
|
|
44
|
+
if (detentValueInput === 'content') {
|
|
45
45
|
return contentHeight > 0 ? Math.min(contentHeight, maxHeight) : maxHeight;
|
|
46
46
|
}
|
|
47
47
|
throw new Error(`Invalid detent: \`${detentValueInput}\`.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["programmatic","value","detentValue","detent","isDetentProgrammatic","VELOCITY_THRESHOLD","findSnapTarget","currentTranslate","velocityY","currentIndex","allPositions","draggablePositions","filter","position","isDraggable","effectivePositions","length","targetIndex","minDistance","Infinity","distance","Math","abs","translateY","index","lowerPosition","sort","a","b","undefined","upperPosition","resolveDetent","contentHeight","maxHeight","detentValueInput","min","Error","clampIndex","detentCount","max"],"sourceRoot":"../../src","sources":["bottomSheetUtils.ts"],"mappings":";;AAMA,OAAO,MAAMA,YAAY,GAAIC,KAAkB,KAAc;EAC3DA,KAAK;EACLD,YAAY,EAAE;AAChB,CAAC,CAAC;AAEF,OAAO,MAAME,WAAW,GAAIC,MAAc,IAAkB;EAC1D,IAAI,OAAOA,MAAM,KAAK,QAAQ,IAAIA,MAAM,KAAK,IAAI,EAAE,OAAOA,MAAM,CAACF,KAAK;EACtE,OAAOE,MAAM;AACf,CAAC;AAED,OAAO,MAAMC,oBAAoB,GAAID,MAAc,IAAc;EAC/D,IAAI,OAAOA,MAAM,KAAK,QAAQ,IAAIA,MAAM,KAAK,IAAI,EAAE;IACjD,OAAOA,MAAM,CAACH,YAAY,KAAK,IAAI;EACrC;EACA,OAAO,KAAK;AACd,CAAC;AAED,MAAMK,kBAAkB,GAAG,GAAG;AAE9B,OAAO,MAAMC,cAAc,GAAGA,CAC5BC,gBAAwB,EACxBC,SAAiB,EACjBC,YAAoB,EACpBC,YAA2E,KACxE;EACH,MAAMC,kBAAkB,GAAGD,YAAY,CAACE,MAAM,CAC3CC,QAAQ,IAAKA,QAAQ,CAACC,WACzB,CAAC;EACD,MAAMC,kBAAkB,GACtBJ,kBAAkB,CAACK,MAAM,GAAG,CAAC,GAAGL,kBAAkB,GAAGD,YAAY;EAEnE,IAAIO,WAAW,GAAGR,YAAY;EAC9B,IAAIS,WAAW,GAAGC,QAAQ;EAE1B,KAAK,MAAMN,QAAQ,IAAIE,kBAAkB,EAAE;IACzC,MAAMK,QAAQ,GAAGC,IAAI,CAACC,GAAG,CAACf,gBAAgB,GAAGM,QAAQ,CAACU,UAAU,CAAC;IACjE,IAAIH,QAAQ,GAAGF,WAAW,EAAE;MAC1BA,WAAW,GAAGE,QAAQ;MACtBH,WAAW,GAAGJ,QAAQ,CAACW,KAAK;IAC9B;EACF;EAEA,IAAIH,IAAI,CAACC,GAAG,CAACd,SAAS,CAAC,GAAGH,kBAAkB,EAAE;IAC5C,IAAIG,SAAS,GAAG,CAAC,EAAE;MACjB,MAAMiB,aAAa,GAAGV,kBAAkB,CACrCH,MAAM,CAAEC,QAAQ,IAAKA,QAAQ,CAACU,UAAU,GAAGhB,gBAAgB,GAAG,CAAC,CAAC,CAChEmB,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKD,CAAC,CAACJ,UAAU,GAAGK,CAAC,CAACL,UAAU,CAAC,CAAC,CAAC,CAAC;MACjD,IAAIE,aAAa,KAAKI,SAAS,EAAEZ,WAAW,GAAGQ,aAAa,CAACD,KAAK;IACpE,CAAC,MAAM;MACL,MAAMM,aAAa,GAAGf,kBAAkB,CACrCH,MAAM,CAAEC,QAAQ,IAAKA,QAAQ,CAACU,UAAU,GAAGhB,gBAAgB,GAAG,CAAC,CAAC,CAChEmB,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKA,CAAC,CAACL,UAAU,GAAGI,CAAC,CAACJ,UAAU,CAAC,CAAC,CAAC,CAAC;MACjD,IAAIO,aAAa,KAAKD,SAAS,EAAEZ,WAAW,GAAGa,aAAa,CAACN,KAAK;IACpE;EACF;EACA,OAAOP,WAAW;AACpB,CAAC;AAED,OAAO,MAAMc,aAAa,GAAGA,CAC3B5B,MAAc,EACd6B,aAAqB,EACrBC,SAAiB,KACd;EACH,MAAMC,gBAAgB,GAAGhC,WAAW,CAACC,MAAM,CAAC;EAC5C,IAAI,OAAO+B,gBAAgB,KAAK,QAAQ,EAAE,OAAOA,gBAAgB;EACjE,IAAIA,gBAAgB,KAAK,
|
|
1
|
+
{"version":3,"names":["programmatic","value","detentValue","detent","isDetentProgrammatic","VELOCITY_THRESHOLD","findSnapTarget","currentTranslate","velocityY","currentIndex","allPositions","draggablePositions","filter","position","isDraggable","effectivePositions","length","targetIndex","minDistance","Infinity","distance","Math","abs","translateY","index","lowerPosition","sort","a","b","undefined","upperPosition","resolveDetent","contentHeight","maxHeight","detentValueInput","min","Error","clampIndex","detentCount","max"],"sourceRoot":"../../src","sources":["bottomSheetUtils.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../../src/BottomSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../../src/BottomSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAqB,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM5E,OAAO,EAAE,KAAK,MAAM,EAAiB,MAAM,oBAAoB,CAAC;AAChE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,WAAW,GAAI,qGAUzB,gBAAgB,4CA+ElB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CodegenTypes, type ViewProps } from 'react-native';
|
|
1
|
+
import { type CodegenTypes, type ColorValue, type ViewProps } from 'react-native';
|
|
2
2
|
type NativeDetent = Readonly<{
|
|
3
3
|
height: CodegenTypes.Double;
|
|
4
4
|
programmatic: boolean;
|
|
@@ -7,6 +7,8 @@ export interface NativeProps extends ViewProps {
|
|
|
7
7
|
detents: ReadonlyArray<NativeDetent>;
|
|
8
8
|
index: CodegenTypes.Int32;
|
|
9
9
|
animateIn: boolean;
|
|
10
|
+
modal: boolean;
|
|
11
|
+
scrimColor: ColorValue;
|
|
10
12
|
onIndexChange?: CodegenTypes.DirectEventHandler<Readonly<{
|
|
11
13
|
index: CodegenTypes.Int32;
|
|
12
14
|
}>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheetNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/BottomSheetNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,KAAK,YAAY,GAAG,QAAQ,CAAC;IAC3B,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC,CAAC;AAEH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAC7C,QAAQ,CAAC;QAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAA;KAAE,CAAC,CACxC,CAAC;IACF,gBAAgB,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAChD,QAAQ,CAAC;QAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAA;KAAE,CAAC,CAC5C,CAAC;CACH;;AAED,wBAAsE"}
|
|
1
|
+
{"version":3,"file":"BottomSheetNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/BottomSheetNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,KAAK,YAAY,GAAG,QAAQ,CAAC;IAC3B,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC,CAAC;AAEH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,aAAa,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAC7C,QAAQ,CAAC;QAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAA;KAAE,CAAC,CACxC,CAAC;IACF,gBAAgB,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAChD,QAAQ,CAAC;QAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAA;KAAE,CAAC,CAC5C,CAAC;CACH;;AAED,wBAAsE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bottomSheetUtils.d.ts","sourceRoot":"","sources":["../../../src/bottomSheetUtils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"bottomSheetUtils.d.ts","sourceRoot":"","sources":["../../../src/bottomSheetUtils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7C,MAAM,MAAM,MAAM,GACd,WAAW,GACX;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnD,eAAO,MAAM,YAAY,GAAI,OAAO,WAAW,KAAG,MAGhD,CAAC;AAEH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,KAAG,WAG5C,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,KAAG,OAKrD,CAAC;AAIF,eAAO,MAAM,cAAc,GACzB,kBAAkB,MAAM,EACxB,WAAW,MAAM,EACjB,cAAc,MAAM,EACpB,cAAc;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,EAAE,WAiC5E,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,eAAe,MAAM,EACrB,WAAW,MAAM,WAQlB,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,EAAE,aAAa,MAAM,WAG5D,CAAC"}
|
package/package.json
CHANGED
package/src/BottomSheet.tsx
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, useState, type ReactNode } from 'react';
|
|
2
2
|
import type { LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
Animated,
|
|
5
|
-
Pressable,
|
|
6
|
-
StyleSheet,
|
|
7
|
-
View,
|
|
8
|
-
useWindowDimensions,
|
|
9
|
-
} from 'react-native';
|
|
3
|
+
import { Animated, StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
10
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
11
5
|
|
|
12
6
|
import BottomSheetNativeComponent from './BottomSheetNativeComponent';
|
|
@@ -15,23 +9,6 @@ import { type Detent, resolveDetent } from './bottomSheetUtils';
|
|
|
15
9
|
export type { Detent, DetentValue } from './bottomSheetUtils';
|
|
16
10
|
export { programmatic } from './bottomSheetUtils';
|
|
17
11
|
|
|
18
|
-
const DefaultScrim = ({
|
|
19
|
-
progress,
|
|
20
|
-
color,
|
|
21
|
-
}: {
|
|
22
|
-
progress: Animated.Value;
|
|
23
|
-
color: string;
|
|
24
|
-
}) => {
|
|
25
|
-
return (
|
|
26
|
-
<Animated.View
|
|
27
|
-
style={[
|
|
28
|
-
StyleSheet.absoluteFill,
|
|
29
|
-
{ flex: 1, backgroundColor: color, opacity: progress },
|
|
30
|
-
]}
|
|
31
|
-
/>
|
|
32
|
-
);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
12
|
export interface BottomSheetProps {
|
|
36
13
|
children: ReactNode;
|
|
37
14
|
style?: StyleProp<ViewStyle>;
|
|
@@ -47,7 +24,7 @@ export interface BottomSheetProps {
|
|
|
47
24
|
export const BottomSheet = ({
|
|
48
25
|
children,
|
|
49
26
|
style,
|
|
50
|
-
detents = [0, '
|
|
27
|
+
detents = [0, 'content'],
|
|
51
28
|
index,
|
|
52
29
|
animateIn = true,
|
|
53
30
|
onIndexChange,
|
|
@@ -55,12 +32,10 @@ export const BottomSheet = ({
|
|
|
55
32
|
modal = false,
|
|
56
33
|
scrimColor = 'rgba(0, 0, 0, 0.5)',
|
|
57
34
|
}: BottomSheetProps) => {
|
|
58
|
-
const { height:
|
|
35
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
59
36
|
const insets = useSafeAreaInsets();
|
|
60
|
-
const maxHeight =
|
|
37
|
+
const maxHeight = windowHeight - insets.top;
|
|
61
38
|
const [contentHeight, setContentHeight] = useState(0);
|
|
62
|
-
const currentPositionRef = useRef(0);
|
|
63
|
-
const scrimProgress = useRef(new Animated.Value(0)).current;
|
|
64
39
|
const sheetOpacity = useRef(new Animated.Value(0)).current;
|
|
65
40
|
|
|
66
41
|
const resolvedDetents = detents.map((detent) => {
|
|
@@ -77,45 +52,6 @@ export const BottomSheet = ({
|
|
|
77
52
|
|
|
78
53
|
const clampedIndex = Math.max(0, Math.min(index, resolvedDetents.length - 1));
|
|
79
54
|
const isCollapsed = (resolvedDetents[clampedIndex]?.height ?? 0) === 0;
|
|
80
|
-
const scrimPressEnabledRef = useRef(!modal || isCollapsed);
|
|
81
|
-
const previousIsCollapsedRef = useRef(isCollapsed);
|
|
82
|
-
const firstNonzeroDetent =
|
|
83
|
-
resolvedDetents.find((detent) => detent.height > 0)?.height ?? 0;
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
const progress =
|
|
87
|
-
firstNonzeroDetent <= 0
|
|
88
|
-
? 0
|
|
89
|
-
: Math.min(
|
|
90
|
-
1,
|
|
91
|
-
Math.max(0, currentPositionRef.current / firstNonzeroDetent)
|
|
92
|
-
);
|
|
93
|
-
scrimProgress.setValue(progress);
|
|
94
|
-
}, [firstNonzeroDetent, scrimProgress]);
|
|
95
|
-
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
if (!modal) {
|
|
98
|
-
scrimPressEnabledRef.current = true;
|
|
99
|
-
previousIsCollapsedRef.current = isCollapsed;
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (previousIsCollapsedRef.current && !isCollapsed) {
|
|
104
|
-
scrimPressEnabledRef.current = false;
|
|
105
|
-
previousIsCollapsedRef.current = isCollapsed;
|
|
106
|
-
|
|
107
|
-
const frame = requestAnimationFrame(() => {
|
|
108
|
-
scrimPressEnabledRef.current = true;
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return () => cancelAnimationFrame(frame);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
scrimPressEnabledRef.current = !isCollapsed;
|
|
115
|
-
previousIsCollapsedRef.current = isCollapsed;
|
|
116
|
-
return undefined;
|
|
117
|
-
}, [isCollapsed, modal]);
|
|
118
|
-
|
|
119
55
|
const handleIndexChange = (event: { nativeEvent: { index: number } }) => {
|
|
120
56
|
onIndexChange?.(event.nativeEvent.index);
|
|
121
57
|
};
|
|
@@ -124,45 +60,15 @@ export const BottomSheet = ({
|
|
|
124
60
|
nativeEvent: { position: number };
|
|
125
61
|
}) => {
|
|
126
62
|
const height = event.nativeEvent.position;
|
|
127
|
-
currentPositionRef.current = height;
|
|
128
|
-
const progress =
|
|
129
|
-
firstNonzeroDetent <= 0
|
|
130
|
-
? 0
|
|
131
|
-
: Math.min(1, Math.max(0, height / firstNonzeroDetent));
|
|
132
|
-
scrimProgress.setValue(progress);
|
|
133
63
|
sheetOpacity.setValue(height === 0 ? 0 : 1);
|
|
134
64
|
onPositionChange?.(height);
|
|
135
65
|
};
|
|
136
66
|
|
|
137
|
-
const closedIndex = resolvedDetents.findIndex(
|
|
138
|
-
(detent) => detent.height === 0
|
|
139
|
-
);
|
|
140
|
-
const handleScrimPress = () => {
|
|
141
|
-
if (
|
|
142
|
-
closedIndex === -1 ||
|
|
143
|
-
clampedIndex === closedIndex ||
|
|
144
|
-
!scrimPressEnabledRef.current
|
|
145
|
-
) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
onIndexChange?.(closedIndex);
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const scrimElement = modal ? (
|
|
153
|
-
<DefaultScrim progress={scrimProgress} color={scrimColor} />
|
|
154
|
-
) : null;
|
|
155
|
-
|
|
156
67
|
const sheet = (
|
|
157
68
|
<Animated.View
|
|
158
69
|
style={StyleSheet.absoluteFill}
|
|
159
70
|
pointerEvents={modal ? (isCollapsed ? 'none' : 'auto') : 'box-none'}
|
|
160
71
|
>
|
|
161
|
-
{modal && scrimElement !== null ? (
|
|
162
|
-
<Pressable style={StyleSheet.absoluteFill} onPress={handleScrimPress}>
|
|
163
|
-
{scrimElement}
|
|
164
|
-
</Pressable>
|
|
165
|
-
) : null}
|
|
166
72
|
<Animated.View
|
|
167
73
|
pointerEvents="box-none"
|
|
168
74
|
style={[StyleSheet.absoluteFill, { opacity: sheetOpacity }]}
|
|
@@ -175,13 +81,18 @@ export const BottomSheet = ({
|
|
|
175
81
|
left: 0,
|
|
176
82
|
right: 0,
|
|
177
83
|
bottom: 0,
|
|
178
|
-
height
|
|
84
|
+
// The native host always spans the full height of its container.
|
|
85
|
+
// Detents are still capped to `maxHeight`, so the sheet itself
|
|
86
|
+
// never extends under the status bar.
|
|
87
|
+
height: windowHeight,
|
|
179
88
|
},
|
|
180
89
|
style,
|
|
181
90
|
]}
|
|
182
91
|
detents={resolvedDetents}
|
|
183
92
|
index={index}
|
|
184
93
|
animateIn={animateIn}
|
|
94
|
+
modal={modal}
|
|
95
|
+
scrimColor={scrimColor}
|
|
185
96
|
onIndexChange={handleIndexChange}
|
|
186
97
|
onPositionChange={handlePositionChange}
|
|
187
98
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
codegenNativeComponent,
|
|
3
3
|
type CodegenTypes,
|
|
4
|
+
type ColorValue,
|
|
4
5
|
type ViewProps,
|
|
5
6
|
} from 'react-native';
|
|
6
7
|
|
|
@@ -13,6 +14,8 @@ export interface NativeProps extends ViewProps {
|
|
|
13
14
|
detents: ReadonlyArray<NativeDetent>;
|
|
14
15
|
index: CodegenTypes.Int32;
|
|
15
16
|
animateIn: boolean;
|
|
17
|
+
modal: boolean;
|
|
18
|
+
scrimColor: ColorValue;
|
|
16
19
|
onIndexChange?: CodegenTypes.DirectEventHandler<
|
|
17
20
|
Readonly<{ index: CodegenTypes.Int32 }>
|
|
18
21
|
>;
|
package/src/bottomSheetUtils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DetentValue = number | '
|
|
1
|
+
export type DetentValue = number | 'content';
|
|
2
2
|
|
|
3
3
|
export type Detent =
|
|
4
4
|
| DetentValue
|
|
@@ -69,7 +69,7 @@ export const resolveDetent = (
|
|
|
69
69
|
) => {
|
|
70
70
|
const detentValueInput = detentValue(detent);
|
|
71
71
|
if (typeof detentValueInput === 'number') return detentValueInput;
|
|
72
|
-
if (detentValueInput === '
|
|
72
|
+
if (detentValueInput === 'content') {
|
|
73
73
|
return contentHeight > 0 ? Math.min(contentHeight, maxHeight) : maxHeight;
|
|
74
74
|
}
|
|
75
75
|
throw new Error(`Invalid detent: \`${detentValueInput}\`.`);
|