@lodev09/react-native-true-sheet 3.1.0 → 3.2.0

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.
Files changed (69) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +5 -1
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +103 -118
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +7 -2
  4. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +2 -2
  5. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +64 -81
  6. package/android/src/main/res/values/styles.xml +1 -0
  7. package/ios/TrueSheetView.mm +20 -3
  8. package/ios/TrueSheetViewController.h +2 -0
  9. package/ios/TrueSheetViewController.mm +36 -18
  10. package/lib/module/TrueSheet.js +3 -1
  11. package/lib/module/TrueSheet.js.map +1 -1
  12. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -2
  13. package/lib/module/navigation/TrueSheetRouter.js +12 -18
  14. package/lib/module/navigation/TrueSheetRouter.js.map +1 -1
  15. package/lib/module/navigation/TrueSheetView.js +29 -128
  16. package/lib/module/navigation/TrueSheetView.js.map +1 -1
  17. package/lib/module/navigation/createTrueSheetNavigator.js +3 -3
  18. package/lib/module/navigation/createTrueSheetNavigator.js.map +1 -1
  19. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +46 -0
  20. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -0
  21. package/lib/module/navigation/screen/TrueSheetScreen.js +39 -0
  22. package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -0
  23. package/lib/module/navigation/screen/index.js +5 -0
  24. package/lib/module/navigation/screen/index.js.map +1 -0
  25. package/lib/module/navigation/screen/types.js +4 -0
  26. package/lib/module/navigation/screen/types.js.map +1 -0
  27. package/lib/module/navigation/screen/useSheetScreenState.js +77 -0
  28. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -0
  29. package/lib/module/navigation/useTrueSheetNavigation.js +1 -3
  30. package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -1
  31. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  32. package/lib/typescript/src/TrueSheet.types.d.ts +24 -0
  33. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  34. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +3 -2
  35. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  36. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +4 -4
  37. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +1 -1
  38. package/lib/typescript/src/navigation/TrueSheetView.d.ts +1 -1
  39. package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +1 -1
  40. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +1 -1
  41. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +1 -1
  42. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +3 -0
  43. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -0
  44. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +3 -0
  45. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -0
  46. package/lib/typescript/src/navigation/screen/index.d.ts +4 -0
  47. package/lib/typescript/src/navigation/screen/index.d.ts.map +1 -0
  48. package/lib/typescript/src/navigation/screen/types.d.ts +18 -0
  49. package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -0
  50. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +35 -0
  51. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -0
  52. package/lib/typescript/src/navigation/types.d.ts +29 -1
  53. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  54. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +1 -1
  55. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/TrueSheet.tsx +3 -1
  58. package/src/TrueSheet.types.ts +26 -0
  59. package/src/fabric/TrueSheetViewNativeComponent.ts +5 -2
  60. package/src/navigation/TrueSheetRouter.ts +6 -12
  61. package/src/navigation/TrueSheetView.tsx +39 -212
  62. package/src/navigation/createTrueSheetNavigator.tsx +3 -3
  63. package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +47 -0
  64. package/src/navigation/screen/TrueSheetScreen.tsx +37 -0
  65. package/src/navigation/screen/index.ts +3 -0
  66. package/src/navigation/screen/types.ts +20 -0
  67. package/src/navigation/screen/useSheetScreenState.ts +106 -0
  68. package/src/navigation/types.ts +31 -0
  69. package/src/navigation/useTrueSheetNavigation.ts +2 -4
@@ -267,7 +267,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
267
267
  viewController.sheetCornerRadius = radius
268
268
  }
269
269
 
270
- fun setSheetBackgroundColor(color: Int) {
270
+ fun setSheetBackgroundColor(color: Int?) {
271
271
  if (viewController.sheetBackgroundColor == color) return
272
272
  viewController.sheetBackgroundColor = color
273
273
  }
@@ -299,6 +299,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
299
299
  viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen
300
300
  }
301
301
 
