@lodev09/react-native-true-sheet 4.0.0-beta.3 → 4.0.0-beta.30

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 (244) hide show
  1. package/README.md +28 -5
  2. package/android/build.gradle +23 -1
  3. package/android/proguard-rules.pro +3 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +38 -109
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +127 -0
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +29 -3
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +234 -66
  14. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +491 -186
  15. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +64 -10
  16. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -14
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +135 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +23 -23
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +130 -6
  20. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  21. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +14 -17
  22. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
  23. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +148 -0
  24. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +33 -40
  25. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  26. package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
  27. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  28. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  29. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  30. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  31. package/android/src/main/jni/TrueSheetStateUpdater.cpp +37 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +114 -30
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +14 -3
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +5 -6
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +10 -6
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  46. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  47. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  48. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  49. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  50. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  51. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  52. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  53. package/ios/TrueSheetContainerView.h +23 -7
  54. package/ios/TrueSheetContainerView.mm +45 -25
  55. package/ios/TrueSheetContentView.h +44 -15
  56. package/ios/TrueSheetContentView.mm +221 -190
  57. package/ios/TrueSheetFooterView.mm +133 -28
  58. package/ios/TrueSheetModule.h +2 -7
  59. package/ios/TrueSheetModule.mm +18 -18
  60. package/ios/TrueSheetOverlayView.h +42 -0
  61. package/ios/TrueSheetOverlayView.mm +175 -0
  62. package/ios/TrueSheetView.h +2 -0
  63. package/ios/TrueSheetView.mm +268 -52
  64. package/ios/TrueSheetViewController.h +36 -3
  65. package/ios/TrueSheetViewController.mm +797 -102
  66. package/ios/core/RNScreensEventObserver.mm +31 -28
  67. package/ios/core/TrueSheetBlurView.h +0 -2
  68. package/ios/core/TrueSheetBlurView.mm +3 -36
  69. package/ios/core/TrueSheetDetentCalculator.h +18 -1
  70. package/ios/core/TrueSheetDetentCalculator.mm +44 -9
  71. package/ios/core/TrueSheetDimView.h +27 -0
  72. package/ios/core/TrueSheetDimView.mm +41 -0
  73. package/ios/core/TrueSheetGrabberView.mm +13 -3
  74. package/ios/core/TrueSheetKeyboardObserver.h +7 -0
  75. package/ios/core/TrueSheetKeyboardObserver.mm +107 -15
  76. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  77. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  78. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  79. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  80. package/ios/tvos/TrueSheetStubs.mm +12 -0
  81. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  82. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  83. package/lib/module/TrueSheet.js +222 -54
  84. package/lib/module/TrueSheet.js.map +1 -1
  85. package/lib/module/TrueSheet.web.js +364 -275
  86. package/lib/module/TrueSheet.web.js.map +1 -1
  87. package/lib/module/TrueSheetBackground.js +15 -0
  88. package/lib/module/TrueSheetBackground.js.map +1 -0
  89. package/lib/module/TrueSheetOverlay.js +30 -0
  90. package/lib/module/TrueSheetOverlay.js.map +1 -0
  91. package/lib/module/TrueSheetOverlay.web.js +35 -0
  92. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  93. package/lib/module/TrueSheetPeek.web.js +20 -15
  94. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  95. package/lib/module/TrueSheetProvider.web.js +13 -9
  96. package/lib/module/TrueSheetProvider.web.js.map +1 -1
  97. package/lib/module/detentBackgrounds.js +21 -0
  98. package/lib/module/detentBackgrounds.js.map +1 -0
  99. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  100. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  101. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  102. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +89 -73
  103. package/lib/module/index.js +1 -0
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/mocks/index.js +18 -2
  106. package/lib/module/mocks/index.js.map +1 -1
  107. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  108. package/lib/module/navigation/base-types.js +2 -0
  109. package/lib/module/navigation/base-types.js.map +1 -0
  110. package/lib/module/navigation/createTrueSheetRouter.js +158 -100
  111. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  112. package/lib/module/navigation/expo-router/base-types.js +2 -0
  113. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  114. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +71 -0
  115. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
  116. package/lib/module/navigation/expo-router/index.js +18 -7
  117. package/lib/module/navigation/expo-router/index.js.map +1 -1
  118. package/lib/module/navigation/expo-router/types.js +4 -0
  119. package/lib/module/navigation/expo-router/types.js.map +1 -0
  120. package/lib/module/navigation/index.js.map +1 -1
  121. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  122. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  123. package/lib/module/scrollable.js +28 -0
  124. package/lib/module/scrollable.js.map +1 -0
  125. package/lib/module/web/constants.js +1 -1
  126. package/lib/module/web/constants.js.map +1 -1
  127. package/lib/module/web/keyboard.js +69 -0
  128. package/lib/module/web/keyboard.js.map +1 -0
  129. package/lib/module/web/layout.js +168 -0
  130. package/lib/module/web/layout.js.map +1 -0
  131. package/lib/module/web/vaul/constants.js +9 -0
  132. package/lib/module/web/vaul/constants.js.map +1 -1
  133. package/lib/module/web/vaul/context.js +4 -0
  134. package/lib/module/web/vaul/context.js.map +1 -1
  135. package/lib/module/web/vaul/helpers.js +1 -0
  136. package/lib/module/web/vaul/helpers.js.map +1 -1
  137. package/lib/module/web/vaul/index.js +83 -21
  138. package/lib/module/web/vaul/index.js.map +1 -1
  139. package/lib/module/web/vaul/use-snap-points.js +57 -28
  140. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  141. package/lib/typescript/src/TrueSheet.d.ts +18 -1
  142. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  143. package/lib/typescript/src/TrueSheet.types.d.ts +209 -35
  144. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  145. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  146. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  147. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  148. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  149. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  150. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  151. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  152. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  153. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  154. package/lib/typescript/src/TrueSheetProvider.web.d.ts +5 -1
  155. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
  156. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  157. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  158. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -3
  159. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  160. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  161. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  162. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +76 -65
  163. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  164. package/lib/typescript/src/index.d.ts +1 -0
  165. package/lib/typescript/src/index.d.ts.map +1 -1
  166. package/lib/typescript/src/mocks/index.d.ts +4 -0
  167. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  168. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  169. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  170. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  171. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  172. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +36 -10
  173. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  174. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  175. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  176. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +33 -0
  177. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
  178. package/lib/typescript/src/navigation/expo-router/index.d.ts +9 -6
  179. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  180. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  181. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  182. package/lib/typescript/src/navigation/index.d.ts +3 -4
  183. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  184. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  185. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  186. package/lib/typescript/src/navigation/types.d.ts +43 -6
  187. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  188. package/lib/typescript/src/scrollable.d.ts +2 -0
  189. package/lib/typescript/src/scrollable.d.ts.map +1 -0
  190. package/lib/typescript/src/web/constants.d.ts +1 -1
  191. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  192. package/lib/typescript/src/web/keyboard.d.ts +15 -0
  193. package/lib/typescript/src/web/keyboard.d.ts.map +1 -0
  194. package/lib/typescript/src/web/layout.d.ts +20 -0
  195. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  196. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  197. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  198. package/lib/typescript/src/web/vaul/context.d.ts +5 -1
  199. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  200. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  201. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  202. package/lib/typescript/src/web/vaul/index.d.ts +20 -1
  203. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  204. package/lib/typescript/src/web/vaul/types.d.ts +9 -0
  205. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -1
  206. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +6 -4
  207. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  208. package/package.json +18 -8
  209. package/react-native.config.js +1 -0
  210. package/src/TrueSheet.tsx +257 -62
  211. package/src/TrueSheet.types.ts +225 -35
  212. package/src/TrueSheet.web.tsx +445 -333
  213. package/src/TrueSheetBackground.tsx +17 -0
  214. package/src/TrueSheetOverlay.tsx +24 -0
  215. package/src/TrueSheetOverlay.web.tsx +33 -0
  216. package/src/TrueSheetPeek.web.tsx +25 -23
  217. package/src/TrueSheetProvider.web.tsx +22 -13
  218. package/src/detentBackgrounds.ts +24 -0
  219. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  220. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  221. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  222. package/src/fabric/TrueSheetViewNativeComponent.ts +89 -73
  223. package/src/index.ts +1 -0
  224. package/src/mocks/index.ts +17 -2
  225. package/src/mocks/navigationExpoRouter.ts +6 -5
  226. package/src/navigation/base-types.ts +20 -0
  227. package/src/navigation/createTrueSheetRouter.ts +220 -144
  228. package/src/navigation/expo-router/base-types.ts +18 -0
  229. package/src/navigation/expo-router/extendTrueSheetRouter.ts +99 -0
  230. package/src/navigation/expo-router/index.ts +41 -30
  231. package/src/navigation/expo-router/types.ts +116 -0
  232. package/src/navigation/index.ts +6 -7
  233. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  234. package/src/navigation/types.ts +81 -23
  235. package/src/scrollable.ts +25 -0
  236. package/src/web/constants.ts +1 -1
  237. package/src/web/keyboard.ts +79 -0
  238. package/src/web/layout.ts +196 -0
  239. package/src/web/vaul/constants.ts +9 -0
  240. package/src/web/vaul/context.ts +7 -1
  241. package/src/web/vaul/helpers.ts +5 -0
  242. package/src/web/vaul/index.tsx +113 -16
  243. package/src/web/vaul/types.ts +10 -0
  244. package/src/web/vaul/use-snap-points.ts +86 -33
