@lodev09/react-native-true-sheet 3.1.0-beta.9 → 3.1.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.
Files changed (54) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +8 -2
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +18 -2
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +103 -121
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +7 -2
  5. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +2 -2
  6. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +64 -81
  7. package/android/src/main/res/values/styles.xml +1 -0
  8. package/ios/TrueSheetModule.mm +17 -2
  9. package/ios/TrueSheetView.h +2 -0
  10. package/ios/TrueSheetView.mm +32 -3
  11. package/ios/TrueSheetViewController.h +2 -0
  12. package/ios/TrueSheetViewController.mm +45 -18
  13. package/lib/module/TrueSheet.js +3 -1
  14. package/lib/module/TrueSheet.js.map +1 -1
  15. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -2
  16. package/lib/module/navigation/TrueSheetRouter.js +119 -0
  17. package/lib/module/navigation/TrueSheetRouter.js.map +1 -0
  18. package/lib/module/navigation/TrueSheetView.js +169 -0
  19. package/lib/module/navigation/TrueSheetView.js.map +1 -0
  20. package/lib/module/navigation/createTrueSheetNavigator.js +59 -0
  21. package/lib/module/navigation/createTrueSheetNavigator.js.map +1 -0
  22. package/lib/module/navigation/index.js +6 -0
  23. package/lib/module/navigation/index.js.map +1 -0
  24. package/lib/module/navigation/types.js +4 -0
  25. package/lib/module/navigation/types.js.map +1 -0
  26. package/lib/module/navigation/useTrueSheetNavigation.js +26 -0
  27. package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -0
  28. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  29. package/lib/typescript/src/TrueSheet.types.d.ts +24 -0
  30. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  31. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +3 -2
  32. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  33. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +57 -0
  34. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +1 -0
  35. package/lib/typescript/src/navigation/TrueSheetView.d.ts +10 -0
  36. package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +1 -0
  37. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +35 -0
  38. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +1 -0
  39. package/lib/typescript/src/navigation/index.d.ts +6 -0
  40. package/lib/typescript/src/navigation/index.d.ts.map +1 -0
  41. package/lib/typescript/src/navigation/types.d.ts +125 -0
  42. package/lib/typescript/src/navigation/types.d.ts.map +1 -0
  43. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +23 -0
  44. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -0
  45. package/package.json +12 -1
  46. package/src/TrueSheet.tsx +3 -1
  47. package/src/TrueSheet.types.ts +26 -0
  48. package/src/fabric/TrueSheetViewNativeComponent.ts +5 -2
  49. package/src/navigation/TrueSheetRouter.ts +172 -0
  50. package/src/navigation/TrueSheetView.tsx +271 -0
  51. package/src/navigation/createTrueSheetNavigator.tsx +89 -0
  52. package/src/navigation/index.ts +14 -0
  53. package/src/navigation/types.ts +176 -0
  54. package/src/navigation/useTrueSheetNavigation.ts +28 -0
@@ -85,8 +85,14 @@ class TrueSheetModule(reactContext: ReactApplicationContext) :
85
85
  */
86
86
  @ReactMethod
87
87
  fun resizeByRef(viewTag: Double, index: Double, promise: Promise) {
88
- // Resize is just an alias for present (always animated)
89
- presentByRef(viewTag, index, true, promise)
88
+ val tag = viewTag.toInt()
89
+ val detentIndex = index.toInt()
90
+
91
+ withTrueSheetView(tag, promise) { view ->
92
+ view.resize(detentIndex) {
93
+ promise.resolve(null)
94
+ }
95
+ }
90
96
  }
91
97
 
