@lodev09/react-native-true-sheet 4.0.0-beta.0 → 4.0.0-beta.10

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 (430) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +62 -30
  3. package/RNTrueSheet.podspec +10 -2
  4. package/android/build.gradle +24 -2
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +189 -47
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt +7 -0
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +401 -19
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +18 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +145 -21
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterViewManager.kt +14 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderView.kt +38 -0
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderViewManager.kt +28 -0
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +84 -24
  14. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -0
  15. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  16. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +6 -4
  17. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekView.kt +72 -0
  18. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekViewManager.kt +33 -0
  19. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +63 -0
  20. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +564 -226
  21. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +1399 -598
  22. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +161 -29
  23. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +63 -0
  24. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +36 -0
  25. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +255 -0
  26. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +375 -0
  27. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +299 -0
  28. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +162 -0
  29. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +180 -0
  30. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetKeyboardObserver.kt +207 -0
  31. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  32. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +220 -0
  33. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
  34. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
  35. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +127 -0
  36. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
  37. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +84 -0
  38. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +93 -67
  39. package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
  40. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +42 -0
  41. package/android/src/main/jni/CMakeLists.txt +63 -0
  42. package/android/src/main/jni/TrueSheetSpec.h +20 -0
  43. package/android/src/main/jni/TrueSheetStateUpdater.cpp +96 -0
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +16 -0
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +68 -0
  46. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +33 -0
  47. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +11 -0
  48. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +39 -0
  49. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +24 -0
  50. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +63 -0
  51. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +28 -0
  52. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +11 -0
  53. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +41 -0
  54. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +18 -0
  55. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +16 -0
  56. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
  57. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  58. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  59. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
  60. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +28 -0
  61. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +30 -0
  62. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +45 -0
  63. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +23 -0
  64. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +52 -0
  65. package/ios/TrueSheetContainerView.h +88 -6
  66. package/ios/TrueSheetContainerView.mm +278 -21
  67. package/ios/TrueSheetContentView.h +54 -1
  68. package/ios/TrueSheetContentView.mm +371 -47
  69. package/ios/TrueSheetFooterView.h +33 -2
  70. package/ios/TrueSheetFooterView.mm +187 -35
  71. package/ios/{events/OnWillDismissEvent.h → TrueSheetHeaderView.h} +10 -7
  72. package/ios/TrueSheetHeaderView.mm +64 -0
  73. package/ios/TrueSheetModule.h +2 -7
  74. package/ios/TrueSheetModule.mm +98 -20
  75. package/ios/TrueSheetOverlayView.h +32 -0
  76. package/ios/TrueSheetOverlayView.mm +161 -0
  77. package/ios/TrueSheetPeekView.h +32 -0
  78. package/ios/TrueSheetPeekView.mm +99 -0
  79. package/ios/TrueSheetView.h +7 -15
  80. package/ios/TrueSheetView.mm +775 -215
  81. package/ios/TrueSheetViewController.h +94 -16
  82. package/ios/TrueSheetViewController.mm +1316 -331
  83. package/ios/core/RNScreensEventObserver.h +46 -0
  84. package/ios/core/RNScreensEventObserver.mm +280 -0
  85. package/ios/core/TrueSheetBlurView.h +27 -0
  86. package/ios/core/TrueSheetBlurView.mm +82 -0
  87. package/ios/core/TrueSheetDetentCalculator.h +107 -0
  88. package/ios/core/TrueSheetDetentCalculator.mm +197 -0
  89. package/ios/core/TrueSheetGrabberView.h +72 -0
  90. package/ios/core/TrueSheetGrabberView.mm +188 -0
  91. package/ios/core/TrueSheetKeyboardObserver.h +39 -0
  92. package/ios/core/TrueSheetKeyboardObserver.mm +112 -0
  93. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  94. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  95. package/ios/events/TrueSheetDragEvents.h +39 -0
  96. package/ios/events/TrueSheetDragEvents.mm +62 -0
  97. package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
  98. package/ios/events/TrueSheetFocusEvents.mm +49 -0
  99. package/ios/events/TrueSheetLifecycleEvents.h +42 -0
  100. package/ios/events/TrueSheetLifecycleEvents.mm +79 -0
  101. package/ios/events/TrueSheetStateEvents.h +35 -0
  102. package/ios/events/TrueSheetStateEvents.mm +49 -0
  103. package/ios/tvos/TrueSheetStubs.mm +166 -0
  104. package/ios/{events/OnMountEvent.h → utils/BlurUtil.h} +5 -7
  105. package/ios/utils/BlurUtil.mm +69 -0
  106. package/ios/utils/GestureUtil.h +7 -0
  107. package/ios/utils/GestureUtil.mm +12 -0
  108. package/ios/utils/PlatformUtil.h +15 -0
  109. package/ios/utils/UIView+FirstResponder.h +15 -0
  110. package/ios/utils/UIView+FirstResponder.mm +26 -0
  111. package/ios/utils/UIView+ScrollEdgeInteraction.h +26 -0
  112. package/ios/utils/UIView+ScrollEdgeInteraction.mm +84 -0
  113. package/ios/utils/WindowUtil.mm +17 -1
  114. package/lib/module/TrueSheet.js +349 -74
  115. package/lib/module/TrueSheet.js.map +1 -1
  116. package/lib/module/TrueSheet.web.js +1270 -0
  117. package/lib/module/TrueSheet.web.js.map +1 -0
  118. package/lib/module/TrueSheetOverlay.js +30 -0
  119. package/lib/module/TrueSheetOverlay.js.map +1 -0
  120. package/lib/module/TrueSheetOverlay.web.js +35 -0
  121. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  122. package/lib/module/TrueSheetPeek.js +15 -0
  123. package/lib/module/TrueSheetPeek.js.map +1 -0
  124. package/lib/module/TrueSheetPeek.web.js +55 -0
  125. package/lib/module/TrueSheetPeek.web.js.map +1 -0
  126. package/lib/module/TrueSheetProvider.js +28 -0
  127. package/lib/module/TrueSheetProvider.js.map +1 -0
  128. package/lib/module/TrueSheetProvider.web.js +157 -0
  129. package/lib/module/TrueSheetProvider.web.js.map +1 -0
  130. package/lib/module/fabric/TrueSheetContainerViewNativeComponent.ts +1 -1
  131. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
  132. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
  133. package/lib/module/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
  134. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  135. package/lib/module/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
  136. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +110 -18
  137. package/lib/module/index.js +4 -3
  138. package/lib/module/index.js.map +1 -1
  139. package/lib/module/mocks/index.js +103 -0
  140. package/lib/module/mocks/index.js.map +1 -0
  141. package/lib/module/mocks/navigation.js +95 -0
  142. package/lib/module/mocks/navigation.js.map +1 -0
  143. package/lib/module/mocks/navigationExpoRouter.js +21 -0
  144. package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
  145. package/lib/module/mocks/reanimated.js +91 -0
  146. package/lib/module/mocks/reanimated.js.map +1 -0
  147. package/lib/module/navigation/TrueSheetNavigatorContent.js +73 -0
  148. package/lib/module/navigation/TrueSheetNavigatorContent.js.map +1 -0
  149. package/lib/module/navigation/actions.js +50 -0
  150. package/lib/module/navigation/actions.js.map +1 -0
  151. package/lib/module/navigation/base-types.js +2 -0
  152. package/lib/module/navigation/base-types.js.map +1 -0
  153. package/lib/module/navigation/createTrueSheetRouter.js +157 -0
  154. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -0
  155. package/lib/module/navigation/expo-router/base-types.js +2 -0
  156. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  157. package/lib/module/navigation/expo-router/index.js +50 -0
  158. package/lib/module/navigation/expo-router/index.js.map +1 -0
  159. package/lib/module/navigation/expo-router/types.js +4 -0
  160. package/lib/module/navigation/expo-router/types.js.map +1 -0
  161. package/lib/module/navigation/index.js +47 -0
  162. package/lib/module/navigation/index.js.map +1 -0
  163. package/lib/module/navigation/navigator.js +10 -0
  164. package/lib/module/navigation/navigator.js.map +1 -0
  165. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +56 -0
  166. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -0
  167. package/lib/module/navigation/screen/TrueSheetScreen.js +49 -0
  168. package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -0
  169. package/lib/module/navigation/screen/index.js +5 -0
  170. package/lib/module/navigation/screen/index.js.map +1 -0
  171. package/lib/module/navigation/screen/types.js +4 -0
  172. package/lib/module/navigation/screen/types.js.map +1 -0
  173. package/lib/module/navigation/screen/useSheetScreenState.js +69 -0
  174. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -0
  175. package/lib/module/navigation/types.js +4 -0
  176. package/lib/module/navigation/types.js.map +1 -0
  177. package/lib/module/navigation/useTrueSheetNavigation.js +24 -0
  178. package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -0
  179. package/lib/module/reanimated/ReanimatedTrueSheet.js +15 -9
  180. package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
  181. package/lib/module/reanimated/ReanimatedTrueSheet.web.js +81 -0
  182. package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -0
  183. package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
  184. package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
  185. package/lib/module/reanimated/index.js +2 -2
  186. package/lib/module/reanimated/index.js.map +1 -1
  187. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +20 -0
  188. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -0
  189. package/lib/module/specs/NativeTrueSheetModule.js.map +1 -1
  190. package/lib/module/web/constants.js +16 -0
  191. package/lib/module/web/constants.js.map +1 -0
  192. package/lib/module/web/dom.js +10 -0
  193. package/lib/module/web/dom.js.map +1 -0
  194. package/lib/module/web/vaul/README.md +9 -0
  195. package/lib/module/web/vaul/browser.js +30 -0
  196. package/lib/module/web/vaul/browser.js.map +1 -0
  197. package/lib/module/web/vaul/constants.js +19 -0
  198. package/lib/module/web/vaul/constants.js.map +1 -0
  199. package/lib/module/web/vaul/context.js +59 -0
  200. package/lib/module/web/vaul/context.js.map +1 -0
  201. package/lib/module/web/vaul/helpers.js +99 -0
  202. package/lib/module/web/vaul/helpers.js.map +1 -0
  203. package/lib/module/web/vaul/index.js +1342 -0
  204. package/lib/module/web/vaul/index.js.map +1 -0
  205. package/lib/module/web/vaul/style.css +287 -0
  206. package/lib/module/web/vaul/style.css.d.js +2 -0
  207. package/lib/module/web/vaul/style.css.d.js.map +1 -0
  208. package/lib/module/web/vaul/types.js +2 -0
  209. package/lib/module/web/vaul/types.js.map +1 -0
  210. package/lib/module/web/vaul/use-composed-refs.js +34 -0
  211. package/lib/module/web/vaul/use-composed-refs.js.map +1 -0
  212. package/lib/module/web/vaul/use-controllable-state.js +54 -0
  213. package/lib/module/web/vaul/use-controllable-state.js.map +1 -0
  214. package/lib/module/web/vaul/use-position-fixed.js +123 -0
  215. package/lib/module/web/vaul/use-position-fixed.js.map +1 -0
  216. package/lib/module/web/vaul/use-prevent-scroll.js +258 -0
  217. package/lib/module/web/vaul/use-prevent-scroll.js.map +1 -0
  218. package/lib/module/web/vaul/use-scale-background.js +57 -0
  219. package/lib/module/web/vaul/use-scale-background.js.map +1 -0
  220. package/lib/module/web/vaul/use-snap-points.js +305 -0
  221. package/lib/module/web/vaul/use-snap-points.js.map +1 -0
  222. package/lib/typescript/src/TrueSheet.d.ts +57 -14
  223. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  224. package/lib/typescript/src/TrueSheet.types.d.ts +535 -55
  225. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  226. package/lib/typescript/src/TrueSheet.web.d.ts +5 -0
  227. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -0
  228. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  229. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  230. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  231. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  232. package/lib/typescript/src/TrueSheetPeek.d.ts +9 -0
  233. package/lib/typescript/src/TrueSheetPeek.d.ts.map +1 -0
  234. package/lib/typescript/src/TrueSheetPeek.web.d.ts +28 -0
  235. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -0
  236. package/lib/typescript/src/TrueSheetProvider.d.ts +18 -0
  237. package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -0
  238. package/lib/typescript/src/TrueSheetProvider.web.d.ts +27 -0
  239. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -0
  240. package/lib/typescript/src/fabric/TrueSheetContainerViewNativeComponent.d.ts.map +1 -1
  241. package/lib/typescript/src/fabric/TrueSheetContentViewNativeComponent.d.ts.map +1 -1
  242. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -0
  243. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  244. package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts +6 -0
  245. package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts.map +1 -0
  246. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  247. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  248. package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts +6 -0
  249. package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts.map +1 -0
  250. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +71 -16
  251. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  252. package/lib/typescript/src/index.d.ts +3 -2
  253. package/lib/typescript/src/index.d.ts.map +1 -1
  254. package/lib/typescript/src/mocks/index.d.ts +47 -0
  255. package/lib/typescript/src/mocks/index.d.ts.map +1 -0
  256. package/lib/typescript/src/mocks/navigation.d.ts +70 -0
  257. package/lib/typescript/src/mocks/navigation.d.ts.map +1 -0
  258. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +22 -0
  259. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -0
  260. package/lib/typescript/src/mocks/reanimated.d.ts +48 -0
  261. package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -0
  262. package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts +5 -0
  263. package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts.map +1 -0
  264. package/lib/typescript/src/navigation/actions.d.ts +71 -0
  265. package/lib/typescript/src/navigation/actions.d.ts.map +1 -0
  266. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  267. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  268. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +3 -0
  269. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -0
  270. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  271. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  272. package/lib/typescript/src/navigation/expo-router/index.d.ts +37 -0
  273. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -0
  274. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  275. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  276. package/lib/typescript/src/navigation/index.d.ts +45 -0
  277. package/lib/typescript/src/navigation/index.d.ts.map +1 -0
  278. package/lib/typescript/src/navigation/navigator.d.ts +7 -0
  279. package/lib/typescript/src/navigation/navigator.d.ts.map +1 -0
  280. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +3 -0
  281. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -0
  282. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +3 -0
  283. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -0
  284. package/lib/typescript/src/navigation/screen/index.d.ts +4 -0
  285. package/lib/typescript/src/navigation/screen/index.d.ts.map +1 -0
  286. package/lib/typescript/src/navigation/screen/types.d.ts +14 -0
  287. package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -0
  288. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +34 -0
  289. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -0
  290. package/lib/typescript/src/navigation/types.d.ts +213 -0
  291. package/lib/typescript/src/navigation/types.d.ts.map +1 -0
  292. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +23 -0
  293. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -0
  294. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
  295. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +41 -0
  296. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -0
  297. package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
  298. package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
  299. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +15 -0
  300. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -0
  301. package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts +25 -3
  302. package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts.map +1 -1
  303. package/lib/typescript/src/web/constants.d.ts +14 -0
  304. package/lib/typescript/src/web/constants.d.ts.map +1 -0
  305. package/lib/typescript/src/web/dom.d.ts +7 -0
  306. package/lib/typescript/src/web/dom.d.ts.map +1 -0
  307. package/lib/typescript/src/web/vaul/browser.d.ts +8 -0
  308. package/lib/typescript/src/web/vaul/browser.d.ts.map +1 -0
  309. package/lib/typescript/src/web/vaul/constants.d.ts +14 -0
  310. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -0
  311. package/lib/typescript/src/web/vaul/context.d.ts +54 -0
  312. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -0
  313. package/lib/typescript/src/web/vaul/helpers.d.ts +17 -0
  314. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -0
  315. package/lib/typescript/src/web/vaul/index.d.ts +234 -0
  316. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -0
  317. package/lib/typescript/src/web/vaul/types.d.ts +7 -0
  318. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -0
  319. package/lib/typescript/src/web/vaul/use-composed-refs.d.ts +14 -0
  320. package/lib/typescript/src/web/vaul/use-composed-refs.d.ts.map +1 -0
  321. package/lib/typescript/src/web/vaul/use-controllable-state.d.ts +9 -0
  322. package/lib/typescript/src/web/vaul/use-controllable-state.d.ts.map +1 -0
  323. package/lib/typescript/src/web/vaul/use-position-fixed.d.ts +19 -0
  324. package/lib/typescript/src/web/vaul/use-position-fixed.d.ts.map +1 -0
  325. package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts +18 -0
  326. package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts.map +1 -0
  327. package/lib/typescript/src/web/vaul/use-scale-background.d.ts +2 -0
  328. package/lib/typescript/src/web/vaul/use-scale-background.d.ts.map +1 -0
  329. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +38 -0
  330. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -0
  331. package/package.json +104 -37
  332. package/react-native.config.js +10 -2
  333. package/src/TrueSheet.tsx +424 -70
  334. package/src/TrueSheet.types.ts +602 -72
  335. package/src/TrueSheet.web.tsx +1437 -0
  336. package/src/TrueSheetOverlay.tsx +24 -0
  337. package/src/TrueSheetOverlay.web.tsx +33 -0
  338. package/src/TrueSheetPeek.tsx +11 -0
  339. package/src/TrueSheetPeek.web.tsx +64 -0
  340. package/src/TrueSheetProvider.tsx +33 -0
  341. package/src/TrueSheetProvider.web.tsx +220 -0
  342. package/src/fabric/TrueSheetContainerViewNativeComponent.ts +1 -1
  343. package/src/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
  344. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
  345. package/src/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
  346. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  347. package/src/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
  348. package/src/fabric/TrueSheetViewNativeComponent.ts +110 -18
  349. package/src/index.ts +3 -2
  350. package/src/mocks/index.ts +96 -0
  351. package/src/mocks/navigation.ts +79 -0
  352. package/src/mocks/navigationExpoRouter.ts +41 -0
  353. package/src/mocks/reanimated.ts +100 -0
  354. package/src/navigation/TrueSheetNavigatorContent.tsx +106 -0
  355. package/src/navigation/actions.ts +78 -0
  356. package/src/navigation/base-types.ts +20 -0
  357. package/src/navigation/createTrueSheetRouter.ts +194 -0
  358. package/src/navigation/expo-router/base-types.ts +18 -0
  359. package/src/navigation/expo-router/index.ts +90 -0
  360. package/src/navigation/expo-router/types.ts +116 -0
  361. package/src/navigation/index.ts +80 -0
  362. package/src/navigation/navigator.ts +18 -0
  363. package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +61 -0
  364. package/src/navigation/screen/TrueSheetScreen.tsx +54 -0
  365. package/src/navigation/screen/index.ts +3 -0
  366. package/src/navigation/screen/types.ts +19 -0
  367. package/src/navigation/screen/useSheetScreenState.ts +102 -0
  368. package/src/navigation/types.ts +302 -0
  369. package/src/navigation/useTrueSheetNavigation.ts +26 -0
  370. package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
  371. package/src/reanimated/ReanimatedTrueSheet.web.tsx +75 -0
  372. package/src/reanimated/ReanimatedTrueSheetProvider.tsx +12 -8
  373. package/src/reanimated/useReanimatedPositionChangeHandler.web.ts +31 -0
  374. package/src/specs/NativeTrueSheetModule.ts +28 -3
  375. package/src/web/constants.ts +13 -0
  376. package/src/web/dom.ts +9 -0
  377. package/src/web/vaul/README.md +9 -0
  378. package/src/web/vaul/browser.ts +38 -0
  379. package/src/web/vaul/constants.ts +24 -0
  380. package/src/web/vaul/context.ts +102 -0
  381. package/src/web/vaul/helpers.ts +131 -0
  382. package/src/web/vaul/index.tsx +1785 -0
  383. package/src/web/vaul/style.css +287 -0
  384. package/src/web/vaul/style.css.d.ts +1 -0
  385. package/src/web/vaul/types.ts +7 -0
  386. package/src/web/vaul/use-composed-refs.ts +35 -0
  387. package/src/web/vaul/use-controllable-state.ts +66 -0
  388. package/src/web/vaul/use-position-fixed.ts +147 -0
  389. package/src/web/vaul/use-prevent-scroll.ts +309 -0
  390. package/src/web/vaul/use-scale-background.ts +64 -0
  391. package/src/web/vaul/use-snap-points.ts +455 -0
  392. package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
  393. package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
  394. package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
  395. package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
  396. package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
  397. package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
  398. package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
  399. package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
  400. package/android/src/main/java/com/lodev09/truesheet/events/SizeChangeEvent.kt +0 -27
  401. package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
  402. package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
  403. package/android/src/main/res/values/styles.xml +0 -8
  404. package/ios/events/OnDetentChangeEvent.h +0 -28
  405. package/ios/events/OnDetentChangeEvent.mm +0 -30
  406. package/ios/events/OnDidDismissEvent.h +0 -26
  407. package/ios/events/OnDidDismissEvent.mm +0 -25
  408. package/ios/events/OnDidPresentEvent.h +0 -28
  409. package/ios/events/OnDidPresentEvent.mm +0 -30
  410. package/ios/events/OnDragBeginEvent.h +0 -28
  411. package/ios/events/OnDragBeginEvent.mm +0 -30
  412. package/ios/events/OnDragChangeEvent.h +0 -28
  413. package/ios/events/OnDragChangeEvent.mm +0 -30
  414. package/ios/events/OnDragEndEvent.h +0 -28
  415. package/ios/events/OnDragEndEvent.mm +0 -30
  416. package/ios/events/OnMountEvent.mm +0 -25
  417. package/ios/events/OnPositionChangeEvent.mm +0 -32
  418. package/ios/events/OnSizeChangeEvent.h +0 -28
  419. package/ios/events/OnSizeChangeEvent.mm +0 -30
  420. package/ios/events/OnWillDismissEvent.mm +0 -25
  421. package/ios/events/OnWillPresentEvent.h +0 -28
  422. package/ios/events/OnWillPresentEvent.mm +0 -30
  423. package/ios/utils/LayoutUtil.h +0 -44
  424. package/ios/utils/LayoutUtil.mm +0 -50
  425. package/lib/module/TrueSheetGrabber.js +0 -51
  426. package/lib/module/TrueSheetGrabber.js.map +0 -1
  427. package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
  428. package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
  429. package/src/TrueSheetGrabber.tsx +0 -82
  430. package/src/__mocks__/index.js +0 -67