@@ -1,12 +1,12 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.graphics.Color
4
5
  import android.view.View
5
6
  import android.view.ViewGroup
6
7
  import android.view.accessibility.AccessibilityEvent
7
8
  import androidx.annotation.UiThread
8
9
  import com.facebook.react.bridge.LifecycleEventListener
9
- import com.facebook.react.bridge.WritableNativeMap
10
10
  import com.facebook.react.uimanager.PixelUtil.dpToPx
11
11
  import com.facebook.react.uimanager.PixelUtil.pxToDp
12
12
  import com.facebook.react.uimanager.StateWrapper
@@ -19,9 +19,11 @@ import com.lodev09.truesheet.core.AccessibilityOptions
19
19
  import com.lodev09.truesheet.core.GrabberOptions
20
20
  import com.lodev09.truesheet.core.RNScreensEventObserver
21
21
  import com.lodev09.truesheet.core.RNScreensEventObserverDelegate
22
+ import com.lodev09.truesheet.core.TrueSheetDismissThreshold
22
23
  import com.lodev09.truesheet.core.TrueSheetStackManager
23
24
  import com.lodev09.truesheet.events.*
24
25
  import com.lodev09.truesheet.utils.KeyboardUtils
26
+ import com.lodev09.truesheet.utils.findRootContainerView
25
27
 