302
+ fun setInsetAdjustment(insetAdjustment: String) {
303
+ viewController.insetAdjustment = insetAdjustment
304
+ }
305
+
302
306
  // ==================== State Management ====================
303
307
 
304
308
  /**
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
4
4
  import android.graphics.Color
5
5
  import android.graphics.drawable.ShapeDrawable
6
6
  import android.graphics.drawable.shapes.RoundRectShape
7
+ import android.util.Log
7
8
  import android.util.TypedValue
8
9
  import android.view.MotionEvent
9
10
  import android.view.View
@@ -115,9 +116,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
115
116
  var currentDetentIndex: Int = -1
116
117
  private set
117
118
 
118
- // Resolved detent positions (Y coordinate when sheet rests at each detent)
119
- private val resolvedDetentPositions = mutableListOf<Int>()
120
-
121
119
  private var isDragging = false
122
120
  private var isDismissing = false
123
121
  private var isReconfiguring = false
@@ -144,7 +142,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
144
142
  var grabber: Boolean = true
145
143
  var grabberOptions: GrabberOptions? = null
146
144
  var sheetCornerRadius: Float = -1f
147
- var sheetBackgroundColor: Int = 0
145
+ var sheetBackgroundColor: Int? = null
148
146
  var edgeToEdgeFullScreen: Boolean = false
149
147
 
150
148
  var dismissible: Boolean = true
@@ -167,12 +165,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
167
165
  // MARK: - Computed Properties
168
166
  // ====================================================================
169
167
 
170
- val statusBarHeight: Int
171
- get() = ScreenUtils.getStatusBarHeight(reactContext)
168
+ val bottomInset: Int
169
+ get() = if (edgeToEdgeEnabled) ScreenUtils.getInsets(reactContext).bottom else 0
170
+
171
+ val topInset: Int
172
+ get() = if (edgeToEdgeEnabled) ScreenUtils.getInsets(reactContext).top else 0
173
+
174
+ var insetAdjustment: String = "automatic"
172
175
 
173
- /** Navigation bar height, added to sheet height to match iOS behavior. */
174
- private val bottomInset: Int
175
- get() = ScreenUtils.getNavigationBarHeight(reactContext)
176
+ /** Auto add bottom inset for consistency with iOS when insetAdjustment is 'automatic' */
177
+ val contentBottomInset: Int
178
+ get() = if (insetAdjustment == "automatic") bottomInset else 0
176
179
 
177
180
  /** Edge-to-edge enabled by default on API 36+, or when explicitly configured. */
178
181
  private val edgeToEdgeEnabled: Boolean
@@ -181,10 +184,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
181
184
  return BuildConfig.EDGE_TO_EDGE_ENABLED || dialog?.edgeToEdgeEnabled == true || defaultEnabled
182
185
  }
183
186
 
184
- /** Top inset when edge-to-edge is enabled but not full-screen. */
185
- private val sheetTopInset: Int
186
- get() = if (edgeToEdgeEnabled && !edgeToEdgeFullScreen) statusBarHeight else 0
187
-
188
187
  internal var eventDispatcher: EventDispatcher? = null
189
188
  private val jSTouchDispatcher = JSTouchDispatcher(this)
190
189
  private var jSPointerDispatcher: JSPointerDispatcher? = null
@@ -197,7 +196,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
197
196
  // ====================================================================
198
197
 
199
198
  init {
200
- screenHeight = ScreenUtils.getScreenHeight(reactContext, edgeToEdgeEnabled)
199
+ screenHeight = ScreenUtils.getScreenHeight(reactContext)
201
200
  screenWidth = ScreenUtils.getScreenWidth(reactContext)
202
201
  jSPointerDispatcher = JSPointerDispatcher(this)
203
202
  }
@@ -269,9 +268,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
269
268
  setupGrabber()
270
269
 