92
98
  /**
@@ -1,6 +1,7 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.util.Log
4
5
  import android.view.View
5
6
  import android.view.ViewStructure
6
7
  import android.view.accessibility.AccessibilityEvent
@@ -12,6 +13,7 @@ import com.facebook.react.uimanager.StateWrapper
12
13
  import com.facebook.react.uimanager.ThemedReactContext
13
14
  import com.facebook.react.uimanager.UIManagerHelper
14
15
  import com.facebook.react.uimanager.events.EventDispatcher
16
+ import com.facebook.react.util.RNLog
15
17
  import com.facebook.react.views.view.ReactViewGroup
16
18
  import com.lodev09.truesheet.core.GrabberOptions
17
19
  import com.lodev09.truesheet.core.TrueSheetDialogObserver
@@ -92,7 +94,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
92
94
  viewController.setupBackground()
93
95
  viewController.setupGrabber()
94
96
  updateSheetIfNeeded()
95
- viewController.setStateForDetentIndex(viewController.currentDetentIndex)
96
97
  }
97
98
  }
98
99
 
@@ -266,7 +267,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
266
267
  viewController.sheetCornerRadius = radius
267
268
  }
268
269
 
269
- fun setSheetBackgroundColor(color: Int) {
270
+ fun setSheetBackgroundColor(color: Int?) {
270
271
  if (viewController.sheetBackgroundColor == color) return
271
272
  viewController.sheetBackgroundColor = color
272
273
  }
@@ -298,6 +299,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
298
299
  viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen
299
300
  }
300
301
 
302
+ fun setInsetAdjustment(insetAdjustment: String) {
303
+ viewController.insetAdjustment = insetAdjustment
304
+ }
305
+
301
306
  // ==================== State Management ====================
302
307
 
303
308
  /**
@@ -331,6 +336,17 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
331
336
  viewController.dismiss(animated)
332
337
  }
333
338
 
339
+ @UiThread
340
+ fun resize(detentIndex: Int, promiseCallback: () -> Unit) {
341
+ if (!viewController.isPresented) {
342
+ RNLog.w(reactContext, "TrueSheet: Cannot resize. Sheet is not presented.")
343
+ promiseCallback()
344
+ return
345
+ }
346
+
347
+ present(detentIndex, true, promiseCallback)
348
+ }
349
+
334
350
  /**
335
351
  * Debounced sheet update to handle rapid content/header size changes.
336
352
  * Uses post to ensure all layout passes complete before reconfiguring.
@@ -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,18 +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
451
- emitChangePositionDelegate(screenHeight, realtime = false)
445
+ if (emitPosition) {
446
+ emitDismissedPosition()
447
+ }
452
448
  }
453
449
 
454
450
  /** Shows a previously hidden dialog. */
