@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
@@ -1,14 +1,52 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.os.Build
5
+ import android.text.Editable
6
+ import android.text.TextWatcher
7
+ import android.view.View
8
+ import android.view.ViewGroup
9
+ import android.view.ViewTreeObserver
10
+ import android.widget.EditText
11
+ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
12
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
4
13
  import com.facebook.react.uimanager.ThemedReactContext
5
14
  import com.facebook.react.views.view.ReactViewGroup
15
+ import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
16
+ import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
17
+ import com.lodev09.truesheet.utils.isDescendantOf
18
+ import com.lodev09.truesheet.utils.smoothScrollTo
19
+
20
+ data class ScrollableOptions(
21
+ val keyboardScrollOffset: Float = 0f,
22
+ val keyboardOffset: Float = 0f,
23
+ val scrollingExpandsSheet: Boolean = true,
24
+ val contentInsetAdjustment: Boolean = true
25
+ )
6
26
 
7
27
  /**
8
28
  * Delegate interface for content view size changes
9
29
  */
10
30
  interface TrueSheetContentViewDelegate {
11
31
  fun contentViewDidChangeSize(width: Int, height: Int)
32
+ fun contentViewDidScroll()
33
+ fun contentViewScrollViewDidChange()
34
+
35
+ /**
36
+ * Keyboard occlusion adjustment below the content — positive for an absolute
37
+ * footer floating over the viewport's bottom edge (extends the caret reveal),
38
+ * negative for a relative footer sitting behind the keyboard shielding that
39
+ * much of its overlap (reduces the keyboard padding).
40
+ */
41
+ val footerKeyboardOcclusion: Int
42
+
43
+ /**
44
+ * A relative footer sits below the content and absorbs the bottom safe-area
45
+ * inset — the scrollable doesn't reach the sheet's bottom edge, so no
46
+ * content inset is applied (mirrors iOS, where UIKit resolves this
47
+ * geometrically via the scroll view's safe area).
48
+ */
49
+ val hasRelativeFooter: Boolean
12
50
  }
13
51
 
14
52
  /**
@@ -16,31 +54,375 @@ interface TrueSheetContentViewDelegate {
16
54
  * This is the first child of TrueSheetContainerView
17
55
  */
18
56
  @SuppressLint("ViewConstructor")
