@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
@@ -0,0 +1,375 @@
1
+ package com.lodev09.truesheet.core
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.content.res.Configuration
6
+ import android.view.MotionEvent
7
+ import android.view.View
8
+ import android.view.ViewConfiguration
9
+ import android.view.ViewGroup
10
+ import android.widget.EditText
11
+ import androidx.coordinatorlayout.widget.CoordinatorLayout
12
+ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
13
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
14
+ import com.facebook.react.uimanager.PointerEvents
15
+ import com.facebook.react.uimanager.ReactPointerEventsView
16
+ import com.facebook.react.uimanager.TouchTargetHelper
17
+ import com.lodev09.truesheet.utils.KeyboardUtils
18
+ import com.lodev09.truesheet.utils.isDescendantOf
19
+
20
+ interface TrueSheetCoordinatorLayoutDelegate {
21
+ fun coordinatorLayoutDidLayout(changed: Boolean)
22
+ fun coordinatorLayoutDidChangeConfiguration()
23
+ fun findScrollView(): ViewGroup?
24
+ fun findSheetView(): TrueSheetBottomSheetView?
25
+
26
+ /**
27
+ * A downward pull the sheet couldn't follow ended; `overdragPx` is the travel it
28
+ * didn't follow, `keyboardWasVisible` whether the keyboard was up when it began.
29
+ */
30
+ fun coordinatorLayoutDidPullDown(overdragPx: Int, keyboardWasVisible: Boolean)
31
+ }
32
+
33
+ /**
34
+ * Custom CoordinatorLayout that hosts the bottom sheet and dim view.
35
+ * Implements ReactPointerEventsView to allow touch events to pass through
36
+ * to underlying React Native views when appropriate.
37
+ */
38
+ @SuppressLint("ViewConstructor")
39
+ class TrueSheetCoordinatorLayout(context: Context) :
40
+ CoordinatorLayout(context),
41
+ ReactPointerEventsView {
42
+
43
+ var delegate: TrueSheetCoordinatorLayoutDelegate? = null
44
+
45
+ private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
46
+ private var dragging = false
47
+ private var initialY = 0f
48
+ private var activePointerId = 0
49
+
50
+ // Horizontal-dominance tracking. iOS lets horizontal child gestures win over
51
+ // the sheet's vertical pan; we mirror that by locking out BottomSheetBehavior
52
+ // interception once the first significant movement of a stream is horizontal.
53
+ private var streamInitialX = 0f
54
+ private var streamInitialY = 0f
55
+ private var streamHorizontalLocked = false
56
+ private var streamDirectionDecided = false
57
+
58
+ // Native-gesture-claim tracking. RNGH handlers activate past BottomSheetBehavior's
59
+ // drag slop and never call requestDisallowInterceptTouchEvent, so the sheet would
60
+ // steal vertical gestures (e.g. a vertical pan) before they can activate. While the
61
+ // touch is inside a descendant GestureHandlerRootView we hold off interception within
62
+ // a grace distance; once a child claims the gesture (relayed from the RootView impls
63
+ // via childDidClaimNativeGesture) we yield the rest of the stream, mirroring iOS.
64
+ private var streamHasGestureRoot = false
65
+ private var streamGestureClaimed = false
66
+
67
+ // Whether the sheet owns vertical drags for this stream: the touch target is inside
68
+ // the sheet but outside the detected scrollable (e.g. a header overlaying it), with
69
+ // nothing vertically scrollable along its chain. Such targets consume the stream
70
+ // natively (no nested scroll), and BottomSheetBehavior won't intercept because the
71
+ // scrollable's bounds still contain the point (touchingScrollingChild) — intercept
72
+ // manually.
73
+ private var streamSheetDraggable = false
74
+ private var streamDraggableEditText = false
75
+
76
+ // Pull-down tracking. BottomSheetBehavior gives no callback when a downward drag
77
+ // is clamped by `isHideable = false` — the sheet stays put (or the scrollable
78
+ // overscrolls) and no state change fires. Accumulate the downward finger travel
79
+ // neither the sheet nor its scrollable followed and report it on release so the
80
+ // controller can surface a blocked dismiss attempt.
81
+ private var pullTracking = false
82
+ private var pullStartX = 0f
83
+ private var pullStartY = 0f
84
+ private var pullLastY = 0f
85
+ private var pullOverdrag = 0f
86
+ private var pullKeyboardVisible = false
87
+
88
+ init {
89
+ layoutParams = LayoutParams(
90
+ LayoutParams.MATCH_PARENT,
91
+ LayoutParams.MATCH_PARENT
92
+ )
93
+
94
+ clipChildren = false
95
+ clipToPadding = false
96
+ }
97
+
98
+ override fun onLayout(
99
+ changed: Boolean,
100
+ l: Int,
101
+ t: Int,
102
+ r: Int,
103
+ b: Int
104
+ ) {
105
+ super.onLayout(changed, l, t, r, b)
106
+ delegate?.coordinatorLayoutDidLayout(changed)
107
+ }
108
+
109
+ override fun onConfigurationChanged(newConfig: Configuration?) {
110
+ super.onConfigurationChanged(newConfig)
111
+ delegate?.coordinatorLayoutDidChangeConfiguration()
112
+ }
113
+
114
+ override val pointerEvents: PointerEvents
115
+ get() = PointerEvents.BOX_NONE
116
+
117
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
118
+ when (ev.actionMasked) {
119
+ MotionEvent.ACTION_DOWN -> {
120
+ val sheet = delegate?.findSheetView()
121
+ pullTracking = sheet != null && isTouchInside(sheet, ev)
122
+ pullStartX = ev.rawX
123
+ pullStartY = ev.rawY
124
+ pullLastY = ev.rawY
125
+ pullOverdrag = 0f
126
+ pullKeyboardVisible = pullTracking && KeyboardUtils.isKeyboardVisible(this)
127
+ }
128
+
129
+ MotionEvent.ACTION_MOVE -> if (pullTracking) {
130
+ val sheet = delegate?.findSheetView()
131
+ val topBefore = sheet?.top
132
+ val handled = super.dispatchTouchEvent(ev)
133
+ val step = ev.rawY - pullLastY
134
+ pullLastY = ev.rawY
135
+ // Finger moved down but the sheet stayed put and the scrollable is at
136
+ // its top — the pull was clamped
137
+ if (step > 0 && sheet != null && sheet.top == topBefore && delegate?.findScrollView()?.canScrollVertically(-1) != true) {
138
+ pullOverdrag += step
139
+ }
140
+ return handled
141
+ }
142
+
143
+ MotionEvent.ACTION_UP -> if (pullTracking) {
144
+ pullTracking = false
145
+ val dx = kotlin.math.abs(ev.rawX - pullStartX)
146
+ val dy = ev.rawY - pullStartY
147
+ // Let the behavior settle first so the controller sees the resting position.
148
+ val handled = super.dispatchTouchEvent(ev)
149
+ if (dy > dx && pullOverdrag > PULL_DOWN_THRESHOLD_DP.dpToPx()) {
150
+ delegate?.coordinatorLayoutDidPullDown(pullOverdrag.toInt(), pullKeyboardVisible)
151
+ }
152
+ return handled
153
+ }
154
+
155
+ MotionEvent.ACTION_CANCEL -> pullTracking = false
156
+ }
157
+ return super.dispatchTouchEvent(ev)
158
+ }
159
+
160
+ /**
161
+ * Clears stale `nestedScrollingChildRef` from BottomSheetBehavior.
162
+ *
163
+ * The cached `nestedScrollingChildRef` can become stale when a child sheet
164
+ * with a ScrollView is dismissed and its ScrollView returns to this hierarchy.
165
+ */
166
+ private fun clearStaleNestedScrollingChildRef() {
167
+ val sheet = delegate?.findSheetView() ?: return
168
+ val behavior = sheet.behavior ?: return
169
+ try {
170
+ val field = behavior.javaClass.superclass.getDeclaredField("nestedScrollingChildRef")
171
+ field.isAccessible = true
172
+ @Suppress("UNCHECKED_CAST")
173
+ val ref = field.get(behavior) as? java.lang.ref.WeakReference<android.view.View> ?: return
174
+ val view = ref.get()
175
+ if (view == null || !view.isDescendantOf(sheet)) {
176
+ ref.clear()
177
+ }
178
+ } catch (_: Exception) {}
179
+ }
180
+
181
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
182
+ // RN's ReactEditText fires this(true) on ACTION_DOWN, killing sheet drag over an input.
183
+ // Swallow only that case — nothing between the touched EditText and the sheet scrolls
184
+ // (an overflowing multiline EditText or an input inside a scrollable that can actually
185
+ // scroll still scrolls). Other children (maps, sliders, scrollables) keep the standard
186
+ // disallow contract.
187
+ if (disallowIntercept && streamDraggableEditText) return
188
+ super.requestDisallowInterceptTouchEvent(disallowIntercept)
189
+ }
190
+
191
+ /**
192
+ * Resolves the stream's touch target (respecting zIndex and pointerEvents) and
193
+ * decides whether the sheet may drag from it — inside the sheet, outside the detected
194
+ * scrollable, and nothing along the target's chain scrolls vertically.
195
+ */
196
+ private fun updateStreamTargetFlags(ev: MotionEvent) {
197
+ streamSheetDraggable = false
198
+ streamDraggableEditText = false
199
+
200
+ val sheet = delegate?.findSheetView() ?: return
201
+ val target = TouchTargetHelper.findTargetPathAndCoordinatesForTouch(ev.x, ev.y, this, FloatArray(2))
202
+ .firstNotNullOfOrNull { it.getView() } ?: return
203
+ if (target !== sheet && !target.isDescendantOf(sheet)) return
204
+
205
+ // A detected scrollable that can scroll (or hosts pull-to-refresh) owns its
206
+ // touches — nested scrolling drags the sheet. When it can't scroll (e.g.
207
+ // the sheet is fully expanded and the content fits) it eats the gesture
208
+ // instead, so fall through and let the sheet drag from it — an EditText
209
+ // target needs the disallow-intercept swallow or its stream dies.
210
+ val scrollView = delegate?.findScrollView()
211
+ if (scrollView != null && (target === scrollView || target.isDescendantOf(scrollView))) {
212
+ val hasRefreshControl = scrollView.parent is SwipeRefreshLayout
213
+ if (hasRefreshControl || scrollView.scrollY > 0 || scrollView.canScrollVertically(1)) return
214
+ }
215
+
216
+ var view: View? = target
217
+ while (view != null && view !== this) {
218
+ if (view.canScrollVertically(1) || view.canScrollVertically(-1)) return
219
+ view = view.parent as? View
220
+ }
221
+
222
+ streamSheetDraggable = true
223
+ streamDraggableEditText = target is EditText
224
+ }
225
+
226
+ private fun isTouchInside(view: View, ev: MotionEvent): Boolean {
227
+ val loc = IntArray(2)
228
+ view.getLocationOnScreen(loc)
229
+ val x = ev.rawX.toInt()
230
+ val y = ev.rawY.toInt()
231
+ return x >= loc[0] && x <= loc[0] + view.width && y >= loc[1] && y <= loc[1] + view.height
232
+ }
233
+
234
+ private fun findDescendantViewAt(view: View, rawX: Int, rawY: Int, predicate: (View) -> Boolean): View? {
235
+ val loc = IntArray(2)
236
+ view.getLocationOnScreen(loc)
237
+ if (rawX < loc[0] || rawX > loc[0] + view.width || rawY < loc[1] || rawY > loc[1] + view.height) return null
238
+ if (predicate(view)) return view
239
+ if (view is ViewGroup) {
240
+ for (i in 0 until view.childCount) {
241
+ findDescendantViewAt(view.getChildAt(i), rawX, rawY, predicate)?.let { return it }
242
+ }
243
+ }
244
+ return null
245
+ }
246
+
247
+ /**
248
+ * Called by descendant RootView implementations (controller, footer) when a child
249
+ * starts a native gesture — e.g. an RNGH handler activated or a scrollable began
250
+ * scrolling. Yields sheet interception for the rest of the touch stream.
251
+ */
252
+ fun childDidClaimNativeGesture() {
253
+ streamGestureClaimed = true
254
+ }
255
+
256
+ /**
257
+ * Intercepts touch events for ScrollViews that can't scroll (content < viewport),
258
+ * allowing the sheet to be dragged in these cases.
259
+ *
260
+ * TODO: Remove this workaround once NestedScrollView is merged into react-native core.
261
+ * See: https://github.com/facebook/react-native/pull/44099
262
+ */
263
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
264
+ val action = ev.actionMasked
265
+
266
+ if (action == MotionEvent.ACTION_DOWN) {
267
+ clearStaleNestedScrollingChildRef()
268
+
269
+ streamInitialX = ev.rawX
270
+ streamInitialY = ev.rawY
271
+ streamHorizontalLocked = false
272
+ streamDirectionDecided = false
273
+ streamGestureClaimed = false
274
+ streamHasGestureRoot = findDescendantViewAt(this, ev.rawX.toInt(), ev.rawY.toInt()) {
275
+ it.javaClass.name == GESTURE_HANDLER_ROOT_VIEW_CLASS
276
+ } != null
277
+ updateStreamTargetFlags(ev)
278
+ }
279
+
280
+ // Decide gesture direction on first significant movement of the stream.
281
+ // If horizontal wins, lock out BottomSheetBehavior for the rest of the stream
282
+ // so child handlers (carousels, swipe buttons, etc.) can claim the touch.
283
+ if (!streamDirectionDecided && action == MotionEvent.ACTION_MOVE) {
284
+ val dx = kotlin.math.abs(ev.rawX - streamInitialX)
285
+ val dy = kotlin.math.abs(ev.rawY - streamInitialY)
286
+ if (dx > touchSlop || dy > touchSlop) {
287
+ streamDirectionDecided = true
288
+ streamHorizontalLocked = dx > dy
289
+ }
290
+ }
291
+
292
+ if (streamHorizontalLocked) {
293
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
294
+ streamHorizontalLocked = false
295
+ streamDirectionDecided = false
296
+ }
297
+ return false
298
+ }
299
+
300
+ if (streamGestureClaimed) {
301
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
302
+ streamGestureClaimed = false
303
+ }
304
+ return false
305
+ }
306
+
307
+ // Hold off BottomSheetBehavior within the grace distance so a gesture handler
308
+ // under the touch can activate and claim the stream first.
309
+ if (streamHasGestureRoot &&
310
+ action == MotionEvent.ACTION_MOVE &&
311
+ kotlin.math.abs(ev.rawY - streamInitialY) < touchSlop * GESTURE_CLAIM_SLOP_FACTOR
312
+ ) {
313
+ return false
314
+ }
315
+
316
+ val scrollView = delegate?.findScrollView()
317
+ val hasRefreshControl = scrollView?.parent is SwipeRefreshLayout
318
+ val cannotScroll = scrollView != null &&
319
+ !hasRefreshControl &&
320
+ scrollView.scrollY == 0 &&
321
+ !scrollView.canScrollVertically(1)
322
+
323
+ if (cannotScroll || streamSheetDraggable) {
324
+ when (ev.action and MotionEvent.ACTION_MASK) {
325
+ MotionEvent.ACTION_DOWN -> {
326
+ dragging = false
327
+ initialY = ev.y
328
+ activePointerId = ev.getPointerId(0)
329
+ }
330
+
331
+ MotionEvent.ACTION_MOVE -> {
332
+ val pointerIndex = ev.findPointerIndex(activePointerId)
333
+ if (pointerIndex != -1) {
334
+ val y = ev.getY(pointerIndex)
335
+ val deltaY = initialY - y
336
+ if (kotlin.math.abs(deltaY) > touchSlop) {
337
+ dragging = true
338
+ parent?.requestDisallowInterceptTouchEvent(true)
339
+ }
340
+ }
341
+ }
342
+
343
+ MotionEvent.ACTION_UP,
344
+ MotionEvent.ACTION_CANCEL -> {
345
+ dragging = false
346
+ }
347
+ }
348
+ } else {
349
+ dragging = false
350
+ }
351
+
352
+ return dragging || super.onInterceptTouchEvent(ev)
353
+ }
354
+
355
+ @SuppressLint("ClickableViewAccessibility")
356
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
357
+ if (dragging) {
358
+ when (ev.action and MotionEvent.ACTION_MASK) {
359
+ MotionEvent.ACTION_UP,
360
+ MotionEvent.ACTION_CANCEL -> {
361
+ dragging = false
362
+ }
363
+ }
364
+ // Let parent CoordinatorLayout handle the touch for BottomSheetBehavior
365
+ return super.onTouchEvent(ev)
366
+ }
367
+ return super.onTouchEvent(ev)
368
+ }
369
+
370
+ companion object {
371
+ private const val GESTURE_HANDLER_ROOT_VIEW_CLASS = "com.swmansion.gesturehandler.react.RNGestureHandlerRootView"
372
+ private const val GESTURE_CLAIM_SLOP_FACTOR = 3
373
+ private const val PULL_DOWN_THRESHOLD_DP = 24f
374
+ }
375
+ }
@@ -0,0 +1,299 @@
1
+ package com.lodev09.truesheet.core
2
+
3
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
4
+ import com.facebook.react.uimanager.PixelUtil.pxToDp
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.util.RNLog
7
+ import com.google.android.material.bottomsheet.BottomSheetBehavior
8
+
9
+ /**
10
+ * Provides screen dimensions and content measurements for detent calculations.
11
+ */
12
+ interface TrueSheetDetentCalculatorDelegate {
13
+ val screenHeight: Int
14
+ val realScreenHeight: Int
15
+ val detents: MutableList<Double>
16
+ val contentHeight: Int
17
+ val headerHeight: Int
18
+ val absoluteHeader: Boolean
19
+ val footerHeight: Int
20
+ val absoluteFooter: Boolean
21
+ val peekContentHeight: Int
22
+ val contentBottomInset: Int
23
+ val maxContentHeight: Int?
24
+ val keyboardInset: Int
25
+ val topInset: Int
26
+
27
+ /**
28
+ * True while the keyboard holds the sheet up: the collapsed stop then sits at
29
+ * the keyboard-free first detent (the "floor") so a drag-down dismisses the
30
+ * keyboard before it can dismiss the sheet.
31
+ */
32
+ val hasKeyboardFloor: Boolean
33
+ }
34
+
35
+ /**
36
+ * Handles all detent-related calculations for the bottom sheet.
37
+ */
38
+ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
39
+
40
+ var delegate: TrueSheetDetentCalculatorDelegate? = null
41
+
42
+ private val screenHeight: Int get() = delegate?.screenHeight ?: 0
43
+ private val realScreenHeight: Int get() = delegate?.realScreenHeight ?: 0
44
+ private val detents: List<Double> get() = delegate?.detents ?: emptyList()
45
+ private val contentHeight: Int get() = delegate?.contentHeight ?: 0
46
+
47
+ private val headerHeight: Int get() = delegate?.headerHeight ?: 0
48
+ private val footerHeight: Int get() = delegate?.footerHeight ?: 0
49
+ private val peekContentHeight: Int get() = delegate?.peekContentHeight ?: 0
50
+ private val contentBottomInset: Int get() = delegate?.contentBottomInset ?: 0
51
+ private val maxContentHeight: Int? get() = delegate?.maxContentHeight
52
+ private val keyboardInset: Int get() = delegate?.keyboardInset ?: 0
53
+ private val topInset: Int get() = delegate?.topInset ?: 0
54
+ private val hasKeyboardFloor: Boolean get() = delegate?.hasKeyboardFloor == true
55
+
56
+ /**
57
+ * Height for auto (-1.0) detents: content + header + footer height.
58
+ * An absolute (floating) header or footer overlaps the content, so it contributes no height.
59
+ */
60
+ private val autoDetentHeight: Int
61
+ get() = contentHeight +
62
+ (if (delegate?.absoluteHeader == true) 0 else headerHeight) +
63
+ (if (delegate?.absoluteFooter == true) 0 else footerHeight)
64
+
65
+ /**
66
+ * Bottom inset for auto detents. A relative footer owns the sheet's bottom
67
+ * edge, so it absorbs the inset instead of adding it to the auto height.
68
+ */
69
+ private val autoDetentBottomInset: Int
70
+ get() = if (footerHeight > 0 && delegate?.absoluteFooter == false) 0 else contentBottomInset
71
+
72
+ /**
73
+ * Bottom inset for peek detents. An absolute footer counts toward the peek
74
+ * height and absorbs the inset, so it isn't added again.
75
+ */
76
+ private val peekDetentBottomInset: Int
77
+ get() = if (footerHeight > 0 && delegate?.absoluteFooter == true) 0 else contentBottomInset
78
+
79
+ /**
80
+ * Height for peek (-2.0) detents: header + footer + peek content height.
81
+ * A relative footer is pushed off-screen at peek, so it contributes no height.
82
+ * Falls back to 150dp when none is present.
83
+ */
84
+ private val peekDetentHeight: Int
85
+ get() {
86
+ val height = headerHeight +
87
+ (if (delegate?.absoluteFooter == true) footerHeight else 0) +
88
+ peekContentHeight
89
+ return if (height > 0) height else DEFAULT_PEEK_HEIGHT.dpToPx().toInt()
90
+ }
91
+
92
+ /**
93
+ * Calculate the height in pixels for a given detent value.
94
+ * @param detent The detent value: -1.0 for content-fit, -2.0 for peek, or 0.0-1.0 for screen fraction
95
+ */
96
+ fun getDetentHeight(detent: Double, includeKeyboard: Boolean = true): Int {
97
+ val baseHeight = if (detent == -1.0) {
98
+ autoDetentHeight + autoDetentBottomInset
99
+ } else if (detent == -2.0) {
100
+ peekDetentHeight + peekDetentBottomInset
101
+ } else {
102
+ if (detent <= 0.0 || detent > 1.0) {
103
+ throw IllegalArgumentException("TrueSheet: detent fraction ($detent) must be between 0 and 1")
104
+ }
105
+ (detent * screenHeight).toInt() + contentBottomInset
106
+ }
107
+
108
+ val height = if (includeKeyboard) baseHeight + keyboardInset else baseHeight
109
+ val maxAllowedHeight = screenHeight + contentBottomInset
110
+ return maxContentHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
111
+ }
112
+
113
+ /**
114
+ * Get the expected sheet top position for a detent index.
115
+ */
116
+ fun getSheetTopForDetentIndex(index: Int): Int {
117
+ if (index < 0 || index >= detents.size) {
118
+ RNLog.w(reactContext, "TrueSheet: Detent index ($index) is out of bounds (0..${detents.size - 1})")
119
+ return realScreenHeight
120
+ }
121
+ // With a keyboard floor only the last detent stays keyboard-shifted — it's
122
+ // the keyboard-expanded resting position; the stops below it are keyboard-free
123
+ val isKeyboardFreeStop = hasKeyboardFloor && index != detents.size - 1
124
+ return getSheetTop(detents[index], includeKeyboard = !isKeyboardFreeStop)
125
+ }
126
+
127
+ /**
128
+ * Top of the lowest stop the sheet can rest at: the keyboard floor while it's
129
+ * active, else the first detent.
130
+ */
131
+ fun getLowestSheetTop(): Int = if (hasKeyboardFloor) getSheetTop(detents[0], includeKeyboard = false) else getSheetTopForDetentIndex(0)
132
+
133
+ /**
134
+ * Top of the half stop while the keyboard floor is active: the keyboard-free
135
+ * second detent, or the floor itself for a single detent.
136
+ */
137
+ fun getKeyboardHalfStopTop(): Int = getSheetTop(detents[minOf(1, detents.size - 1)], includeKeyboard = false)
138
+
139
+ // Clamp to the space the sheet can actually occupy — matching
140
+ // setupSheetDetents. A keyboard-inflated detent height can exceed it,
141
+ // placing the expected top above the screen while the real sheet stops
142
+ // at the top inset.
143
+ private fun getSheetTop(detent: Double, includeKeyboard: Boolean = true): Int {
144
+ val maxAvailableHeight = realScreenHeight - topInset
145
+ return realScreenHeight - minOf(getDetentHeight(detent, includeKeyboard), maxAvailableHeight)
146
+ }
147
+
148
+ /**
149
+ * Calculate visible sheet height from sheet top position.
150
+ */
151
+ fun getVisibleSheetHeight(sheetTop: Int): Int = realScreenHeight - sheetTop
152
+
153
+ /**
154
+ * Convert visible sheet height to position in dp.
155
+ */
156
+ fun getPositionDp(visibleSheetHeight: Int): Float = (screenHeight - visibleSheetHeight).pxToDp()
157
+
158
+ /**
159
+ * Returns the raw screen fraction for a detent index (without bottomInset).
160
+ */
161
+ fun getDetentValueForIndex(index: Int): Float {
162
+ if (index < 0 || index >= detents.size) return 0f
163
+ val value = detents[index]
164
+ return if (value == -1.0) {
165
+ autoDetentHeight.toFloat() / screenHeight.toFloat()
166
+ } else if (value == -2.0) {
167
+ peekDetentHeight.toFloat() / screenHeight.toFloat()
168
+ } else {
169
+ value.toFloat()
170
+ }
171
+ }
172
+
173
+ // ====================================================================
174
+ // MARK: - State Mapping
175
+ // ====================================================================
176
+
177
+ /**
178
+ * Maps detent index to BottomSheetBehavior state based on detent count.
179
+ */
180
+ fun getStateForDetentIndex(index: Int): Int {
181
+ val stateMap = getDetentStateMap() ?: return BottomSheetBehavior.STATE_HIDDEN
182
+ // With a keyboard floor the keyboard-expanded last detent lives at the
183
+ // expanded stop — the half stop (which may share its index) is keyboard-free
184
+ if (hasKeyboardFloor && index == detents.size - 1) return BottomSheetBehavior.STATE_EXPANDED
185
+ return stateMap.entries.find { it.value == index }?.key ?: BottomSheetBehavior.STATE_HIDDEN
186
+ }
187
+
188
+ /**
189
+ * Maps BottomSheetBehavior state to detent index.
190
+ * @return The detent index, or null if state is not mapped
191
+ */
192
+ fun getDetentIndexForState(state: Int): Int? {
193
+ val stateMap = getDetentStateMap() ?: return null
194
+ return stateMap[state]
195
+ }
196
+
197
+ /**
198
+ * Returns state-to-index mapping based on detent count.
199
+ */
200
+ private fun getDetentStateMap(): Map<Int, Int>? =
201
+ when (detents.size) {
202
+ // With one detent halfExpandedRatio matches the peek height, so the
203
+ // behavior can settle HALF_EXPANDED at the same position (e.g. an upward
204
+ // drag release) — map it too, or the settle is unmappable
205
+ 1 -> mapOf(
206
+ BottomSheetBehavior.STATE_COLLAPSED to 0,
207
+ BottomSheetBehavior.STATE_HALF_EXPANDED to 0,
208
+ BottomSheetBehavior.STATE_EXPANDED to 0
209
+ )
210
+
211
+ 2 -> mapOf(
212
+ BottomSheetBehavior.STATE_COLLAPSED to 0,
213
+ BottomSheetBehavior.STATE_HALF_EXPANDED to 1,
214
+ BottomSheetBehavior.STATE_EXPANDED to 1
215
+ )
216
+
217
+ 3 -> mapOf(
218
+ BottomSheetBehavior.STATE_COLLAPSED to 0,
219
+ BottomSheetBehavior.STATE_HALF_EXPANDED to 1,
220
+ BottomSheetBehavior.STATE_EXPANDED to 2
221
+ )
222
+
223
+ else -> null
224
+ }
225
+
226
+ // ====================================================================
227
+ // MARK: - Interpolation
228
+ // ====================================================================
229
+
230
+ /**
231
+ * Find which segment the position falls into for interpolation.
232
+ * @return Triple(fromIndex, toIndex, progress) where progress is 0-1, or null if no detents
233
+ */
234
+ private fun findSegmentForPosition(positionPx: Int): Triple<Int, Int, Float>? {
235
+ val count = detents.size
236
+ if (count == 0) return null
237
+
238
+ // A single keyboard-shifted detent can rest down at its keyboard floor —
239
+ // that range is still index 0, not a dismissal
240
+ val floorPos = getLowestSheetTop()
241
+
242
+ // Position is below the lowest stop (sheet is being dragged down to dismiss)
243
+ if (positionPx > floorPos) {
244
+ val range = realScreenHeight - floorPos
245
+ val progress = if (range > 0) (positionPx - floorPos).toFloat() / range else 0f
246
+ return Triple(-1, 0, progress)
247
+ }
248
+
249
+ if (count == 1) return Triple(0, 0, 0f)
250
+
251
+ val lastPos = getSheetTopForDetentIndex(count - 1)
252
+ // Position is above last detent
253
+ if (positionPx < lastPos) {
254
+ return Triple(count - 1, count - 1, 0f)
255
+ }
256
+
257
+ // Find the segment containing this position
258
+ for (i in 0 until count - 1) {
259
+ val pos = getSheetTopForDetentIndex(i)
260
+ val nextPos = getSheetTopForDetentIndex(i + 1)
261
+
262
+ // Skip degenerate segments — keyboard growth can clamp adjacent detents
263
+ // to the same top; falling through reports the topmost detent
264
+ if (pos == nextPos) continue
265
+
266
+ if (positionPx in nextPos..pos) {
267
+ val range = pos - nextPos
268
+ val progress = if (range > 0) (pos - positionPx).toFloat() / range else 0f
269
+ return Triple(i, i + 1, maxOf(0f, minOf(1f, progress)))
270
+ }
271
+ }
272
+
273
+ return Triple(count - 1, count - 1, 0f)
274
+ }
275
+
276
+ /**
277
+ * Returns interpolated continuous index (e.g., 0.5 = halfway between detent
278
+ * 0 and 1) and screen fraction for a position, sharing a single segment
279
+ * lookup — this runs on every position frame.
280
+ */
281
+ fun getInterpolatedPositionInfo(positionPx: Int): Pair<Float, Float> {
282
+ if (detents.isEmpty()) return Pair(-1f, 0f)
283
+
284
+ val (fromIndex, toIndex, progress) = findSegmentForPosition(positionPx)
285
+ ?: return Pair(0f, getDetentValueForIndex(0))
286
+
287
+ if (fromIndex == -1) {
288
+ return Pair(-progress, maxOf(0f, getDetentValueForIndex(0) * (1 - progress)))
289
+ }
290
+
291
+ val fromDetent = getDetentValueForIndex(fromIndex)
292
+ val toDetent = getDetentValueForIndex(toIndex)
293
+ return Pair(fromIndex + progress, fromDetent + progress * (toDetent - fromDetent))
294
+ }
295
+
296
+ companion object {
297
+ private const val DEFAULT_PEEK_HEIGHT = 150f
298
+ }
299
+ }