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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +28 -5
  2. package/android/build.gradle +23 -1
  3. package/android/proguard-rules.pro +3 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +38 -109
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +127 -0
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +29 -3
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +234 -66
  14. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +491 -186
  15. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +64 -10
  16. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -14
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +135 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +23 -23
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +130 -6
  20. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  21. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +14 -17
  22. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
  23. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +148 -0
  24. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +33 -40
  25. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  26. package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
  27. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  28. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  29. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  30. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  31. package/android/src/main/jni/TrueSheetStateUpdater.cpp +37 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +114 -30
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +14 -3
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +5 -6
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +10 -6
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  46. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  47. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  48. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  49. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  50. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  51. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  52. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  53. package/ios/TrueSheetContainerView.h +23 -7
  54. package/ios/TrueSheetContainerView.mm +45 -25
  55. package/ios/TrueSheetContentView.h +44 -15
  56. package/ios/TrueSheetContentView.mm +221 -190
  57. package/ios/TrueSheetFooterView.mm +133 -28
  58. package/ios/TrueSheetModule.h +2 -7
  59. package/ios/TrueSheetModule.mm +18 -18
  60. package/ios/TrueSheetOverlayView.h +42 -0
  61. package/ios/TrueSheetOverlayView.mm +175 -0
  62. package/ios/TrueSheetView.h +2 -0
  63. package/ios/TrueSheetView.mm +268 -52
  64. package/ios/TrueSheetViewController.h +36 -3
  65. package/ios/TrueSheetViewController.mm +797 -102
  66. package/ios/core/RNScreensEventObserver.mm +31 -28
  67. package/ios/core/TrueSheetBlurView.h +0 -2
  68. package/ios/core/TrueSheetBlurView.mm +3 -36
  69. package/ios/core/TrueSheetDetentCalculator.h +18 -1
  70. package/ios/core/TrueSheetDetentCalculator.mm +44 -9
  71. package/ios/core/TrueSheetDimView.h +27 -0
  72. package/ios/core/TrueSheetDimView.mm +41 -0
  73. package/ios/core/TrueSheetGrabberView.mm +13 -3
  74. package/ios/core/TrueSheetKeyboardObserver.h +7 -0
  75. package/ios/core/TrueSheetKeyboardObserver.mm +107 -15
  76. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  77. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  78. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  79. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  80. package/ios/tvos/TrueSheetStubs.mm +12 -0
  81. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  82. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  83. package/lib/module/TrueSheet.js +222 -54
  84. package/lib/module/TrueSheet.js.map +1 -1
  85. package/lib/module/TrueSheet.web.js +364 -275
  86. package/lib/module/TrueSheet.web.js.map +1 -1
  87. package/lib/module/TrueSheetBackground.js +15 -0
  88. package/lib/module/TrueSheetBackground.js.map +1 -0
  89. package/lib/module/TrueSheetOverlay.js +30 -0
  90. package/lib/module/TrueSheetOverlay.js.map +1 -0
  91. package/lib/module/TrueSheetOverlay.web.js +35 -0
  92. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  93. package/lib/module/TrueSheetPeek.web.js +20 -15
  94. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  95. package/lib/module/TrueSheetProvider.web.js +13 -9
  96. package/lib/module/TrueSheetProvider.web.js.map +1 -1
  97. package/lib/module/detentBackgrounds.js +21 -0
  98. package/lib/module/detentBackgrounds.js.map +1 -0
  99. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  100. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  101. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  102. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +89 -73
  103. package/lib/module/index.js +1 -0
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/mocks/index.js +18 -2
  106. package/lib/module/mocks/index.js.map +1 -1
  107. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  108. package/lib/module/navigation/base-types.js +2 -0
  109. package/lib/module/navigation/base-types.js.map +1 -0
  110. package/lib/module/navigation/createTrueSheetRouter.js +158 -100
  111. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  112. package/lib/module/navigation/expo-router/base-types.js +2 -0
  113. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  114. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +71 -0
  115. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
  116. package/lib/module/navigation/expo-router/index.js +18 -7
  117. package/lib/module/navigation/expo-router/index.js.map +1 -1
  118. package/lib/module/navigation/expo-router/types.js +4 -0
  119. package/lib/module/navigation/expo-router/types.js.map +1 -0
  120. package/lib/module/navigation/index.js.map +1 -1
  121. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  122. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  123. package/lib/module/scrollable.js +28 -0
  124. package/lib/module/scrollable.js.map +1 -0
  125. package/lib/module/web/constants.js +1 -1
  126. package/lib/module/web/constants.js.map +1 -1
  127. package/lib/module/web/keyboard.js +69 -0
  128. package/lib/module/web/keyboard.js.map +1 -0
  129. package/lib/module/web/layout.js +168 -0
  130. package/lib/module/web/layout.js.map +1 -0
  131. package/lib/module/web/vaul/constants.js +9 -0
  132. package/lib/module/web/vaul/constants.js.map +1 -1
  133. package/lib/module/web/vaul/context.js +4 -0
  134. package/lib/module/web/vaul/context.js.map +1 -1
  135. package/lib/module/web/vaul/helpers.js +1 -0
  136. package/lib/module/web/vaul/helpers.js.map +1 -1
  137. package/lib/module/web/vaul/index.js +83 -21
  138. package/lib/module/web/vaul/index.js.map +1 -1
  139. package/lib/module/web/vaul/use-snap-points.js +57 -28
  140. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  141. package/lib/typescript/src/TrueSheet.d.ts +18 -1
  142. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  143. package/lib/typescript/src/TrueSheet.types.d.ts +209 -35
  144. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  145. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  146. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  147. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  148. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  149. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  150. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  151. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  152. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  153. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  154. package/lib/typescript/src/TrueSheetProvider.web.d.ts +5 -1
  155. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
  156. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  157. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  158. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -3
  159. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  160. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  161. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  162. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +76 -65
  163. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  164. package/lib/typescript/src/index.d.ts +1 -0
  165. package/lib/typescript/src/index.d.ts.map +1 -1
  166. package/lib/typescript/src/mocks/index.d.ts +4 -0
  167. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  168. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  169. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  170. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  171. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  172. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +36 -10
  173. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  174. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  175. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  176. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +33 -0
  177. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
  178. package/lib/typescript/src/navigation/expo-router/index.d.ts +9 -6
  179. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  180. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  181. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  182. package/lib/typescript/src/navigation/index.d.ts +3 -4
  183. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  184. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  185. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  186. package/lib/typescript/src/navigation/types.d.ts +43 -6
  187. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  188. package/lib/typescript/src/scrollable.d.ts +2 -0
  189. package/lib/typescript/src/scrollable.d.ts.map +1 -0
  190. package/lib/typescript/src/web/constants.d.ts +1 -1
  191. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  192. package/lib/typescript/src/web/keyboard.d.ts +15 -0
  193. package/lib/typescript/src/web/keyboard.d.ts.map +1 -0
  194. package/lib/typescript/src/web/layout.d.ts +20 -0
  195. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  196. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  197. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  198. package/lib/typescript/src/web/vaul/context.d.ts +5 -1
  199. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  200. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  201. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  202. package/lib/typescript/src/web/vaul/index.d.ts +20 -1
  203. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  204. package/lib/typescript/src/web/vaul/types.d.ts +9 -0
  205. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -1
  206. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +6 -4
  207. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  208. package/package.json +18 -8
  209. package/react-native.config.js +1 -0
  210. package/src/TrueSheet.tsx +257 -62
  211. package/src/TrueSheet.types.ts +225 -35
  212. package/src/TrueSheet.web.tsx +445 -333
  213. package/src/TrueSheetBackground.tsx +17 -0
  214. package/src/TrueSheetOverlay.tsx +24 -0
  215. package/src/TrueSheetOverlay.web.tsx +33 -0
  216. package/src/TrueSheetPeek.web.tsx +25 -23
  217. package/src/TrueSheetProvider.web.tsx +22 -13
  218. package/src/detentBackgrounds.ts +24 -0
  219. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  220. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  221. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  222. package/src/fabric/TrueSheetViewNativeComponent.ts +89 -73
  223. package/src/index.ts +1 -0
  224. package/src/mocks/index.ts +17 -2
  225. package/src/mocks/navigationExpoRouter.ts +6 -5
  226. package/src/navigation/base-types.ts +20 -0
  227. package/src/navigation/createTrueSheetRouter.ts +220 -144
  228. package/src/navigation/expo-router/base-types.ts +18 -0
  229. package/src/navigation/expo-router/extendTrueSheetRouter.ts +99 -0
  230. package/src/navigation/expo-router/index.ts +41 -30
  231. package/src/navigation/expo-router/types.ts +116 -0
  232. package/src/navigation/index.ts +6 -7
  233. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  234. package/src/navigation/types.ts +81 -23
  235. package/src/scrollable.ts +25 -0
  236. package/src/web/constants.ts +1 -1
  237. package/src/web/keyboard.ts +79 -0
  238. package/src/web/layout.ts +196 -0
  239. package/src/web/vaul/constants.ts +9 -0
  240. package/src/web/vaul/context.ts +7 -1
  241. package/src/web/vaul/helpers.ts +5 -0
  242. package/src/web/vaul/index.tsx +113 -16
  243. package/src/web/vaul/types.ts +10 -0
  244. package/src/web/vaul/use-snap-points.ts +86 -33
