@lodev09/react-native-true-sheet 3.0.0-beta.8 → 3.0.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 (98) hide show
  1. package/README.md +13 -6
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +29 -33
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +3 -1
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +48 -43
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +387 -88
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +22 -4
  7. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -5
  8. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +67 -0
  9. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +44 -0
  10. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
  11. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
  12. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +94 -0
  13. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
  14. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +37 -33
  15. package/android/src/main/res/anim/true_sheet_slide_in.xml +13 -0
  16. package/android/src/main/res/anim/true_sheet_slide_out.xml +13 -0
  17. package/android/src/main/res/values/styles.xml +13 -1
  18. package/ios/TrueSheetContainerView.mm +4 -0
  19. package/ios/TrueSheetContentView.h +2 -1
  20. package/ios/TrueSheetContentView.mm +91 -11
  21. package/ios/TrueSheetView.mm +65 -41
  22. package/ios/TrueSheetViewController.h +21 -10
  23. package/ios/TrueSheetViewController.mm +330 -165
  24. package/ios/core/TrueSheetBlurView.h +24 -0
  25. package/ios/{utils/ConversionUtil.mm → core/TrueSheetBlurView.mm} +65 -3
  26. package/ios/events/TrueSheetDragEvents.h +39 -0
  27. package/ios/events/TrueSheetDragEvents.mm +62 -0
  28. package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
  29. package/ios/events/TrueSheetFocusEvents.mm +49 -0
  30. package/ios/events/TrueSheetLifecycleEvents.h +40 -0
  31. package/ios/events/TrueSheetLifecycleEvents.mm +71 -0
  32. package/ios/events/TrueSheetStateEvents.h +35 -0
  33. package/ios/events/TrueSheetStateEvents.mm +49 -0
  34. package/ios/utils/GestureUtil.h +7 -0
  35. package/ios/utils/GestureUtil.mm +12 -0
  36. package/lib/module/TrueSheet.js +65 -12
  37. package/lib/module/TrueSheet.js.map +1 -1
  38. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -5
  39. package/lib/module/index.js +0 -1
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/reanimated/ReanimatedTrueSheet.js +13 -7
  42. package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
  43. package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
  44. package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
  45. package/lib/typescript/src/TrueSheet.d.ts +4 -0
  46. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  47. package/lib/typescript/src/TrueSheet.types.d.ts +58 -6
  48. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  49. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +14 -5
  50. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  51. package/lib/typescript/src/index.d.ts +0 -1
  52. package/lib/typescript/src/index.d.ts.map +1 -1
  53. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
  54. package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
  55. package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/TrueSheet.tsx +80 -10
  58. package/src/TrueSheet.types.ts +65 -6
  59. package/src/__mocks__/index.js +0 -5
  60. package/src/fabric/TrueSheetViewNativeComponent.ts +15 -5
  61. package/src/index.ts +0 -1
  62. package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
  63. package/src/reanimated/ReanimatedTrueSheetProvider.tsx +11 -3
  64. package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
  65. package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
  66. package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
  67. package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
  68. package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
  69. package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
  70. package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
  71. package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
  72. package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
  73. package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
  74. package/ios/events/OnDetentChangeEvent.h +0 -28
  75. package/ios/events/OnDetentChangeEvent.mm +0 -30
  76. package/ios/events/OnDidDismissEvent.h +0 -26
  77. package/ios/events/OnDidDismissEvent.mm +0 -25
  78. package/ios/events/OnDidPresentEvent.h +0 -28
  79. package/ios/events/OnDidPresentEvent.mm +0 -30
  80. package/ios/events/OnDragBeginEvent.h +0 -28
  81. package/ios/events/OnDragBeginEvent.mm +0 -30
  82. package/ios/events/OnDragChangeEvent.h +0 -28
  83. package/ios/events/OnDragChangeEvent.mm +0 -30
  84. package/ios/events/OnDragEndEvent.h +0 -28
  85. package/ios/events/OnDragEndEvent.mm +0 -30
  86. package/ios/events/OnMountEvent.h +0 -26
  87. package/ios/events/OnMountEvent.mm +0 -25
  88. package/ios/events/OnPositionChangeEvent.mm +0 -32
  89. package/ios/events/OnWillDismissEvent.h +0 -26
  90. package/ios/events/OnWillDismissEvent.mm +0 -25
  91. package/ios/events/OnWillPresentEvent.h +0 -28
  92. package/ios/events/OnWillPresentEvent.mm +0 -30
  93. package/ios/utils/ConversionUtil.h +0 -24
  94. package/lib/module/TrueSheetGrabber.js +0 -51
  95. package/lib/module/TrueSheetGrabber.js.map +0 -1
  96. package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
  97. package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
  98. package/src/TrueSheetGrabber.tsx +0 -82