@@ -13,60 +13,84 @@
13
13
  #import "TrueSheetContentView.h"
14
14
  #import "TrueSheetFooterView.h"
15
15
  #import "TrueSheetModule.h"
16
+ #import "TrueSheetOverlayView.h"
16
17
  #import "TrueSheetViewController.h"
17
- #import "events/OnDetentChangeEvent.h"
18
- #import "events/OnDidDismissEvent.h"
19
- #import "events/OnDidPresentEvent.h"
20
- #import "events/OnDragBeginEvent.h"
21
- #import "events/OnDragChangeEvent.h"
22
- #import "events/OnDragEndEvent.h"
23
- #import "events/OnMountEvent.h"
24
- #import "events/OnPositionChangeEvent.h"
25
- #import "events/OnSizeChangeEvent.h"
26
- #import "events/OnWillDismissEvent.h"
27
- #import "events/OnWillPresentEvent.h"
28
- #import "utils/LayoutUtil.h"
18
+ #import "core/RNScreensEventObserver.h"
19
+ #import "events/TrueSheetDragEvents.h"
20
+ #import "events/TrueSheetFocusEvents.h"
21
+ #import "events/TrueSheetLifecycleEvents.h"
22
+ #import "events/TrueSheetStateEvents.h"
29
23
  #import "utils/WindowUtil.h"