19
- class TrueSheetContentView(context: ThemedReactContext) : ReactViewGroup(context) {
57
+ class TrueSheetContentView(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
20
58
  var delegate: TrueSheetContentViewDelegate? = null
21
59
 
22
- private var lastWidth = 0
23
- private var lastHeight = 0
60
+ private var detectedScrollView: ViewGroup? = null
61
+
62
+ /**
63
+ * Content height measured unconstrained by the shadow node — the height the
64
+ * content wants regardless of container bounds (see
65
+ * TrueSheetContentViewShadowNode). Falls back to the view height before the
66
+ * first state update.
67
+ */
68
+ val naturalHeight: Int
69
+ get() = if (lastNaturalHeight > 0) lastNaturalHeight else height
70
+
71
+ private var lastNaturalHeight = 0
72
+
73
+ fun updateNaturalHeight(heightDp: Double) {
74
+ val heightPx = heightDp.toFloat().dpToPx().toInt()
75
+ if (heightPx == lastNaturalHeight) return
76
+ lastNaturalHeight = heightPx
77
+ delegate?.contentViewDidChangeSize(width, heightPx)
78
+ }
79
+
80
+ private var keyboardScrollOffset: Float = 0f
81
+
82
+ /**
83
+ * Adjustment added to the keyboard bottom inset applied to the detected
84
+ * scrollable — negative values reduce the inset (e.g. to cancel out safe-area
85
+ * padding already baked into the content's paddingBottom).
86
+ */
87
+ private var keyboardOffset: Float = 0f
88
+
89
+ /**
90
+ * How much of keyboardOffset actually landed in the applied inset — the
91
+ * caret reveal compensates by this so it still targets the real keyboard edge.
92
+ */
93
+ private var appliedKeyboardOffset = 0
94
+
95
+ private var keyboardObserver: TrueSheetKeyboardObserver? = null
96
+
97
+ private var watchedEditText: EditText? = null
98
+ private var caretTextWatcher: TextWatcher? = null
99
+
100
+ var scrollableOptions: ScrollableOptions? = null
101
+ set(value) {
102
+ field = value
103
+ keyboardScrollOffset = value?.keyboardScrollOffset?.dpToPx() ?: 0f
104
+ keyboardOffset = value?.keyboardOffset?.dpToPx() ?: 0f
105
+ updateContentInset()
106
+ }
107
+
108
+ /**
109
+ * Bottom safe-area inset applied to the scrollable's content padding while
110
+ * the content can scroll — Android counterpart of iOS's
111
+ * `contentInsetAdjustmentBehavior="automatic"`. Already zero when the
112
+ * sheet's `insetAdjustment` is `'never'`.
113
+ */
114
+ var bottomInset: Int = 0
115
+ set(value) {
116
+ if (field == value) return
117
+ field = value
118
+ updateContentInset()
119
+ }
120
+
121
+ private var baseBottomInset = 0
122
+ private var keyboardBottomInset = 0
123
+
124
+ private val scrollableLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
125
+ updateContentInset()
126
+ clampScrollPosition()
127
+ }
128
+
129
+ private val scrollContentLayoutListener = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
130
+ updateContentInset()
131
+ }
132
+
133
+ override fun onAttachedToWindow() {
134
+ super.onAttachedToWindow()
135
+ viewTreeObserver.addOnGlobalLayoutListener(scrollableLayoutListener)
136
+ }
137
+
138
+ override fun onDetachedFromWindow() {
139
+ viewTreeObserver.removeOnGlobalLayoutListener(scrollableLayoutListener)
140
+ super.onDetachedFromWindow()
141
+ }
142
+
143
+ /**
144
+ * React tag of the user-provided scrollable (see the `scrollableRef` prop).
145
+ * Setting a new handle clears the currently resolved ScrollView.
146
+ */
147
+ var scrollableHandle: Int = -1
148
+ set(value) {
149
+ if (field == value) return
150
+ field = value
151
+ clearScrollable()
152
+ }
153
+
154
+ override fun addView(child: View?, index: Int) {
155
+ super.addView(child, index)
156
+ checkScrollViewChanged()
157
+ }
158
+
159
+ override fun removeViewAt(index: Int) {
160
+ super.removeViewAt(index)
161
+ checkScrollViewChanged()
162
+ }
163
+
164
+ private fun checkScrollViewChanged() {
165
+ if (detectedScrollView == null || detectedScrollView?.isDescendantOf(this) == false) {
166
+ delegate?.contentViewScrollViewDidChange()
167
+ }
168
+ }
169
+
170
+ fun setupScrollable() {
171
+ // Check if the detected scroll view is still valid (still in view hierarchy)
172
+ if (detectedScrollView != null && detectedScrollView?.isDescendantOf(this) == false) {
173
+ clearScrollable()
174
+ }
175
+
176
+ if (detectedScrollView != null) {
177
+ return
178
+ }
179
+
180
+ val scrollView = findScrollView() ?: return
181
+
182
+ detectedScrollView = scrollView
183
+
184
+ scrollView.isNestedScrollingEnabled = true
185
+ (scrollView.parent as? SwipeRefreshLayout)?.isNestedScrollingEnabled = false
186
+
187
+ scrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
188
+ if (scrollY != oldScrollY) {
189
+ delegate?.contentViewDidScroll()
190
+ }
191
+ }
192
+
193
+ // Restore the inset before ReactScrollView's listener clamps the offset.
194
+ scrollView.getChildAt(0)?.let { child ->
195
+ if (scrollView is View.OnLayoutChangeListener) child.removeOnLayoutChangeListener(scrollView)
196
+ child.addOnLayoutChangeListener(scrollContentLayoutListener)
197
+ if (scrollView is View.OnLayoutChangeListener) child.addOnLayoutChangeListener(scrollView)
198
+ }
199
+
200
+ updateContentInset()
201
+
202
+ // If keyboard is currently showing, re-apply the keyboard inset to the new ScrollView
203
+ val keyboardHeight = keyboardObserver?.currentHeight ?: 0
204
+ if (keyboardHeight > 0) {
205
+ updateScrollViewInsetForKeyboard(keyboardHeight)
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Re-applies the base content inset — the bottom safe-area inset while the
211
+ * content can scroll, no relative footer absorbs it, and
212
+ * `contentInsetAdjustmentBehavior` isn't disabled.
213
+ */
214
+ fun updateContentInset() {
215
+ val scrollView = detectedScrollView ?: return
24
216
 
25
- override fun onLayout(
26
- changed: Boolean,
27
- left: Int,
28
- top: Int,
29
- right: Int,
30
- bottom: Int
31
- ) {
32
- super.onLayout(changed, left, top, right, bottom)
217
+ val enabled = scrollableOptions?.contentInsetAdjustment ?: true
218
+ baseBottomInset = if (enabled && delegate?.hasRelativeFooter != true && canScrollContent(scrollView)) {
219
+ bottomInset
220
+ } else {
221
+ 0
222
+ }
223
+
224
+ applyBottomInset()
225
+ }
226
+
227
+ // Fabric's measured height excludes the native inset, avoiding feedback.
228
+ private fun canScrollContent(scrollView: ViewGroup): Boolean {
229
+ val child = scrollView.getChildAt(0) ?: return false
230
+ return child.measuredHeight > scrollView.height - scrollView.paddingTop - scrollView.paddingBottom
231
+ }
232
+
233
+ // The keyboard covers the safe-area region, so the larger inset wins over a sum
234
+ private fun applyBottomInset() {
235
+ setScrollContentInset(maxOf(baseBottomInset, keyboardBottomInset))
236
+ }
237
+
238
+ private fun setScrollContentInset(inset: Int) {
239
+ val scrollView = detectedScrollView ?: return
240
+ val child = scrollView.getChildAt(0) ?: return
241
+ scrollView.clipToPadding = false
242
+ // ScrollView's canScrollVertically ignores bottom padding. Extend the
243
+ // content bounds instead, keeping Fabric's measured height as the baseline.
244
+ child.bottom = child.top + child.measuredHeight + inset
245
+ }
246
+
247
+ fun clearScrollable() {
248
+ detectedScrollView?.setOnScrollChangeListener(null as View.OnScrollChangeListener?)
249
+ detectedScrollView?.getChildAt(0)?.removeOnLayoutChangeListener(scrollContentLayoutListener)
250
+ detectedScrollView?.isNestedScrollingEnabled = false
251
+ (detectedScrollView?.parent as? SwipeRefreshLayout)?.isNestedScrollingEnabled = true
252
+ setScrollContentInset(0)
253
+ detectedScrollView = null
254
+ baseBottomInset = 0
255
+ keyboardBottomInset = 0
256
+ appliedKeyboardOffset = 0
257
+ }
258
+
259
+ /**
260
+ * Resolves the user-provided `scrollableHandle` within the content subtree —
261
+ * React tags are view ids on Android.
262
+ */
263
+ fun findScrollView(): ViewGroup? {
264
+ if (detectedScrollView != null) return detectedScrollView
265
+ if (scrollableHandle <= 0) return null
266
+ return findViewById<View>(scrollableHandle) as? ViewGroup
267
+ }
268
+
269
+ // ==================== Keyboard Handling ====================
270
+
271
+ fun setupKeyboardHandler() {
272
+ if (keyboardObserver != null) return
273
+
274
+ keyboardObserver = TrueSheetKeyboardObserver(this, reactContext).apply {
275
+ delegate = object : TrueSheetKeyboardObserverDelegate {
276
+ // Drive the inset per keyboard animation frame so the content tracks
277
+ // the keyboard edge instead of jumping to the final inset up front.
278
+ // On hide, the shrinking inset gradually clamps the scroll position
279
+ // back in sync with the keyboard.
280
+ override fun keyboardDidChangeHeight(height: Int) {
281
+ updateScrollViewInsetForKeyboard(height)
282
+
283
+ // Follow the caret frame-by-frame — only API 30+ delivers
284
+ // incremental heights; legacy fires once after the keyboard is
285
+ // already up, where keyboardDidShow's smooth reveal takes over
286
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !isHiding) {
287
+ scrollToFocusedInput(smooth = false)
288
+ }
289
+ }
290
+
291
+ override fun keyboardDidShow(height: Int) {
292
+ attachCaretListener()
293
+ scrollToFocusedInput()
294
+ }
295
+
296
+ override fun keyboardWillHide() {
297
+ detachCaretListener()
298
+ }
299
+
300
+ override fun focusDidChange(newFocus: View) {
301
+ attachCaretListener()
302
+ scrollToFocusedInput()
303
+ }
304
+ }
305
+ start()
306
+ }
307
+ }
308
+
309
+ fun cleanupKeyboardHandler() {
310
+ detachCaretListener()
311
+ keyboardObserver?.stop()
312
+ keyboardObserver = null
313
+ }
314
+
315
+ private fun attachCaretListener() {
316
+ val focused = findFocus() as? EditText
317
+ if (focused === watchedEditText) return
33
318
 
34
- // Notify delegate when content size changes
35
- val newWidth = right - left
36
- val newHeight = bottom - top
319
+ detachCaretListener()
320
+ if (focused == null) return
37
321
 
38
- if (newWidth != lastWidth || newHeight != lastHeight) {
39
- lastWidth = newWidth
40
- lastHeight = newHeight
322
+ val watcher = object : TextWatcher {
323
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
324
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
325
+ override fun afterTextChanged(s: Editable?) {
326
+ focused.post { scrollToFocusedInput() }
327
+ }
328
+ }
329
+
330
+ focused.addTextChangedListener(watcher)
331
+ watchedEditText = focused
332
+ caretTextWatcher = watcher
333
+ }
41
334
 
42
- // Notify delegate of size change
43
- delegate?.contentViewDidChangeSize(newWidth, newHeight)
335
+ private fun detachCaretListener() {
336
+ caretTextWatcher?.let { watchedEditText?.removeTextChangedListener(it) }
337
+ caretTextWatcher = null
338
+ watchedEditText = null
339
+ }
340
+
341
+ private fun updateScrollViewInsetForKeyboard(keyboardHeight: Int) {
342
+ // A relative footer stays behind the keyboard below the content, so its
343
+ // height shields that much of the keyboard's overlap. An absolute footer
344
+ // floats within the viewport — its clearance is the content padding's job
345
+ // (the caret reveal accounts for it, see scrollToFocusedInput).
346
+ keyboardBottomInset = if (keyboardHeight > 0) {
347
+ val baseInset = maxOf(0, keyboardHeight + minOf(0, delegate?.footerKeyboardOcclusion ?: 0))
348
+ val adjustedInset = maxOf(0, baseInset + keyboardOffset.toInt())
349
+ appliedKeyboardOffset = adjustedInset - baseInset
350
+ adjustedInset
351
+ } else {
352
+ appliedKeyboardOffset = 0
353
+ 0
44
354
  }
355
+ applyBottomInset()
356
+ clampScrollPosition()
357
+ }
358
+
359
+ // Content bounds changes don't re-clamp the scroll position until the next layout
360
+ // pass — clamp synchronously so the content follows the shrinking inset on
361
+ // every keyboard frame instead of snapping afterwards
362
+ private fun clampScrollPosition() {
363
+ val scrollView = detectedScrollView ?: return
364
+ val child = scrollView.getChildAt(0) ?: return
365
+ val maxScrollY = maxOf(0, child.height - (scrollView.height - scrollView.paddingTop - scrollView.paddingBottom))
366
+ if (scrollView.scrollY > maxScrollY) {
367
+ scrollView.scrollTo(scrollView.scrollX, maxScrollY)
368
+ }
369
+ }
370
+
371
+ private fun scrollToFocusedInput(smooth: Boolean = true) {
372
+ val scrollView = detectedScrollView ?: findScrollView() ?: return
373
+ val focusedView = findFocus() ?: return
374
+
375
+ val focusedLocation = IntArray(2)
376
+ val scrollViewLocation = IntArray(2)
377
+ focusedView.getLocationOnScreen(focusedLocation)
378
+ scrollView.getLocationOnScreen(scrollViewLocation)
379
+
380
+ val viewTop = focusedLocation[1] - scrollViewLocation[1] + scrollView.scrollY
381
+
382
+ // Resolve the caret's line within the focused input so we follow the cursor
383
+ // rather than the input's full bounds (which over-scrolls tall inputs).
384
+ val editText = focusedView as? EditText
385
+ val layout = editText?.layout
386
+
387
+ val caretTop: Int
388
+ val caretBottom: Int
389
+ if (editText != null && layout != null) {
390
+ val line = layout.getLineForOffset(editText.selectionEnd.coerceAtLeast(0))
391
+ val textTop = viewTop + editText.totalPaddingTop - editText.scrollY
392
+ caretTop = textTop + layout.getLineTop(line)
393
+ caretBottom = textTop + layout.getLineBottom(line)
394
+ } else {
395
+ caretTop = viewTop
396
+ caretBottom = viewTop + focusedView.height
397
+ }
398
+
399
+ // An absolute footer floats over the viewport's bottom edge — extend the
400
+ // caret target so it clears the footer, not just the keyboard.
401
+ val footerOcclusion = maxOf(0, delegate?.footerKeyboardOcclusion ?: 0)
402
+
403
+ val offset = keyboardScrollOffset.toInt()
404
+ val visibleHeight = scrollView.height - scrollView.paddingBottom - maxOf(baseBottomInset, keyboardBottomInset) + appliedKeyboardOffset
405
+ val visibleTop = scrollView.scrollY
406
+ val visibleBottom = scrollView.scrollY + visibleHeight
407
+
408
+ val targetY = when {
409
+ caretBottom + offset + footerOcclusion > visibleBottom ->
410
+ caretBottom + offset + footerOcclusion - visibleHeight
411
+
412
+ caretTop - offset < visibleTop ->
413
+ (caretTop - offset).coerceAtLeast(0)
414
+
415
+ else -> return
416
+ }
417
+
418
+ if (smooth) {
419
+ scrollView.smoothScrollTo(0, targetY)
420
+ } else {
421
+ scrollView.scrollTo(0, targetY)
422
+ }
423
+ }
424
+
425
+ companion object {
426
+ const val TAG_NAME = "TrueSheet"
45
427
  }
46
428
  }
@@ -1,8 +1,12 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.PointerEvents
5
+ import com.facebook.react.uimanager.ReactStylesDiffMap
6
+ import com.facebook.react.uimanager.StateWrapper
4
7
  import com.facebook.react.uimanager.ThemedReactContext
5
8
  import com.facebook.react.uimanager.ViewGroupManager
9
+ import com.facebook.react.uimanager.annotations.ReactProp
6
10
 
7
11
  /**
8
12
  * ViewManager for TrueSheetContentView
@@ -15,6 +19,20 @@ class TrueSheetContentViewManager : ViewGroupManager<TrueSheetContentView>() {
15
19
 
16
20
  override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetContentView = TrueSheetContentView(reactContext)
17
21
 
22
+ override fun updateState(view: TrueSheetContentView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? {
23
+ stateWrapper?.stateData?.let { data ->
24
+ if (data.hasKey("naturalHeight")) {
25
+ view.updateNaturalHeight(data.getDouble("naturalHeight"))
26
+ }
27
+ }
28
+ return null
29
+ }
30
+
31
+ @ReactProp(name = "pointerEvents")
32
+ fun setPointerEvents(view: TrueSheetContentView, pointerEventsStr: String?) {
33
+ view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
34
+ }
35
+
18
36
  companion object {
19
37
  const val REACT_CLASS = "TrueSheetContentView"
20
38
  }
@@ -1,47 +1,171 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.view.MotionEvent
5
+ import android.view.View
6
+ import android.view.ViewParent
7
+ import com.facebook.react.bridge.WritableNativeMap
8
+ import com.facebook.react.uimanager.JSPointerDispatcher
9
+ import com.facebook.react.uimanager.JSTouchDispatcher
10
+ import com.facebook.react.uimanager.PixelUtil.pxToDp
11
+ import com.facebook.react.uimanager.PointerEvents
12
+ import com.facebook.react.uimanager.RootView
13
+ import com.facebook.react.uimanager.StateWrapper
4
14
  import com.facebook.react.uimanager.ThemedReactContext
15
+ import com.facebook.react.uimanager.events.EventDispatcher
5
16
  import com.facebook.react.views.view.ReactViewGroup
17
+ import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
18
+ import com.lodev09.truesheet.utils.TouchEventDeduper
6
19
 
7
20
  /**
8
- * Delegate interface for footer view size changes
21
+ * Delegate interface for footer view size changes and event dispatching
9
22
  */
10
23
  interface TrueSheetFooterViewDelegate {
11
24
  fun footerViewDidChangeSize(width: Int, height: Int)
25
+ val eventDispatcher: EventDispatcher?
12
26
  }
13
27
 
14
28
  /**
15
29
  * Footer view that holds the footer content
16
30
  * This is the second child of TrueSheetContainerView
17
31
  * Positioned absolutely at the bottom of the sheet
32
+ *
33
+ * Implements RootView to handle touch events when positioned outside parent bounds.
18
34
  */
19
35
  @SuppressLint("ViewConstructor")
20
- class TrueSheetFooterView(context: ThemedReactContext) : ReactViewGroup(context) {
36
+ class TrueSheetFooterView(private val reactContext: ThemedReactContext) :
37
+ ReactViewGroup(reactContext),
38
+ RootView {
39
+
21
40
  var delegate: TrueSheetFooterViewDelegate? = null
41
+ var stateWrapper: StateWrapper? = null
42
+
43
+ private val eventDispatcher: EventDispatcher?
44
+ get() = delegate?.eventDispatcher
22
45
 
23
46
  private var lastWidth = 0
24
47
  private var lastHeight = 0
25
48
 
26
- override fun onLayout(
27
- changed: Boolean,
28
- left: Int,
29
- top: Int,
30
- right: Int,
31
- bottom: Int
32
- ) {
33
- super.onLayout(changed, left, top, right, bottom)
34
-
35
- // Notify delegate when footer size changes
36
- val newWidth = right - left
37
- val newHeight = bottom - top
38
-
39
- if (newWidth != lastWidth || newHeight != lastHeight) {
40
- lastWidth = newWidth
41
- lastHeight = newHeight
42
-
43
- // Notify delegate of size change
44
- delegate?.footerViewDidChangeSize(newWidth, newHeight)
49
+ var bottomInset = 0
50
+ private set
51
+
52
+ /**
53
+ * Height the footer occupies above the keyboard — its layout height minus
54
+ * the safe-area inset, which rides below the keyboard's top edge while the
55
+ * keyboard is open (see positionFooter).
56
+ */
57
+ val keyboardOcclusionHeight: Int
58
+ get() = maxOf(0, height - bottomInset)
59
+
60
+ /**
61
+ * Tells the shadow node to pad the footer's bottom edge with the sheet's
62
+ * bottom safe-area inset the footer owns the sheet's bottom edge, so it
63
+ * absorbs the inset and its background fills it.
64
+ */
65
+ fun setBottomInset(inset: Int) {
66
+ if (bottomInset == inset) return
67
+ bottomInset = inset
68
+
69
+ val sw = stateWrapper ?: return
70
+ val insetDp = bottomInset.toFloat().pxToDp()
71
+
72
+ // Synchronous update — the footer must be padded before detents are
73
+ // configured, otherwise the auto detent is set up an inset short
74
+ if (TrueSheetStateUpdater.updateFooterState(sw, insetDp)) return
75
+
76
+ // Fallback: async state update
77
+ val newState = WritableNativeMap()
78
+ newState.putDouble("bottomInset", insetDp.toDouble())
79
+ sw.updateState(newState)
80
+ }
81
+
82
+ private val jsTouchDispatcher = JSTouchDispatcher(this)
83
+ private var jsPointerDispatcher: JSPointerDispatcher? = null
84
+ private val touchDeduper = TouchEventDeduper()
85
+
86
+ init {
87
+ jsPointerDispatcher = JSPointerDispatcher(this)
88
+ }
89
+
90
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
91
+ super.onSizeChanged(w, h, oldw, oldh)
92
+
93
+ if (w != lastWidth || h != lastHeight) {
94
+ lastWidth = w
95
+ lastHeight = h
96
+ delegate?.footerViewDidChangeSize(w, h)
97
+ }
98
+ }
99
+
100
+ // ==================== RootView Implementation ====================
101
+
102
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
103
+ eventDispatcher?.let { dispatcher ->
104
+ if (touchDeduper.shouldDispatch(event)) {
105
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
106
+ }
107
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
108
+ }
109
+ return super.onInterceptTouchEvent(event)
110
+ }
111
+
112
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
113
+ // Mirrors ReactSurfaceView: keep receiving onInterceptTouchEvent so
114
+ // jsTouchDispatcher sees the gesture end, but forward the request up the tree.
115
+ parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
116
+ }
117
+
118
+ override fun onTouchEvent(event: MotionEvent): Boolean {
119
+ if (pointerEvents == PointerEvents.NONE || pointerEvents == PointerEvents.BOX_NONE) {
120
+ return false
121
+ }
122
+
123
+ eventDispatcher?.let { dispatcher ->
124
+ if (touchDeduper.shouldDispatch(event)) {
125
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
126
+ }
127
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
128
+ }
129
+ super.onTouchEvent(event)
130
+ return true
131
+ }
132
+
133
+ override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
134
+ eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, true) }
135
+ return super.onHoverEvent(event)
136
+ }
137
+
138
+ override fun onHoverEvent(event: MotionEvent): Boolean {
139
+ eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, false) }
140
+ return super.onHoverEvent(event)
141
+ }
142
+
143
+ override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
144
+ findCoordinatorLayout()?.childDidClaimNativeGesture()
145
+ eventDispatcher?.let { dispatcher ->
146
+ jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher)
147
+ jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, dispatcher)
45
148
  }
46
149
  }
150
+
151
+ private fun findCoordinatorLayout(): TrueSheetCoordinatorLayout? {
152
+ var current: ViewParent? = parent
153
+ while (current != null && current !is TrueSheetCoordinatorLayout) {
154
+ current = current.parent
155
+ }
156
+ return current as? TrueSheetCoordinatorLayout
157
+ }
158
+
159
+ override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
160
+ eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
161
+ jsPointerDispatcher?.onChildEndedNativeGesture()
162
+ }
163
+
164
+ override fun handleException(t: Throwable) {
165
+ reactContext.reactApplicationContext.handleException(RuntimeException(t))
166
+ }
167
+
168
+ companion object {
169
+ const val TAG_NAME = "TrueSheet"
170
+ }
47
171
  }