@@ -0,0 +1,148 @@
1
+ package com.lodev09.truesheet.core
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.view.MotionEvent
5
+ import android.view.View
6
+ import android.widget.FrameLayout
7
+ import com.facebook.react.uimanager.JSPointerDispatcher
8
+ import com.facebook.react.uimanager.JSTouchDispatcher
9
+ import com.facebook.react.uimanager.RootView
10
+ import com.facebook.react.uimanager.ThemedReactContext
11
+ import com.facebook.react.uimanager.TouchTargetHelper
12
+ import com.facebook.react.uimanager.events.EventDispatcher
13
+ import com.lodev09.truesheet.utils.TouchEventDeduper
14
+
15
+ interface TrueSheetOverlayRootViewDelegate {
16
+ val eventDispatcher: EventDispatcher?
17
+ fun overlayRootViewDidChangeSize(width: Int, height: Int)
18
+ }
19
+
20
+ /**
21
+ * Window-level view hosting an overlay's children, attached to the activity's
22
+ * content view above the sheet coordinators.
23
+ *
24
+ * Implements RootView to dispatch touches to JS. Touches that miss every child
25
+ * fall through to the views beneath.
26
+ */
27
+ @SuppressLint("ViewConstructor")
28
+ class TrueSheetOverlayRootView(private val reactContext: ThemedReactContext) :
29
+ FrameLayout(reactContext),
30
+ RootView {
31
+
32
+ var delegate: TrueSheetOverlayRootViewDelegate? = null
33
+
34
+ private val eventDispatcher: EventDispatcher?
35
+ get() = delegate?.eventDispatcher
36
+
37
+ private val jsTouchDispatcher = JSTouchDispatcher(this)
38
+ private val jsPointerDispatcher = JSPointerDispatcher(this)
39
+ private val touchDeduper = TouchEventDeduper()
40
+
41
+ // Whether the current touch stream landed on a child
42
+ private var isHandlingStream = false
43
+
44
+ init {
45
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
46
+
47
+ // React children draw outside their bounds (shadows, overflow)
48
+ clipChildren = false
49
+ clipToPadding = false
50
+
51
+ // Draws and receives touches above the sheet coordinators regardless of add order
52
+ translationZ = OVERLAY_Z
53
+ }
54
+
55
+ // Children are measured and positioned by Fabric — a FrameLayout pass would stretch them to match_parent
56
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
57
+ setMeasuredDimension(
58
+ MeasureSpec.getSize(widthMeasureSpec),
59
+ MeasureSpec.getSize(heightMeasureSpec)
60
+ )
61
+ }
62
+
63
+ override fun onLayout(
64
+ changed: Boolean,
65
+ left: Int,
66
+ top: Int,
67
+ right: Int,
68
+ bottom: Int
69
+ ) {
70
+ }
71
+
72
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
73
+ super.onSizeChanged(w, h, oldw, oldh)
74
+ delegate?.overlayRootViewDidChangeSize(w, h)
75
+ }
76
+
77
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
78
+ if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
79
+ // The path ends with this root, so a touch that misses every child resolves to it
80
+ val target = TouchTargetHelper
81
+ .findTargetPathAndCoordinatesForTouch(ev.x, ev.y, this, FloatArray(2))
82
+ .firstNotNullOfOrNull { it.getView() }
83
+ isHandlingStream = target != null && target !== this
84
+ }
85
+ if (!isHandlingStream) return false
86
+ return super.dispatchTouchEvent(ev)
87
+ }
88
+
89
+ // ==================== RootView Implementation ====================
90
+
91
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
92
+ eventDispatcher?.let { dispatcher ->
93
+ if (touchDeduper.shouldDispatch(event)) {
94
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
95
+ }
96
+ jsPointerDispatcher.handleMotionEvent(event, dispatcher, true)
97
+ }
98
+ return super.onInterceptTouchEvent(event)
99
+ }
100
+
101
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
102
+ // Mirrors ReactSurfaceView: keep receiving onInterceptTouchEvent so
103
+ // jsTouchDispatcher sees the gesture end, but forward the request up the tree.
104
+ parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
105
+ }
106
+
107
+ @SuppressLint("ClickableViewAccessibility")
108
+ override fun onTouchEvent(event: MotionEvent): Boolean {
109
+ eventDispatcher?.let { dispatcher ->
110
+ if (touchDeduper.shouldDispatch(event)) {
111
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
112
+ }
113
+ jsPointerDispatcher.handleMotionEvent(event, dispatcher, false)
114
+ }
115
+ super.onTouchEvent(event)
116
+ return true
117
+ }
118
+
119
+ override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
120
+ eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, true) }
121
+ return super.onHoverEvent(event)
122
+ }
123
+
124
+ override fun onHoverEvent(event: MotionEvent): Boolean {
125
+ eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, false) }
126
+ return super.onHoverEvent(event)
127
+ }
128
+
129
+ override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
130
+ eventDispatcher?.let { dispatcher ->
131
+ jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher)
132
+ jsPointerDispatcher.onChildStartedNativeGesture(childView, ev, dispatcher)
133
+ }
134
+ }
135
+
136
+ override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
137
+ eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
138
+ jsPointerDispatcher.onChildEndedNativeGesture()
139
+ }
140
+
141
+ override fun handleException(t: Throwable) {
142
+ reactContext.reactApplicationContext.handleException(RuntimeException(t))
143
+ }
144
+
145
+ companion object {
146
+ private const val OVERLAY_Z = 1000f
147
+ }
148
+ }
@@ -13,23 +13,31 @@ object TrueSheetStackManager {
13
13
  private val presentedSheetStack = mutableListOf<TrueSheetView>()
14
14
 
15
15
  /**
16
- * Gets the parent sheet at the given index, if any.
17
- * Only returns a parent if it's in the same container.
16
+ * Returns the topmost presented and visible sheet, skipping sheets that are dismissing.
17
+ * A dismissing sheet can't parent a new one: its dim would suppress the child's own dim
18
+ * and then disappear with it, leaving the child undimmed.
18
19
  * Must be called within synchronized block.
19
20
  */
20
- private fun getParentSheetAt(index: Int, rootContainer: ViewGroup?): TrueSheetView? {
21
- if (index <= 0) return null
22
- return presentedSheetStack[index - 1].takeIf { it.rootContainerView == rootContainer }
23
- }
21
+ private fun findTopmostSheet(): TrueSheetView? =
22
+ presentedSheetStack.lastOrNull {
23
+ it.viewController.run { isPresented && isSheetVisible && !isBeingDismissed }
24
+ }
24
25
 
25
26
  /**
26
- * Returns the topmost presented and visible sheet.
27
+ * Returns the topmost sheet hidden by screen navigation that was presented from the same
28
+ * screen as the given sheet. Sheets presented on the covering screen must not parent to it,
29
+ * or the hidden sheet would receive focus and blur events while it can't be seen.
27
30
  * Must be called within synchronized block.
28
31
  */
29
- private fun findTopmostSheet(): TrueSheetView? =
30
- presentedSheetStack.lastOrNull {
31
- it.viewController.isPresented && it.viewController.isSheetVisible
32
+ private fun findScreenHiddenSheet(sheetView: TrueSheetView): TrueSheetView? {
33
+ val screenTag = RNScreensEventObserver.findPresenterScreenTag(sheetView)
34
+ if (screenTag == 0) return null
35
+
36
+ return presentedSheetStack.lastOrNull {
37
+ it.viewController.run { isPresented && wasHiddenByScreen && !isBeingDismissed } &&
38
+ it.screensEventObserver?.presenterScreenTag == screenTag
32
39
  }
40
+ }
33
41
 
34
42
  /**
35
43
  * Registers a sheet in the stack and returns its parent sheet if any.
@@ -38,7 +46,8 @@ object TrueSheetStackManager {
38
46
  @JvmStatic
39
47
  fun registerSheet(sheetView: TrueSheetView): TrueSheetView? {
40
48
  synchronized(presentedSheetStack) {
41
- val parentSheet = findTopmostSheet()?.takeIf { it.rootContainerView == sheetView.rootContainerView }
49
+ val parentSheet = (findTopmostSheet() ?: findScreenHiddenSheet(sheetView))
50
+ ?.takeIf { it.rootContainerView == sheetView.rootContainerView }
42
51
 
43
52
  if (!presentedSheetStack.contains(sheetView)) {
44
53
  presentedSheetStack.add(sheetView)
@@ -60,24 +69,20 @@ object TrueSheetStackManager {
60
69
 
61
70
  /**
62
71
  * Updates parent sheet translation based on the child sheet's position.
63
- * Only affects parent sheets in the same container.
64
72
  */
65
73
  @JvmStatic
66
74
  fun updateParentTranslation(sheetView: TrueSheetView) {
67
- synchronized(presentedSheetStack) {
68
- val index = presentedSheetStack.indexOf(sheetView)
69
- val parentSheet = getParentSheetAt(index, sheetView.rootContainerView) ?: return
70
-
71
- // Post to ensure layout is complete before reading position
72
- sheetView.viewController.post {
73
- val childMinSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(0)
74
- val childCurrentSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(
75
- sheetView.viewController.currentDetentIndex
76
- )
77
- // Cap to minimum detent position
78
- val childSheetTop = maxOf(childMinSheetTop, childCurrentSheetTop)
79
- parentSheet.updateTranslationForChild(childSheetTop)
80
- }
75
+ val parentSheet = sheetView.viewController.parentSheetView ?: return
76
+
77
+ // Post to ensure layout is complete before reading position
78
+ sheetView.viewController.post {
79
+ val childMinSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(0)
80
+ val childCurrentSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(
81
+ sheetView.viewController.currentDetentIndex
82
+ )
83
+ // Cap to minimum detent position
84
+ val childSheetTop = maxOf(childMinSheetTop, childCurrentSheetTop)
85
+ parentSheet.updateTranslationForChild(childSheetTop)
81
86
  }
82
87
  }
83
88
 
@@ -111,18 +116,6 @@ object TrueSheetStackManager {
111
116
  }
112
117
  }
113
118
 
114
- /**
115
- * Gets the parent sheet of the given sheet, if any.
116
- * Only returns a parent if it's in the same container.
117
- */
118
- @JvmStatic
119
- fun getParentSheet(sheetView: TrueSheetView): TrueSheetView? {
120
- synchronized(presentedSheetStack) {
121
- val index = presentedSheetStack.indexOf(sheetView)
122
- return getParentSheetAt(index, sheetView.rootContainerView)
123
- }
124
- }
125
-
126
119
  /**
127
120
  * Returns true if the given sheet is the topmost presented sheet in its container.
128
121
  */
@@ -168,7 +161,7 @@ object TrueSheetStackManager {
168
161
  return
169
162
  }
170
163
 
171
- // The dimmed sheet and any sheets above it stay accessible
164
+ // The dimmed sheet, any sheets above it, and overlays (rendered above every sheet) stay accessible
172
165
  val accessibleCoordinators = presentedSheetStack
173
166
  .subList(topDimmedIndex, presentedSheetStack.size)
174
167
  .mapNotNull { it.viewController.coordinatorLayout }
@@ -176,7 +169,7 @@ object TrueSheetStackManager {
176
169
 
177
170
  for (i in 0 until root.childCount) {
178
171
  val child = root.getChildAt(i)
179
- if (child in accessibleCoordinators) {
172
+ if (child in accessibleCoordinators || child is TrueSheetOverlayRootView) {
180
173
  savedBackgroundImportance.remove(child)?.let { child.importantForAccessibility = it }
181
174
  continue
182
175
  }
@@ -93,6 +93,21 @@ class DidDismissEvent(surfaceId: Int, viewId: Int) : Event<DidDismissEvent>(surf
93
93
  }
94
94
  }
95
95
 
96
+ /**
97
+ * Fired when the user tries to dismiss a non-dismissible sheet
98
+ */
99
+ class DismissAttemptEvent(surfaceId: Int, viewId: Int) : Event<DismissAttemptEvent>(surfaceId, viewId) {
100
+
101
+ override fun getEventName(): String = EVENT_NAME
102
+
103
+ override fun getEventData(): WritableMap = Arguments.createMap()
104
+
105
+ companion object {
106
+ const val EVENT_NAME = "topDismissAttempt"
107
+ const val REGISTRATION_NAME = "onDismissAttempt"
108
+ }
109
+ }
110
+
96
111
  /**
97
112
  * Fired when the sheet visibility changes due to screen transitions
98
113
  */
@@ -0,0 +1,19 @@
1
+ package com.lodev09.truesheet.utils
2
+
3
+ import android.content.Context
4
+ import android.content.res.Configuration
5
+ import com.facebook.react.bridge.ColorPropConverter
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.bridge.ReadableType
8
+
9
+ /**
10
+ * Resolves a processed color (number or `PlatformColor` map) at [key], or null when missing.
11
+ */
12
+ fun ReadableMap.getColorOrNull(key: String, context: Context): Int? {
13
+ if (!hasKey(key) || isNull(key)) return null
14
+ val value = if (getType(key) == ReadableType.Map) getMap(key) else getDouble(key)
15
+ return ColorPropConverter.getColor(value, context)
16
+ }
17
+
18
+ val Context.isDarkMode: Boolean
19
+ get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
@@ -3,6 +3,7 @@ package com.lodev09.truesheet.utils
3
3
  import android.content.Context
4
4
  import android.os.Build
5
5
  import android.view.View
6
+ import android.view.WindowInsets
6
7
  import android.view.inputmethod.InputMethodManager
7
8
  import androidx.core.view.ViewCompat
8
9
  import androidx.core.view.WindowInsetsAnimationCompat
@@ -19,7 +20,7 @@ object KeyboardUtils {
19
20
  return isKeyboardVisible(rootView)
20
21
  }
21
22
 
22
- private fun isKeyboardVisible(view: View): Boolean {
23
+ fun isKeyboardVisible(view: View): Boolean {
23
24
  val insets = ViewCompat.getRootWindowInsets(view) ?: return false
24
25
  return insets.isVisible(WindowInsetsCompat.Type.ime())
25
26
  }
@@ -36,10 +37,22 @@ object KeyboardUtils {
36
37
  * Dismisses the soft keyboard with an optional callback when the animation completes.
37
38
  */
38
39
  fun dismiss(view: View, onComplete: (() -> Unit)?) {
40
+ if (!isKeyboardVisible(view)) {
41
+ onComplete?.invoke()
42
+ return
43
+ }
44
+
39
45
  val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
40
46
  val focusedView = view.rootView.findFocus()
41
47
 
42
- if (focusedView == null || !isKeyboardVisible(view)) {
48
+ // A keyboard can stay up with nothing focused, e.g. a blur that lands after focus was restored,
49
+ // so hide it through the window instead of treating it as already hidden.
50
+ if (focusedView == null) {
51
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
52
+ view.windowInsetsController?.hide(WindowInsets.Type.ime())
53
+ } else {
54
+ imm?.hideSoftInputFromWindow(view.windowToken, 0)
55
+ }
43
56
  onComplete?.invoke()
44
57
  return
45
58
  }
@@ -104,18 +104,6 @@ object ScreenUtils {
104
104
  */
105
105
  fun getScreenHeight(reactContext: ReactContext): Int = reactContext.resources.displayMetrics.heightPixels
106
106
 
107
- /**
108
- * Get the location of a view in screen coordinates
109
- *
110
- * @param view The view to get screen location for
111
- * @return IntArray with [x, y] coordinates in screen space
112
- */
113
- fun getScreenLocation(view: View): IntArray {
114
- val location = IntArray(2)
115
- view.getLocationOnScreen(location)
116
- return location
117
- }
118
-
119
107
  /**
120
108
  * Returns true if the device is a phone in portrait orientation.
121
109
  */
@@ -2,8 +2,10 @@ package com.lodev09.truesheet.utils
2
2
 
3
3
  import android.view.View
4
4
  import android.view.ViewGroup
5
+ import android.view.ViewParent
5
6
  import android.widget.ScrollView
6
7
  import androidx.core.widget.NestedScrollView
8
+ import com.facebook.react.uimanager.ThemedReactContext
7
9
 
8
10
  fun View.isDescendantOf(ancestor: View): Boolean {
9
11
  if (!isAttachedToWindow) return false
@@ -15,16 +17,26 @@ fun View.isDescendantOf(ancestor: View): Boolean {
15
17
  return false
16
18
  }
17
19
 
18
- fun ViewGroup.smoothScrollBy(dx: Int, dy: Int) {
19
- when (this) {
20
- is ScrollView -> smoothScrollBy(dx, dy)
21
- is NestedScrollView -> smoothScrollBy(dx, dy)
22
- }
23
- }
24
-
25
20
  fun ViewGroup.smoothScrollTo(x: Int, y: Int) {
26
21
  when (this) {
27
22
  is ScrollView -> smoothScrollTo(x, y)
28
23
  is NestedScrollView -> smoothScrollTo(x, y)
29
24
  }
30
25
  }
26
+
27
+ /**
28
+ * The content view hosting this view — the activity's, or a Modal dialog's.
29
+ * Window-level views (sheet coordinators, overlays) attach here.
30
+ */
31
+ fun View.findRootContainerView(reactContext: ThemedReactContext): ViewGroup? {
32
+ var current: ViewParent? = parent
33
+
34
+ while (current != null) {
35
+ if (current is ViewGroup && current.id == android.R.id.content) {
36
+ return current
37
+ }
38
+ current = current.parent
39
+ }
40
+
41
+ return reactContext.currentActivity?.findViewById(android.R.id.content)
42
+ }
@@ -5,6 +5,7 @@
5
5
  #include <jsi/jsi.h>
6
6
  #include <react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h>
7
7
  #include <react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h>
8
+ #include <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h>
8
9
  #include <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
9
10
 
10
11
  namespace facebook {
@@ -58,7 +58,8 @@ static jboolean updateStateImmediate(
58
58
  static jboolean updateFooterStateImmediate(
59
59
  jni::alias_ref<jclass> /*clazz*/,
60
60
  jni::alias_ref<jobject> stateWrapper,
61
- jfloat bottomInset) {
61
+ jfloat bottomInset,
62
+ jfloat translateY) {
62
63
  auto state = getStateFromWrapper(stateWrapper);
63
64
  if (!state) {
64
65
  return JNI_FALSE;
@@ -67,15 +68,44 @@ static jboolean updateFooterStateImmediate(
67
68
  auto concreteState =
68
69
  std::static_pointer_cast<const ConcreteState<TrueSheetFooterViewState>>(state);
69
70
 
70
- // Copy the latest data so fields this bridge doesn't set survive the update
71
- TrueSheetFooterViewState newState = concreteState->getData();
72
- newState.bottomInset = bottomInset;
73
- newState.initialized = true;
74
- concreteState->updateState(std::move(newState), EventQueue::UpdateMode::unstable_Immediate);
71
+ // Carries the translation too — RN's EventQueue drops a still-queued update
72
+ // for the same view (see updateFooterTranslation) when this one is enqueued
73
+ concreteState->updateState(
74
+ [bottomInset, translateY](const TrueSheetFooterViewState &oldData) -> StateData::Shared {
75
+ auto newData = oldData;
76
+ newData.bottomInset = bottomInset;
77
+ newData.translateY = translateY;
78
+ newData.initialized = true;
79
+ return std::make_shared<const TrueSheetFooterViewState>(newData);
80
+ },
81
+ EventQueue::UpdateMode::unstable_Immediate);
75
82
 
76
83
  return JNI_TRUE;
77
84
  }
78
85
 
86
+ // Mirrors the footer's resting translation into its state so measure()
87
+ // matches where it's drawn. Async — it only feeds measure().
88
+ static void updateFooterTranslation(
89
+ jni::alias_ref<jclass> /*clazz*/,
90
+ jni::alias_ref<jobject> stateWrapper,
91
+ jfloat translateY) {
92
+ auto state = getStateFromWrapper(stateWrapper);
93
+ if (!state) {
94
+ return;
95
+ }
96
+
97
+ auto concreteState =
98
+ std::static_pointer_cast<const ConcreteState<TrueSheetFooterViewState>>(state);
99
+
100
+ concreteState->updateState(
101
+ [translateY](const TrueSheetFooterViewState &oldData) -> StateData::Shared {
102
+ auto newData = oldData;
103
+ newData.translateY = translateY;
104
+ return std::make_shared<const TrueSheetFooterViewState>(newData);
105
+ },
106
+ EventQueue::UpdateMode::Asynchronous);
107
+ }
108
+
79
109
  // Publishes the precalculated bottom safe-area inset so a late-mounted
80
110
  // footer's first layout is already padded (see TrueSheetFooterViewShadowNode).
81
111
  static void setBottomSafeArea(jni::alias_ref<jclass> /*clazz*/, jfloat insetDp) {
@@ -90,6 +120,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /*reserved*/) {
90
120
  ->registerNatives({
91
121
  makeNativeMethod("nativeUpdateState", facebook::react::updateStateImmediate),
92
122
  makeNativeMethod("nativeUpdateFooterState", facebook::react::updateFooterStateImmediate),
123
+ makeNativeMethod("nativeUpdateFooterTranslation", facebook::react::updateFooterTranslation),
93
124
  makeNativeMethod("nativeSetBottomSafeArea", facebook::react::setBottomSafeArea),
94
125
  });
95
126
  });
@@ -11,14 +11,6 @@ namespace facebook::react {
11
11
  class TrueSheetContentViewComponentDescriptor final
12
12
  : public ConcreteComponentDescriptor<TrueSheetContentViewShadowNode> {
13
13
  using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
14
-
15
- void adopt(ShadowNode &shadowNode) const override {
16
- auto &concreteShadowNode =
17
- static_cast<TrueSheetContentViewShadowNode &>(shadowNode);
18
- concreteShadowNode.adjustLayoutWithState();
19
-
20
- ConcreteComponentDescriptor::adopt(shadowNode);
21
- }
22
14
  };
23
15
 
24
- } // namespace facebook::react
16
+ } // namespace facebook::react