271
270
  sheetContainer?.post {
272
- val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
273
- storeResolvedPosition(currentDetentIndex)
274
- emitChangePositionDelegate(positionPx, realtime = false)
271
+ bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
275
272
  positionFooter()
276
273
  }
277
274
 
@@ -293,7 +290,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
293
290
 
294
291
  isDismissing = true
295
292
  emitWillDismissEvents()
296
- emitChangePositionDelegate(screenHeight, realtime = false)
293
+ emitDismissedPosition()
297
294
  }
298
295
 
299
296
  dialog.setOnDismissListener {
@@ -309,9 +306,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
309
306
  object : BottomSheetBehavior.BottomSheetCallback() {
310
307
  override fun onSlide(sheetView: View, slideOffset: Float) {
311
308
  val behavior = behavior ?: return
312
- val positionPx = getCurrentPositionPx(sheetView)
313
309
 
314
- emitChangePositionDelegate(positionPx, realtime = true)
310
+ emitChangePositionDelegate(sheetView, realtime = true)
315
311
 
316
312
  when (behavior.state) {
317
313
  BottomSheetBehavior.STATE_DRAGGING,
@@ -343,8 +339,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
343
339
  if (isReconfiguring) return
344
340
 
345
341
  getDetentInfoForState(newState)?.let { detentInfo ->
346
- storeResolvedPosition(detentInfo.index)
347
-
348
342
  if (isDragging) {
349
343
  val detent = getDetentValueForIndex(detentInfo.index)
350
344
  delegate?.viewControllerDidDragEnd(detentInfo.index, detentInfo.position, detent)
@@ -432,11 +426,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
432
426
  val isExpanded: Boolean
433
427
  get() {
434
428
  val sheetTop = bottomSheetView?.top ?: return false
435
- return sheetTop <= statusBarHeight
429
+ return sheetTop <= topInset
436
430
  }
437
431
 
438
432
  val currentSheetTop: Int
439
- get() = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
433
+ get() = bottomSheetView?.top ?: screenHeight
440
434
 
441
435
  fun getExpectedSheetTop(detentIndex: Int): Int {
442
436
  if (detentIndex < 0 || detentIndex >= detents.size) return screenHeight
@@ -445,15 +439,21 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
445
439
  }
446
440
 
447
441
  /** Hides without dismissing. Used for sheet stacking and RN Screens modals. */
448
- fun hideDialog() {
442
+ fun hideDialog(emitPosition: Boolean = false) {
449
443
  isDialogVisible = false
450
444
  dialog?.window?.decorView?.visibility = INVISIBLE
445
+ if (emitPosition) {
446
+ emitDismissedPosition()
447
+ }
451
448
  }
452
449
 
453
450
  /** Shows a previously hidden dialog. */
454
- fun showDialog() {
451
+ fun showDialog(emitPosition: Boolean = false) {
455
452
  isDialogVisible = true
456
453
  dialog?.window?.decorView?.visibility = VISIBLE
454
+ if (emitPosition) {
455
+ bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
456
+ }
457
457
  }
458
458
 
459
459
  // ====================================================================
@@ -496,10 +496,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
496
496
 
497
497
  isDismissing = true
498
498
  emitWillDismissEvents()
499
-
500
- this.post {
501
- emitChangePositionDelegate(screenHeight, realtime = false)
502
- }
499
+ emitDismissedPosition()
503
500
 
504
501
  if (!animated) {
505
502
  dialog?.window?.setWindowAnimations(0)
@@ -515,19 +512,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
515
512
  fun setupSheetDetents() {
516
513
  val behavior = this.behavior ?: return
517
514
 
518
- if (resolvedDetentPositions.size != detents.size) {
519
- resolvedDetentPositions.clear()
520
- repeat(detents.size) { resolvedDetentPositions.add(0) }
521
- }
522
-
523
- for (i in detents.indices) {
524
- if (detents[i] == -1.0) {
525
- val detentHeight = getDetentHeight(detents[i])
526
- resolvedDetentPositions[i] = screenHeight - detentHeight
527
- }
528
- }
529
-
530
515
  isReconfiguring = true
516
+ val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
517
+ val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
531
518
 
532
519
  behavior.apply {
533
520
  isFitToContents = false
@@ -535,31 +522,26 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
535
522
 
536
523
  val oldExpandOffset = expandedOffset
537
524
 
538
- when (detents.size) {
539
- 1 -> {
540
- setPeekHeight(getDetentHeight(detents[0]), isPresented)
541
- halfExpandedRatio = minOf(peekHeight.toFloat() / screenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
542
- expandedOffset = screenHeight - peekHeight
543
- isFitToContents = expandedOffset == 0
544
- }
525
+ val maxAvailableHeight = realHeight - edgeToEdgeTopInset
545
526
 
546
- 2 -> {
547
- setPeekHeight(getDetentHeight(detents[0]), isPresented)
548
- halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
549
- expandedOffset = screenHeight - getDetentHeight(detents[1])
550
- isFitToContents = expandedOffset == 0
551
- }
527
+ setPeekHeight(getDetentHeight(detents[0]), isPresented)
552
528
 
553
- 3 -> {
554
- setPeekHeight(getDetentHeight(detents[0]), isPresented)
555
- halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
556
- expandedOffset = screenHeight - getDetentHeight(detents[2])
557
- }
529
+ val halfExpandedDetentHeight = when (detents.size) {
530
+ 1 -> peekHeight
531
+ else -> getDetentHeight(detents[1])
558
532
  }
559
533
 
534
+ val maxDetentHeight = getDetentHeight(detents.last())
535
+
536
+ val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
537
+ halfExpandedRatio = minOf(adjustedHalfExpandedHeight.toFloat() / realHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
538
+
539
+ expandedOffset = maxOf(edgeToEdgeTopInset, realHeight - maxDetentHeight)
540
+ isFitToContents = detents.size < 3 && expandedOffset == 0
541
+
560
542
  if (oldExpandOffset != expandedOffset || expandedOffset == 0) {
561
- val offset = if (expandedOffset == 0) statusBarHeight else 0
562
- val newHeight = screenHeight - expandedOffset - offset
543
+ val offset = if (expandedOffset == 0) topInset else 0
544
+ val newHeight = realHeight - expandedOffset - offset
563
545
  delegate?.viewControllerDidChangeSize(width, newHeight)
564
546
  }
565
547
 
@@ -592,7 +574,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
592
574
 
593
575
  val cornerRadius = if (sheetCornerRadius < 0) DEFAULT_CORNER_RADIUS.dpToPx() else sheetCornerRadius
594
576
  val outerRadii = floatArrayOf(cornerRadius, cornerRadius, cornerRadius, cornerRadius, 0f, 0f, 0f, 0f)
595
- val backgroundColor = if (sheetBackgroundColor != 0) sheetBackgroundColor else getDefaultBackgroundColor()
577
+ val backgroundColor = sheetBackgroundColor ?: getDefaultBackgroundColor()
596
578
 
597
579
  bottomSheet.background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
598
580
  paint.color = backgroundColor
@@ -633,15 +615,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
633
615
  val bottomSheet = bottomSheetView ?: return
634
616
 
635
617
  val footerHeight = footerView.height
636
- val bottomSheetY = ScreenUtils.getScreenY(bottomSheet)
618
+ val sheetHeight = bottomSheet.height
619
+ val sheetTop = bottomSheet.top
637
620
 
638
- var footerY = (screenHeight - bottomSheetY - footerHeight).toFloat()
621
+ // Footer Y relative to sheet: place at bottom of sheet container minus footer height
622
+ var footerY = (sheetHeight - sheetTop - footerHeight).toFloat()
639
623
 
640
624
  if (slideOffset != null && slideOffset < 0) {
641
625
  footerY -= (footerHeight * slideOffset)
642
626
  }
643
627
 
644
- val maxAllowedY = (screenHeight - statusBarHeight - footerHeight).toFloat()
628
+ // Clamp to prevent footer from going above visible area
629
+ val maxAllowedY = (sheetHeight - topInset - footerHeight).toFloat()
645
630
  footerView.y = minOf(footerY, maxAllowedY)
646
631
  }
647
632
 
@@ -671,52 +656,54 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
671
656
  // MARK: - Position & Drag Handling
672
657
  // ====================================================================
673
658
 
674
- private fun emitChangePositionDelegate(positionPx: Int, realtime: Boolean) {
675
- if (positionPx == lastEmittedPositionPx) return
676
-
677
- lastEmittedPositionPx = positionPx
678
- val position = positionPx.pxToDp()
679
- val interpolatedIndex = getInterpolatedIndexForPosition(positionPx)
680
- val detent = getInterpolatedDetentForPosition(positionPx)
681
- delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
659
+ /**
660
+ * Calculate the visible sheet height from a sheet view.
661
+ * Uses real screen height for consistency across API levels.
662
+ */
663
+ private fun getVisibleSheetHeight(sheetView: View): Int {
664
+ val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
665
+ return realHeight - sheetView.top
682
666
  }
683
667
 
684
- private fun storeResolvedPosition(index: Int) {
685
- if (index < 0 || index >= resolvedDetentPositions.size) return
686
- val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: return
687
- if (positionPx in 1..<screenHeight) {
688
- resolvedDetentPositions[index] = positionPx
689
- }
690
- }
691
-
692
- fun storeCurrentResolvedPosition() {
693
- storeResolvedPosition(currentDetentIndex)
694
- }
668
+ private fun getPositionDp(visibleSheetHeight: Int): Float = (screenHeight - visibleSheetHeight).pxToDp()
695
669
 
696
- private fun getEstimatedPositionForIndex(index: Int): Int {
697
- if (index < 0 || index >= resolvedDetentPositions.size) return screenHeight
670
+ private fun emitChangePositionDelegate(sheetView: View, realtime: Boolean) {
671
+ if (sheetView.top == lastEmittedPositionPx) return
698
672
 
699
- val storedPos = resolvedDetentPositions[index]
700
- if (storedPos > 0) return storedPos
673
+ lastEmittedPositionPx = sheetView.top
674
+ val position = getPositionDp(getVisibleSheetHeight(sheetView))
675
+ val interpolatedIndex = getInterpolatedIndexForPosition(sheetView.top)
676
+ val detent = getInterpolatedDetentForPosition(sheetView.top)
677
+ delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
678
+ }
701
679
 
702
- if (index < detents.size) {
703
- val detentHeight = getDetentHeight(detents[index])
704
- return screenHeight - detentHeight
705
- }
680
+ private fun emitDismissedPosition() {
681
+ val position = screenHeight.pxToDp()
682
+ lastEmittedPositionPx = -1
683
+ delegate?.viewControllerDidChangePosition(-1f, position, 0f, false)
684
+ }
706
685
 
707
- return screenHeight
686
+ /**
687
+ * Get the expected sheetTop position for a detent index.
688
+ */
689
+ private fun getSheetTopForDetentIndex(index: Int): Int {
690
+ val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
691
+ if (index < 0 || index >= detents.size) return realHeight
692
+ val detentHeight = getDetentHeight(detents[index])
693
+ return realHeight - detentHeight
708
694
  }
709
695
 
710
696
  /** Returns (fromIndex, toIndex, progress) for interpolation, or null if < 2 detents. */
711
697
  private fun findSegmentForPosition(positionPx: Int): Triple<Int, Int, Float>? {
712
- val count = resolvedDetentPositions.size
698
+ val count = detents.size
713
699
  if (count < 2) return null
714
700
 
715
- val firstPos = getEstimatedPositionForIndex(0)
716
- val lastPos = getEstimatedPositionForIndex(count - 1)
701
+ val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
702
+ val firstPos = getSheetTopForDetentIndex(0)
703
+ val lastPos = getSheetTopForDetentIndex(count - 1)
717
704
 
718
705
  if (positionPx > firstPos) {
719
- val range = screenHeight - firstPos
706
+ val range = realHeight - firstPos
720
707
  val progress = if (range > 0) (positionPx - firstPos).toFloat() / range else 0f
721
708
  return Triple(-1, 0, progress)
722
709
  }
@@ -726,8 +713,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
726
713
  }
727
714
 
728
715
  for (i in 0 until count - 1) {
729
- val pos = getEstimatedPositionForIndex(i)
730
- val nextPos = getEstimatedPositionForIndex(i + 1)
716
+ val pos = getSheetTopForDetentIndex(i)
717
+ val nextPos = getSheetTopForDetentIndex(i + 1)
731
718
 
732
719
  if (positionPx in nextPos..pos) {
733
720
  val range = pos - nextPos
@@ -741,7 +728,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
741
728
 
742
729
  /** Returns continuous index (e.g., 0.5 = halfway between detent 0 and 1). */
743
730
  private fun getInterpolatedIndexForPosition(positionPx: Int): Float {
744
- val count = resolvedDetentPositions.size
731
+ val count = detents.size
745
732
  if (count == 0) return -1f
746
733
  if (count == 1) return 0f
747
734
 
@@ -754,7 +741,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
754
741
 
755
742
  /** Returns interpolated screen fraction for position. */
756
743
  private fun getInterpolatedDetentForPosition(positionPx: Int): Float {
757
- val count = resolvedDetentPositions.size
744
+ val count = detents.size
758
745
  if (count == 0) return 0f
759
746
 
760
747
  val segment = findSegmentForPosition(positionPx) ?: return getDetentValueForIndex(0)
@@ -782,12 +769,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
782
769
  }
783
770
 
784
771
  private fun getCurrentDetentInfo(sheetView: View): DetentInfo {
785
- val screenY = ScreenUtils.getScreenY(sheetView)
786
- return DetentInfo(currentDetentIndex, screenY.pxToDp())
772
+ val position = getPositionDp(getVisibleSheetHeight(sheetView))
773
+ return DetentInfo(currentDetentIndex, position)
787
774
  }
788
775
 
789
- private fun getCurrentPositionPx(sheetView: View): Int = ScreenUtils.getScreenY(sheetView)
790
-
791
776
  private fun handleDragBegin(sheetView: View) {
792
777
  val detentInfo = getCurrentDetentInfo(sheetView)
793
778
  val detent = getDetentValueForIndex(detentInfo.index)
@@ -808,15 +793,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
808
793
 
809
794
  private fun getDetentHeight(detent: Double): Int {
810
795
  val height: Int = if (detent == -1.0) {
811
- contentHeight + headerHeight + bottomInset
796
+ // Auto height: add bottomInset to content to match iOS behavior
797
+ contentHeight + headerHeight + contentBottomInset
812
798
  } else {
813
799
  if (detent <= 0.0 || detent > 1.0) {
814
800
  throw IllegalArgumentException("TrueSheet: detent fraction ($detent) must be between 0 and 1")
815
801
  }
816
- (detent * screenHeight).toInt() + bottomInset
802
+ // Fractional detent: add bottomInset to match iOS behavior
803
+ (detent * screenHeight).toInt() + contentBottomInset
817
804
  }
818
805
 
819
- val maxAllowedHeight = screenHeight - sheetTopInset
806
+ val maxAllowedHeight = screenHeight + contentBottomInset
820
807
  return maxSheetHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
821
808
  }
822
809
 
@@ -866,15 +853,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
866
853
  }
867
854
 
868
855
  private fun getPositionForDetentIndex(index: Int): Float {
869
- if (index < 0 || index >= detents.size) return 0f
856
+ if (index < 0 || index >= detents.size) return screenHeight.pxToDp()
870
857
 
871
858
  bottomSheetView?.let {
872
- val screenY = ScreenUtils.getScreenY(it)
873
- if (screenY > 0) return screenY.pxToDp()
859
+ val visibleSheetHeight = getVisibleSheetHeight(it)
860
+ if (visibleSheetHeight > 0) return getPositionDp(visibleSheetHeight)
874
861
  }
875
862
 
876
863
  val detentHeight = getDetentHeight(detents[index])
877
- return (screenHeight - detentHeight).pxToDp()
864
+ return getPositionDp(detentHeight)
878
865
  }
879
866
 
880
867
  fun getDetentInfoForIndex(index: Int) = getDetentInfoForState(getStateForDetentIndex(index)) ?: DetentInfo(0, 0f)
@@ -893,20 +880,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
893
880
  if (w == oldw && h == oldh) return
894
881
 
895
882
  // Skip continuous size changes when fullScreen + edge-to-edge
896
- if (h + statusBarHeight > screenHeight && isExpanded && oldw == w) {
883
+ if (h + topInset > screenHeight && isExpanded && oldw == w) {
897
884
  return
898
885
  }
899
886
 
900
887
  val oldScreenHeight = screenHeight
901
- screenHeight = ScreenUtils.getScreenHeight(reactContext, edgeToEdgeEnabled)
888
+ screenHeight = ScreenUtils.getScreenHeight(reactContext)
902
889
 
903
890
  if (isPresented && oldScreenHeight != screenHeight && oldScreenHeight > 0) {
904
891
  setupSheetDetents()
905
892
  this.post {
906
893
  positionFooter()
907
- storeResolvedPosition(currentDetentIndex)
908
- val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
909
- emitChangePositionDelegate(positionPx, realtime = false)
894
+ bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
910
895
  }
911
896
  }
912
897
  }
@@ -97,8 +97,8 @@ class TrueSheetViewManager :
97
97
  view.setDetents(detents)
98
98
  }
99
99
 
100
- @ReactProp(name = "background", defaultInt = 0)
101
- override fun setBackground(view: TrueSheetView, color: Int) {
100
+ @ReactProp(name = "backgroundColor", customType = "Color")
101
+ override fun setBackgroundColor(view: TrueSheetView, color: Int?) {
102
102
  view.setSheetBackgroundColor(color)
103
103
  }
104
104
 
@@ -196,6 +196,11 @@ class TrueSheetViewManager :
196
196
  view.setEdgeToEdgeFullScreen(edgeToEdgeFullScreen)
197
197
  }
198
198
 
199
+ @ReactProp(name = "insetAdjustment")
200
+ override fun setInsetAdjustment(view: TrueSheetView, insetAdjustment: String?) {
201
+ view.setInsetAdjustment(insetAdjustment ?: "automatic")
202
+ }
203
+
199
204
  @ReactProp(name = "scrollable", defaultBoolean = false)
200
205
  override fun setScrollable(view: TrueSheetView, value: Boolean) {
201
206
  // iOS-specific prop - no-op on Android
@@ -25,7 +25,7 @@ object TrueSheetDialogObserver {
25
25
  val parentTop = it.viewController.currentSheetTop
26
26
  val newSheetTop = sheetView.viewController.getExpectedSheetTop(detentIndex)
27
27
  if (!it.viewController.isExpanded && parentTop <= newSheetTop) {
28
- it.viewController.hideDialog()
28
+ it.viewController.hideDialog(emitPosition = true)
29
29
  }
30
30
  }
31
31
 
@@ -46,7 +46,7 @@ object TrueSheetDialogObserver {
46
46
  synchronized(presentedSheetStack) {
47
47
  presentedSheetStack.remove(sheetView)
48
48
  if (hadParent) {
49
- presentedSheetStack.lastOrNull()?.viewController?.showDialog()
49
+ presentedSheetStack.lastOrNull()?.viewController?.showDialog(emitPosition = true)
50
50
  }
51
51
  }
52
52
  }