@swmansion/react-native-bottom-sheet 0.8.3-next.2 → 0.9.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 +109 -25
- package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetViewManager.kt +8 -2
- package/ios/BottomSheetComponentView.mm +6 -1
- package/ios/BottomSheetContentView.h +1 -0
- package/ios/BottomSheetContentView.mm +5 -0
- package/ios/RNSBottomSheetHostingView.swift +127 -17
- package/lib/module/BottomSheet.js +26 -15
- package/lib/module/BottomSheet.js.map +1 -1
- package/lib/module/BottomSheetNativeComponent.ts +4 -2
- package/lib/typescript/src/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetNativeComponent.d.ts +4 -2
- package/lib/typescript/src/BottomSheetNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/BottomSheet.tsx +33 -17
- package/src/BottomSheetNativeComponent.ts +4 -2
|
@@ -21,6 +21,12 @@ import com.facebook.react.views.view.ReactViewGroup
|
|
|
21
21
|
import com.facebook.react.uimanager.events.NativeGestureUtil
|
|
22
22
|
import kotlin.math.abs
|
|
23
23
|
|
|
24
|
+
private enum class DetentKind {
|
|
25
|
+
POINTS,
|
|
26
|
+
CONTENT,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private data class RawDetentSpec(val value: Float, val kind: DetentKind, val programmatic: Boolean)
|
|
24
30
|
private data class DetentSpec(val height: Float, val programmatic: Boolean)
|
|
25
31
|
|
|
26
32
|
interface BottomSheetViewListener {
|
|
@@ -38,6 +44,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
38
44
|
|
|
39
45
|
// MARK: - State
|
|
40
46
|
|
|
47
|
+
private var rawDetentSpecs: List<RawDetentSpec> = emptyList()
|
|
41
48
|
private var detentSpecs: List<DetentSpec> = emptyList()
|
|
42
49
|
private var targetIndex: Int = 0
|
|
43
50
|
var animateIn: Boolean = true
|
|
@@ -68,8 +75,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
68
75
|
private var lastTouchY = 0f
|
|
69
76
|
private var activePointerId = MotionEvent.INVALID_POINTER_ID
|
|
70
77
|
private var scrimPressed = false
|
|
71
|
-
private var scrimColor = Color.
|
|
78
|
+
private var scrimColor = Color.TRANSPARENT
|
|
72
79
|
private var scrimProgress = 0f
|
|
80
|
+
private var maxDetentHeight = Float.NaN
|
|
81
|
+
private var contentHeightMarker: View? = null
|
|
82
|
+
|
|
83
|
+
private val contentHeightMarkerLayoutListener =
|
|
84
|
+
View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
|
85
|
+
refreshDetentsFromLayout()
|
|
86
|
+
}
|
|
73
87
|
|
|
74
88
|
init {
|
|
75
89
|
clipChildren = false
|
|
@@ -94,10 +108,12 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
94
108
|
|
|
95
109
|
fun addSheetChild(child: View, index: Int) {
|
|
96
110
|
sheetContainer.addView(child, index)
|
|
111
|
+
refreshContentHeightMarker()
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
fun removeSheetChildAt(index: Int) {
|
|
100
115
|
sheetContainer.removeViewAt(index)
|
|
116
|
+
refreshContentHeightMarker()
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
// MARK: - Child view management
|
|
@@ -115,11 +131,13 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
115
131
|
super.removeView(view)
|
|
116
132
|
} else {
|
|
117
133
|
sheetContainer.removeView(view)
|
|
134
|
+
refreshContentHeightMarker()
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
override fun removeViewAt(index: Int) {
|
|
122
139
|
sheetContainer.removeViewAt(index)
|
|
140
|
+
refreshContentHeightMarker()
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
// MARK: - Layout
|
|
@@ -130,6 +148,8 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
130
148
|
val h = b - t
|
|
131
149
|
if (w <= 0 || h <= 0) return
|
|
132
150
|
|
|
151
|
+
refreshContentHeightMarker()
|
|
152
|
+
refreshDetentsFromLayout()
|
|
133
153
|
layoutSheetContainer(w, h)
|
|
134
154
|
|
|
135
155
|
if (!hasLaidOut && detentSpecs.isNotEmpty()) {
|
|
@@ -139,7 +159,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
139
159
|
targetIndex = indexToApply.coerceIn(0, detentSpecs.size - 1)
|
|
140
160
|
|
|
141
161
|
if (animateIn) {
|
|
142
|
-
val closedTy = detentSpecs.
|
|
162
|
+
val closedTy = detentSpecs.maxOfOrNull { it.height } ?: h.toFloat()
|
|
143
163
|
sheetContainer.translationY = closedTy
|
|
144
164
|
emitPosition()
|
|
145
165
|
snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = false)
|
|
@@ -168,7 +188,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
168
188
|
}
|
|
169
189
|
|
|
170
190
|
private fun layoutSheetContainer(viewWidth: Int, viewHeight: Int) {
|
|
171
|
-
val maxHeight = detentSpecs.
|
|
191
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight(viewHeight)
|
|
172
192
|
val containerTop = (viewHeight - maxHeight).toInt()
|
|
173
193
|
sheetContainer.layout(0, containerTop, viewWidth, containerTop + maxHeight.toInt())
|
|
174
194
|
layoutSheetChildren()
|
|
@@ -177,11 +197,71 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
177
197
|
// MARK: - Prop setters
|
|
178
198
|
|
|
179
199
|
fun setDetents(raw: List<Map<String, Any>>) {
|
|
180
|
-
|
|
181
|
-
val
|
|
200
|
+
rawDetentSpecs = raw.mapNotNull { dict ->
|
|
201
|
+
val value = (dict["value"] as? Number)?.toDouble() ?: return@mapNotNull null
|
|
202
|
+
val kind =
|
|
203
|
+
when ((dict["kind"] as? String)?.lowercase()) {
|
|
204
|
+
"content" -> DetentKind.CONTENT
|
|
205
|
+
else -> DetentKind.POINTS
|
|
206
|
+
}
|
|
182
207
|
val programmatic = dict["programmatic"] as? Boolean ?: false
|
|
183
|
-
|
|
208
|
+
RawDetentSpec(value = (value * density).toFloat(), kind = kind, programmatic = programmatic)
|
|
209
|
+
}
|
|
210
|
+
refreshDetentsFromLayout()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fun setIndex(newIndex: Int) {
|
|
214
|
+
if (newIndex < 0) return
|
|
215
|
+
|
|
216
|
+
if (!hasLaidOut) {
|
|
217
|
+
pendingIndex = newIndex
|
|
218
|
+
targetIndex = newIndex
|
|
219
|
+
return
|
|
184
220
|
}
|
|
221
|
+
|
|
222
|
+
if (newIndex >= detentSpecs.size || newIndex == targetIndex) return
|
|
223
|
+
snapToIndex(newIndex, 0f, emitIndexChange = false)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fun setScrimColor(color: Int?) {
|
|
227
|
+
scrimColor = color ?: Color.TRANSPARENT
|
|
228
|
+
invalidate()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fun setMaxDetentHeight(maxDetentHeight: Double) {
|
|
232
|
+
this.maxDetentHeight = (maxDetentHeight * density).toFloat()
|
|
233
|
+
refreshDetentsFromLayout()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private fun resolvedMaxDetentHeight(viewHeight: Int = height): Float {
|
|
237
|
+
val viewHeightPx = viewHeight.toFloat()
|
|
238
|
+
if (!maxDetentHeight.isFinite() || maxDetentHeight <= 0f) {
|
|
239
|
+
return viewHeightPx
|
|
240
|
+
}
|
|
241
|
+
return maxDetentHeight.coerceIn(0f, viewHeightPx)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private fun resolveDetentSpecs(): List<DetentSpec> {
|
|
245
|
+
val maxHeight = resolvedMaxDetentHeight()
|
|
246
|
+
val contentHeight = currentContentHeight().takeIf { it.isFinite() && it > 0f } ?: maxHeight
|
|
247
|
+
return rawDetentSpecs.map { spec ->
|
|
248
|
+
val height =
|
|
249
|
+
when (spec.kind) {
|
|
250
|
+
DetentKind.POINTS -> spec.value
|
|
251
|
+
DetentKind.CONTENT -> contentHeight.coerceAtMost(maxHeight)
|
|
252
|
+
}.coerceIn(0f, maxHeight)
|
|
253
|
+
DetentSpec(height = height, programmatic = spec.programmatic)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private fun refreshDetentsFromLayout() {
|
|
258
|
+
val resolvedDetents = resolveDetentSpecs()
|
|
259
|
+
if (resolvedDetents == detentSpecs) {
|
|
260
|
+
updateScrim()
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
detentSpecs = resolvedDetents
|
|
185
265
|
if (width > 0 && height > 0 && detentSpecs.isNotEmpty()) {
|
|
186
266
|
layoutSheetContainer(width, height)
|
|
187
267
|
|
|
@@ -195,7 +275,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
195
275
|
activeAnimationEmitsSettle = false
|
|
196
276
|
stopChoreographer()
|
|
197
277
|
sheetContainer.translationY =
|
|
198
|
-
currentTy.coerceIn(0f, detentSpecs.
|
|
278
|
+
currentTy.coerceIn(0f, detentSpecs.maxOfOrNull { it.height } ?: currentTy)
|
|
199
279
|
emitPosition()
|
|
200
280
|
snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = shouldEmitSettle)
|
|
201
281
|
} else {
|
|
@@ -209,28 +289,29 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
209
289
|
updateScrim()
|
|
210
290
|
}
|
|
211
291
|
|
|
212
|
-
fun
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
pendingIndex = newIndex
|
|
217
|
-
targetIndex = newIndex
|
|
218
|
-
return
|
|
219
|
-
}
|
|
292
|
+
private fun currentContentHeight(): Float {
|
|
293
|
+
val marker = contentHeightMarker ?: return Float.NaN
|
|
294
|
+
return marker.top.toFloat()
|
|
295
|
+
}
|
|
220
296
|
|
|
221
|
-
|
|
222
|
-
|
|
297
|
+
private fun refreshContentHeightMarker() {
|
|
298
|
+
val marker = findContentHeightMarker()
|
|
299
|
+
if (marker === contentHeightMarker) return
|
|
300
|
+
contentHeightMarker?.removeOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
301
|
+
contentHeightMarker = marker
|
|
302
|
+
contentHeightMarker?.addOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
223
303
|
}
|
|
224
304
|
|
|
225
|
-
fun
|
|
226
|
-
|
|
227
|
-
|
|
305
|
+
private fun findContentHeightMarker(): View? {
|
|
306
|
+
val contentView = sheetContainer.getChildAt(0) as? ViewGroup ?: return null
|
|
307
|
+
if (contentView.childCount == 0) return null
|
|
308
|
+
return contentView.getChildAt(contentView.childCount - 1)
|
|
228
309
|
}
|
|
229
310
|
|
|
230
311
|
// MARK: - Snap logic
|
|
231
312
|
|
|
232
313
|
private fun translationY(index: Int): Float {
|
|
233
|
-
val maxHeight = detentSpecs.
|
|
314
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
234
315
|
val snapHeight = detentSpecs.getOrNull(index)?.height ?: 0f
|
|
235
316
|
return maxHeight - snapHeight
|
|
236
317
|
}
|
|
@@ -257,7 +338,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
257
338
|
get() = sheetContainer.translationY <= draggableMinTy + 1f
|
|
258
339
|
|
|
259
340
|
private fun emitPosition() {
|
|
260
|
-
val maxHeight = detentSpecs.
|
|
341
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
261
342
|
val ty = sheetContainer.translationY
|
|
262
343
|
val position = maxHeight - ty
|
|
263
344
|
updateScrim(position)
|
|
@@ -274,7 +355,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
274
355
|
private var lastShadowOffsetY = Float.NaN
|
|
275
356
|
|
|
276
357
|
private fun updateShadowState(translationY: Float) {
|
|
277
|
-
val maxDetentHeight = detentSpecs.
|
|
358
|
+
val maxDetentHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
278
359
|
val containerTop = height.toFloat() - maxDetentHeight
|
|
279
360
|
val offsetY = ((containerTop + translationY) / density).toDouble()
|
|
280
361
|
if (offsetY.toFloat() == lastShadowOffsetY) return
|
|
@@ -472,7 +553,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
472
553
|
v
|
|
473
554
|
} ?: 0f
|
|
474
555
|
velocityTracker = null
|
|
475
|
-
val maxHeight = detentSpecs.
|
|
556
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
476
557
|
val currentHeight = maxHeight - sheetContainer.translationY
|
|
477
558
|
val index = bestSnapIndex(currentHeight, velocity)
|
|
478
559
|
snapToIndex(index, velocity)
|
|
@@ -562,6 +643,9 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
562
643
|
stopChoreographer()
|
|
563
644
|
velocityTracker?.recycle()
|
|
564
645
|
velocityTracker = null
|
|
646
|
+
contentHeightMarker?.removeOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
647
|
+
contentHeightMarker = null
|
|
648
|
+
rawDetentSpecs = emptyList()
|
|
565
649
|
detentSpecs = emptyList()
|
|
566
650
|
targetIndex = 0
|
|
567
651
|
pendingIndex = null
|
|
@@ -610,7 +694,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
610
694
|
}
|
|
611
695
|
|
|
612
696
|
private fun currentSheetHeight(): Float {
|
|
613
|
-
val maxHeight = detentSpecs.
|
|
697
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
614
698
|
return maxHeight - sheetContainer.translationY
|
|
615
699
|
}
|
|
616
700
|
|
package/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetViewManager.kt
CHANGED
|
@@ -101,8 +101,9 @@ class BottomSheetViewManager :
|
|
|
101
101
|
for (i in 0 until detents.size()) {
|
|
102
102
|
val map = detents.getMap(i) ?: continue
|
|
103
103
|
list.add(
|
|
104
|
-
mapOf(
|
|
105
|
-
"
|
|
104
|
+
mapOf<String, Any>(
|
|
105
|
+
"value" to map.getDouble("value"),
|
|
106
|
+
"kind" to (map.getString("kind") ?: "points"),
|
|
106
107
|
"programmatic" to map.getBoolean("programmatic"),
|
|
107
108
|
)
|
|
108
109
|
)
|
|
@@ -110,6 +111,11 @@ class BottomSheetViewManager :
|
|
|
110
111
|
view.setDetents(list)
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
@ReactProp(name = "maxDetentHeight")
|
|
115
|
+
override fun setMaxDetentHeight(view: BottomSheetView, maxDetentHeight: Double) {
|
|
116
|
+
view.setMaxDetentHeight(maxDetentHeight)
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
@ReactProp(name = "index")
|
|
114
120
|
override fun setIndex(view: BottomSheetView, index: Int) {
|
|
115
121
|
view.setIndex(index)
|
|
@@ -51,13 +51,18 @@ using namespace facebook::react;
|
|
|
51
51
|
NSMutableArray<NSDictionary *> *detentsArray = [NSMutableArray new];
|
|
52
52
|
for (const auto &detent : newViewProps.detents) {
|
|
53
53
|
[detentsArray addObject:@{
|
|
54
|
-
@"
|
|
54
|
+
@"value": @(detent.value),
|
|
55
|
+
@"kind": detent.kind == "content" ? @"content" : @"points",
|
|
55
56
|
@"programmatic": @(detent.programmatic),
|
|
56
57
|
}];
|
|
57
58
|
}
|
|
58
59
|
[_sheetView setDetents:detentsArray];
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
if (newViewProps.maxDetentHeight != oldViewProps.maxDetentHeight) {
|
|
63
|
+
[_sheetView setMaxDetentHeight:newViewProps.maxDetentHeight];
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
if (_needsIndexSyncAfterRecycle || newViewProps.index != oldViewProps.index) {
|
|
62
67
|
[_sheetView setDetentIndex:newViewProps.index];
|
|
63
68
|
_needsIndexSyncAfterRecycle = NO;
|
|
@@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
18
18
|
@property (nonatomic, readonly) UIView *sheetContainer;
|
|
19
19
|
|
|
20
20
|
- (void)setDetents:(NSArray<NSDictionary *> *)raw;
|
|
21
|
+
- (void)setMaxDetentHeight:(CGFloat)maxDetentHeight;
|
|
21
22
|
- (void)setDetentIndex:(NSInteger)newIndex;
|
|
22
23
|
- (void)setScrimColor:(UIColor *_Nullable)color;
|
|
23
24
|
- (CGFloat)currentContentOffsetY;
|
|
@@ -6,21 +6,36 @@ import UIKit
|
|
|
6
6
|
func bottomSheetHostingView(_ view: RNSBottomSheetHostingView, didChangePosition position: CGFloat)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
private struct DetentSpec {
|
|
9
|
+
private struct DetentSpec: Equatable {
|
|
10
10
|
let height: CGFloat
|
|
11
11
|
let programmatic: Bool
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
private enum DetentKind {
|
|
15
|
+
case points
|
|
16
|
+
case content
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private struct RawDetentSpec {
|
|
20
|
+
let value: CGFloat
|
|
21
|
+
let kind: DetentKind
|
|
22
|
+
let programmatic: Bool
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
@objcMembers
|
|
15
26
|
public final class RNSBottomSheetHostingView: UIView {
|
|
16
27
|
public weak var eventDelegate: RNSBottomSheetHostingViewDelegate?
|
|
17
28
|
public var modal: Bool = false {
|
|
18
29
|
didSet { updateScrim() }
|
|
19
30
|
}
|
|
20
|
-
public var scrimColor: UIColor? =
|
|
31
|
+
public var scrimColor: UIColor? = .clear {
|
|
21
32
|
didSet { scrimView.backgroundColor = scrimColor }
|
|
22
33
|
}
|
|
34
|
+
public var maxDetentHeight: CGFloat = .nan {
|
|
35
|
+
didSet { refreshDetentsFromLayout() }
|
|
36
|
+
}
|
|
23
37
|
|
|
38
|
+
private var rawDetentSpecs: [RawDetentSpec] = []
|
|
24
39
|
private var detentSpecs: [DetentSpec] = [] {
|
|
25
40
|
didSet {
|
|
26
41
|
setNeedsLayout()
|
|
@@ -41,6 +56,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
41
56
|
private var hasLaidOut = false
|
|
42
57
|
private var isPanning = false
|
|
43
58
|
private var isContentInteractionDisabled = false
|
|
59
|
+
private weak var contentHeightMarker: UIView?
|
|
44
60
|
|
|
45
61
|
public override init(frame: CGRect) {
|
|
46
62
|
super.init(frame: frame)
|
|
@@ -100,7 +116,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
100
116
|
guard bounds.width > 0, bounds.height > 0 else { return }
|
|
101
117
|
|
|
102
118
|
scrimView.frame = bounds
|
|
103
|
-
|
|
119
|
+
refreshDetentsFromLayout()
|
|
120
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
104
121
|
sheetContainer.bounds = CGRect(x: 0, y: 0, width: bounds.width, height: maxHeight)
|
|
105
122
|
sheetContainer.center = CGPoint(x: bounds.width / 2, y: bounds.height - maxHeight / 2)
|
|
106
123
|
|
|
@@ -111,7 +128,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
111
128
|
targetIndex = max(0, min(detentSpecs.count - 1, indexToApply))
|
|
112
129
|
|
|
113
130
|
if animateIn {
|
|
114
|
-
let closedTy =
|
|
131
|
+
let closedTy = maximumResolvedDetentHeight ?? bounds.height
|
|
115
132
|
sheetContainer.transform = CGAffineTransform(translationX: 0, y: closedTy)
|
|
116
133
|
emitPosition()
|
|
117
134
|
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: false)
|
|
@@ -128,7 +145,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
128
145
|
}
|
|
129
146
|
|
|
130
147
|
private var presentedSheetFrame: CGRect {
|
|
131
|
-
if let presentation = sheetContainer.layer.presentation() {
|
|
148
|
+
if activeAnimator != nil, let presentation = sheetContainer.layer.presentation() {
|
|
132
149
|
return presentation.frame
|
|
133
150
|
}
|
|
134
151
|
return sheetContainer.frame
|
|
@@ -156,13 +173,16 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
156
173
|
}
|
|
157
174
|
|
|
158
175
|
public func setDetents(_ raw: [NSDictionary]) {
|
|
159
|
-
|
|
160
|
-
guard let
|
|
176
|
+
rawDetentSpecs = raw.compactMap { dict in
|
|
177
|
+
guard let value = dict["value"] as? Double ?? (dict["value"] as? NSNumber)?.doubleValue else {
|
|
161
178
|
return nil
|
|
162
179
|
}
|
|
180
|
+
let kindString = (dict["kind"] as? String) ?? ((dict["kind"] as? NSString) as String?) ?? "points"
|
|
181
|
+
let kind: DetentKind = kindString == "content" ? .content : .points
|
|
163
182
|
let programmatic = (dict["programmatic"] as? Bool) ?? (dict["programmatic"] as? NSNumber)?.boolValue ?? false
|
|
164
|
-
return
|
|
183
|
+
return RawDetentSpec(value: CGFloat(value), kind: kind, programmatic: programmatic)
|
|
165
184
|
}
|
|
185
|
+
refreshDetentsFromLayout()
|
|
166
186
|
guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
|
|
167
187
|
return
|
|
168
188
|
}
|
|
@@ -180,7 +200,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
180
200
|
activeAnimatorEmitsSettle = false
|
|
181
201
|
sheetContainer.transform = CGAffineTransform(
|
|
182
202
|
translationX: 0,
|
|
183
|
-
y: min(max(visualTy, 0),
|
|
203
|
+
y: min(max(visualTy, 0), maximumResolvedDetentHeight ?? visualTy)
|
|
184
204
|
)
|
|
185
205
|
emitPosition()
|
|
186
206
|
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: shouldEmitSettle)
|
|
@@ -206,22 +226,28 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
206
226
|
|
|
207
227
|
public func mountChildComponentView(_ childView: UIView, atIndex index: Int) {
|
|
208
228
|
sheetContainer.insertSubview(childView, at: index)
|
|
229
|
+
refreshContentHeightMarker()
|
|
230
|
+
setNeedsLayout()
|
|
209
231
|
}
|
|
210
232
|
|
|
211
233
|
public func unmountChildComponentView(_ childView: UIView) {
|
|
212
234
|
childView.removeFromSuperview()
|
|
235
|
+
refreshContentHeightMarker()
|
|
236
|
+
setNeedsLayout()
|
|
213
237
|
}
|
|
214
238
|
|
|
215
239
|
public func resetSheetState() {
|
|
216
240
|
activeAnimator?.stopAnimation(true)
|
|
217
241
|
activeAnimator = nil
|
|
218
242
|
stopDisplayLink()
|
|
243
|
+
rawDetentSpecs = []
|
|
219
244
|
detentSpecs = []
|
|
220
245
|
targetIndex = 0
|
|
221
246
|
pendingIndex = nil
|
|
222
247
|
hasLaidOut = false
|
|
223
248
|
isPanning = false
|
|
224
249
|
setContentInteractionEnabled(true)
|
|
250
|
+
contentHeightMarker = nil
|
|
225
251
|
sheetContainer.transform = .identity
|
|
226
252
|
scrimView.alpha = 0
|
|
227
253
|
scrimView.isHidden = true
|
|
@@ -238,7 +264,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
238
264
|
}
|
|
239
265
|
|
|
240
266
|
private func translationY(for index: Int) -> CGFloat {
|
|
241
|
-
let maxHeight =
|
|
267
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
242
268
|
let snapHeight = detent(at: index).height
|
|
243
269
|
return maxHeight - snapHeight
|
|
244
270
|
}
|
|
@@ -259,15 +285,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
259
285
|
}
|
|
260
286
|
|
|
261
287
|
private var currentSheetHeight: CGFloat {
|
|
262
|
-
let maxHeight =
|
|
263
|
-
let ty =
|
|
288
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
289
|
+
let ty = currentTranslationY
|
|
264
290
|
return maxHeight - ty
|
|
265
291
|
}
|
|
266
292
|
|
|
267
293
|
public var currentContentOffsetY: CGFloat {
|
|
268
|
-
let maxHeight =
|
|
294
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
269
295
|
let containerTop = bounds.height - maxHeight
|
|
270
|
-
let ty =
|
|
296
|
+
let ty = currentTranslationY
|
|
271
297
|
return containerTop + ty
|
|
272
298
|
}
|
|
273
299
|
|
|
@@ -276,8 +302,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
276
302
|
}
|
|
277
303
|
|
|
278
304
|
private func emitPosition() {
|
|
279
|
-
let maxHeight =
|
|
280
|
-
let ty =
|
|
305
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
306
|
+
let ty = currentTranslationY
|
|
281
307
|
let position = maxHeight - ty
|
|
282
308
|
updateScrim(forPosition: position)
|
|
283
309
|
updateSheetVisibility(forPosition: position)
|
|
@@ -371,7 +397,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
371
397
|
}
|
|
372
398
|
|
|
373
399
|
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
|
|
374
|
-
let maxHeight =
|
|
400
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
375
401
|
|
|
376
402
|
switch gesture.state {
|
|
377
403
|
case .began:
|
|
@@ -498,6 +524,83 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
498
524
|
}
|
|
499
525
|
return scrollView.contentOffset.y <= 0
|
|
500
526
|
}
|
|
527
|
+
|
|
528
|
+
private var resolvedMaxDetentHeight: CGFloat {
|
|
529
|
+
if !maxDetentHeight.isFinite || maxDetentHeight <= 0 {
|
|
530
|
+
return bounds.height
|
|
531
|
+
}
|
|
532
|
+
return min(max(0, maxDetentHeight), bounds.height)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private var maximumResolvedDetentHeight: CGFloat? {
|
|
536
|
+
detentSpecs.map(\.height).max()
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private func resolveDetentSpecs() -> [DetentSpec] {
|
|
540
|
+
let maxHeight = resolvedMaxDetentHeight
|
|
541
|
+
let contentHeight = currentContentHeight.map { min($0, maxHeight) } ?? maxHeight
|
|
542
|
+
return rawDetentSpecs.map { spec in
|
|
543
|
+
let height: CGFloat
|
|
544
|
+
switch spec.kind {
|
|
545
|
+
case .points:
|
|
546
|
+
height = spec.value
|
|
547
|
+
case .content:
|
|
548
|
+
height = contentHeight
|
|
549
|
+
}
|
|
550
|
+
return DetentSpec(height: min(max(0, height), maxHeight), programmatic: spec.programmatic)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private func refreshDetentsFromLayout() {
|
|
555
|
+
refreshContentHeightMarker()
|
|
556
|
+
let resolvedDetents = resolveDetentSpecs()
|
|
557
|
+
guard resolvedDetents != detentSpecs else {
|
|
558
|
+
updateScrim()
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
detentSpecs = resolvedDetents
|
|
563
|
+
|
|
564
|
+
guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if hasLaidOut && !isPanning {
|
|
569
|
+
targetIndex = max(0, min(detentSpecs.count - 1, targetIndex))
|
|
570
|
+
|
|
571
|
+
if let animator = activeAnimator {
|
|
572
|
+
stopDisplayLink()
|
|
573
|
+
let visualTy = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.ty
|
|
574
|
+
let shouldEmitSettle = activeAnimatorEmitsSettle
|
|
575
|
+
animator.stopAnimation(true)
|
|
576
|
+
activeAnimator = nil
|
|
577
|
+
activeAnimatorEmitsSettle = false
|
|
578
|
+
sheetContainer.transform = CGAffineTransform(
|
|
579
|
+
translationX: 0,
|
|
580
|
+
y: min(max(visualTy, 0), maximumResolvedDetentHeight ?? visualTy)
|
|
581
|
+
)
|
|
582
|
+
emitPosition()
|
|
583
|
+
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: shouldEmitSettle)
|
|
584
|
+
} else {
|
|
585
|
+
sheetContainer.transform = CGAffineTransform(translationX: 0, y: translationY(for: targetIndex))
|
|
586
|
+
emitPosition()
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private func refreshContentHeightMarker() {
|
|
592
|
+
contentHeightMarker = findContentHeightMarker()
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private func findContentHeightMarker() -> UIView? {
|
|
596
|
+
guard let contentView = sheetContainer.subviews.first else { return nil }
|
|
597
|
+
return contentView.subviews.last
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private var currentContentHeight: CGFloat? {
|
|
601
|
+
guard let marker = contentHeightMarker else { return nil }
|
|
602
|
+
return marker.frame.minY.isFinite ? marker.frame.minY : nil
|
|
603
|
+
}
|
|
501
604
|
}
|
|
502
605
|
|
|
503
606
|
extension RNSBottomSheetHostingView: UIGestureRecognizerDelegate {
|
|
@@ -518,6 +621,13 @@ extension RNSBottomSheetHostingView: UIGestureRecognizerDelegate {
|
|
|
518
621
|
}
|
|
519
622
|
|
|
520
623
|
private extension RNSBottomSheetHostingView {
|
|
624
|
+
var currentTranslationY: CGFloat {
|
|
625
|
+
if activeAnimator != nil, let presentation = sheetContainer.layer.presentation() {
|
|
626
|
+
return presentation.affineTransform().ty
|
|
627
|
+
}
|
|
628
|
+
return sheetContainer.transform.ty
|
|
629
|
+
}
|
|
630
|
+
|
|
521
631
|
func updateScrim() {
|
|
522
632
|
updateScrim(forPosition: currentSheetHeight)
|
|
523
633
|
updateSheetVisibility(forPosition: currentSheetHeight)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react';
|
|
4
3
|
import { StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
5
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
5
|
import BottomSheetNativeComponent from './BottomSheetNativeComponent';
|
|
7
6
|
import { Portal } from "./BottomSheetProvider.js";
|
|
8
|
-
import { resolveDetent } from "./bottomSheetUtils.js";
|
|
9
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
8
|
export { programmatic } from "./bottomSheetUtils.js";
|
|
11
9
|
export const BottomSheet = ({
|
|
@@ -18,26 +16,32 @@ export const BottomSheet = ({
|
|
|
18
16
|
onSettle,
|
|
19
17
|
onPositionChange,
|
|
20
18
|
modal = false,
|
|
21
|
-
scrimColor
|
|
19
|
+
scrimColor
|
|
22
20
|
}) => {
|
|
23
21
|
const {
|
|
24
22
|
height: windowHeight
|
|
25
23
|
} = useWindowDimensions();
|
|
26
24
|
const insets = useSafeAreaInsets();
|
|
27
25
|
const maxHeight = windowHeight - insets.top;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const value =
|
|
26
|
+
const nativeDetents = detents.map(detent => {
|
|
27
|
+
const programmatic = isDetentProgrammatic(detent);
|
|
28
|
+
const value = resolveDetentValue(detent);
|
|
29
|
+
if (value === 'content') {
|
|
30
|
+
return {
|
|
31
|
+
value: 0,
|
|
32
|
+
kind: 'content',
|
|
33
|
+
programmatic
|
|
34
|
+
};
|
|
35
|
+
}
|
|
31
36
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
value: Math.max(0, Math.min(value, maxHeight)),
|
|
38
|
+
kind: 'points',
|
|
39
|
+
programmatic
|
|
34
40
|
};
|
|
35
41
|
});
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const clampedIndex = Math.max(0, Math.min(index, resolvedDetents.length - 1));
|
|
40
|
-
const isCollapsed = (resolvedDetents[clampedIndex]?.height ?? 0) === 0;
|
|
42
|
+
const clampedIndex = Math.max(0, Math.min(index, nativeDetents.length - 1));
|
|
43
|
+
const selectedDetentValue = detents[clampedIndex] ? resolveDetentValue(detents[clampedIndex]) : 0;
|
|
44
|
+
const isCollapsed = selectedDetentValue === 0;
|
|
41
45
|
const handleIndexChange = event => {
|
|
42
46
|
onIndexChange?.(event.nativeEvent.index);
|
|
43
47
|
};
|
|
@@ -66,7 +70,8 @@ export const BottomSheet = ({
|
|
|
66
70
|
// never extends under the status bar.
|
|
67
71
|
height: windowHeight
|
|
68
72
|
}, style],
|
|
69
|
-
detents:
|
|
73
|
+
detents: nativeDetents,
|
|
74
|
+
maxDetentHeight: maxHeight,
|
|
70
75
|
index: index,
|
|
71
76
|
animateIn: animateIn,
|
|
72
77
|
modal: modal,
|
|
@@ -81,7 +86,7 @@ export const BottomSheet = ({
|
|
|
81
86
|
maxHeight
|
|
82
87
|
},
|
|
83
88
|
children: [children, /*#__PURE__*/_jsx(View, {
|
|
84
|
-
|
|
89
|
+
collapsable: false,
|
|
85
90
|
pointerEvents: "none"
|
|
86
91
|
})]
|
|
87
92
|
})
|
|
@@ -101,4 +106,10 @@ function isDetentProgrammatic(detent) {
|
|
|
101
106
|
}
|
|
102
107
|
return false;
|
|
103
108
|
}
|
|
109
|
+
function resolveDetentValue(detent) {
|
|
110
|
+
if (typeof detent === 'object' && detent !== null) {
|
|
111
|
+
return detent.value;
|
|
112
|
+
}
|
|
113
|
+
return detent;
|
|
114
|
+
}
|
|
104
115
|
//# sourceMappingURL=BottomSheet.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["StyleSheet","View","useWindowDimensions","useSafeAreaInsets","BottomSheetNativeComponent","Portal","jsx","_jsx","jsxs","_jsxs","programmatic","BottomSheet","children","style","detents","index","animateIn","onIndexChange","onSettle","onPositionChange","modal","scrimColor","height","windowHeight","insets","maxHeight","top","nativeDetents","map","detent","isDetentProgrammatic","value","resolveDetentValue","kind","Math","max","min","clampedIndex","length","selectedDetentValue","isCollapsed","handleIndexChange","event","nativeEvent","handleSettle","handlePositionChange","position","sheet","absoluteFill","pointerEvents","left","right","bottom","maxDetentHeight","collapsable","flex"],"sourceRoot":"../../src","sources":["BottomSheet.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -6,16 +6,18 @@ import {
|
|
|
6
6
|
} from 'react-native';
|
|
7
7
|
|
|
8
8
|
type NativeDetent = Readonly<{
|
|
9
|
-
|
|
9
|
+
value: CodegenTypes.Double;
|
|
10
|
+
kind: string;
|
|
10
11
|
programmatic: boolean;
|
|
11
12
|
}>;
|
|
12
13
|
|
|
13
14
|
export interface NativeProps extends ViewProps {
|
|
14
15
|
detents: ReadonlyArray<NativeDetent>;
|
|
16
|
+
maxDetentHeight: CodegenTypes.Double;
|
|
15
17
|
index: CodegenTypes.Int32;
|
|
16
18
|
animateIn: boolean;
|
|
17
19
|
modal: boolean;
|
|
18
|
-
scrimColor
|
|
20
|
+
scrimColor?: ColorValue;
|
|
19
21
|
onIndexChange?: CodegenTypes.DirectEventHandler<
|
|
20
22
|
Readonly<{ index: CodegenTypes.Int32 }>
|
|
21
23
|
>;
|
|
@@ -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,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAMzD,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,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,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,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,+GAWzB,gBAAgB,4CAuFlB,CAAC"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { type CodegenTypes, type ColorValue, type ViewProps } from 'react-native';
|
|
2
2
|
type NativeDetent = Readonly<{
|
|
3
|
-
|
|
3
|
+
value: CodegenTypes.Double;
|
|
4
|
+
kind: string;
|
|
4
5
|
programmatic: boolean;
|
|
5
6
|
}>;
|
|
6
7
|
export interface NativeProps extends ViewProps {
|
|
7
8
|
detents: ReadonlyArray<NativeDetent>;
|
|
9
|
+
maxDetentHeight: CodegenTypes.Double;
|
|
8
10
|
index: CodegenTypes.Int32;
|
|
9
11
|
animateIn: boolean;
|
|
10
12
|
modal: boolean;
|
|
11
|
-
scrimColor
|
|
13
|
+
scrimColor?: ColorValue;
|
|
12
14
|
onIndexChange?: CodegenTypes.DirectEventHandler<Readonly<{
|
|
13
15
|
index: CodegenTypes.Int32;
|
|
14
16
|
}>>;
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC,CAAC;AAEH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,eAAe,EAAE,YAAY,CAAC,MAAM,CAAC;IACrC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAC7C,QAAQ,CAAC;QAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAA;KAAE,CAAC,CACxC,CAAC;IACF,QAAQ,CAAC,EAAE,YAAY,CAAC,kBAAkB,CACxC,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"}
|
package/package.json
CHANGED
package/src/BottomSheet.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
import { StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
|
|
6
6
|
import BottomSheetNativeComponent from './BottomSheetNativeComponent';
|
|
7
7
|
import { Portal } from './BottomSheetProvider';
|
|
8
|
-
import { type Detent
|
|
8
|
+
import { type Detent } from './bottomSheetUtils';
|
|
9
9
|
export type { Detent, DetentValue } from './bottomSheetUtils';
|
|
10
10
|
export { programmatic } from './bottomSheetUtils';
|
|
11
11
|
|
|
@@ -32,27 +32,35 @@ export const BottomSheet = ({
|
|
|
32
32
|
onSettle,
|
|
33
33
|
onPositionChange,
|
|
34
34
|
modal = false,
|
|
35
|
-
scrimColor
|
|
35
|
+
scrimColor,
|
|
36
36
|
}: BottomSheetProps) => {
|
|
37
37
|
const { height: windowHeight } = useWindowDimensions();
|
|
38
38
|
const insets = useSafeAreaInsets();
|
|
39
39
|
const maxHeight = windowHeight - insets.top;
|
|
40
|
-
const
|
|
40
|
+
const nativeDetents = detents.map((detent) => {
|
|
41
|
+
const programmatic = isDetentProgrammatic(detent);
|
|
42
|
+
const value = resolveDetentValue(detent);
|
|
43
|
+
|
|
44
|
+
if (value === 'content') {
|
|
45
|
+
return {
|
|
46
|
+
value: 0,
|
|
47
|
+
kind: 'content',
|
|
48
|
+
programmatic,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
41
51
|
|
|
42
|
-
const resolvedDetents = detents.map((detent) => {
|
|
43
|
-
const value = resolveDetent(detent, contentHeight, maxHeight);
|
|
44
52
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
value: Math.max(0, Math.min(value, maxHeight)),
|
|
54
|
+
kind: 'points',
|
|
55
|
+
programmatic,
|
|
47
56
|
};
|
|
48
57
|
});
|
|
49
58
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const isCollapsed = (resolvedDetents[clampedIndex]?.height ?? 0) === 0;
|
|
59
|
+
const clampedIndex = Math.max(0, Math.min(index, nativeDetents.length - 1));
|
|
60
|
+
const selectedDetentValue = detents[clampedIndex]
|
|
61
|
+
? resolveDetentValue(detents[clampedIndex])
|
|
62
|
+
: 0;
|
|
63
|
+
const isCollapsed = selectedDetentValue === 0;
|
|
56
64
|
const handleIndexChange = (event: { nativeEvent: { index: number } }) => {
|
|
57
65
|
onIndexChange?.(event.nativeEvent.index);
|
|
58
66
|
};
|
|
@@ -88,7 +96,8 @@ export const BottomSheet = ({
|
|
|
88
96
|
},
|
|
89
97
|
style,
|
|
90
98
|
]}
|
|
91
|
-
detents={
|
|
99
|
+
detents={nativeDetents}
|
|
100
|
+
maxDetentHeight={maxHeight}
|
|
92
101
|
index={index}
|
|
93
102
|
animateIn={animateIn}
|
|
94
103
|
modal={modal}
|
|
@@ -99,7 +108,7 @@ export const BottomSheet = ({
|
|
|
99
108
|
>
|
|
100
109
|
<View collapsable={false} style={{ flex: 1, maxHeight }}>
|
|
101
110
|
{children}
|
|
102
|
-
<View
|
|
111
|
+
<View collapsable={false} pointerEvents="none" />
|
|
103
112
|
</View>
|
|
104
113
|
</BottomSheetNativeComponent>
|
|
105
114
|
</View>
|
|
@@ -119,3 +128,10 @@ function isDetentProgrammatic(detent: Detent): boolean {
|
|
|
119
128
|
}
|
|
120
129
|
return false;
|
|
121
130
|
}
|
|
131
|
+
|
|
132
|
+
function resolveDetentValue(detent: Detent) {
|
|
133
|
+
if (typeof detent === 'object' && detent !== null) {
|
|
134
|
+
return detent.value;
|
|
135
|
+
}
|
|
136
|
+
return detent;
|
|
137
|
+
}
|
|
@@ -6,16 +6,18 @@ import {
|
|
|
6
6
|
} from 'react-native';
|
|
7
7
|
|
|
8
8
|
type NativeDetent = Readonly<{
|
|
9
|
-
|
|
9
|
+
value: CodegenTypes.Double;
|
|
10
|
+
kind: string;
|
|
10
11
|
programmatic: boolean;
|
|
11
12
|
}>;
|
|
12
13
|
|
|
13
14
|
export interface NativeProps extends ViewProps {
|
|
14
15
|
detents: ReadonlyArray<NativeDetent>;
|
|
16
|
+
maxDetentHeight: CodegenTypes.Double;
|
|
15
17
|
index: CodegenTypes.Int32;
|
|
16
18
|
animateIn: boolean;
|
|
17
19
|
modal: boolean;
|
|
18
|
-
scrimColor
|
|
20
|
+
scrimColor?: ColorValue;
|
|
19
21
|
onIndexChange?: CodegenTypes.DirectEventHandler<
|
|
20
22
|
Readonly<{ index: CodegenTypes.Int32 }>
|
|
21
23
|
>;
|