@@ -67,7 +67,11 @@ class TrueSheetViewManager :
67
67
  DragBeginEvent.EVENT_NAME to hashMapOf("registrationName" to DragBeginEvent.REGISTRATION_NAME),
68
68
  DragChangeEvent.EVENT_NAME to hashMapOf("registrationName" to DragChangeEvent.REGISTRATION_NAME),
69
69
  DragEndEvent.EVENT_NAME to hashMapOf("registrationName" to DragEndEvent.REGISTRATION_NAME),
70
- PositionChangeEvent.EVENT_NAME to hashMapOf("registrationName" to PositionChangeEvent.REGISTRATION_NAME)
70
+ PositionChangeEvent.EVENT_NAME to hashMapOf("registrationName" to PositionChangeEvent.REGISTRATION_NAME),
71
+ WillFocusEvent.EVENT_NAME to hashMapOf("registrationName" to WillFocusEvent.REGISTRATION_NAME),
72
+ FocusEvent.EVENT_NAME to hashMapOf("registrationName" to FocusEvent.REGISTRATION_NAME),
73
+ WillBlurEvent.EVENT_NAME to hashMapOf("registrationName" to WillBlurEvent.REGISTRATION_NAME),
74
+ BlurEvent.EVENT_NAME to hashMapOf("registrationName" to BlurEvent.REGISTRATION_NAME)
71
75
  )
72
76
 
73
77
  // ==================== Props ====================
@@ -110,6 +114,11 @@ class TrueSheetViewManager :
110
114
  view.setDismissible(dismissible)
111
115
  }
112
116
 
117
+ @ReactProp(name = "draggable", defaultBoolean = true)
118
+ override fun setDraggable(view: TrueSheetView, draggable: Boolean) {
119
+ view.setDraggable(draggable)
120
+ }
121
+
113
122
  @ReactProp(name = "dimmed", defaultBoolean = true)
114
123
  override fun setDimmed(view: TrueSheetView, dimmed: Boolean) {
115
124
  view.setDimmed(dimmed)
@@ -149,7 +158,16 @@ class TrueSheetViewManager :
149
158
  @ReactProp(name = "blurTint")
150
159
  override fun setBlurTint(view: TrueSheetView, tint: String?) {
151
160
  // iOS-specific prop - no-op on Android
152
- view.setBlurTint(tint)
161
+ }
162
+
163
+ @ReactProp(name = "blurIntensity", defaultDouble = 1.0)
164
+ override fun setBlurIntensity(view: TrueSheetView, value: Double) {
165
+ // iOS-specific prop - no-op on Android
166
+ }
167
+
168
+ @ReactProp(name = "blurInteraction", defaultBoolean = false)
169
+ override fun setBlurInteraction(view: TrueSheetView, value: Boolean) {
170
+ // iOS-specific prop - no-op on Android
153
171
  }
154
172
 
155
173
  @ReactProp(name = "edgeToEdgeFullScreen", defaultBoolean = false)
@@ -157,8 +175,8 @@ class TrueSheetViewManager :
157
175
  view.setEdgeToEdgeFullScreen(edgeToEdgeFullScreen)
158
176
  }
159
177
 
