@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.
@@ -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
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.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 {
@@ -209,28 +289,29 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
209
289
  updateScrim()
210
290
  }
211
291
 
212
- fun setIndex(newIndex: Int) {
213
- if (newIndex < 0) return
214
-
215
- if (!hasLaidOut) {
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
- if (newIndex >= detentSpecs.size || newIndex == targetIndex) return
222
- snapToIndex(newIndex, 0f, emitIndexChange = false)
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 setScrimColor(color: Int?) {
226
- scrimColor = color ?: Color.argb(128, 0, 0, 0)
227
- invalidate()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
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.lastOrNull()?.height ?: height.toFloat()
697
+ val maxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
614
698
  return maxHeight - sheetContainer.translationY
615
699
  }
616
700
 
@@ -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,13 +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)
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), detentSpecs.last?.height ?? visualTy)
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 = detentSpecs.last?.height ?? bounds.height
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 = detentSpecs.last?.height ?? bounds.height
263
- let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.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 = detentSpecs.last?.height ?? bounds.height
294
+ let maxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
269
295
  let containerTop = bounds.height - maxHeight
270
- let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.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 = detentSpecs.last?.height ?? bounds.height
280
- let ty = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.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 = detentSpecs.last?.height ?? bounds.height
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 = '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-next.2",
3
+ "version": "0.9.0-next.1",
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
  >;