30
24
 
31
- #import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
32
25
  #import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
33
26
  #import <react/renderer/components/TrueSheetSpec/Props.h>
34
27
  #import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
28
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetInsets.h>
29
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
30
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h>
31
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
35
32
 
36
33
  #import <React/RCTConversions.h>
37
34
  #import <React/RCTFabricComponentsPlugins.h>
35
+ #import <React/RCTLog.h>
38
36
  #import <React/RCTSurfaceTouchHandler.h>
39
37
  #import <React/RCTUtils.h>
38
+ #import <react/renderer/core/State.h>
40
39
 
41
40
  using namespace facebook::react;
42
41
 
43
- @interface TrueSheetView () <TrueSheetViewControllerDelegate, TrueSheetContainerViewDelegate>
42
+ @interface TrueSheetView () <TrueSheetViewControllerDelegate,
43
+ TrueSheetContainerViewDelegate,
44
+ RNScreensEventObserverDelegate>
44
45
  @end
45
46
 
46
47
  @implementation TrueSheetView {
47
48
  TrueSheetContainerView *_containerView;
48
49
  TrueSheetViewController *_controller;
49
50
  RCTSurfaceTouchHandler *_touchHandler;
51
+ TrueSheetViewShadowNode::ConcreteState::Shared _state;
52
+ UIView *_snapshotView;
53
+ CGSize _lastStateSize;
50
54
  NSInteger _initialDetentIndex;
55
+ TrueSheetViewInsetAdjustment _insetAdjustment;
56
+ ScrollableOptions *_scrollableOptions;
57
+ NSInteger _scrollableHandle;
51
58
  BOOL _initialDetentAnimated;
59
+ BOOL _isSheetUpdatePending;
60
+ BOOL _pendingLayoutUpdate;
61
+ BOOL _didInitiallyPresent;
62
+ BOOL _dismissedByNavigation;
63
+ BOOL _pendingNavigationRepresent;
64
+ BOOL _pendingMountEvent;
65
+ BOOL _pendingSizeChange;
66
+ BOOL _pendingPropsUpdate;
67
+ NSArray *_pendingDetents;
68
+ RNScreensEventObserver *_screensEventObserver;
52
69
  }
53
70
 
71
+ #pragma mark - Initialization
72
+
54
73
  - (instancetype)initWithFrame:(CGRect)frame {
55
74
  if (self = [super initWithFrame:frame]) {
56
75
  static const auto defaultProps = std::make_shared<const TrueSheetViewProps>();
57
76
  _props = defaultProps;
58
77
 
59
- // Initialize controller - persists across container lifecycle
78
+ self.hidden = YES;
79
+ self.userInteractionEnabled = NO;
80
+
60
81
  _controller = [[TrueSheetViewController alloc] init];
61
82
  _controller.delegate = self;
62
83
 
63
- // Initialize touch handler - will be attached to container when mounted
64
84
  _touchHandler = [[RCTSurfaceTouchHandler alloc] init];
65
-
66
85
  _containerView = nil;
67
-
86
+ _snapshotView = nil;
87
+ _lastStateSize = CGSizeZero;
68
88
  _initialDetentIndex = -1;
69
89
  _initialDetentAnimated = YES;
90
+ _isSheetUpdatePending = NO;
91
+
92
+ _screensEventObserver = [[RNScreensEventObserver alloc] init];
93
+ _screensEventObserver.delegate = self;
70
94
  }
71
95
  return self;
72
96
  }
