@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.
@@ -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.argb(128, 0, 0, 0)
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.lastOrNull()?.height ?: h.toFloat()
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.lastOrNull()?.height ?: viewHeight.toFloat()
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
- detentSpecs = raw.mapNotNull { dict ->
181
- val height = (dict["height"] as? Number)?.toDouble() ?: return@mapNotNull null
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
- DetentSpec(height = (height * density).toFloat(), programmatic = programmatic)
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.lastOrNull()?.height ?: currentTy)
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
- sheetContainer.translationY = translationY(targetIndex)
203
- emitPosition()
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 setIndex(newIndex: Int) {
213
- if (newIndex < 0) return
214
-
215
- if (!hasLaidOut) {
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
- if (newIndex >= detentSpecs.size || newIndex == targetIndex) return
222
- snapToIndex(newIndex, 0f, emitIndexChange = false)
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 setScrimColor(color: Int?) {
226
- scrimColor = color ?: Color.argb(128, 0, 0, 0)
227
- invalidate()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
703
+ val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
614
704
  return maxHeight - sheetContainer.translationY
615
705
  }
616
706
 
@@ -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
- "height" to map.getDouble("height"),
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
- @"height": @(detent.height),
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;
@@ -55,6 +55,11 @@
55
55
  [_impl setDetents:raw];
56
56
  }
57
57
 
58
+ - (void)setMaxDetentHeight:(CGFloat)maxDetentHeight
59
+ {
60
+ _impl.maxDetentHeight = maxDetentHeight;
61
+ }
62
+
58
63
  - (void)setDetentIndex:(NSInteger)newIndex
59
64
  {
60
65
  [_impl setDetentIndex:newIndex];
@@ -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? = UIColor.black.withAlphaComponent(0.5) {
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
- let maxHeight = detentSpecs.last?.height ?? bounds.height
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 = detentSpecs.last?.height ?? bounds.height
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
- detentSpecs = raw.compactMap { dict in
160
- guard let height = dict["height"] as? Double ?? (dict["height"] as? NSNumber)?.doubleValue else {
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 DetentSpec(height: CGFloat(height), programmatic: programmatic)
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 = detentSpecs.last?.height ?? bounds.height
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 = detentSpecs.last?.height ?? bounds.height
263
- let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.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 = detentSpecs.last?.height ?? bounds.height
268
+ let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
269
269
  let containerTop = bounds.height - maxHeight
270
- let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.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 = detentSpecs.last?.height ?? bounds.height
280
- let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.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 = detentSpecs.last?.height ?? bounds.height
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 = 'rgba(0, 0, 0, 0.5)'
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 [contentHeight, setContentHeight] = useState(0);
29
- const resolvedDetents = detents.map(detent => {
30
- const value = resolveDetent(detent, contentHeight, maxHeight);
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
- height: Math.max(0, Math.min(value, maxHeight)),
33
- programmatic: isDetentProgrammatic(detent)
37
+ value: Math.max(0, Math.min(value, maxHeight)),
38
+ kind: 'points',
39
+ programmatic
34
40
  };
35
41
  });
36
- const handleSentinelLayout = event => {
37
- setContentHeight(event.nativeEvent.layout.y);
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: resolvedDetents,
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
- onLayout: handleSentinelLayout,
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":["useState","StyleSheet","View","useWindowDimensions","useSafeAreaInsets","BottomSheetNativeComponent","Portal","resolveDetent","jsx","_jsx","jsxs","_jsxs","programmatic","BottomSheet","children","style","detents","index","animateIn","onIndexChange","onSettle","onPositionChange","modal","scrimColor","height","windowHeight","insets","maxHeight","top","contentHeight","setContentHeight","resolvedDetents","map","detent","value","Math","max","min","isDetentProgrammatic","handleSentinelLayout","event","nativeEvent","layout","y","clampedIndex","length","isCollapsed","handleIndexChange","handleSettle","handlePositionChange","position","sheet","absoluteFill","pointerEvents","left","right","bottom","collapsable","flex","onLayout"],"sourceRoot":"../../src","sources":["BottomSheet.tsx"],"mappings":"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","ignoreList":[]}
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
- height: CodegenTypes.Double;
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: ColorValue;
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,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,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,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,4CA8ElB,CAAC"}
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
- height: CodegenTypes.Double;
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: ColorValue;
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,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,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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/react-native-bottom-sheet",
3
- "version": "0.8.3",
3
+ "version": "0.9.0-next.2",
4
4
  "description": "Provides bottom-sheet components for React Native.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1,11 +1,11 @@
1
- import { useState, type ReactNode } from 'react';
2
- import type { LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native';
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, resolveDetent } from './bottomSheetUtils';
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 = 'rgba(0, 0, 0, 0.5)',
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 [contentHeight, setContentHeight] = useState(0);
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
- height: Math.max(0, Math.min(value, maxHeight)),
46
- programmatic: isDetentProgrammatic(detent),
53
+ value: Math.max(0, Math.min(value, maxHeight)),
54
+ kind: 'points',
55
+ programmatic,
47
56
  };
48
57
  });
49
58
 
50
- const handleSentinelLayout = (event: LayoutChangeEvent) => {
51
- setContentHeight(event.nativeEvent.layout.y);
52
- };
53
-
54
- const clampedIndex = Math.max(0, Math.min(index, resolvedDetents.length - 1));
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={resolvedDetents}
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 onLayout={handleSentinelLayout} pointerEvents="none" />
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
- height: CodegenTypes.Double;
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: ColorValue;
20
+ scrimColor?: ColorValue;
19
21
  onIndexChange?: CodegenTypes.DirectEventHandler<
20
22
  Readonly<{ index: CodegenTypes.Int32 }>
21
23
  >;