26
28
  /**
27
29
  * Main TrueSheet host view that manages the sheet and dispatches events to JavaScript.
@@ -48,27 +50,45 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
48
50
 
49
51
  private var lastContainerWidth: Int = 0
50
52
  private var lastContainerHeight: Int = 0
53
+ private var hasCommittedContainerSize: Boolean = false
51
54
 
52
55
  var stateWrapper: StateWrapper? = null
53
56
  set(value) {
54
57
  field = value
58
+ if (value == null || hasCommittedContainerSize) return
55
59
 
56
- // On first state wrapper assignment, immediately update state with screen dimensions.
57
- // This ensures Yoga has initial width/height for content layout before presenting.
58
- if (value != null && lastContainerWidth == 0 && lastContainerHeight == 0) {
59
- updateState(viewController.screenWidth, viewController.screenHeight)
60
+ // A preallocated view gets its initial state before the node is committed, so the size
61
+ // update is dropped. Resend until a committed state has it, or Yoga sizes the sheet to its parent.
62
+ if ((value.stateData?.getDouble("containerHeight") ?: 0.0) > 0.0) {
63
+ hasCommittedContainerSize = true
64
+ return
60
65
  }
66
+
67
+ lastContainerWidth = 0
68
+ lastContainerHeight = 0
69
+ updateState(viewController.screenWidth, viewController.screenHeight)
61
70
  }
62
71
 
63
72
  // Debounce flag to coalesce rapid layout changes into a single sheet update
64
73
  private var isSheetUpdatePending: Boolean = false
65
74
 
75
+ // didFocus waiting for the slide-back after a child dismissed
76
+ private var isDidFocusPending: Boolean = false
77
+
66
78
  // Root container for the coordinator layout (activity or Modal dialog content view)
67
79
  internal var rootContainerView: ViewGroup? = null
68
80
 
69
81
  // Screen event observer for react-native-screens integration
70
82
  internal var screensEventObserver: RNScreensEventObserver? = null
71
83
 
84
+ // Present deferred until the keyboard hides; a dismiss before then cancels it
85
+ private var pendingPresentCallback: (() -> Unit)? = null
86
+ private var pendingPresentGeneration = 0
87
+
88
+ // Sheet the deferred present will stack on — dismissing it drops the present, like iOS
89
+ internal var pendingPresentParent: TrueSheetView? = null
90
+ private set
91
+
72
92
  // ==================== Initialization ====================
73
93
 
74
94
  init {
@@ -188,6 +208,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
188
208
 
189
209
  cleanupScreenEventObserver()
190
210
  didInitiallyPresent = false
211
+ cancelPendingPresent()
191
212
 
192
213
  viewController.dismissPromise = { viewController.delegate = null }
193
214
 
@@ -204,6 +225,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
204
225
  setupScrollable()
205
226
 
206
227
  if (viewController.isPresented) {
228
+ viewController.setupBackground()
207
229
  viewController.sheetView?.setupBackground()
208
230
  viewController.sheetView?.setupGrabber()
209
231
  viewController.sheetView?.updateGravity()
@@ -224,15 +246,15 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
224
246
  viewController.maxContentWidth = width
225
247
  }
226
248
 
227
- fun setAnchor(anchor: String?) {
228
- val value = TrueSheetAnchor.fromString(anchor)
229
- if (viewController.anchor == value) return
230
- viewController.anchor = value
249
+ fun setPlacement(placement: String?) {
250
+ val value = TrueSheetPlacement.fromString(placement)
251
+ if (viewController.placement == value) return
252
+ viewController.placement = value
231
253
  }
232
254
 
233
- fun setAnchorOffset(offset: Int) {
234
- if (viewController.anchorOffset == offset) return
235
- viewController.anchorOffset = offset
255
+ fun setPlacementOffset(offset: Int) {
256
+ if (viewController.placementOffset == offset) return
257
+ viewController.placementOffset = offset
236
258
  }
237
259
 
238
260
  fun setDimmed(dimmed: Boolean) {
@@ -245,6 +267,24 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
245
267
  }
246
268
  }
247
269
 
270
+ fun setDimColor(color: Int?) {
271
+ val resolved = color ?: Color.BLACK
272
+ if (viewController.dimColor == resolved) return
273
+ viewController.dimColor = resolved
274
+ if (viewController.isPresented) {
275
+ viewController.updateDimAmount()
276
+ }
277
+ }
278
+
279
+ fun setDimOpacity(opacity: Float) {
280
+ val resolved = if (opacity < 0f) TrueSheetViewController.DEFAULT_DIM_OPACITY else opacity.coerceIn(0f, 1f)
281
+ if (viewController.dimOpacity == resolved) return
282
+ viewController.dimOpacity = resolved
283
+ if (viewController.isPresented) {
284
+ viewController.updateDimAmount()
285
+ }
286
+ }
287
+
248
288
  fun setDimmedDetentIndex(index: Int) {
249
289
  if (viewController.dimmedDetentIndex == index) return
250
290
  viewController.dimmedDetentIndex = index
@@ -260,9 +300,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
260
300
  viewController.sheetCornerRadius = radius
261
301
  }
262
302
 
303
+ fun setDetentBackgrounds(colors: List<Int?>) {
304
+ viewController.detentBackgrounds = colors
305
+ }
306
+
263
307
  fun setSheetBackgroundColor(color: Int?) {
264
- if (viewController.sheetBackgroundColor == color) return
265
- viewController.sheetBackgroundColor = color
308
+ viewController.backgroundColor = color
266
309
  }
267
310
 
268
311
  fun setDismissible(dismissible: Boolean) {
@@ -273,6 +316,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
273
316
  viewController.draggable = draggable
274
317
  }
275
318
 
319
+ fun setDismissThreshold(dismissThreshold: String?) {
320
+ viewController.dismissThreshold = TrueSheetDismissThreshold.fromString(dismissThreshold)
321
+ }
322
+
276
323
  fun setGrabber(grabber: Boolean) {
277
324
  viewController.grabber = grabber
278
325
  }
@@ -304,6 +351,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
304
351
  setupScrollable()
305
352
  }
306
353
 
354
+ fun setScrollableHandle(handle: Int) {
355
+ if (viewController.scrollableHandle == handle) return
356
+ viewController.scrollableHandle = handle
357
+ setupScrollable()
358
+ }
359
+
307
360
  fun setAbsoluteHeader(absolute: Boolean) {
308
361
  viewController.absoluteHeader = absolute
309
362
  }
@@ -317,14 +370,20 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
317
370
  viewController.positionFooter()
318
371
  }
319
372
 
373
+ fun setFooterAvoidsKeyboard(avoids: Boolean) {
374
+ viewController.footerAvoidsKeyboard = avoids
375
+ viewController.containerView?.footerAvoidsKeyboard = avoids
376
+ viewController.positionFooter()
377
+ }
378
+
320
379
  private fun setupScrollable() {
321
380
  viewController.containerView?.let {
322
- it.insetAdjustment = viewController.insetAdjustment
323
- it.scrollViewBottomInset = viewController.contentBottomInset
324
381
  it.scrollableOptions = viewController.scrollableOptions
325
- it.hasAutoDetent = viewController.detents.contains(-1.0)
382
+ it.scrollableHandle = viewController.scrollableHandle
383
+ it.scrollableBottomInset = viewController.contentBottomInset
326
384
  it.footerBottomInset = viewController.contentBottomInset
327
385
  it.absoluteFooter = viewController.absoluteFooter
386
+ it.footerAvoidsKeyboard = viewController.footerAvoidsKeyboard
328
387
  it.setupScrollable()
329
388
  }
330
389
 
@@ -343,10 +402,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
343
402
 
344
403
  // For stacked sheets on the same screen, inherit parent's presenter screen tag.
345
404
  // If parent was hidden by screen navigation, this sheet is on a different screen.
346
- val parentScreenTag = viewController.parentSheetView?.screensEventObserver?.presenterScreenTag ?: 0
405
+ val parentObserver = viewController.parentSheetView?.screensEventObserver
406
+ val parentScreenTag = parentObserver?.presenterScreenTag ?: 0
347
407
  val parentHiddenByScreen = viewController.parentSheetView?.viewController?.wasHiddenByScreen == true
348
408
  if (parentScreenTag != 0 && !parentHiddenByScreen) {
349
409
  presenterScreenTag = parentScreenTag
410
+ ancestorScreenTags = parentObserver?.ancestorScreenTags ?: emptySet()
350
411
  } else {
351
412
  capturePresenterScreenFromView(this@TrueSheetView)
352
413
  }
@@ -371,25 +432,14 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
371
432
  lastContainerWidth = width
372
433
  lastContainerHeight = height
373
434
 
374
- val sw = stateWrapper ?: return
375
- val widthDp = width.toFloat().pxToDp()
376
- val heightDp = height.toFloat().pxToDp()
377
-
378
- // Synchronous update — commits and mounts within the same UI-thread frame
379
- if (TrueSheetStateUpdater.updateState(sw, widthDp, heightDp)) return
380
-
381
- // Fallback: async state update
382
- val newStateData = WritableNativeMap()
383
- newStateData.putDouble("containerWidth", widthDp.toDouble())
384
- newStateData.putDouble("containerHeight", heightDp.toDouble())
385
- sw.updateState(newStateData)
435
+ stateWrapper?.let { TrueSheetStateUpdater.updateContainerSize(it, width, height) }
386
436
  }
387
437
 
388
438
  // ==================== Sheet Actions ====================
389
439
 
390
440
  @UiThread
391
441
  fun present(detentIndex: Int, animated: Boolean = true, promiseCallback: () -> Unit) {
392
- if (viewController.isPresented) {
442
+ if (viewController.isPresented || pendingPresentCallback != null) {
393
443
  RNLog.w(reactContext, "TrueSheet: sheet is already presented. Use resize() to change detent.")
394
444
  promiseCallback()
395
445
  return
@@ -398,21 +448,49 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
398
448
  viewController.createSheet()
399
449
  setupScrollable()
400
450
 
451
+ // Attach before any keyboard wait so the touches meanwhile don't reach the views below
452
+ rootContainerView = findRootContainerView()
453
+ rootContainerView?.let { container ->
454
+ viewController.coordinatorLayout?.let {
455
+ it.blocksTouches = true
456
+ container.addView(it)
457
+ // Touches skip a view without a frame — don't wait for the next layout pass
458
+ it.layout(0, 0, container.width, container.height)
459
+ }
460
+ }
461
+
401
462
  // Dismiss keyboard if focused view is within a sheet or if target detent will be dimmed
402
463
  val parentSheet = TrueSheetStackManager.getTopmostSheet()
403
464
  val isFocusedViewWithinSheet = parentSheet?.viewController?.isFocusedViewWithinSheet() == true
404
465
  val shouldDismissKeyboard = isFocusedViewWithinSheet || viewController.isDimmedAtDetentIndex(detentIndex)
405
- if (KeyboardUtils.isKeyboardVisible(reactContext) && shouldDismissKeyboard) {
466
+ if (shouldDismissKeyboard && KeyboardUtils.isKeyboardVisible(reactContext)) {
406
467
  viewController.saveFocusedView()
468
+ pendingPresentCallback = promiseCallback
469
+ pendingPresentParent = parentSheet
470
+ // A cancelled request's callback can still land (a timer below API 30) — only the latest may present
471
+ val generation = ++pendingPresentGeneration
407
472
  KeyboardUtils.dismiss(this) {
408
- post { present(detentIndex, animated, promiseCallback) }
473
+ post {
474
+ if (generation != pendingPresentGeneration) return@post
475
+ val callback = pendingPresentCallback ?: return@post
476
+ pendingPresentCallback = null
477
+ pendingPresentParent = null
478
+ presentSheet(detentIndex, animated, callback)
479
+ }
409
480
  }
481
+ // Blur like iOS so JS focus state stays in sync until restoreFocusedView() refocuses it
482
+ rootView.findFocus()?.clearFocus()
410
483
  return
411
484
  }
412
485
 
413
- // Attach coordinator to the root container
414
- rootContainerView = findRootContainerView()
415
- viewController.coordinatorLayout?.let { rootContainerView?.addView(it) }
486
+ presentSheet(detentIndex, animated, promiseCallback)
487
+ }
488
+
489
+ private fun presentSheet(detentIndex: Int, animated: Boolean, promiseCallback: () -> Unit) {
490
+ if (viewController.isPresented) {
491
+ promiseCallback()
492
+ return
493
+ }
416
494
 
417
495
  // Register with observer to track sheet stack hierarchy
418
496
  viewController.parentSheetView = TrueSheetStackManager.registerSheet(this)
@@ -421,13 +499,42 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
421
499
  viewController.present(detentIndex, animated)
422
500
  }
423
501
 
502
+ private fun detachCoordinator() {
503
+ viewController.coordinatorLayout?.let { rootContainerView?.removeView(it) }
504
+ rootContainerView = null
505
+ }
506
+
424
507
  @UiThread
425
508
  fun handleBackPress() {
426
509
  viewController.handleBackPress()
427
510
  }
428
511
 
512
+ /**
513
+ * Drops a present still waiting on the keyboard and resolves its caller. Returns whether one was pending.
514
+ * Skip [restoreFocus] when the focused view is going away with its dismissing sheet.
515
+ */
516
+ fun cancelPendingPresent(restoreFocus: Boolean = true): Boolean {
517
+ val callback = pendingPresentCallback ?: return false
518
+ pendingPresentCallback = null
519
+ pendingPresentParent = null
520
+ detachCoordinator()
521
+ if (restoreFocus) {
522
+ viewController.restoreFocusedView()
523
+ } else {
524
+ viewController.clearFocusedView()
525
+ }
526
+ callback()
527
+ return true
528
+ }
529
+
429
530
  @UiThread