@@ -74,29 +98,109 @@ using namespace facebook::react;
74
98
  - (void)didMoveToWindow {
75
99
  [super didMoveToWindow];
76
100
 
77
- if (!self.window) {
101
+ if (!self.window)
78
102
  return;
79
- }
80
103
 
81
- // Register this view with the TurboModule when added to window
82
- // This ensures the tag is properly set by the framework
83
104
  if (self.tag > 0) {
84
105
  [TrueSheetModule registerView:self withTag:@(self.tag)];
85
106
  }
107
+
108
+ if (_pendingNavigationRepresent && !_controller.isPresented) {
109
+ _pendingNavigationRepresent = NO;
110
+ [self presentAtIndex:_controller.activeDetentIndex animated:YES completion:nil];
111
+ return;
112
+ }
113
+
114
+ [self presentInitialIfNeeded];
115
+ }
116
+
117
+ // JS holds initialDetentIndex back one commit so effect-driven content (e.g. a
118
+ // navigation footer set via setOptions) commits together with it — by the time
119
+ // the prop lands the tree is final and the sheet presents at its final height.
120
+ // Called from didMoveToWindow (re-attach) and finalizeUpdates (the prop
121
+ // arriving after attach, which didMoveToWindow won't re-fire for).
122
+ - (void)presentInitialIfNeeded {
123
+ if (_initialDetentIndex < 0 || _didInitiallyPresent)
124
+ return;
125
+
126
+ UIViewController *vc = [self findPresentingViewController];
127
+
128
+ // Only present if the view controller is in the same window
129
+ if (!vc || vc.view.window != self.window) {
130
+ // Animate next time when sheet finally moves to the correct window
131
+ _initialDetentAnimated = YES;
132
+ return;
133
+ }
134
+
135
+ // A sheet still animating out of the presenter (e.g. our own controller,
136
+ // recycled mid-dismissal) blocks presentation — retry once its transition ends.
137
+ UIViewController *dismissing = vc.presentedViewController;
138
+ if (dismissing && dismissing.isBeingDismissed) {
139
+ _initialDetentAnimated = YES;
140
+
141
+ __weak __typeof(self) weakSelf = self;
142
+ void (^retry)(void) = ^{
143
+ dispatch_async(dispatch_get_main_queue(), ^{
144
+ [weakSelf presentInitialIfNeeded];
145
+ });
146
+ };
147
+
148
+ id<UIViewControllerTransitionCoordinator> coordinator = dismissing.transitionCoordinator;
149
+ if (coordinator) {
150
+ [coordinator animateAlongsideTransition:nil
151
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
152
+ retry();
153
+ }];
154
+ } else {
155
+ retry();
156
+ }
157
+ return;
158
+ }
159
+
160
+ _didInitiallyPresent = YES;
161
+
162
+ // Deferred a runloop turn so sibling mounts from the current transaction
163
+ // (e.g. a footer committed alongside the prop) land before measuring.
164
+ __weak __typeof(self) weakSelf = self;
165
+ dispatch_async(dispatch_get_main_queue(), ^{
166
+ __typeof(self) strongSelf = weakSelf;
167
+ if (!strongSelf || strongSelf->_controller.isPresented || strongSelf->_controller.isBeingPresented)
168
+ return;
169
+
170
+ [strongSelf presentAtIndex:strongSelf->_initialDetentIndex
171
+ animated:strongSelf->_initialDetentAnimated
172
+ completion:^(BOOL success, NSError *_Nullable error) {
173
+ // Present can fail if the view detached during this turn —
174
+ // reset so the next attach/prop update retries.
175
+ if (!success) {
176
+ strongSelf->_didInitiallyPresent = NO;
177
+ }
178
+ }];
179
+ });
86
180
  }
87
181
 
88
182
  - (void)dealloc {
89
- // Dismiss controller if presented
183
+ [_screensEventObserver stopObserving];
184
+ _screensEventObserver = nil;
185
+
90
186
  if (_controller && _controller.presentingViewController) {
91
- [_controller dismissViewControllerAnimated:NO completion:nil];
187
+ // Find the root presenting controller to dismiss the entire stack
188
+ UIViewController *root = _controller.presentingViewController;
189
+ while (root.presentingViewController != nil) {
190
+ root = root.presentingViewController;
191
+ }
192
+ [root dismissViewControllerAnimated:YES completion:nil];
92
193
  }
93
194
 
94
- // Clean up controller
195
+ _didInitiallyPresent = NO;
196
+ _dismissedByNavigation = NO;
197
+ _pendingNavigationRepresent = NO;
198
+
95
199
  _controller.delegate = nil;
96
200
  _controller = nil;
97
201
 
98
- // Unregister this view from the TurboModule
99
- [TrueSheetModule unregisterViewWithTag:@(self.tag)];
202
+ [_snapshotView removeFromSuperview];
203
+ _snapshotView = nil;
100
204
  }
101
205
 
102
206
  #pragma mark - RCTComponentViewProtocol
@@ -105,319 +209,775 @@ using namespace facebook::react;
105
209
  return concreteComponentDescriptorProvider<TrueSheetViewComponentDescriptor>();
106
210
  }
107
211
 