160
- @ReactProp(name = "fitScrollView", defaultBoolean = false)
161
- override fun setFitScrollView(view: TrueSheetView, value: Boolean) {
178
+ @ReactProp(name = "scrollable", defaultBoolean = false)
179
+ override fun setScrollable(view: TrueSheetView, value: Boolean) {
162
180
  // iOS-specific prop - no-op on Android
163
181
  }
164
182
 
@@ -19,11 +19,6 @@ class RNScreensFragmentObserver(
19
19
  private var fragmentLifecycleCallback: FragmentManager.FragmentLifecycleCallbacks? = null
20
20
  private val activeModalFragments: MutableSet<Fragment> = mutableSetOf()
21
21
 
22
- /**
23
- * Check if there are active modal fragments being tracked.
24
- */
25
- fun hasActiveModals(): Boolean = activeModalFragments.isNotEmpty()
26
-
27
22
  /**
28
23
  * Start observing fragment lifecycle events.
29
24
  */
@@ -0,0 +1,67 @@
1
+ package com.lodev09.truesheet.core
2
+
3
+ import com.lodev09.truesheet.TrueSheetView
4
+
5
+ /**
6
+ * Manages TrueSheet stacking behavior.
7
+ * Tracks presented sheets and handles visibility when sheets stack on top of each other.
8
+ */
9
+ object TrueSheetDialogObserver {
10
+
11
+ private val presentedSheetStack = mutableListOf<TrueSheetView>()
12
+
13
+ /**
14
+ * Called when a sheet is about to be presented.
15
+ * Returns the visible parent sheet to stack on, or null if none.
16
+ */
17
+ @JvmStatic
18
+ fun onSheetWillPresent(sheetView: TrueSheetView, detentIndex: Int): TrueSheetView? {
19
+ synchronized(presentedSheetStack) {
20
+ val parentSheet = presentedSheetStack.lastOrNull()
21
+ ?.takeIf { it.viewController.isPresented && it.viewController.isDialogVisible }
22
+
23
+ // Hide parent if the new sheet would cover it
24
+ parentSheet?.let {
25
+ val parentTop = it.viewController.currentSheetTop
26
+ val newSheetTop = sheetView.viewController.getExpectedSheetTop(detentIndex)
27
+ if (!it.viewController.isExpanded && parentTop <= newSheetTop) {
28
+ it.viewController.hideDialog()
29
+ }
30
+ }
31
+
32
+ if (!presentedSheetStack.contains(sheetView)) {
33
+ presentedSheetStack.add(sheetView)
34
+ }
35
+
36
+ return parentSheet
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Called when a sheet has been dismissed.
42
+ * Shows the parent sheet if this sheet was stacked on it.
43
+ */
44
+ @JvmStatic
45
+ fun onSheetDidDismiss(sheetView: TrueSheetView, hadParent: Boolean) {
46
+ synchronized(presentedSheetStack) {
47
+ presentedSheetStack.remove(sheetView)
48
+ if (hadParent) {
49
+ presentedSheetStack.lastOrNull()?.viewController?.showDialog()
50
+ }
51
+ }
52
+ }
53
+
54
+ @JvmStatic
55
+ fun removeSheet(sheetView: TrueSheetView) {
56
+ synchronized(presentedSheetStack) {
57
+ presentedSheetStack.remove(sheetView)
58
+ }
59
+ }
60
+
61
+ @JvmStatic
62
+ fun clear() {
63
+ synchronized(presentedSheetStack) {
64
+ presentedSheetStack.clear()
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,44 @@
1
+ package com.lodev09.truesheet.core
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.graphics.Color
6
+ import android.graphics.drawable.GradientDrawable
7
+ import android.view.Gravity
8
+ import android.view.View
9
+ import android.widget.FrameLayout
10
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
11
+
12
+ /**
13
+ * Native grabber (drag handle) view for the bottom sheet.
14
+ * Displays a small pill-shaped indicator at the top of the sheet.
15
+ */
16
+ @SuppressLint("ViewConstructor")
17
+ class TrueSheetGrabberView(context: Context) : View(context) {
18
+
19
+ companion object {
20
+ private const val GRABBER_WIDTH = 32f // dp
21
+ private const val GRABBER_HEIGHT = 4f // dp
22
+ private const val GRABBER_TOP_MARGIN = 16f // dp
23
+ private val GRABBER_COLOR = Color.argb((0.4 * 255).toInt(), 73, 69, 79) // #49454F @ 40%
24
+ }
25
+
26
+ init {
27
+ layoutParams = FrameLayout.LayoutParams(
28
+ GRABBER_WIDTH.dpToPx().toInt(),
29
+ GRABBER_HEIGHT.dpToPx().toInt()
30
+ ).apply {
31
+ gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
32
+ topMargin = GRABBER_TOP_MARGIN.dpToPx().toInt()
33
+ }
34
+
35
+ background = GradientDrawable().apply {
36
+ shape = GradientDrawable.RECTANGLE
37
+ cornerRadius = (GRABBER_HEIGHT / 2).dpToPx()
38
+ setColor(GRABBER_COLOR)
39
+ }
40
+
41
+ // High elevation to ensure grabber appears above content views
42
+ elevation = 100f
43
+ }
44
+ }
@@ -0,0 +1,71 @@
1
+ package com.lodev09.truesheet.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+
7
+ /**
8
+ * Fired when dragging begins
9
+ * Payload: { index: number, position: number, detent: number }
10
+ */
11
+ class DragBeginEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
12
+ Event<DragBeginEvent>(surfaceId, viewId) {
13
+
14
+ override fun getEventName(): String = EVENT_NAME
15
+
16
+ override fun getEventData(): WritableMap =
17
+ Arguments.createMap().apply {
18
+ putInt("index", index)
19
+ putDouble("position", position.toDouble())
20
+ putDouble("detent", detent.toDouble())
21
+ }
22
+
23
+ companion object {
24
+ const val EVENT_NAME = "topDragBegin"
25
+ const val REGISTRATION_NAME = "onDragBegin"
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Fired continuously during dragging
31
+ * Payload: { index: number, position: number, detent: number }
32
+ */
33
+ class DragChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
34
+ Event<DragChangeEvent>(surfaceId, viewId) {
35
+
36
+ override fun getEventName(): String = EVENT_NAME
37
+
38
+ override fun getEventData(): WritableMap =
39
+ Arguments.createMap().apply {
40
+ putInt("index", index)
41
+ putDouble("position", position.toDouble())
42
+ putDouble("detent", detent.toDouble())
43
+ }
44
+
45
+ companion object {
46
+ const val EVENT_NAME = "topDragChange"
47
+ const val REGISTRATION_NAME = "onDragChange"
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Fired when dragging ends
53
+ * Payload: { index: number, position: number, detent: number }
54
+ */
55
+ class DragEndEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
56
+ Event<DragEndEvent>(surfaceId, viewId) {
57
+
58
+ override fun getEventName(): String = EVENT_NAME
59
+
60
+ override fun getEventData(): WritableMap =
61
+ Arguments.createMap().apply {
62
+ putInt("index", index)
63
+ putDouble("position", position.toDouble())
64
+ putDouble("detent", detent.toDouble())
65
+ }
66
+
67
+ companion object {
68
+ const val EVENT_NAME = "topDragEnd"
69
+ const val REGISTRATION_NAME = "onDragEnd"
70
+ }
71
+ }
@@ -0,0 +1,65 @@
1
+ package com.lodev09.truesheet.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+
7
+ /**
8
+ * Fired when the sheet is about to regain focus because a sheet on top of it is being dismissed
9
+ */
10
+ class WillFocusEvent(surfaceId: Int, viewId: Int) : Event<WillFocusEvent>(surfaceId, viewId) {
11
+
12
+ override fun getEventName(): String = EVENT_NAME
13
+
14
+ override fun getEventData(): WritableMap = Arguments.createMap()
15
+
16
+ companion object {
17
+ const val EVENT_NAME = "topWillFocus"
18
+ const val REGISTRATION_NAME = "onWillFocus"
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Fired when the sheet regains focus after a sheet on top of it is dismissed
24
+ */
25
+ class FocusEvent(surfaceId: Int, viewId: Int) : Event<FocusEvent>(surfaceId, viewId) {
26
+
27
+ override fun getEventName(): String = EVENT_NAME
28
+
29
+ override fun getEventData(): WritableMap = Arguments.createMap()
30
+
31
+ companion object {
32
+ const val EVENT_NAME = "topDidFocus"
33
+ const val REGISTRATION_NAME = "onDidFocus"
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Fired when the sheet is about to lose focus because another sheet is being presented on top of it
39
+ */
40
+ class WillBlurEvent(surfaceId: Int, viewId: Int) : Event<WillBlurEvent>(surfaceId, viewId) {
41
+
42
+ override fun getEventName(): String = EVENT_NAME
43
+
44
+ override fun getEventData(): WritableMap = Arguments.createMap()
45
+
46
+ companion object {
47
+ const val EVENT_NAME = "topWillBlur"
48
+ const val REGISTRATION_NAME = "onWillBlur"
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Fired when the sheet loses focus because another sheet is presented on top of it
54
+ */
55
+ class BlurEvent(surfaceId: Int, viewId: Int) : Event<BlurEvent>(surfaceId, viewId) {
56
+
57
+ override fun getEventName(): String = EVENT_NAME
58
+
59
+ override fun getEventData(): WritableMap = Arguments.createMap()
60
+
61
+ companion object {
62
+ const val EVENT_NAME = "topDidBlur"
63
+ const val REGISTRATION_NAME = "onDidBlur"
64
+ }
65
+ }
@@ -0,0 +1,94 @@
1
+ package com.lodev09.truesheet.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+
7
+ /**
8
+ * Fired when the sheet component is mounted and ready
9
+ */
10
+ class MountEvent(surfaceId: Int, viewId: Int) : Event<MountEvent>(surfaceId, viewId) {
11
+
12
+ override fun getEventName(): String = EVENT_NAME
13
+
14
+ override fun getEventData(): WritableMap = Arguments.createMap()
15
+
16
+ companion object {
17
+ const val EVENT_NAME = "topMount"
18
+ const val REGISTRATION_NAME = "onMount"
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Fired before the sheet is presented
24
+ * Payload: { index: number, position: number, detent: number }
25
+ */
26
+ class WillPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
27
+ Event<WillPresentEvent>(surfaceId, viewId) {
28
+
29
+ override fun getEventName(): String = EVENT_NAME
30
+
31
+ override fun getEventData(): WritableMap =
32
+ Arguments.createMap().apply {
33
+ putInt("index", index)
34
+ putDouble("position", position.toDouble())
35
+ putDouble("detent", detent.toDouble())
36
+ }
37
+
38
+ companion object {
39
+ const val EVENT_NAME = "topWillPresent"
40
+ const val REGISTRATION_NAME = "onWillPresent"
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Fired after the sheet is presented
46
+ * Payload: { index: number, position: number, detent: number }
47
+ */
48
+ class DidPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
49
+ Event<DidPresentEvent>(surfaceId, viewId) {
50
+
51
+ override fun getEventName(): String = EVENT_NAME
52
+
53
+ override fun getEventData(): WritableMap =
54
+ Arguments.createMap().apply {
55
+ putInt("index", index)
56
+ putDouble("position", position.toDouble())
57
+ putDouble("detent", detent.toDouble())
58
+ }
59
+
60
+ companion object {
61
+ const val EVENT_NAME = "topDidPresent"
62
+ const val REGISTRATION_NAME = "onDidPresent"
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Fired before the sheet is dismissed
68
+ */
69
+ class WillDismissEvent(surfaceId: Int, viewId: Int) : Event<WillDismissEvent>(surfaceId, viewId) {
70
+
71
+ override fun getEventName(): String = EVENT_NAME
72
+
73
+ override fun getEventData(): WritableMap = Arguments.createMap()
74
+
75
+ companion object {
76
+ const val EVENT_NAME = "topWillDismiss"
77
+ const val REGISTRATION_NAME = "onWillDismiss"
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Fired after the sheet is dismissed
83
+ */
84
+ class DidDismissEvent(surfaceId: Int, viewId: Int) : Event<DidDismissEvent>(surfaceId, viewId) {
85
+
86
+ override fun getEventName(): String = EVENT_NAME
87
+
88
+ override fun getEventData(): WritableMap = Arguments.createMap()
89
+
90
+ companion object {
91
+ const val EVENT_NAME = "topDidDismiss"
92
+ const val REGISTRATION_NAME = "onDidDismiss"
93
+ }
94
+ }
@@ -0,0 +1,56 @@
1
+ package com.lodev09.truesheet.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+
7
+ /**
8
+ * Fired when the detent changes
9
+ * Payload: { index: number, position: number, detent: number }
10
+ */
11
+ class DetentChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
12
+ Event<DetentChangeEvent>(surfaceId, viewId) {
13
+
14
+ override fun getEventName(): String = EVENT_NAME
15
+
16
+ override fun getEventData(): WritableMap =
17
+ Arguments.createMap().apply {
18
+ putInt("index", index)
19
+ putDouble("position", position.toDouble())
20
+ putDouble("detent", detent.toDouble())
21
+ }
22
+
23
+ companion object {
24
+ const val EVENT_NAME = "topDetentChange"
25
+ const val REGISTRATION_NAME = "onDetentChange"
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Fired continuously for position updates during drag and animation
31
+ * Payload: { index: number, position: number, detent: number, realtime: boolean }
32
+ */
33
+ class PositionChangeEvent(
34
+ surfaceId: Int,
35
+ viewId: Int,
36
+ private val index: Float,
37
+ private val position: Float,
38
+ private val detent: Float,
39
+ private val realtime: Boolean = false
40
+ ) : Event<PositionChangeEvent>(surfaceId, viewId) {
41
+
42
+ override fun getEventName(): String = EVENT_NAME
43
+
44
+ override fun getEventData(): WritableMap =
45
+ Arguments.createMap().apply {
46
+ putDouble("index", index.toDouble())
47
+ putDouble("position", position.toDouble())
48
+ putDouble("detent", detent.toDouble())
49
+ putBoolean("realtime", realtime)
50
+ }
51
+
52
+ companion object {
53
+ const val EVENT_NAME = "topPositionChange"
54
+ const val REGISTRATION_NAME = "onPositionChange"
55
+ }
56
+ }
@@ -19,6 +19,18 @@ object ScreenUtils {
19
19
  context.resources.getIdentifier(name, "dimen", "android")
20
20
  ).takeIf { it > 0 } ?: 0
21
21
 
22
+ /**
23
+ * Get the WindowInsets for API 30+, or null for older APIs
24
+ */
25
+ private fun getWindowInsets(context: ReactContext): WindowInsets? {
26
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
27
+ return context.getSystemService(WindowManager::class.java)
28
+ ?.currentWindowMetrics
29
+ ?.windowInsets
30
+ }
31
+ return null
32
+ }
33
+
22
34
  /**
23
35
  * Get the status bar height
24
36
  *
@@ -26,20 +38,29 @@ object ScreenUtils {
26
38
  * @return Status bar height in pixels
27
39
  */
28
40
  fun getStatusBarHeight(context: ReactContext): Int {
29
- // Modern approach using WindowInsets (API 30+)
30
41
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
31
- val windowManager = context.getSystemService(WindowManager::class.java)
32
- val windowMetrics = windowManager?.currentWindowMetrics
33
- val insets = windowMetrics?.windowInsets?.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
34
- if (insets != null) {
35
- return insets.top
36
- }
42
+ getWindowInsets(context)
43
+ ?.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
44
+ ?.let { return it.top }
37
45
  }
38
-
39
- // Fallback to legacy approach for older APIs
40
46
  return getIdentifierHeight(context, "status_bar_height")
41
47
  }
42
48
 
49
+ /**
50
+ * Get the navigation bar height (bottom inset)
51
+ *
52
+ * @param context React context
53
+ * @return Navigation bar height in pixels
54
+ */
55
+ fun getNavigationBarHeight(context: ReactContext): Int {
56
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
57
+ getWindowInsets(context)
58
+ ?.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
59
+ ?.let { return it.bottom }
60
+ }
61
+ return getIdentifierHeight(context, "navigation_bar_height")
62
+ }
63
+
43
64
  /**
44
65
  * Calculate the screen height
45
66
  *
@@ -55,36 +76,20 @@ object ScreenUtils {
55
76
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
56
77
  context.display?.getRealMetrics(displayMetrics)
57
78
  } else {
79
+ @Suppress("DEPRECATION")
58
80
  windowManager.defaultDisplay.getMetrics(displayMetrics)
59
81
  }
60
82
 
61
83
  val screenHeight = displayMetrics.heightPixels
62
- val statusBarHeight = getStatusBarHeight(context)
63
-
64
- val hasNavigationBar = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
65
- context.getSystemService(WindowManager::class.java)
66
- ?.currentWindowMetrics
67
- ?.windowInsets
68
- ?.isVisible(WindowInsets.Type.navigationBars()) ?: false
69
- } else {
70
- context.resources.getIdentifier("navigation_bar_height", "dimen", "android") > 0
71
- }
72
-
73
- val navigationBarHeight = if (hasNavigationBar) {
74
- getIdentifierHeight(context, "navigation_bar_height")
75
- } else {
76
- 0
77
- }
78
84
 
79
85
  return if (edgeToEdge) {
80
- // getRealMetrics includes navigation bar height
81
- // windowManager.defaultDisplay.getMetrics doesn't
82
- when (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
83
- true -> screenHeight
84
- false -> screenHeight + navigationBarHeight
86
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
87
+ screenHeight
88
+ } else {
89
+ screenHeight + getNavigationBarHeight(context)
85
90
  }
86
91
  } else {
87
- screenHeight - statusBarHeight - navigationBarHeight
92
+ screenHeight - getStatusBarHeight(context) - getNavigationBarHeight(context)
88
93
  }
89
94
  }
90
95
 
@@ -98,8 +103,7 @@ object ScreenUtils {
98
103
  val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
99
104
 
100
105
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
101
- val windowMetrics = windowManager.currentWindowMetrics
102
- windowMetrics.bounds.width()
106
+ windowManager.currentWindowMetrics.bounds.width()
103
107
  } else {
104
108
  val displayMetrics = DisplayMetrics()
105
109
  @Suppress("DEPRECATION")
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <set xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <translate
4
+ android:duration="200"
5
+ android:fromYDelta="20%p"
6
+ android:toYDelta="0"
7
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
8
+ <alpha
9
+ android:duration="200"
10
+ android:fromAlpha="0.0"
11
+ android:toAlpha="1.0"
12
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
13
+ </set>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <set xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <translate
4
+ android:duration="150"
5
+ android:fromYDelta="0"
6
+ android:toYDelta="20%p"
7
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
8
+ <alpha
9
+ android:duration="150"
10
+ android:fromAlpha="1.0"
11
+ android:toAlpha="0.0"
12
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
13
+ </set>
@@ -1,8 +1,20 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <resources>
3
- <!-- Custom BottomSheetDialog style with edge-to-edge enabled -->
3
+ <!-- Smooth slide animation for BottomSheetDialog -->
4
+ <style name="TrueSheetAnimation" parent="Animation.AppCompat.Dialog">
5
+ <item name="android:windowEnterAnimation">@anim/true_sheet_slide_in</item>
6
+ <item name="android:windowExitAnimation">@anim/true_sheet_slide_out</item>
7
+ </style>
8
+
9
+ <!-- Default BottomSheetDialog style with smooth animations -->
10
+ <style name="TrueSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
11
+ <item name="android:windowAnimationStyle">@style/TrueSheetAnimation</item>
12
+ </style>
13
+
14
+ <!-- BottomSheetDialog style with edge-to-edge and smooth animations -->
4
15
  <style name="TrueSheetEdgeToEdgeEnabledDialog" parent="Theme.Design.Light.BottomSheetDialog">
5
16
  <item name="android:windowIsFloating">false</item>
6
17
  <item name="enableEdgeToEdge">true</item>
18
+ <item name="android:windowAnimationStyle">@style/TrueSheetAnimation</item>
7
19
  </style>
8
20
  </resources>
@@ -167,6 +167,10 @@ using namespace facebook::react;
167
167
  }
168
168
  }
169
169
 
170
+ - (void)contentViewDidChangeChildren {
171
+ [self setupContentScrollViewPinning];
172
+ }
173
+
170
174
  #pragma mark - TrueSheetHeaderViewDelegate
171
175
 
172
176
  - (void)headerViewDidChangeSize:(CGSize)newSize {
@@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
21
21
  @protocol TrueSheetContentViewDelegate <NSObject>
22
22
 
23
23
  - (void)contentViewDidChangeSize:(CGSize)newSize;
24
+ - (void)contentViewDidChangeChildren;
24
25
 
25
26
  @end
26
27
 
@@ -28,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
28
29
 
29
30
  @property (nonatomic, weak, nullable) id<TrueSheetContentViewDelegate> delegate;
30
31
 
31
- - (RCTScrollViewComponentView *_Nullable)findScrollView;
32
+ - (RCTScrollViewComponentView *_Nullable)findScrollView:(UIView *_Nullable *_Nullable)outTopSibling;
32
33
 
33
34
  /**
34
35
  * Setup ScrollView pinning