430
531
  fun dismiss(animated: Boolean = true, promiseCallback: () -> Unit) {
532
+ // Dismissing before a deferred present starts cancels it — nothing was presented
533
+ if (cancelPendingPresent()) {
534
+ promiseCallback()
535
+ return
536
+ }
537
+
431
538
  if (viewController.isBeingDismissed || !viewController.isPresented) {
432
539
  RNLog.w(reactContext, "TrueSheet: sheet is already dismissed. No need to dismiss it again.")
433
540
  promiseCallback()
@@ -450,6 +557,9 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
450
557
  return
451
558
  }
452
559
 
560
+ // A present waiting on the keyboard would stack above this sheet — drop it with the rest
561
+ TrueSheetModule.cancelPendingPresents(parent = this)
562
+
453
563
  val sheetsAbove = TrueSheetStackManager.getSheetsAbove(this)
454
564
  // Create snapshot only for topmost sheet (first in reversed list)
455
565
  sheetsAbove.firstOrNull()?.viewController?.createSheetSnapshot()
@@ -486,7 +596,13 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
486
596
  if (viewController.containerView == null) return@post
487
597
 
488
598
  viewController.setupSheetDetentsForSizeChange()
489
- TrueSheetStackManager.updateParentTranslation(this)
599
+ // While settling, onSlide drives the parent translation in realtime —
600
+ // an animated update here would race it frame-by-frame, staggering the
601
+ // emitted position. Keyboard-driven resizes leave the parent alone (like
602
+ // iOS); keyboardDidHide resizes again once the detents are keyboard-free.
603
+ if (!viewController.isSettling && !viewController.isKeyboardActive) {
604
+ TrueSheetStackManager.updateParentTranslation(this)
605
+ }
490
606
  }
