@swmansion/react-native-bottom-sheet 0.8.3 → 0.9.0-next.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/android/src/main/java/com/swmansion/reactnativebottomsheet/BottomSheetView.kt +117 -27
- 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 +132 -42
- 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
|
|
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
|
|
184
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,12 +275,18 @@ 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 {
|
|
202
|
-
|
|
203
|
-
|
|
282
|
+
val targetTy = translationY(targetIndex)
|
|
283
|
+
val currentTy = sheetContainer.translationY
|
|
284
|
+
if (abs(targetTy - currentTy) <= 0.5f) {
|
|
285
|
+
sheetContainer.translationY = targetTy
|
|
286
|
+
emitPosition()
|
|
287
|
+
} else {
|
|
288
|
+
snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = false)
|
|
289
|
+
}
|
|
204
290
|
}
|
|
205
291
|
}
|
|
206
292
|
}
|
|
@@ -209,28 +295,29 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
209
295
|
updateScrim()
|
|
210
296
|
}
|
|
211
297
|
|
|
212
|
-
fun
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
pendingIndex = newIndex
|
|
217
|
-
targetIndex = newIndex
|
|
218
|
-
return
|
|
219
|
-
}
|
|
298
|
+
private fun currentContentHeight(): Float {
|
|
299
|
+
val marker = contentHeightMarker ?: return Float.NaN
|
|
300
|
+
return marker.top.toFloat()
|
|
301
|
+
}
|
|
220
302
|
|
|
221
|
-
|
|
222
|
-
|
|
303
|
+
private fun refreshContentHeightMarker() {
|
|
304
|
+
val marker = findContentHeightMarker()
|
|
305
|
+
if (marker === contentHeightMarker) return
|
|
306
|
+
contentHeightMarker?.removeOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
307
|
+
contentHeightMarker = marker
|
|
308
|
+
contentHeightMarker?.addOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
223
309
|
}
|
|
224
310
|
|
|
225
|
-
fun
|
|
226
|
-
|
|
227
|
-
|
|
311
|
+
private fun findContentHeightMarker(): View? {
|
|
312
|
+
val contentView = sheetContainer.getChildAt(0) as? ViewGroup ?: return null
|
|
313
|
+
if (contentView.childCount == 0) return null
|
|
314
|
+
return contentView.getChildAt(contentView.childCount - 1)
|
|
228
315
|
}
|
|
229
316
|
|
|
230
317
|
// MARK: - Snap logic
|
|
231
318
|
|
|
232
319
|
private fun translationY(index: Int): Float {
|
|
233
|
-
val maxHeight = detentSpecs.
|
|
320
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
234
321
|
val snapHeight = detentSpecs.getOrNull(index)?.height ?: 0f
|
|
235
322
|
return maxHeight - snapHeight
|
|
236
323
|
}
|
|
@@ -257,7 +344,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
257
344
|
get() = sheetContainer.translationY <= draggableMinTy + 1f
|
|
258
345
|
|
|
259
346
|
private fun emitPosition() {
|
|
260
|
-
val maxHeight = detentSpecs.
|
|
347
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
261
348
|
val ty = sheetContainer.translationY
|
|
262
349
|
val position = maxHeight - ty
|
|
263
350
|
updateScrim(position)
|
|
@@ -274,7 +361,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
274
361
|
private var lastShadowOffsetY = Float.NaN
|
|
275
362
|
|
|
276
363
|
private fun updateShadowState(translationY: Float) {
|
|
277
|
-
val maxDetentHeight = detentSpecs.
|
|
364
|
+
val maxDetentHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
278
365
|
val containerTop = height.toFloat() - maxDetentHeight
|
|
279
366
|
val offsetY = ((containerTop + translationY) / density).toDouble()
|
|
280
367
|
if (offsetY.toFloat() == lastShadowOffsetY) return
|
|
@@ -472,7 +559,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
472
559
|
v
|
|
473
560
|
} ?: 0f
|
|
474
561
|
velocityTracker = null
|
|
475
|
-
val maxHeight = detentSpecs.
|
|
562
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
476
563
|
val currentHeight = maxHeight - sheetContainer.translationY
|
|
477
564
|
val index = bestSnapIndex(currentHeight, velocity)
|
|
478
565
|
snapToIndex(index, velocity)
|
|
@@ -562,6 +649,9 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
562
649
|
stopChoreographer()
|
|
563
650
|
velocityTracker?.recycle()
|
|
564
651
|
velocityTracker = null
|
|
652
|
+
contentHeightMarker?.removeOnLayoutChangeListener(contentHeightMarkerLayoutListener)
|
|
653
|
+
contentHeightMarker = null
|
|
654
|
+
rawDetentSpecs = emptyList()
|
|
565
655
|
detentSpecs = emptyList()
|
|
566
656
|
targetIndex = 0
|
|
567
657
|
pendingIndex = null
|
|
@@ -610,7 +700,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
|
|
|
610
700
|
}
|
|
611
701
|
|
|
612
702
|
private fun currentSheetHeight(): Float {
|
|
613
|
-
val maxHeight = detentSpecs.
|
|
703
|
+
val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
|
|
614
704
|
return maxHeight - sheetContainer.translationY
|
|
615
705
|
}
|
|
616
706
|
|
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,39 +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
|
|
165
|
-
}
|
|
166
|
-
guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if hasLaidOut && !isPanning {
|
|
171
|
-
targetIndex = max(0, min(detentSpecs.count - 1, targetIndex))
|
|
172
|
-
layoutIfNeeded()
|
|
173
|
-
|
|
174
|
-
if let animator = activeAnimator {
|
|
175
|
-
stopDisplayLink()
|
|
176
|
-
let visualTy = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.ty
|
|
177
|
-
let shouldEmitSettle = activeAnimatorEmitsSettle
|
|
178
|
-
animator.stopAnimation(true)
|
|
179
|
-
activeAnimator = nil
|
|
180
|
-
activeAnimatorEmitsSettle = false
|
|
181
|
-
sheetContainer.transform = CGAffineTransform(
|
|
182
|
-
translationX: 0,
|
|
183
|
-
y: min(max(visualTy, 0), detentSpecs.last?.height ?? visualTy)
|
|
184
|
-
)
|
|
185
|
-
emitPosition()
|
|
186
|
-
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: shouldEmitSettle)
|
|
187
|
-
} else {
|
|
188
|
-
sheetContainer.transform = CGAffineTransform(translationX: 0, y: translationY(for: targetIndex))
|
|
189
|
-
emitPosition()
|
|
190
|
-
}
|
|
183
|
+
return RawDetentSpec(value: CGFloat(value), kind: kind, programmatic: programmatic)
|
|
191
184
|
}
|
|
185
|
+
refreshDetentsFromLayout()
|
|
192
186
|
}
|
|
193
187
|
|
|
194
188
|
public func setDetentIndex(_ newIndex: Int) {
|
|
@@ -206,22 +200,28 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
206
200
|
|
|
207
201
|
public func mountChildComponentView(_ childView: UIView, atIndex index: Int) {
|
|
208
202
|
sheetContainer.insertSubview(childView, at: index)
|
|
203
|
+
refreshContentHeightMarker()
|
|
204
|
+
setNeedsLayout()
|
|
209
205
|
}
|
|
210
206
|
|
|
211
207
|
public func unmountChildComponentView(_ childView: UIView) {
|
|
212
208
|
childView.removeFromSuperview()
|
|
209
|
+
refreshContentHeightMarker()
|
|
210
|
+
setNeedsLayout()
|
|
213
211
|
}
|
|
214
212
|
|
|
215
213
|
public func resetSheetState() {
|
|
216
214
|
activeAnimator?.stopAnimation(true)
|
|
217
215
|
activeAnimator = nil
|
|
218
216
|
stopDisplayLink()
|
|
217
|
+
rawDetentSpecs = []
|
|
219
218
|
detentSpecs = []
|
|
220
219
|
targetIndex = 0
|
|
221
220
|
pendingIndex = nil
|
|
222
221
|
hasLaidOut = false
|
|
223
222
|
isPanning = false
|
|
224
223
|
setContentInteractionEnabled(true)
|
|
224
|
+
contentHeightMarker = nil
|
|
225
225
|
sheetContainer.transform = .identity
|
|
226
226
|
scrimView.alpha = 0
|
|
227
227
|
scrimView.isHidden = true
|
|
@@ -238,7 +238,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
private func translationY(for index: Int) -> CGFloat {
|
|
241
|
-
let maxHeight =
|
|
241
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
242
242
|
let snapHeight = detent(at: index).height
|
|
243
243
|
return maxHeight - snapHeight
|
|
244
244
|
}
|
|
@@ -259,15 +259,15 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
private var currentSheetHeight: CGFloat {
|
|
262
|
-
let maxHeight =
|
|
263
|
-
let ty =
|
|
262
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
263
|
+
let ty = currentTranslationY
|
|
264
264
|
return maxHeight - ty
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
public var currentContentOffsetY: CGFloat {
|
|
268
|
-
let maxHeight =
|
|
268
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
269
269
|
let containerTop = bounds.height - maxHeight
|
|
270
|
-
let ty =
|
|
270
|
+
let ty = currentTranslationY
|
|
271
271
|
return containerTop + ty
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -276,8 +276,8 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
private func emitPosition() {
|
|
279
|
-
let maxHeight =
|
|
280
|
-
let ty =
|
|
279
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
280
|
+
let ty = currentTranslationY
|
|
281
281
|
let position = maxHeight - ty
|
|
282
282
|
updateScrim(forPosition: position)
|
|
283
283
|
updateSheetVisibility(forPosition: position)
|
|
@@ -371,7 +371,7 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
|
|
374
|
-
let maxHeight =
|
|
374
|
+
let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
|
|
375
375
|
|
|
376
376
|
switch gesture.state {
|
|
377
377
|
case .began:
|
|
@@ -498,6 +498,89 @@ public final class RNSBottomSheetHostingView: UIView {
|
|
|
498
498
|
}
|
|
499
499
|
return scrollView.contentOffset.y <= 0
|
|
500
500
|
}
|
|
501
|
+
|
|
502
|
+
private var resolvedMaxDetentHeight: CGFloat {
|
|
503
|
+
if !maxDetentHeight.isFinite || maxDetentHeight <= 0 {
|
|
504
|
+
return bounds.height
|
|
505
|
+
}
|
|
506
|
+
return min(max(0, maxDetentHeight), bounds.height)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private var maximumResolvedDetentHeight: CGFloat? {
|
|
510
|
+
detentSpecs.map(\.height).max()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private func resolveDetentSpecs() -> [DetentSpec] {
|
|
514
|
+
let maxHeight = resolvedMaxDetentHeight
|
|
515
|
+
let contentHeight = currentContentHeight.map { min($0, maxHeight) } ?? maxHeight
|
|
516
|
+
return rawDetentSpecs.map { spec in
|
|
517
|
+
let height: CGFloat
|
|
518
|
+
switch spec.kind {
|
|
519
|
+
case .points:
|
|
520
|
+
height = spec.value
|
|
521
|
+
case .content:
|
|
522
|
+
height = contentHeight
|
|
523
|
+
}
|
|
524
|
+
return DetentSpec(height: min(max(0, height), maxHeight), programmatic: spec.programmatic)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private func refreshDetentsFromLayout() {
|
|
529
|
+
refreshContentHeightMarker()
|
|
530
|
+
let resolvedDetents = resolveDetentSpecs()
|
|
531
|
+
guard resolvedDetents != detentSpecs else {
|
|
532
|
+
updateScrim()
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
detentSpecs = resolvedDetents
|
|
537
|
+
|
|
538
|
+
guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if hasLaidOut && !isPanning {
|
|
543
|
+
targetIndex = max(0, min(detentSpecs.count - 1, targetIndex))
|
|
544
|
+
|
|
545
|
+
if let animator = activeAnimator {
|
|
546
|
+
stopDisplayLink()
|
|
547
|
+
let visualTy = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.ty
|
|
548
|
+
let shouldEmitSettle = activeAnimatorEmitsSettle
|
|
549
|
+
animator.stopAnimation(true)
|
|
550
|
+
activeAnimator = nil
|
|
551
|
+
activeAnimatorEmitsSettle = false
|
|
552
|
+
sheetContainer.transform = CGAffineTransform(
|
|
553
|
+
translationX: 0,
|
|
554
|
+
y: min(max(visualTy, 0), maximumResolvedDetentHeight ?? visualTy)
|
|
555
|
+
)
|
|
556
|
+
emitPosition()
|
|
557
|
+
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: shouldEmitSettle)
|
|
558
|
+
} else {
|
|
559
|
+
let targetTy = translationY(for: targetIndex)
|
|
560
|
+
let currentTy = currentTranslationY
|
|
561
|
+
if abs(targetTy - currentTy) <= 0.5 {
|
|
562
|
+
sheetContainer.transform = CGAffineTransform(translationX: 0, y: targetTy)
|
|
563
|
+
emitPosition()
|
|
564
|
+
} else {
|
|
565
|
+
snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: false)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private func refreshContentHeightMarker() {
|
|
572
|
+
contentHeightMarker = findContentHeightMarker()
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private func findContentHeightMarker() -> UIView? {
|
|
576
|
+
guard let contentView = sheetContainer.subviews.first else { return nil }
|
|
577
|
+
return contentView.subviews.last
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private var currentContentHeight: CGFloat? {
|
|
581
|
+
guard let marker = contentHeightMarker else { return nil }
|
|
582
|
+
return marker.frame.minY.isFinite ? marker.frame.minY : nil
|
|
583
|
+
}
|
|
501
584
|
}
|
|
502
585
|
|
|
503
586
|
extension RNSBottomSheetHostingView: UIGestureRecognizerDelegate {
|
|
@@ -518,6 +601,13 @@ extension RNSBottomSheetHostingView: UIGestureRecognizerDelegate {
|
|
|
518
601
|
}
|
|
519
602
|
|
|
520
603
|
private extension RNSBottomSheetHostingView {
|
|
604
|
+
var currentTranslationY: CGFloat {
|
|
605
|
+
if activeAnimator != nil, let presentation = sheetContainer.layer.presentation() {
|
|
606
|
+
return presentation.affineTransform().ty
|
|
607
|
+
}
|
|
608
|
+
return sheetContainer.transform.ty
|
|
609
|
+
}
|
|
610
|
+
|
|
521
611
|
func updateScrim() {
|
|
522
612
|
updateScrim(forPosition: currentSheetHeight)
|
|
523
613
|
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
|
>;
|