212
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
213
+ [super updateProps:props oldProps:oldProps];
214
+
215
+ const auto &newProps = *std::static_pointer_cast<TrueSheetViewProps const>(props);
216
+
217
+ // Detents (-1 represents "auto")
218
+ NSMutableArray *detents = [NSMutableArray new];
219
+ for (const auto &detent : newProps.detents) {
220
+ [detents addObject:@(detent)];
221
+ }
222
+
223
+ if (oldProps) {
224
+ const auto &prevProps = *std::static_pointer_cast<TrueSheetViewProps const>(oldProps);
225
+ if (newProps.detents != prevProps.detents || newProps.insetAdjustment != prevProps.insetAdjustment) {
226
+ _pendingLayoutUpdate = YES;
227
+ }
228
+ }
229
+
230
+ if (_controller.isBeingPresented) {
231
+ _pendingDetents = detents;
232
+ } else {
233
+ _controller.detents = detents;
234
+ }
235
+
236
+ // Background color
237
+ _controller.backgroundColor = RCTUIColorFromSharedColor(newProps.backgroundColor);
238
+
239
+ // Blur tint
240
+ _controller.backgroundBlur = newProps.backgroundBlur;
241
+
242
+ // Blur options
243
+ const auto &blurOpts = newProps.blurOptions;
244
+ _controller.blurIntensity = blurOpts.intensity >= 0 ? @(blurOpts.intensity) : nil;
245
+ _controller.blurInteraction = blurOpts.interaction;
246
+
247
+ // Corner radius
248
+ _controller.cornerRadius = newProps.cornerRadius < 0 ? nil : @(newProps.cornerRadius);
249
+
250
+ // Content height
251
+ _controller.maxContentHeight = newProps.maxContentHeight != 0.0 ? @(newProps.maxContentHeight) : nil;
252
+
253
+ // Content width
254
+ _controller.maxContentWidth = newProps.maxContentWidth != 0.0 ? @(newProps.maxContentWidth) : nil;
255
+
256
+ // Anchor
257
+ _controller.anchor = newProps.anchor;
258
+
259
+ _controller.grabber = newProps.grabber;
260
+
261
+ // Grabber options - check if any non-default values are set
262
+ const auto &grabberOpts = newProps.grabberOptions;
263
+ UIColor *grabberColor = RCTUIColorFromSharedColor(grabberOpts.color);
264
+ BOOL hasGrabberOptions = grabberOpts.width > 0 || grabberOpts.height > 0 || grabberOpts.topMargin > 0 ||
265
+ grabberOpts.cornerRadius >= 0 || grabberColor != nil || !grabberOpts.adaptive;
266
+
267
+ if (hasGrabberOptions) {
268
+ GrabberOptions *options = [[GrabberOptions alloc] init];
269
+ if (grabberOpts.width > 0)
270
+ options.width = @(grabberOpts.width);
271
+ if (grabberOpts.height > 0)
272
+ options.height = @(grabberOpts.height);
273
+ if (grabberOpts.topMargin > 0)
274
+ options.topMargin = @(grabberOpts.topMargin);
275
+ if (grabberOpts.cornerRadius >= 0)
276
+ options.cornerRadius = @(grabberOpts.cornerRadius);
277
+ if (grabberColor)
278
+ options.color = grabberColor;
279
+ options.adaptive = grabberOpts.adaptive;
280
+ _controller.grabberOptions = options;
281
+ } else {
282
+ _controller.grabberOptions = nil;
283
+ }
284
+
285
+ // Accessibility options - applied to the custom grabber
286
+ _controller.accessibilityOptions = newProps.accessibilityOptions;
287
+
288
+ _controller.presentation = newProps.presentation;
289
+ _controller.dismissible = newProps.dismissible;
290
+ _controller.draggable = newProps.draggable;
291
+ _controller.dimmed = newProps.dimmed;
292
+
293
+ if (newProps.dimmedDetentIndex >= 0) {
294
+ _controller.dimmedDetentIndex = @(newProps.dimmedDetentIndex);
295
+ }
296
+
297
+ _initialDetentIndex = newProps.initialDetentIndex;
298
+ _initialDetentAnimated = newProps.initialDetentAnimated;
299
+
300
+ _scrollableHandle = newProps.scrollableHandle;
301
+
302
+ const auto &scrollableOpts = newProps.scrollableOptions;
303
+ BOOL scrollingExpandsSheet = scrollableOpts.scrollingExpandsSheet;
304
+ BOOL contentInsetAdjustment = scrollableOpts.contentInsetAdjustmentBehavior;
305
+ auto topEdgeEffect = scrollableOpts.topScrollEdgeEffect;
306
+ auto bottomEdgeEffect = scrollableOpts.bottomScrollEdgeEffect;
307
+ BOOL hasScrollableOptions = scrollableOpts.keyboardScrollOffset > 0 || scrollableOpts.keyboardOffset != 0 ||
308
+ !scrollingExpandsSheet || !contentInsetAdjustment ||
309
+ topEdgeEffect != TrueSheetViewTopScrollEdgeEffect::Hidden ||
310
+ bottomEdgeEffect != TrueSheetViewBottomScrollEdgeEffect::Hidden;
311
+
312
+ if (hasScrollableOptions) {
313
+ ScrollableOptions *options = [[ScrollableOptions alloc] init];
314
+ options.contentInsetAdjustment = contentInsetAdjustment;
315
+ options.keyboardScrollOffset = scrollableOpts.keyboardScrollOffset;
316
+ options.keyboardOffset = scrollableOpts.keyboardOffset;
317
+ options.scrollingExpandsSheet = scrollingExpandsSheet;
318
+ options.topScrollEdgeEffect = topEdgeEffect;
319
+ options.bottomScrollEdgeEffect = bottomEdgeEffect;
320
+ _scrollableOptions = options;
321
+ } else {
322
+ _scrollableOptions = nil;
323
+ }
324
+
325
+ _controller.scrollingExpandsSheet = scrollingExpandsSheet;
326
+
327
+ _controller.absoluteHeader = newProps.headerOptions.position == TrueSheetViewPosition::Absolute;
328
+ _controller.absoluteFooter = newProps.footerOptions.footerPosition == TrueSheetViewFooterPosition::Absolute;
329
+
330
+ CGFloat footerKeyboardOffset = newProps.footerOptions.keyboardOffset;
331
+ if (_controller.footerKeyboardOffset != footerKeyboardOffset) {
332
+ _controller.footerKeyboardOffset = footerKeyboardOffset;
333
+ [_containerView updateFooterKeyboardOffset];
334
+ }
335
+
336
+ _insetAdjustment = newProps.insetAdjustment;
337
+ _controller.insetAdjustment = _insetAdjustment;
338
+
339
+ [self setupScrollable];
340
+ }
341
+
342
+ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
343
+ _state = std::static_pointer_cast<TrueSheetViewShadowNode::ConcreteState const>(state);
344
+
345
+ if (_controller && !_controller.isStackedBehindChild) {
346
+ CGSize size = _controller.view.frame.size;
347
+ if (size.width < 1 || size.height < 1) {
348
+ // Pre-present the controller has no layout yet. Seed with screen
349
+ // dimensions so content (e.g. a FlatList viewport) can lay out and
350
+ // measure before the sheet presents (parity with Android).
351
+ UIWindow *window = [WindowUtil keyWindow];
352
+ size = window ? window.bounds.size : UIScreen.mainScreen.bounds.size;
353
+ }
354
+ [self viewControllerDidChangeSize:size];
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Updates Fabric state with container dimensions for Yoga layout.
360
+ */
361
+ - (void)updateStateWithSize:(CGSize)size {
362
+ if (!_state)
363
+ return;
364
+
365
+ if (fabs(size.width - _lastStateSize.width) < 0.5 && fabs(size.height - _lastStateSize.height) < 0.5)
366
+ return;
367
+
368
+ _lastStateSize = size;
369
+
370
+ auto stateData = _state->getData();
371
+ stateData.containerWidth = static_cast<float>(size.width);
372
+ stateData.containerHeight = static_cast<float>(size.height);
373
+
374
+ // RN 0.82+ processes immediate state updates in the same layout pass, so Yoga
375
+ // resizes the container synchronously with the sheet (e.g. inside UIKit's
376
+ // animation block during detent transitions).
377
+ _state->updateState(std::move(stateData), facebook::react::EventQueue::UpdateMode::unstable_Immediate);
378
+ }
379
+
380
+ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
381
+ [super finalizeUpdates:updateMask];
382
+
383
+ // Emit pending mount event now that eventEmitter is available
384
+ if (_pendingMountEvent && (updateMask & RNComponentViewUpdateMaskEventEmitter)) {
385
+ _pendingMountEvent = NO;
386
+ [TrueSheetLifecycleEvents emitMount:_eventEmitter];
387
+ }
388
+
389
+ if (!(updateMask & RNComponentViewUpdateMaskProps) || !_controller)
390
+ return;
391
+
392
+ [self setupScrollable];
393
+
394
+ if (_controller.isPresented) {
395
+ [self applySheetPropsUpdate];
396
+ } else if (_controller.isBeingPresented) {
397
+ _pendingPropsUpdate = YES;
398
+ } else if (_initialDetentIndex >= 0) {
399
+ _pendingLayoutUpdate = NO;
400
+ if (self.window) {
401
+ [self presentInitialIfNeeded];
402
+ }
403
+ }
404
+ }
405
+
406
+ - (void)prepareForRecycle {
407
+ [super prepareForRecycle];
408
+
409
+ // Pooled mid-dismissal: the next mount can reuse this host — and its
410
+ // controller — while the previous sheet is still animating out. Detach until
411
+ // that transition ends so its tail (didBlur, didDismiss, position) can't reach
412
+ // the new mount, then hand the controller back and let auto-present retry.
413
+ if (_controller.isBeingDismissed) {
414
+ _controller.delegate = nil;
415
+
416
+ __weak __typeof(self) weakSelf = self;
417
+ void (^reattach)(void) = ^{
418
+ dispatch_async(dispatch_get_main_queue(), ^{
419
+ __typeof(self) strongSelf = weakSelf;
420
+ if (!strongSelf)
421
+ return;
422
+
423
+ strongSelf->_controller.delegate = strongSelf;
424
+ strongSelf->_controller.activeDetentIndex = -1;
425
+ if (strongSelf.window) {
426
+ [strongSelf presentInitialIfNeeded];
427
+ }
428
+ });
429
+ };
430
+
431
+ id<UIViewControllerTransitionCoordinator> coordinator = _controller.transitionCoordinator;
432
+ if (coordinator) {
433
+ [coordinator animateAlongsideTransition:nil
434
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
435
+ reattach();
436
+ }];
437
+ } else {
438
+ reattach();
439
+ }
440
+ }
441
+
442
+ _lastStateSize = CGSizeZero;
443
+ _didInitiallyPresent = NO;
444
+ _dismissedByNavigation = NO;
445
+ _pendingNavigationRepresent = NO;
446
+ }
447
+
448
+ #pragma mark - Child Component Mounting
449
+
450
+ - (void)cleanupContainerView {
451
+ if (_containerView == nil)
452
+ return;
453
+
454
+ _containerView.delegate = nil;
455
+ [_touchHandler detachFromView:_containerView];
456
+ [_containerView removeFromSuperview];
457
+
458
+ _containerView = nil;
459
+ }
460
+
461
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
462
+ if (![childComponentView isKindOfClass:[TrueSheetContainerView class]])
463
+ return;
464
+
465
+ if (_containerView != nil && _containerView != childComponentView) {
466
+ RCTLogWarn(@"TrueSheet: Sheet can only have one container component.");
467
+ [self cleanupContainerView];
468
+ }
469
+
470
+ if (_snapshotView) {
471
+ [_snapshotView removeFromSuperview];
472
+ _snapshotView = nil;
473
+ }
474
+
475
+ _containerView = (TrueSheetContainerView *)childComponentView;
476
+ _containerView.delegate = self;
477
+
478
+ [_touchHandler attachToView:_containerView];
479
+ [_controller.view addSubview:_containerView];
480
+ [_controller.view bringSubviewToFront:_containerView];
481
+ _controller.accessibilityContentView = _containerView;
482
+ [_controller setupAccessibilityContainer];
483
+
484
+ CGFloat contentHeight = [_containerView contentHeight];
485
+ if (contentHeight > 0) {
486
+ _controller.contentHeight = @(contentHeight);
487
+ }
488
+
489
+ CGFloat headerHeight = [_containerView headerHeight];
490
+ if (headerHeight > 0) {
491
+ _controller.headerHeight = @(headerHeight);
492
+ }
493
+
494
+ if ([_containerView footerHeight] > 0) {
495
+ [self syncFooterMetrics];
496
+ }
497
+
498
+ CGFloat peekContentHeight = [_containerView peekContentHeight];
499
+ if (peekContentHeight > 0) {
500
+ _controller.peekContentHeight = @(peekContentHeight);
501
+ }
502
+
503
+ if (_eventEmitter) {
504
+ [TrueSheetLifecycleEvents emitMount:_eventEmitter];
505
+ } else {
506
+ _pendingMountEvent = YES;
507
+ }
508
+ }
509
+
510
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
511
+ if (![childComponentView isKindOfClass:[TrueSheetContainerView class]])
512
+ return;
513
+
514
+ if (_containerView == nil || _containerView != childComponentView)
515
+ return;
516
+
517
+ if (_controller.isPresented) {
518
+ UIView *superView = _containerView.superview;
519
+ UIView *snapshot = [_containerView snapshotViewAfterScreenUpdates:NO];
520
+ if (snapshot) {
521
+ snapshot.frame = _containerView.frame;
522
+ [superView insertSubview:snapshot belowSubview:_containerView];
523
+ _snapshotView = snapshot;
524
+ }
525
+ }
526
+
527
+ [self cleanupContainerView];
528
+ }
529
+
108
530
  #pragma mark - TurboModule Methods
109
531
 
110
532
  - (void)presentAtIndex:(NSInteger)index
111
533
  animated:(BOOL)animated