491
607
  }
492
608
 
@@ -510,18 +626,50 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
510
626
 
511
627
  // Propagate any additional translation up the stack
512
628
  if (additionalTranslation > 0) {
513
- TrueSheetStackManager.getParentSheet(this)?.addTranslation(additionalTranslation)
629
+ viewController.parentSheetView?.addTranslation(additionalTranslation)
630
+ }
631
+ }
632
+
633
+ /**
634
+ * Realtime counterpart of updateTranslationForChild, driven by the child's
635
+ * onSlide frames so stacked sheets move in lockstep instead of on
636
+ * independently-timed animations.
637
+ */
638
+ fun syncTranslationForChild(childSheetTop: Int, childMinSheetTop: Int) {
639
+ if (!viewController.isSheetVisible || viewController.isExpanded) return
640
+
641
+ val mySheetTop = viewController.detentCalculator.getSheetTopForDetentIndex(viewController.currentDetentIndex)
642
+ val targetTranslation = maxOf(0, childMinSheetTop - mySheetTop)
643
+ val actualTranslation = maxOf(0, childSheetTop - mySheetTop)
644
+ val currentTranslation = viewController.currentTranslationY
645
+
646
+ // Ride the child's actual position, but only between the current
647
+ // translation and the settled target — tracks the child frame-by-frame
648
+ // when the alignment changes (auto detent resize) while staying put when
649
+ // the child moves between detents above its minimum. Returning early on
650
+ // no-ops avoids cancelling an in-flight realign animation.
651
+ val newTranslation = actualTranslation.coerceIn(
652
+ minOf(currentTranslation, targetTranslation),
653
+ maxOf(currentTranslation, targetTranslation)
654
+ )
655
+ if (newTranslation == currentTranslation) return
656
+
657
+ viewController.translateSheet(newTranslation, animated = false)
658
+
659
+ val additionalTranslation = newTranslation - currentTranslation
660
+ if (additionalTranslation > 0) {
661
+ viewController.parentSheetView?.addTranslation(additionalTranslation, animated = false)
514
662
  }
515
663
  }