455
- fun showDialog() {
451
+ fun showDialog(emitPosition: Boolean = false) {
456
452
  isDialogVisible = true
457
453
  dialog?.window?.decorView?.visibility = VISIBLE
458
- val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
459
- emitChangePositionDelegate(positionPx, realtime = false)
454
+ if (emitPosition) {
455
+ bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
456
+ }
460
457
  }
461
458
 
462
459
  // ====================================================================
@@ -499,10 +496,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
499
496
 
500
497
  isDismissing = true
501
498
  emitWillDismissEvents()
502
-
503
- this.post {
504
- emitChangePositionDelegate(screenHeight, realtime = false)
505
- }
499
+ emitDismissedPosition()
506
500
 
507
501
  if (!animated) {
508
502
  dialog?.window?.setWindowAnimations(0)
@@ -518,19 +512,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
518
512
  fun setupSheetDetents() {
519
513
  val behavior = this.behavior ?: return
520
514
 
521
- if (resolvedDetentPositions.size != detents.size) {
522
- resolvedDetentPositions.clear()
523
- repeat(detents.size) { resolvedDetentPositions.add(0) }
524
- }
525
-
526
- for (i in detents.indices) {
527
- if (detents[i] == -1.0) {
528
- val detentHeight = getDetentHeight(detents[i])
529
- resolvedDetentPositions[i] = screenHeight - detentHeight
530
- }
531
- }
532
-
533
515
  isReconfiguring = true
516
+ val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
517
+ val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
534
518
 
535
519
  behavior.apply {
536
520
  isFitToContents = false
@@ -538,31 +522,26 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
538
522
 
539
523
  val oldExpandOffset = expandedOffset
540
524
 
541
- when (detents.size) {
542
- 1 -> {
543
- setPeekHeight(getDetentHeight(detents[0]), isPresented)
544
- halfExpandedRatio = minOf(peekHeight.toFloat() / screenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
545
- expandedOffset = screenHeight - peekHeight
546
- isFitToContents = expandedOffset == 0
547
- }
525
+ val maxAvailableHeight = realHeight - edgeToEdgeTopInset
548
526
 
549
- 2 -> {
550
- setPeekHeight(getDetentHeight(detents[0]), isPresented)
551
- halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
552
- expandedOffset = screenHeight - getDetentHeight(detents[1])
553
- isFitToContents = expandedOffset == 0
554
- }
527
+ setPeekHeight(getDetentHeight(detents[0]), isPresented)
555
528
 
556
- 3 -> {
557
- setPeekHeight(getDetentHeight(detents[0]), isPresented)
558
- halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
559
- expandedOffset = screenHeight - getDetentHeight(detents[2])
560
- }
529
+ val halfExpandedDetentHeight = when (detents.size) {
530
+ 1 -> peekHeight
531
+ else -> getDetentHeight(detents[1])
561
532
  }
562
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
+
563
542
  if (oldExpandOffset != expandedOffset || expandedOffset == 0) {
564
- val offset = if (expandedOffset == 0) statusBarHeight else 0
565
- val newHeight = screenHeight - expandedOffset - offset
543
+ val offset = if (expandedOffset == 0) topInset else 0
544
+ val newHeight = realHeight - expandedOffset - offset
566
545
  delegate?.viewControllerDidChangeSize(width, newHeight)
567
546
  }
568
547
 
@@ -595,7 +574,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
595
574
 
596
575
  val cornerRadius = if (sheetCornerRadius < 0) DEFAULT_CORNER_RADIUS.dpToPx() else sheetCornerRadius
597
576
  val outerRadii = floatArrayOf(cornerRadius, cornerRadius, cornerRadius, cornerRadius, 0f, 0f, 0f, 0f)
598
- val backgroundColor = if (sheetBackgroundColor != 0) sheetBackgroundColor else getDefaultBackgroundColor()
577
+ val backgroundColor = sheetBackgroundColor ?: getDefaultBackgroundColor()
599
578
 
600
579
  bottomSheet.background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
601
580
  paint.color = backgroundColor
@@ -636,15 +615,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
636
615
  val bottomSheet = bottomSheetView ?: return
637
616
 
638
617
  val footerHeight = footerView.height
639
- val bottomSheetY = ScreenUtils.getScreenY(bottomSheet)
618
+ val sheetHeight = bottomSheet.height
619
+ val sheetTop = bottomSheet.top
640
620
 
641
- 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()
642
623
 
643
624
  if (slideOffset != null && slideOffset < 0) {
644
625
  footerY -= (footerHeight * slideOffset)
645
626
  }
646
627
 
647
- val maxAllowedY = (screenHeight - statusBarHeight - footerHeight).toFloat()
628
+ // Clamp to prevent footer from going above visible area
629
+ val maxAllowedY = (sheetHeight - topInset - footerHeight).toFloat()
648
630
  footerView.y = minOf(footerY, maxAllowedY)
649
631
  }
650
632
 
@@ -674,52 +656,54 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
674
656
  // MARK: - Position & Drag Handling
675
657
  // ====================================================================
676
658
 
677
- private fun emitChangePositionDelegate(positionPx: Int, realtime: Boolean) {
678
- if (positionPx == lastEmittedPositionPx) return
679
-
680
- lastEmittedPositionPx = positionPx
681
- val position = positionPx.pxToDp()
682
- val interpolatedIndex = getInterpolatedIndexForPosition(positionPx)
683
- val detent = getInterpolatedDetentForPosition(positionPx)
684
- 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
685
666
  }
686
667
 
687
- private fun storeResolvedPosition(index: Int) {
688
- if (index < 0 || index >= resolvedDetentPositions.size) return
689
- val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: return
690
- if (positionPx in 1..<screenHeight) {
691
- resolvedDetentPositions[index] = positionPx
692
- }
693
- }
694
-
695
- fun storeCurrentResolvedPosition() {
696
- storeResolvedPosition(currentDetentIndex)
697
- }
668
+ private fun getPositionDp(visibleSheetHeight: Int): Float = (screenHeight - visibleSheetHeight).pxToDp()
698
669
 
699
- private fun getEstimatedPositionForIndex(index: Int): Int {
700
- if (index < 0 || index >= resolvedDetentPositions.size) return screenHeight
670
+ private fun emitChangePositionDelegate(sheetView: View, realtime: Boolean) {
671
+ if (sheetView.top == lastEmittedPositionPx) return
701
672
 
702
- val storedPos = resolvedDetentPositions[index]
703
- 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
+ }
704
679
 
705
- if (index < detents.size) {
706
- val detentHeight = getDetentHeight(detents[index])
707
- return screenHeight - detentHeight
708
- }
680
+ private fun emitDismissedPosition() {
681
+ val position = screenHeight.pxToDp()
682
+ lastEmittedPositionPx = -1
683
+ delegate?.viewControllerDidChangePosition(-1f, position, 0f, false)
684
+ }
709
685
 
710
- 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
711
694
  }
712
695
 
713
696
  /** Returns (fromIndex, toIndex, progress) for interpolation, or null if < 2 detents. */
714
697
  private fun findSegmentForPosition(positionPx: Int): Triple<Int, Int, Float>? {
715
- val count = resolvedDetentPositions.size
698
+ val count = detents.size
716
699
  if (count < 2) return null
717
700
 
718
- val firstPos = getEstimatedPositionForIndex(0)
719
- val lastPos = getEstimatedPositionForIndex(count - 1)
701
+ val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
702
+ val firstPos = getSheetTopForDetentIndex(0)
703
+ val lastPos = getSheetTopForDetentIndex(count - 1)
720
704
 
721
705
  if (positionPx > firstPos) {
722
- val range = screenHeight - firstPos
706
+ val range = realHeight - firstPos
723
707
  val progress = if (range > 0) (positionPx - firstPos).toFloat() / range else 0f
724
708
  return Triple(-1, 0, progress)
725
709
  }
@@ -729,8 +713,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
729
713
  }
730
714
 
731
715
  for (i in 0 until count - 1) {
732
- val pos = getEstimatedPositionForIndex(i)
733
- val nextPos = getEstimatedPositionForIndex(i + 1)
716
+ val pos = getSheetTopForDetentIndex(i)
717
+ val nextPos = getSheetTopForDetentIndex(i + 1)
734
718
 
735
719
  if (positionPx in nextPos..pos) {
736
720
  val range = pos - nextPos
@@ -744,7 +728,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
744
728
 
745
729
  /** Returns continuous index (e.g., 0.5 = halfway between detent 0 and 1). */
746
730
  private fun getInterpolatedIndexForPosition(positionPx: Int): Float {
747
- val count = resolvedDetentPositions.size
731
+ val count = detents.size
748
732
  if (count == 0) return -1f
749
733
  if (count == 1) return 0f
750
734
 
@@ -757,7 +741,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
757
741
 
758
742
  /** Returns interpolated screen fraction for position. */
759
743
  private fun getInterpolatedDetentForPosition(positionPx: Int): Float {
760
- val count = resolvedDetentPositions.size
744
+ val count = detents.size
761
745
  if (count == 0) return 0f
762
746
 
763
747
  val segment = findSegmentForPosition(positionPx) ?: return getDetentValueForIndex(0)
@@ -785,12 +769,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
785
769
  }
786
770
 
787
771
  private fun getCurrentDetentInfo(sheetView: View): DetentInfo {
788
- val screenY = ScreenUtils.getScreenY(sheetView)
789
- return DetentInfo(currentDetentIndex, screenY.pxToDp())
772
+ val position = getPositionDp(getVisibleSheetHeight(sheetView))
773
+ return DetentInfo(currentDetentIndex, position)
790
774
  }
791
775
 
792
- private fun getCurrentPositionPx(sheetView: View): Int = ScreenUtils.getScreenY(sheetView)
793
-
794
776
  private fun handleDragBegin(sheetView: View) {
795
777
  val detentInfo = getCurrentDetentInfo(sheetView)
796
778
  val detent = getDetentValueForIndex(detentInfo.index)
@@ -811,15 +793,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
811
793
 
812
794
  private fun getDetentHeight(detent: Double): Int {
813
795
  val height: Int = if (detent == -1.0) {
814
- contentHeight + headerHeight + bottomInset
796
+ // Auto height: add bottomInset to content to match iOS behavior
797
+ contentHeight + headerHeight + contentBottomInset
815
798
  } else {
816
799
  if (detent <= 0.0 || detent > 1.0) {
817
800
  throw IllegalArgumentException("TrueSheet: detent fraction ($detent) must be between 0 and 1")
818
801
  }
819
- (detent * screenHeight).toInt() + bottomInset
802
+ // Fractional detent: add bottomInset to match iOS behavior
803
+ (detent * screenHeight).toInt() + contentBottomInset
820
804
  }
821
805
 
822
- val maxAllowedHeight = screenHeight - sheetTopInset
806
+ val maxAllowedHeight = screenHeight + contentBottomInset
823
807
  return maxSheetHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
824
808
  }
825
809
 
@@ -869,15 +853,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
869
853
  }
870
854
 
871
855
  private fun getPositionForDetentIndex(index: Int): Float {
872
- if (index < 0 || index >= detents.size) return 0f
856
+ if (index < 0 || index >= detents.size) return screenHeight.pxToDp()
873
857
 
874
858
  bottomSheetView?.let {
875
- val screenY = ScreenUtils.getScreenY(it)
876
- if (screenY > 0) return screenY.pxToDp()
859
+ val visibleSheetHeight = getVisibleSheetHeight(it)
860
+ if (visibleSheetHeight > 0) return getPositionDp(visibleSheetHeight)
877
861
  }
878
862
 
879
863
  val detentHeight = getDetentHeight(detents[index])
880
- return (screenHeight - detentHeight).pxToDp()
864
+ return getPositionDp(detentHeight)
881
865
  }
882
866
 
883
867
  fun getDetentInfoForIndex(index: Int) = getDetentInfoForState(getStateForDetentIndex(index)) ?: DetentInfo(0, 0f)
@@ -896,20 +880,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
896
880
  if (w == oldw && h == oldh) return
897
881
 
898
882
  // Skip continuous size changes when fullScreen + edge-to-edge
899
- if (h + statusBarHeight > screenHeight && isExpanded && oldw == w) {
883
+ if (h + topInset > screenHeight && isExpanded && oldw == w) {
900
884
  return
901
885
  }
902
886
 
903
887
  val oldScreenHeight = screenHeight
904
- screenHeight = ScreenUtils.getScreenHeight(reactContext, edgeToEdgeEnabled)
888
+ screenHeight = ScreenUtils.getScreenHeight(reactContext)
905
889
 
906
890
  if (isPresented && oldScreenHeight != screenHeight && oldScreenHeight > 0) {
907
891
  setupSheetDetents()
908
892
  this.post {
909
893
  positionFooter()
910
- storeResolvedPosition(currentDetentIndex)
911
- val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
912
- emitChangePositionDelegate(positionPx, realtime = false)
894
+ bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
913
895
  }
914
896
  }
915
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
  }