112
534
  completion:(nullable TrueSheetCompletionBlock)completion {
113
- if (_controller.isPresented) {
114
- [_controller.sheetPresentationController animateChanges:^{
115
- [self->_controller setupActiveDetentWithIndex:index];
116
- }];
117
-
535
+ if (_controller.isBeingPresented || _controller.isPresented) {
536
+ RCTLogWarn(@"TrueSheet: sheet is already presented. Use resize() to change detent.");
118
537
  if (completion) {
119
538
  completion(YES, nil);
120
539
  }
121
540
  return;
122
541
  }
123
542
 
124
- UIViewController *presentingViewController = [self findPresentingViewController];
543
+ // Reset navigation dismiss flag when presenting (handles view recycling edge cases)
544
+ _dismissedByNavigation = NO;
125
545
 
546
+ UIViewController *presentingViewController = [self findPresentingViewController];
126
547
  if (!presentingViewController) {
127
548
  NSError *error = [NSError errorWithDomain:@"com.lodev09.TrueSheet"
128
549
  code:1001
129
550
  userInfo:@{NSLocalizedDescriptionKey : @"No presenting view controller found"}];
130
-
131
551
  if (completion) {
132
552
  completion(NO, error);
133
553
  }
134
554
  return;
135
555
  }
136
556
 
137
- // Setup our sheet properties
557
+ [_controller setupAnchorViewInView:presentingViewController.view];
558
+ [_controller setupSheetSizing];
138
559
  [_controller setupSheetProps];
139
- [_controller setupSheetDetents];
140
560
 
141
- // Set to the given detent index
561
+ // Detect before measuring so a first-time detection doesn't shift sizes mid-animation.
562
+ [self setupScrollable];
563
+
564
+ // Measure synchronously so the presentation starts at the right height —
565
+ // async size reports would otherwise land mid-animation and force a
566
+ // detent retarget that snaps the presentation stack.
567
+ if (_containerView) {
568
+ _controller.contentHeight = @([_containerView contentHeight]);
569
+ _controller.headerHeight = @([_containerView headerHeight]);
570
+ [self syncFooterMetrics];
571
+ _controller.peekContentHeight = @([_containerView peekContentHeight]);
572
+ }
573
+ _pendingSizeChange = NO;
574
+
575
+ [self refreshFooterBottomInset];
576
+ [_controller setupSheetDetents];
142
577
  [_controller setupActiveDetentWithIndex:index];
143
578
 
144
- // Present our sheet
145
- [presentingViewController presentViewController:self->_controller
579
+ [_screensEventObserver capturePresenterScreenFromView:self];
580
+ [_screensEventObserver startObservingWithState:_state.get()->getData()];
581
+
582
+ UIWindow *window = presentingViewController.view.window;
583
+ [presentingViewController presentViewController:_controller
146
584
  animated:animated
147
585
  completion:^{
148
- // Call completion handler
586
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
149
587
  if (completion) {
150
588
  completion(YES, nil);
151
589
  }
152
590
  }];
591
+
592
+ // Presenting adds the sheet's transition view on top of the window once the
593
+ // transition starts — re-front overlays alongside it so the first frame is
594
+ // already ordered (and again on completion in case the transition was queued)
595
+ id<UIViewControllerTransitionCoordinator> coordinator = _controller.transitionCoordinator;
596
+ if (coordinator) {
597
+ [coordinator
598
+ animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
599
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
600
+ }
601
+ completion:nil];
602
+ } else {
603
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
604
+ }
153
605
  }
154
606
 
155
- - (void)dismissAnimated:(BOOL)animated completion:(nullable TrueSheetCompletionBlock)completion {
607
+ - (void)resizeToIndex:(NSInteger)index completion:(nullable TrueSheetCompletionBlock)completion {
156
608
  if (!_controller.isPresented) {
609
+ RCTLogWarn(@"TrueSheet: Cannot resize. Sheet is not presented.");
157
610
  if (completion) {
158
611
  completion(YES, nil);
159
612
  }
160
613
  return;
161
614
  }
162
615
 
163
- [_controller dismissViewControllerAnimated:animated
164
- completion:^{
165
- if (completion) {
166
- completion(YES, nil);
167
- }
168
- }];
169
- }
616
+ [_controller.sheetPresentationController animateChanges:^{
617
+ [self->_controller resizeToDetentIndex:index];
618
+ }];
170
619
 
171
- - (void)resizeToIndex:(NSInteger)index {
172
- if (_controller.isPresented) {
173
- [_controller.sheetPresentationController animateChanges:^{
174
- [self->_controller setupActiveDetentWithIndex:index];
175
- }];
620
+ if (completion) {
621
+ completion(YES, nil);
176
622
  }
177
623
  }
178
624
 
179
- - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
180
- [super updateProps:props oldProps:oldProps];
625
+ - (TrueSheetViewController *)viewController {
626
+ return _controller;
627
+ }
181
628
 
182
- const auto &newProps = *std::static_pointer_cast<TrueSheetViewProps const>(props);
629
+ - (void)emitDismissedPosition {
630
+ [TrueSheetStateEvents emitPositionChange:_eventEmitter
631
+ index:-1
632
+ position:_controller.screenHeight
633
+ detent:0
634
+ realtime:NO];
635
+ }
183
636
 
184
- // Update detents - pass numbers directly (-1 represents "auto")
185
- NSMutableArray *detents = [NSMutableArray new];
186
- for (const auto &detent : newProps.detents) {
187
- [detents addObject:@(detent)];
637
+ - (void)dismissAnimated:(BOOL)animated completion:(nullable TrueSheetCompletionBlock)completion {
638
+ if (_controller.isBeingDismissed || !_controller.isPresented) {
639
+ RCTLogWarn(@"TrueSheet: sheet is already dismissed. No need to dismiss it again.");
640
+
641
+ if (completion) {
642
+ completion(YES, nil);
643
+ }
644
+ return;
188
645
  }
189
646
 
190
- _controller.detents = detents;
647
+ // Dismiss from the presenting view controller to dismiss this sheet and all its children
648
+ UIViewController *presenter = _controller.presentingViewController;
649
+ [presenter dismissViewControllerAnimated:animated
650
+ completion:^{
651
+ if (completion) {
652
+ completion(YES, nil);
653
+ }
654
+ }];
655
+ }
191
656
 
192
- // Update background color - always set it, even if 0 (which could be a valid color like black)
193
- UIColor *color = RCTUIColorFromSharedColor(SharedColor(newProps.background));
194
- _controller.backgroundColor = color;
657
+ - (void)dismissStackAnimated:(BOOL)animated completion:(nullable TrueSheetCompletionBlock)completion {
658
+ if (_controller.isBeingDismissed || !_controller.isPresented) {
659
+ RCTLogWarn(@"TrueSheet: sheet is already dismissed. No need to dismiss it again.");
195
660
 
196
- // Update blur tint - always set it to clear when removed
197
- _controller.blurTint = !newProps.blurTint.empty() ? RCTNSStringFromString(newProps.blurTint) : nil;
661
+ if (completion) {
662
+ completion(YES, nil);
663
+ }
664
+ return;
665
+ }
198
666
 
199
- // Update corner radius
200
- if (newProps.cornerRadius < 0) {
201
- _controller.cornerRadius = nil;
202
- } else {
203
- _controller.cornerRadius = @(newProps.cornerRadius);
667
+ // Only dismiss presented children, not this sheet itself
668
+ if (!_controller.presentedViewController) {
669
+ if (completion) {
670
+ completion(YES, nil);
671
+ }
672
+ return;
204
673
  }
205
674
 
206
- // Update max height
207
- if (newProps.maxHeight != 0.0) {
208
- _controller.maxHeight = @(newProps.maxHeight);
675
+ // Calling dismiss on _controller dismisses all VCs presented on top of it, but keeps _controller presented
676
+ [_controller dismissViewControllerAnimated:animated
677
+ completion:^{
678
+ if (completion) {
679
+ completion(YES, nil);
680
+ }
681
+ }];
682
+ }
683
+
684
+ #pragma mark - TrueSheetContainerViewDelegate
685
+
686
+ /**
687
+ * Debounced sheet update to handle rapid content/header size changes.
688
+ */
689
+ - (void)setupSheetDetentsForSizeChange {
690
+ // Keep the footer's absorbed inset in sync with the latest measured heights
691
+ // even before presenting, so the sheet presents at its final size.
692
+ [self refreshFooterBottomInset];
693
+
694
+ // Retargeting the in-flight presentation makes UIKit snap the whole stack
695
+ // (including the sheet behind) instead of animating. Defer to didPresent —
696
+ // presentAtIndex measured synchronously, so this is usually a no-op.
697
+ if (_controller.isBeingPresented) {
698
+ _pendingSizeChange = YES;
699
+ return;
209
700
  }
210
701
 
211
- // Update grabber
212
- _controller.grabber = newProps.grabber;
702
+ // Not presented: presentAtIndex measures at present time.
703
+ // Dismissing: the sheet is going away — touching detents perturbs the
704
+ // presentation stack, visibly glitching the sheet behind.
705
+ if (!_controller.isPresented || _controller.isBeingDismissed)
706
+ return;
213
707
 
214
- // Update dismissible
215
- _controller.modalInPresentation = !newProps.dismissible;
708
+ if (_isSheetUpdatePending)
709
+ return;
216
710
 
217
- // Update dimmed
218
- _controller.dimmed = newProps.dimmed;
711
+ _isSheetUpdatePending = YES;
219
712
 
220
- // Update dimmedIndex
221
- if (newProps.dimmedIndex >= 0) {
222
- _controller.dimmedIndex = @(newProps.dimmedIndex);
223
- }
713
+ dispatch_async(dispatch_get_main_queue(), ^{
714
+ self->_isSheetUpdatePending = NO;
715
+ if (!self->_containerView || self->_controller.isBeingDismissed)
716
+ return;
224
717
 
225
- // Store initial presentation settings
226
- _initialDetentIndex = newProps.initialDetentIndex;
227
- _initialDetentAnimated = newProps.initialDetentAnimated;
718
+ // Refresh here (not just on peek size events) since the peek's offset
719
+ // within the content can change without its own size changing.
720
+ self->_controller.peekContentHeight = @([self->_containerView peekContentHeight]);
721
+ [self refreshFooterBottomInset];
722
+ [self->_controller setupSheetDetentsForSizeChange];
723
+ });
228
724
  }
229
725
 
230
- - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
231
- [super finalizeUpdates:updateMask];
726
+ - (void)containerViewContentDidChangeSize:(CGSize)newSize {
727
+ _controller.contentHeight = @(newSize.height);
728
+ [self setupSheetDetentsForSizeChange];
729
+ }
232
730
 
233
- // Apply controller updates after all props and children are updated
234
- if (updateMask & RNComponentViewUpdateMaskProps) {
235
- // Apply changes to presented sheet if needed
236
- if (_controller.isPresented) {
237
- [_controller.sheetPresentationController animateChanges:^{
238
- [self->_controller setupSheetDetents];
239
- [self->_controller applyActiveDetent];
240
- }];
241
- } else {
242
- // Handle initial presentation
243
- if (_initialDetentIndex >= 0 && !_controller.isPresented) {
244
- [self presentAtIndex:_initialDetentIndex animated:_initialDetentAnimated completion:nil];
245
- }
246
- }
247
- }
731
+ - (void)containerViewHeaderDidChangeSize:(CGSize)newSize {
732
+ _controller.headerHeight = @(newSize.height);
733
+ [self setupSheetDetentsForSizeChange];
248
734
  }
249
735
 
250
- - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
251
- // Check if it's a container view
252
- if ([childComponentView isKindOfClass:[TrueSheetContainerView class]]) {
253
- if (_containerView != nil) {
254
- NSLog(@"TrueSheet: Sheet can only have one container component.");
255
- return;
256
- }
736
+ - (void)containerViewFooterDidChangeSize:(CGSize)newSize {
737
+ [self syncFooterMetrics];
738
+ [self setupSheetDetentsForSizeChange];
739
+ [_controller setupAccessibilityContainer];
740
+ }
257
741
 
258
- _containerView = (TrueSheetContainerView *)childComponentView;
742
+ - (void)containerViewPeekDidChangeSize:(CGSize)newSize {
743
+ [self setupSheetDetentsForSizeChange];
744
+ }
259
745
 
260
- // Set this view as the container's delegate
261
- _containerView.delegate = self;
746
+ // When the ScrollView changes (e.g. conditional remount), re-detect the new ScrollView.
747
+ - (void)containerViewScrollViewDidChange {
748
+ [self setupScrollable];
749
+ }
262
750
 
263
- // Attach touch handler to container for touch event handling
264
- [_touchHandler attachToView:_containerView];
751
+ #pragma mark - TrueSheetViewControllerDelegate
265
752
 
266
- // Add to parent view hierarchy
267
- [_controller.view addSubview:_containerView];
753
+ - (void)viewControllerWillPresentAtIndex:(NSInteger)index position:(CGFloat)position detent:(CGFloat)detent {
754
+ _controller.activeDetentIndex = index;
755
+ [TrueSheetLifecycleEvents emitWillPresent:_eventEmitter index:index position:position detent:detent];
756
+ }
268
757
 
269
- // Pin container to fill the entire parent view
270
- [LayoutUtil pinView:_containerView toParentView:_controller.view edges:UIRectEdgeAll];
758
+ - (void)viewControllerDidPresentAtIndex:(NSInteger)index position:(CGFloat)position detent:(CGFloat)detent {
759
+ [_containerView setupKeyboardObserverWithViewController:_controller];
760
+ [TrueSheetLifecycleEvents emitDidPresent:_eventEmitter index:index position:position detent:detent];
271
761
 
272
- // Ensure container is above background view
273
- [_controller.view bringSubviewToFront:_containerView];
762
+ if (_pendingPropsUpdate) {
763
+ _pendingPropsUpdate = NO;
764
+ [self applySheetPropsUpdate];
765
+ }
274
766
 
275
- // Get initial content height from container
276
- CGFloat contentHeight = [_containerView contentHeight];
277
- if (contentHeight > 0) {
278
- _controller.contentHeight = @(contentHeight);
279
- }
767
+ if (_pendingSizeChange) {
768
+ _pendingSizeChange = NO;
769
+ [self setupSheetDetentsForSizeChange];
770
+ }
771
+ }
280
772
 
281
- // Emit onMount event when container is mounted
282
- [OnMountEvent emit:_eventEmitter];
773
+ - (void)viewControllerDidDrag:(UIGestureRecognizerState)state
774
+ index:(NSInteger)index
775
+ position:(CGFloat)position
776
+ detent:(CGFloat)detent {
777
+ switch (state) {
778
+ case UIGestureRecognizerStateBegan:
779
+ [TrueSheetDragEvents emitDragBegin:_eventEmitter index:index position:position detent:detent];
780
+ break;
781
+ case UIGestureRecognizerStateChanged:
782
+ [TrueSheetDragEvents emitDragChange:_eventEmitter index:index position:position detent:detent];
783
+ break;
784
+ case UIGestureRecognizerStateEnded:
785
+ case UIGestureRecognizerStateCancelled:
786
+ [TrueSheetDragEvents emitDragEnd:_eventEmitter index:index position:position detent:detent];
787
+ break;
788
+ default:
789
+ break;
283
790
  }
284
791
  }
285
792
 
286
- - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
287
- if ([childComponentView isKindOfClass:[TrueSheetContainerView class]]) {
288
- _containerView.delegate = nil;
793
+ - (void)viewControllerWillDismiss {
794
+ if (!_dismissedByNavigation) {
795
+ [TrueSheetLifecycleEvents emitWillDismiss:_eventEmitter];
796
+ }
797
+ }
289
798
 
290
- // Detach touch handler
291
- if (_touchHandler) {
292
- [_touchHandler detachFromView:_containerView];
293
- }
799
+ - (void)viewControllerDidAttemptDismiss {
800
+ [TrueSheetLifecycleEvents emitDismissAttempt:_eventEmitter];
801
+ }
294
802
 
295
- // Unpin and remove from view hierarchy
296
- [LayoutUtil unpinView:_containerView];
297
- [_containerView removeFromSuperview];
803
+ - (void)viewControllerDidDismiss {
804
+ [_containerView cleanupKeyboardObserver];
805
+ if (!_dismissedByNavigation) {
806
+ _dismissedByNavigation = NO;
807
+ _pendingNavigationRepresent = NO;
298
808
 
299
- _containerView = nil;
809
+ _controller.activeDetentIndex = -1;
810
+ [TrueSheetLifecycleEvents emitDidDismiss:_eventEmitter];
300
811
  }
301
812
  }
302
813
 
303
- - (void)prepareForRecycle {
304
- [super prepareForRecycle];
305
- [self invalidate];
814
+ - (void)viewControllerDidChangeDetent:(NSInteger)index position:(CGFloat)position detent:(CGFloat)detent {
815
+ if (_controller.activeDetentIndex != index) {
816
+ _controller.activeDetentIndex = index;
817
+ }
818
+ [TrueSheetStateEvents emitDetentChange:_eventEmitter index:index position:position detent:detent];
819
+ }
306
820
 
307
- // Unregister from the registry
308
- // Note: Re-registration will happen automatically when the component is reused
309
- [TrueSheetModule unregisterViewWithTag:@(self.tag)];
821
+ - (void)viewControllerDidChangePosition:(CGFloat)index
822
+ position:(CGFloat)position
823
+ detent:(CGFloat)detent
824
+ realtime:(BOOL)realtime {
825
+ [TrueSheetStateEvents emitPositionChange:_eventEmitter index:index position:position detent:detent realtime:realtime];
310
826
  }
311
827
 
312
- #pragma mark - TrueSheetContainerViewDelegate
828
+ - (void)viewControllerDidChangeSize:(CGSize)size {
829
+ [self updateStateWithSize:size];
830
+ }
313
831
 
314
- - (void)containerViewContentDidChangeSize:(CGSize)newSize {
315
- // Clamp content height to container height to prevent unbounded growth with scrollable content
316
- CGFloat containerHeight = _controller.containerHeight;
317
- CGFloat contentHeight = containerHeight > 0 ? MIN(newSize.height, containerHeight) : newSize.height;
832
+ - (void)viewControllerSafeAreaInsetsDidChange {
833
+ [self refreshFooterBottomInset];
834
+ }
318
835
 
319
- _controller.contentHeight = @(contentHeight);
836
+ - (void)viewControllerWillFocus {
837
+ [TrueSheetFocusEvents emitWillFocus:_eventEmitter];
838
+ }
320
839
 
321
- // Update detents if sheet is already presented
322
- if (_controller.isPresented) {
323
- // Tell controller that we are transitioning from layout changes.
324
- // Controller viewDidLayoutSubviews will handle position notification.
325
- _controller.layoutTransitioning = YES;
840
+ - (void)viewControllerDidFocus {
841
+ [TrueSheetFocusEvents emitDidFocus:_eventEmitter];
842
+ }
326
843
 
327
- [_controller.sheetPresentationController animateChanges:^{
328
- [self->_controller setupSheetDetents];
329
- }];
330
- }
844
+ - (void)viewControllerWillBlur {
845
+ [TrueSheetFocusEvents emitWillBlur:_eventEmitter];
331
846
  }
332
847
 
333
- #pragma mark - TrueSheetViewControllerDelegate
848
+ - (void)viewControllerDidBlur {
849
+ [TrueSheetFocusEvents emitDidBlur:_eventEmitter];
850
+ }
334
851
 
335
- - (void)viewControllerWillPresent {
336
- NSInteger index = [_controller currentDetentIndex];
337
- CGFloat position = _controller.currentPosition;
852
+ #pragma mark - RNScreensEventObserverDelegate
338
853
 
339
- _controller.activeDetentIndex = index;
854
+ - (void)presenterScreenWillDisappear {
855
+ if (_controller.isPresented && !_controller.isBeingDismissed) {
856
+ _dismissedByNavigation = YES;
857
+ [self dismissAnimated:YES completion:nil];
858
+ }
859
+ }
340
860
 
341
- [OnWillPresentEvent emit:_eventEmitter index:index position:position];
861
+ - (void)presenterScreenWillAppear {
862
+ if (_dismissedByNavigation && !_controller.isPresented && !_controller.isBeingPresented) {
863
+ _dismissedByNavigation = NO;
342
864
 
343
- // Emit onChangeSize event to layout our container on JS
344
- CGSize controllerSize = _controller.view.frame.size;
345
- [OnSizeChangeEvent emit:_eventEmitter width:controllerSize.width height:controllerSize.height];
865
+ if (self.window) {
866
+ [self presentAtIndex:_controller.activeDetentIndex animated:YES completion:nil];
867
+ } else {
868
+ _pendingNavigationRepresent = YES;
869
+ }
870
+ }
346
871
  }
347
872
 
348
- - (void)viewControllerDidPresent {
349
- NSInteger index = [_controller currentDetentIndex];
350
- CGFloat position = _controller.currentPosition;
873
+ - (void)presenterInteractiveDismissDidBegin {
874
+ [_controller beginInteractiveDismiss];
875
+ }
351
876
 
352
- [OnDidPresentEvent emit:_eventEmitter index:index position:position];
877
+ - (void)presenterInteractiveDismissDidUpdate:(CGFloat)progress {
878
+ [_controller updateInteractiveDismiss:progress];
353
879
  }
354
880
 
355
- - (void)viewControllerDidDrag:(UIGestureRecognizerState)state index:(NSInteger)index position:(CGFloat)position {
356
- switch (state) {
357
- case UIGestureRecognizerStateBegan:
358
- [OnDragBeginEvent emit:_eventEmitter index:index position:position];
359
- break;
881
+ - (void)presenterInteractiveDismissDidEnd:(BOOL)cancelled duration:(NSTimeInterval)duration {
882
+ if (cancelled) {
883
+ [_controller cancelInteractiveDismissWithDuration:duration];
884
+ return;
885
+ }
360
886
 
361
- case UIGestureRecognizerStateChanged:
362
- [OnDragChangeEvent emit:_eventEmitter index:index position:position];
363
- break;
887
+ _dismissedByNavigation = YES;
888
+ __weak __typeof(self) weakSelf = self;
889
+ [_controller finishInteractiveDismissWithDuration:duration
890
+ completion:^{
891
+ [weakSelf dismissAnimated:NO completion:nil];
892
+ }];
893
+ }
364
894
 
365
- case UIGestureRecognizerStateEnded:
366
- case UIGestureRecognizerStateCancelled:
367
- [OnDragEndEvent emit:_eventEmitter index:index position:position];
368
- break;
895
+ #pragma mark - Private Helpers
369
896
 
370
- default:
371
- break;
372
- }
897
+ - (void)setupScrollable {
898
+ if (!_containerView)
899
+ return;
900
+
901
+ _containerView.scrollableOptions = _scrollableOptions;
902
+ _containerView.scrollableHandle = _scrollableHandle;
903
+ _containerView.contentInsetAdjustment = (_scrollableOptions ? _scrollableOptions.contentInsetAdjustment : YES) &&
904
+ _insetAdjustment == TrueSheetViewInsetAdjustment::Automatic;
905
+ [self refreshFooterBottomInset];
906
+ [_containerView setupScrollable];
373
907
  }
374
908
 
375
- - (void)viewControllerWillDismiss {
376
- [OnWillDismissEvent emit:_eventEmitter];
909
+ // footerHeight and appliedFooterBottomInset must stay paired — detent math
910
+ // subtracts exactly the inset baked into the measured footer height (see
911
+ // autoDetentBottomInset/peekDetentBottomInset).
912
+ - (void)syncFooterMetrics {
913
+ _controller.footerHeight = @([_containerView footerHeight]);
914
+ _controller.appliedFooterBottomInset = [_containerView footerAppliedBottomInset];
377
915
  }
378
916
 
379
- - (void)viewControllerDidDismiss {
380
- _controller.activeDetentIndex = -1;
917
+ // Recomputes the inset the footer absorbs and pushes it down. The inset
918
+ // depends on measured heights and detents (see maxNaturalDetentHeight), so
919
+ // this runs on size and detent changes — not just prop updates.
920
+ - (void)refreshFooterBottomInset {
921
+ if (!_containerView)
922
+ return;
381
923
 
382
- [OnDidDismissEvent emit:_eventEmitter];
924
+ // appliedFooterBottomInset is NOT set here — it tracks what's actually
925
+ // baked into the footer's measured height (reported with its layout), not
926
+ // what was just pushed. Setting it at push time desyncs the pair and lets
927
+ // a seeded inset read as natural content height (see maxNaturalDetentHeight).
928
+ CGFloat inset = _controller.footerBottomInset;
929
+ _containerView.footerBottomInset = inset;
930
+
931
+ // Publish the inset so a footer set later (e.g. navigation setOptions) is
932
+ // padded on its very first layout instead of resizing the sheet twice.
933
+ if (_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic) {
934
+ TrueSheetInsets::setBottomSafeArea(inset);
935
+ }
383
936
  }
384
937
 
385
- - (void)viewControllerDidChangeDetent:(NSInteger)index position:(CGFloat)position {
386
- if (_controller.activeDetentIndex != index) {
387
- _controller.activeDetentIndex = index;
938
+ - (void)applySheetPropsUpdate {
939
+ BOOL pendingLayoutUpdate = _pendingLayoutUpdate;
940
+ _pendingLayoutUpdate = NO;
941
+
942
+ if (_pendingDetents) {
943
+ _controller.detents = _pendingDetents;
944
+ _pendingDetents = nil;
945
+ [self refreshFooterBottomInset];
388
946
  }
389
947
 
390
- [OnDetentChangeEvent emit:_eventEmitter index:index position:position];
391
- }
948
+ UIView *presenterView = _controller.presentingViewController.view;
949
+ [_controller setupAnchorViewInView:presenterView];
392
950
 
393
- - (void)viewControllerDidChangePosition:(NSInteger)index position:(CGFloat)position transitioning:(BOOL)transitioning {
394
- [OnPositionChangeEvent emit:_eventEmitter index:index position:position transitioning:transitioning];
395
- }
951
+ [_controller setupSheetSizing];
396
952
 
397
- - (void)viewControllerDidChangeSize:(CGSize)size {
398
- [OnSizeChangeEvent emit:_eventEmitter width:size.width height:size.height];
953
+ [_controller.sheetPresentationController animateChanges:^{
954
+ [self->_controller setupSheetProps];
955
+ if (pendingLayoutUpdate) {
956
+ [self->_controller setupSheetDetentsForDetentsChange];
957
+ } else {
958
+ [self->_controller setupSheetDetents];
959
+ }
960
+ [self->_controller applyActiveDetent];
961
+ }];
962
+ [_controller setupDraggable];
399
963
  }
400
964
 
401
- #pragma mark - Private Helpers
402
-
403
965
  - (UIViewController *)findPresentingViewController {
404
- UIWindow *keyWindow = [WindowUtil keyWindow];
405
-
406
- if (!keyWindow) {
966
+ if (!self.window)
407
967
  return nil;
408
- }
409
968
 
410
- UIViewController *rootViewController = keyWindow.rootViewController;
969
+ UIViewController *rootViewController = self.window.rootViewController;
970
+ if (!rootViewController)
971
+ return nil;
411
972
 
412
- // Find the top-most presented view controller
973
+ // Find topmost presented view controller that is not being dismissed
413
974
  while (rootViewController.presentedViewController) {
414
975
  UIViewController *presented = rootViewController.presentedViewController;
415
976
 
416
- // Skip TrueSheetViewController if it's being dismissed
417
- if ([presented isKindOfClass:[TrueSheetViewController class]] && presented.isBeingDismissed) {
977
+ // Skip any view controller that is being dismissed
978
+ if (presented.isBeingDismissed) {
418
979
  break;
419
980
  }
420
-
421
981
  rootViewController = presented;
422
982
  }
423
983