516
664
 
517
665
  /**
518
666
  * Recursively adds translation to this sheet and all parent sheets.
519
667
  */
520
- private fun addTranslation(amount: Int) {
668
+ private fun addTranslation(amount: Int, animated: Boolean = true) {
521
669
  if (viewController.isExpanded) return
522
670
 
523
- viewController.translateSheet(viewController.currentTranslationY + amount)
524
- TrueSheetStackManager.getParentSheet(this)?.addTranslation(amount)
671
+ viewController.translateSheet(viewController.currentTranslationY + amount, animated)
672
+ viewController.parentSheetView?.addTranslation(amount, animated)
525
673
  }
526
674
 
527
675
  /**
@@ -534,12 +682,33 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
534
682
 
535
683
  // Parent should recalculate its translation based on this sheet's position
536
684
  val mySheetTop = viewController.detentCalculator.getSheetTopForDetentIndex(viewController.currentDetentIndex)
537
- TrueSheetStackManager.getParentSheet(this)?.updateTranslationForChild(mySheetTop)
685
+ viewController.parentSheetView?.updateTranslationForChild(mySheetTop)
686
+ }
687
+
688
+ /**
689
+ * Resets translation after a child dismissed and emits didFocus once the sheet settles.
690
+ * A sheet presented on top before then cancels the slide-back, so the next blur flushes it.
691
+ */
692
+ private fun focusAfterResetTranslation() {
693
+ isDidFocusPending = true
694
+ resetTranslation { emitPendingDidFocus() }
695
+ }
696
+
697
+ private fun emitPendingDidFocus() {
698
+ if (!isDidFocusPending) return
699
+ isDidFocusPending = false
700
+
701
+ if (viewController.isPresented && !viewController.isBeingDismissed) {
702
+ viewControllerDidFocus()
703
+ }
538
704
  }
539
705
 
540
706
  // ==================== TrueSheetViewControllerDelegate ====================
541
707
 
542
708
  override fun viewControllerWillPresent(index: Int, position: Float, detent: Float) {
709
+ // Observe from the start so navigating away mid-present still hides the sheet
710
+ setupScreenEventObserver()
711
+
543
712
  // Update parent sheet translation now that content is measured
544
713
  TrueSheetStackManager.updateParentTranslation(this)
545
714
 
@@ -548,22 +717,28 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
548
717
  }
549
718
 
550
719
  override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
551
- setupScreenEventObserver()
552
-
553
720
  val surfaceId = UIManagerHelper.getSurfaceId(this)
554
721
  eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
555
722
  }
556
723
 
557
724
  override fun viewControllerWillDismiss() {
725
+ // Covers every dismiss path (swipe, back press, stack); the focused view leaves with this sheet
726
+ TrueSheetModule.cancelPendingPresents(parent = this, restoreFocus = false)
727
+
558
728
  val surfaceId = UIManagerHelper.getSurfaceId(this)
559
729
  eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
560
730
  }
561
731
 
732
+ override fun viewControllerDidAttemptDismiss() {
733
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
734
+ eventDispatcher?.dispatchEvent(DismissAttemptEvent(surfaceId, id))
735
+ }
736
+
562
737
  override fun viewControllerDidDismiss(parent: TrueSheetView?) {
563
- // Detach coordinator from the root container view
564
- viewController.coordinatorLayout?.let { rootContainerView?.removeView(it) }
565
- rootContainerView = null
738
+ // A slide-back cut short by this sheet's own dismissal never emits; don't carry it over
739
+ isDidFocusPending = false
566
740
 
741
+ detachCoordinator()
567
742
  cleanupScreenEventObserver()
568
743
 
569
744
  val surfaceId = UIManagerHelper.getSurfaceId(this)
@@ -571,12 +746,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
571
746
 
572
747
  TrueSheetStackManager.unregisterSheet(this)
573
748
 
574
- parent?.resetTranslation {
575
- val parentController = parent.viewController
576
- if (parentController.isPresented && !parentController.isBeingDismissed) {
577
- parent.viewControllerDidFocus()
578
- }
579
- }
749
+ // The sheets below stop blending this sheet's dim
750
+ parent?.viewController?.takeIf { it.isPresented }?.updateDimAmount()
751
+
752
+ parent?.focusAfterResetTranslation()
580
753
  }
581
754
 
582
755
  override fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float) {
@@ -619,6 +792,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
619
792
  }
620
793
 
621
794
  override fun viewControllerWillBlur() {
795
+ emitPendingDidFocus()
796
+
622
797
  val surfaceId = UIManagerHelper.getSurfaceId(this)
623
798
  eventDispatcher?.dispatchEvent(WillBlurEvent(surfaceId, id))
624
799
  }
@@ -643,7 +818,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
643
818
  viewController.commitKeyboardDetent()
644
819
  }
645
820
 
646
- // When the ScrollView changes (e.g. conditional remount), re-pin the new ScrollView
821
+ // When the ScrollView changes (e.g. conditional remount), re-detect the new ScrollView
647
822
  // and request layout so BottomSheetBehavior re-discovers the nested scrolling child.
648
823
  override fun containerViewScrollViewDidChange() {
649
824
  setupScrollable()
@@ -660,6 +835,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
660
835
  viewController.positionFooter()
661
836
  }
662
837
 
838
+ override fun containerViewFooterDidLayout() {
839
+ viewController.positionFooter()
840
+ }
841
+
663
842
  override fun containerViewPeekDidChangeSize(width: Int, height: Int) {
664
843
  // Peek content height affects peek detents
665
844
  updateSheetIfNeeded()
@@ -689,16 +868,5 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
689
868
  * of whichever window this view is in - whether that's the activity's window or a
690
869
  * Modal's dialog window.
691
870
  */
692
- override fun findRootContainerView(): ViewGroup? {
693
- var current: android.view.ViewParent? = parent
694
-
695
- while (current != null) {
696
- if (current is ViewGroup && current.id == android.R.id.content) {
697
- return current
698
- }
699
- current = current.parent
700
- }
701
-
702
- return reactContext.currentActivity?.findViewById(android.R.id.content)
703
- }
871
+ override fun findRootContainerView(): ViewGroup? = findRootContainerView(reactContext)
704
872
  }