@lodev09/react-native-true-sheet 4.0.0-beta.2 → 4.0.0-beta.20

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 (203) hide show
  1. package/README.md +25 -4
  2. package/android/build.gradle +22 -1
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +302 -184
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +21 -110
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +116 -0
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +119 -50
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +360 -163
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +54 -12
  14. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
  15. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
  16. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  19. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  20. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  21. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  22. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  23. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  24. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  25. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
  26. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  27. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  28. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  29. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  30. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
  31. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  45. package/ios/TrueSheetContainerView.h +27 -7
  46. package/ios/TrueSheetContainerView.mm +31 -11
  47. package/ios/TrueSheetContentView.h +43 -15
  48. package/ios/TrueSheetContentView.mm +211 -189
  49. package/ios/TrueSheetFooterView.h +6 -0
  50. package/ios/TrueSheetFooterView.mm +93 -17
  51. package/ios/TrueSheetModule.h +2 -7
  52. package/ios/TrueSheetModule.mm +7 -16
  53. package/ios/TrueSheetOverlayView.h +32 -0
  54. package/ios/TrueSheetOverlayView.mm +161 -0
  55. package/ios/TrueSheetView.mm +212 -55
  56. package/ios/TrueSheetViewController.h +31 -3
  57. package/ios/TrueSheetViewController.mm +393 -94
  58. package/ios/core/TrueSheetBlurView.h +0 -2
  59. package/ios/core/TrueSheetBlurView.mm +3 -36
  60. package/ios/core/TrueSheetDetentCalculator.h +7 -1
  61. package/ios/core/TrueSheetDetentCalculator.mm +27 -4
  62. package/ios/core/TrueSheetGrabberView.mm +13 -3
  63. package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
  64. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  65. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  66. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  67. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  68. package/ios/tvos/TrueSheetStubs.mm +12 -0
  69. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  70. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  71. package/lib/module/TrueSheet.js +173 -39
  72. package/lib/module/TrueSheet.js.map +1 -1
  73. package/lib/module/TrueSheet.web.js +218 -243
  74. package/lib/module/TrueSheet.web.js.map +1 -1
  75. package/lib/module/TrueSheetBackground.js +15 -0
  76. package/lib/module/TrueSheetBackground.js.map +1 -0
  77. package/lib/module/TrueSheetOverlay.js +30 -0
  78. package/lib/module/TrueSheetOverlay.js.map +1 -0
  79. package/lib/module/TrueSheetOverlay.web.js +35 -0
  80. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  81. package/lib/module/TrueSheetPeek.web.js +20 -15
  82. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  83. package/lib/module/detentBackgrounds.js +21 -0
  84. package/lib/module/detentBackgrounds.js.map +1 -0
  85. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  86. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  87. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +14 -10
  88. package/lib/module/index.js +1 -0
  89. package/lib/module/index.js.map +1 -1
  90. package/lib/module/mocks/index.js +18 -2
  91. package/lib/module/mocks/index.js.map +1 -1
  92. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  93. package/lib/module/navigation/base-types.js +2 -0
  94. package/lib/module/navigation/base-types.js.map +1 -0
  95. package/lib/module/navigation/createTrueSheetRouter.js +0 -9
  96. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  97. package/lib/module/navigation/expo-router/base-types.js +2 -0
  98. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  99. package/lib/module/navigation/expo-router/index.js +10 -7
  100. package/lib/module/navigation/expo-router/index.js.map +1 -1
  101. package/lib/module/navigation/expo-router/types.js +4 -0
  102. package/lib/module/navigation/expo-router/types.js.map +1 -0
  103. package/lib/module/navigation/index.js.map +1 -1
  104. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  105. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  106. package/lib/module/web/constants.js +1 -1
  107. package/lib/module/web/constants.js.map +1 -1
  108. package/lib/module/web/layout.js +168 -0
  109. package/lib/module/web/layout.js.map +1 -0
  110. package/lib/module/web/vaul/constants.js +4 -0
  111. package/lib/module/web/vaul/constants.js.map +1 -1
  112. package/lib/module/web/vaul/context.js.map +1 -1
  113. package/lib/module/web/vaul/helpers.js +1 -0
  114. package/lib/module/web/vaul/helpers.js.map +1 -1
  115. package/lib/module/web/vaul/index.js +44 -6
  116. package/lib/module/web/vaul/index.js.map +1 -1
  117. package/lib/module/web/vaul/use-snap-points.js +6 -8
  118. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  119. package/lib/typescript/src/TrueSheet.d.ts +6 -0
  120. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  121. package/lib/typescript/src/TrueSheet.types.d.ts +135 -34
  122. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  123. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  124. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  125. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  126. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  127. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  128. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  129. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  130. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  131. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  132. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  133. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  134. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  135. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  136. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +11 -7
  137. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  138. package/lib/typescript/src/index.d.ts +1 -0
  139. package/lib/typescript/src/index.d.ts.map +1 -1
  140. package/lib/typescript/src/mocks/index.d.ts +4 -0
  141. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  142. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  143. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  144. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  145. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  146. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
  147. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  148. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  149. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  150. package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
  151. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  152. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  153. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  154. package/lib/typescript/src/navigation/index.d.ts +3 -4
  155. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  156. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  157. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  158. package/lib/typescript/src/navigation/types.d.ts +41 -5
  159. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  160. package/lib/typescript/src/web/constants.d.ts +1 -1
  161. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  162. package/lib/typescript/src/web/layout.d.ts +19 -0
  163. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  164. package/lib/typescript/src/web/vaul/constants.d.ts +1 -0
  165. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  166. package/lib/typescript/src/web/vaul/context.d.ts +1 -0
  167. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  168. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  169. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  170. package/lib/typescript/src/web/vaul/index.d.ts +7 -1
  171. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  172. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  173. package/package.json +17 -8
  174. package/react-native.config.js +1 -0
  175. package/src/TrueSheet.tsx +206 -48
  176. package/src/TrueSheet.types.ts +142 -35
  177. package/src/TrueSheet.web.tsx +270 -290
  178. package/src/TrueSheetBackground.tsx +17 -0
  179. package/src/TrueSheetOverlay.tsx +24 -0
  180. package/src/TrueSheetOverlay.web.tsx +33 -0
  181. package/src/TrueSheetPeek.web.tsx +25 -23
  182. package/src/detentBackgrounds.ts +24 -0
  183. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  184. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  185. package/src/fabric/TrueSheetViewNativeComponent.ts +14 -10
  186. package/src/index.ts +1 -0
  187. package/src/mocks/index.ts +17 -2
  188. package/src/mocks/navigationExpoRouter.ts +6 -5
  189. package/src/navigation/base-types.ts +20 -0
  190. package/src/navigation/createTrueSheetRouter.ts +5 -33
  191. package/src/navigation/expo-router/base-types.ts +18 -0
  192. package/src/navigation/expo-router/index.ts +28 -30
  193. package/src/navigation/expo-router/types.ts +116 -0
  194. package/src/navigation/index.ts +6 -7
  195. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  196. package/src/navigation/types.ts +75 -22
  197. package/src/web/constants.ts +1 -1
  198. package/src/web/layout.ts +196 -0
  199. package/src/web/vaul/constants.ts +4 -0
  200. package/src/web/vaul/context.ts +1 -0
  201. package/src/web/vaul/helpers.ts +5 -0
  202. package/src/web/vaul/index.tsx +67 -5
  203. package/src/web/vaul/use-snap-points.ts +6 -14
@@ -0,0 +1,137 @@
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
+ // Draws and receives touches above the sheet coordinators regardless of add order
48
+ translationZ = OVERLAY_Z
49
+ }
50
+
51
+ // Children are positioned by Fabric — a FrameLayout pass would stretch them to match_parent
52
+ override fun onLayout(
53
+ changed: Boolean,
54
+ left: Int,
55
+ top: Int,
56
+ right: Int,
57
+ bottom: Int
58
+ ) {
59
+ }
60
+
61
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
62
+ super.onSizeChanged(w, h, oldw, oldh)
63
+ delegate?.overlayRootViewDidChangeSize(w, h)
64
+ }
65
+
66
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
67
+ if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
68
+ // The path ends with this root, so a touch that misses every child resolves to it
69
+ val target = TouchTargetHelper
70
+ .findTargetPathAndCoordinatesForTouch(ev.x, ev.y, this, FloatArray(2))
71
+ .firstNotNullOfOrNull { it.getView() }
72
+ isHandlingStream = target != null && target !== this
73
+ }
74
+ if (!isHandlingStream) return false
75
+ return super.dispatchTouchEvent(ev)
76
+ }
77
+
78
+ // ==================== RootView Implementation ====================
79
+
80
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
81
+ eventDispatcher?.let { dispatcher ->
82
+ if (touchDeduper.shouldDispatch(event)) {
83
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
84
+ }
85
+ jsPointerDispatcher.handleMotionEvent(event, dispatcher, true)
86
+ }
87
+ return super.onInterceptTouchEvent(event)
88
+ }
89
+
90
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
91
+ // Mirrors ReactSurfaceView: keep receiving onInterceptTouchEvent so
92
+ // jsTouchDispatcher sees the gesture end, but forward the request up the tree.
93
+ parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
94
+ }
95
+
96
+ @SuppressLint("ClickableViewAccessibility")
97
+ override fun onTouchEvent(event: MotionEvent): Boolean {
98
+ eventDispatcher?.let { dispatcher ->
99
+ if (touchDeduper.shouldDispatch(event)) {
100
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
101
+ }
102
+ jsPointerDispatcher.handleMotionEvent(event, dispatcher, false)
103
+ }
104
+ super.onTouchEvent(event)
105
+ return true
106
+ }
107
+
108
+ override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
109
+ eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, true) }
110
+ return super.onHoverEvent(event)
111
+ }
112
+
113
+ override fun onHoverEvent(event: MotionEvent): Boolean {
114
+ eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, false) }
115
+ return super.onHoverEvent(event)
116
+ }
117
+
118
+ override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
119
+ eventDispatcher?.let { dispatcher ->
120
+ jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher)
121
+ jsPointerDispatcher.onChildStartedNativeGesture(childView, ev, dispatcher)
122
+ }
123
+ }
124
+
125
+ override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
126
+ eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
127
+ jsPointerDispatcher.onChildEndedNativeGesture()
128
+ }
129
+
130
+ override fun handleException(t: Throwable) {
131
+ reactContext.reactApplicationContext.handleException(RuntimeException(t))
132
+ }
133
+
134
+ companion object {
135
+ private const val OVERLAY_Z = 1000f
136
+ }
137
+ }
@@ -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
  */
@@ -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 {
@@ -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
@@ -1,6 +1,12 @@
1
1
  #include "TrueSheetContentViewShadowNode.h"
2
2
 
3
+ #include <folly/ScopeGuard.h>
3
4
  #include <react/renderer/components/view/conversions.h>
5
+ #include <react/renderer/core/ComponentDescriptor.h>
6
+ #include <react/renderer/core/LayoutConstraints.h>
7
+ #include <react/renderer/core/LayoutContext.h>
8
+
9
+ #include <limits>
4
10
 
5
11
  namespace facebook::react {
6
12
 
@@ -8,36 +14,110 @@ using namespace yoga;
8
14
 
9
15
  extern const char TrueSheetContentViewComponentName[] = "TrueSheetContentView";
10
16
 
11
- void TrueSheetContentViewShadowNode::adjustLayoutWithState() {
12
- ensureUnsealed();
13
-
14
- auto state = std::static_pointer_cast<
15
- const TrueSheetContentViewShadowNode::ConcreteState>(getState());
16
- auto stateData = state->getData();
17
-
18
- auto &props = getConcreteProps();
19
-
20
- // When a ScrollView is pinned under an auto detent, fill the container so
21
- // descendant flex layouts (flex:1 wrappers, the ScrollView itself) resolve
22
- // against a definite height and the viewport is bounded — natural layout is
23
- // circular there since the sheet height derives from the ScrollView's content
24
- // size instead (see ContentView's naturalHeight on each platform). For fixed
25
- // detents, content lays out naturally like a regular view.
26
- auto flexGrow = stateData.scrollableBounded
27
- ? FloatOptional{1.0f}
28
- : props.yogaStyle.flexGrow();
29
- auto flexShrink = stateData.scrollableBounded
30
- ? FloatOptional{1.0f}
31
- : props.yogaStyle.flexShrink();
32
-
33
- if (yogaNode_.style().flexGrow() != flexGrow ||
34
- yogaNode_.style().flexShrink() != flexShrink) {
35
- yoga::Style adjustedStyle = props.yogaStyle;
36
- adjustedStyle.setFlexGrow(flexGrow);
37
- adjustedStyle.setFlexShrink(flexShrink);
38
- yogaNode_.setStyle(adjustedStyle);
39
- yogaNode_.setDirty(true);
17
+ // The measurement pass re-enters layout() on a clone — guard so it doesn't
18
+ // measure again recursively.
19
+ static thread_local bool gIsMeasuringNaturalHeight = false;
20
+
21
+ TrueSheetContentViewShadowNode::TrueSheetContentViewShadowNode(
22
+ const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment)
23
+ : ConcreteViewShadowNode(sourceShadowNode, fragment),
24
+ measurement_(static_cast<const TrueSheetContentViewShadowNode &>(sourceShadowNode).measurement_),
25
+ measurementSourceChildren_(
26
+ static_cast<const TrueSheetContentViewShadowNode &>(sourceShadowNode).measurementSourceChildren_),
27
+ contentChanged_(static_cast<const TrueSheetContentViewShadowNode &>(sourceShadowNode).contentChanged_ ||
28
+ fragment.props || fragment.children) {
29
+ }
30
+
31
+ void TrueSheetContentViewShadowNode::layout(LayoutContext layoutContext) {
32
+ // The sizing tree needs Yoga's result, not Fabric state updates or events.
33
+ if (gIsMeasuringNaturalHeight) {
34
+ return;
35
+ }
36
+ ConcreteViewShadowNode::layout(layoutContext);
37
+ updateNaturalHeightIfNeeded(layoutContext);
38
+ }
39
+
40
+ // Every descendant must be detached from React's runtime references before
41
+ // Yoga clones it during measurement. Disabling transfer only on the root
42
+ // still lets descendant clones replace React's live nodes. Reuse unchanged
43
+ // measurement subtrees; Yoga owns copy-on-write when their layout changes.
44
+ std::shared_ptr<ShadowNode> TrueSheetContentViewShadowNode::cloneForMeasurement(
45
+ const ShadowNode &node, const Children &previousSourceChildren, const Children &previousMeasurementChildren) {
46
+ auto children = std::make_shared<Children>();
47
+ children->reserve(node.getChildren().size());
48
+ for (size_t index = 0; index < node.getChildren().size(); index++) {
49
+ const auto &child = node.getChildren()[index];
50
+ if (index < previousSourceChildren.size() && ShadowNode::sameFamily(*child, *previousSourceChildren[index])) {
51
+ const auto &previousSource = previousSourceChildren[index];
52
+ const auto &previousMeasurement = previousMeasurementChildren[index];
53
+ children->push_back(child == previousSource ? previousMeasurement
54
+ : cloneForMeasurement(*child, previousSource->getChildren(),
55
+ previousMeasurement->getChildren()));
56
+ } else {
57
+ children->push_back(cloneForMeasurement(*child, {}, {}));
58
+ }
59
+ }
60
+
61
+ auto clone = node.getComponentDescriptor().cloneShadowNode(
62
+ node, {.children = children, .state = node.getState(), .runtimeShadowNodeReference = false});
63
+ if (auto content = dynamic_cast<TrueSheetContentViewShadowNode *>(clone.get())) {
64
+ content->measurement_.reset();
65
+ content->measurementSourceChildren_.reset();
66
+ }
67
+ return clone;
68
+ }
69
+
70
+ // The natural height is the height the content wants when unbounded — the
71
+ // source for the auto detent. When the content's height derives from the
72
+ // container, the committed layout doesn't reflect it — lay out a clone of the
73
+ // subtree with an unconstrained height instead. Yoga respects the user's
74
+ // styles: an explicit-height ScrollView keeps its height while a flexible one
75
+ // expands to its content — no ScrollView discovery involved.
76
+ void TrueSheetContentViewShadowNode::updateNaturalHeightIfNeeded(const LayoutContext &layoutContext) {
77
+ auto stateData = getStateData();
78
+ auto size = getLayoutMetrics().frame.size;
79
+
80
+ Float naturalHeight = size.height;
81
+ // Auto-height content can also be capped by a shrinking ScrollView. Only
82
+ // a fixed, non-flexible height can be taken from the viewport layout.
83
+ if (yogaNode_.isNodeFlexible() || !getConcreteProps().yogaStyle.dimension(Dimension::Height).isPoints()) {
84
+ auto constraints = LayoutConstraints{};
85
+ constraints.minimumSize = {size.width, 0};
86
+ constraints.maximumSize = {size.width, std::numeric_limits<Float>::infinity()};
87
+ constraints.layoutDirection = getLayoutMetrics().layoutDirection;
88
+
89
+ auto measurementContext = layoutContext;
90
+ measurementContext.affectedNodes = nullptr;
91
+ auto errata = YGConfigGetErrata(&yogaConfig_);
92
+
93
+ // Yoga's viewport-only clones carry no new props or children. Reuse the
94
+ // intrinsic height across those layouts instead of measuring each drag frame.
95
+ if (contentChanged_ || !measurement_ || !(measurement_->constraints == constraints) ||
96
+ !(measurement_->context == measurementContext) || measurement_->errata != errata) {
97
+ gIsMeasuringNaturalHeight = true;
98
+ auto restoreMeasurement = folly::makeGuard([] { gIsMeasuringNaturalHeight = false; });
99
+ auto measurementNode =
100
+ measurement_ ? cloneForMeasurement(*this, *measurementSourceChildren_, measurement_->root->getChildren())
101
+ : cloneForMeasurement(*this, {}, {});
102
+ auto &measurement = static_cast<LayoutableShadowNode &>(*measurementNode);
103
+ measurement.layoutTree(measurementContext, constraints);
104
+ measurement_ = std::make_shared<const Measurement>(Measurement{.root = measurementNode,
105
+ .constraints = constraints,
106
+ .context = measurementContext,
107
+ .errata = errata,
108
+ .height = measurement.getLayoutMetrics().frame.size.height});
109
+ }
110
+ naturalHeight = measurement_->height;
111
+ } else {
112
+ measurement_.reset();
113
+ }
114
+ measurementSourceChildren_ = children_;
115
+ contentChanged_ = false;
116
+
117
+ if (stateData.naturalHeight != naturalHeight) {
118
+ stateData.naturalHeight = naturalHeight;
119
+ setStateData(std::move(stateData));
40
120
  }
41
121
  }
42
122
 
43
- } // namespace facebook::react
123
+ } // namespace facebook::react
@@ -5,6 +5,8 @@
5
5
  #include <react/renderer/components/TrueSheetSpec/Props.h>
6
6
  #include <react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h>
7
7
  #include <react/renderer/components/view/ConcreteViewShadowNode.h>
8
+ #include <react/renderer/core/LayoutConstraints.h>
9
+ #include <react/renderer/core/LayoutContext.h>
8
10
 
9
11
  namespace facebook::react {
10
12
 
@@ -14,15 +16,36 @@ JSI_EXPORT extern const char TrueSheetContentViewComponentName[];
14
16
  * `ShadowNode` for <TrueSheetContentView> component.
15
17
  */
16
18
  class JSI_EXPORT TrueSheetContentViewShadowNode final
17
- : public ConcreteViewShadowNode<
18
- TrueSheetContentViewComponentName,
19
- TrueSheetContentViewProps,
20
- TrueSheetContentViewEventEmitter,
21
- TrueSheetContentViewState> {
19
+ : public ConcreteViewShadowNode<TrueSheetContentViewComponentName, TrueSheetContentViewProps,
20
+ TrueSheetContentViewEventEmitter, TrueSheetContentViewState> {
22
21
  using ConcreteViewShadowNode::ConcreteViewShadowNode;
23
22
 
24
23
  public:
25
- void adjustLayoutWithState();
24
+ TrueSheetContentViewShadowNode(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment);
25
+
26
+ #pragma mark - LayoutableShadowNode
27
+
28
+ void layout(LayoutContext layoutContext) override;
29
+
30
+ private:
31
+ using Children = std::vector<std::shared_ptr<const ShadowNode>>;
32
+
33
+ struct Measurement {
34
+ std::shared_ptr<const ShadowNode> root;
35
+ LayoutConstraints constraints;
36
+ LayoutContext context;
37
+ YGErrata errata;
38
+ Float height;
39
+ };
40
+
41
+ std::shared_ptr<const Measurement> measurement_;
42
+ std::shared_ptr<const Children> measurementSourceChildren_;
43
+ bool contentChanged_{true};
44
+
45
+ static std::shared_ptr<ShadowNode> cloneForMeasurement(
46
+ const ShadowNode &node, const Children &previousSourceChildren, const Children &previousMeasurementChildren);
47
+
48
+ void updateNaturalHeightIfNeeded(const LayoutContext &layoutContext);
26
49
  };
27
50
 
28
- } // namespace facebook::react
51
+ } // namespace facebook::react
@@ -4,8 +4,8 @@ namespace facebook::react {
4
4
 
5
5
  #ifdef ANDROID
6
6
  folly::dynamic TrueSheetContentViewState::getDynamic() const {
7
- return folly::dynamic::object("scrollableBounded", scrollableBounded);
7
+ return folly::dynamic::object("naturalHeight", naturalHeight);
8
8
  }
9
9
  #endif
10
10
 
11
- } // namespace facebook::react
11
+ } // namespace facebook::react
@@ -1,5 +1,7 @@
1
1
  #pragma once
2
2
 
3
+ #include <react/renderer/graphics/Float.h>
4
+
3
5
  #ifdef ANDROID
4
6
  #include <folly/dynamic.h>
5
7
  #include <react/renderer/mapbuffer/MapBuffer.h>
@@ -10,21 +12,20 @@ namespace facebook::react {
10
12
 
11
13
  /*
12
14
  * State for <TrueSheetContentView> component.
13
- * Set from native when a ScrollView is pinned so the shadow node bounds the
14
- * content to the container via flexShrink (see TrueSheetContentViewShadowNode).
15
+ * `naturalHeight` is measured by the shadow node during layout — the height
16
+ * the content wants when unbounded (see TrueSheetContentViewShadowNode).
15
17
  */
16
18
  class TrueSheetContentViewState final {
17
19
  public:
18
20
  TrueSheetContentViewState() = default;
19
21
 
20
22
  #ifdef ANDROID
21
- TrueSheetContentViewState(
22
- TrueSheetContentViewState const &previousState,
23
- folly::dynamic data)
24
- : scrollableBounded(data["scrollableBounded"].getBool()) {}
23
+ TrueSheetContentViewState(TrueSheetContentViewState const &previousState, folly::dynamic data)
24
+ : naturalHeight(previousState.naturalHeight) {
25
+ }
25
26
  #endif
26
27
 
27
- bool scrollableBounded{false};
28
+ Float naturalHeight{0};
28
29
 
29
30
  #ifdef ANDROID
30
31
  folly::dynamic getDynamic() const;
@@ -34,4 +35,4 @@ class TrueSheetContentViewState final {
34
35
  #endif
35
36
  };
36
37
 
37
- } // namespace facebook::react
38
+ } // namespace facebook::react
@@ -8,17 +8,15 @@ namespace facebook::react {
8
8
  /*
9
9
  * Descriptor for <TrueSheetFooterView> component.
10
10
  */
11
- class TrueSheetFooterViewComponentDescriptor final
12
- : public ConcreteComponentDescriptor<TrueSheetFooterViewShadowNode> {
11
+ class TrueSheetFooterViewComponentDescriptor final : public ConcreteComponentDescriptor<TrueSheetFooterViewShadowNode> {
13
12
  using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
14
13
 
15
14
  void adopt(ShadowNode &shadowNode) const override {
16
- auto &concreteShadowNode =
17
- static_cast<TrueSheetFooterViewShadowNode &>(shadowNode);
15
+ auto &concreteShadowNode = static_cast<TrueSheetFooterViewShadowNode &>(shadowNode);
18
16
  concreteShadowNode.adjustLayoutWithState();
19
17
 
20
18
  ConcreteComponentDescriptor::adopt(shadowNode);
21
19
  }
22
20
  };
23
21
 
24
- } // namespace facebook::react
22
+ } // namespace facebook::react
@@ -13,8 +13,7 @@ extern const char TrueSheetFooterViewComponentName[] = "TrueSheetFooterView";
13
13
  void TrueSheetFooterViewShadowNode::adjustLayoutWithState() {
14
14
  ensureUnsealed();
15
15
 
16
- auto state = std::static_pointer_cast<
17
- const TrueSheetFooterViewShadowNode::ConcreteState>(getState());
16
+ auto state = std::static_pointer_cast<const TrueSheetFooterViewShadowNode::ConcreteState>(getState());
18
17
  auto stateData = state->getData();
19
18
 
20
19
  auto &props = getConcreteProps();
@@ -25,6 +24,13 @@ void TrueSheetFooterViewShadowNode::adjustLayoutWithState() {
25
24
  float bottomInset = stateData.bottomInset;
26
25
  if (!stateData.initialized && props.autoBottomInset) {
27
26
  bottomInset = TrueSheetInsets::bottomSafeArea();
27
+ if (bottomInset != stateData.bottomInset) {
28
+ // Record the seeded value so the mounted view reads exactly the inset
29
+ // baked into this layout — the global may change before mount.
30
+ auto seededData = stateData;
31
+ seededData.bottomInset = bottomInset;
32
+ setStateData(std::move(seededData));
33
+ }
28
34
  }
29
35
 
30
36
  // A footer pinned to the sheet's bottom edge owns the bottom safe-area
@@ -53,4 +59,4 @@ void TrueSheetFooterViewShadowNode::adjustLayoutWithState() {
53
59
  }
54
60
  }
55
61
 
56
- } // namespace facebook::react
62
+ } // namespace facebook::react
@@ -14,15 +14,12 @@ JSI_EXPORT extern const char TrueSheetFooterViewComponentName[];
14
14
  * `ShadowNode` for <TrueSheetFooterView> component.
15
15
  */
16
16
  class JSI_EXPORT TrueSheetFooterViewShadowNode final
17
- : public ConcreteViewShadowNode<
18
- TrueSheetFooterViewComponentName,
19
- TrueSheetFooterViewProps,
20
- TrueSheetFooterViewEventEmitter,
21
- TrueSheetFooterViewState> {
17
+ : public ConcreteViewShadowNode<TrueSheetFooterViewComponentName, TrueSheetFooterViewProps,
18
+ TrueSheetFooterViewEventEmitter, TrueSheetFooterViewState> {
22
19
  using ConcreteViewShadowNode::ConcreteViewShadowNode;
23
20
 
24
21
  public:
25
22
  void adjustLayoutWithState();
26
23
  };
27
24
 
28
- } // namespace facebook::react
25
+ } // namespace facebook::react
@@ -8,4 +8,4 @@ folly::dynamic TrueSheetFooterViewState::getDynamic() const {
8
8
  }
9
9
  #endif
10
10
 
11
- } // namespace facebook::react
11
+ } // namespace facebook::react
@@ -18,11 +18,9 @@ class TrueSheetFooterViewState final {
18
18
  TrueSheetFooterViewState() = default;
19
19
 
20
20
  #ifdef ANDROID
21
- TrueSheetFooterViewState(
22
- TrueSheetFooterViewState const &previousState,
23
- folly::dynamic data)
24
- : bottomInset(static_cast<float>(data["bottomInset"].getDouble())),
25
- initialized(true) {}
21
+ TrueSheetFooterViewState(TrueSheetFooterViewState const &previousState, folly::dynamic data)
22
+ : bottomInset(static_cast<float>(data["bottomInset"].getDouble())), initialized(true) {
23
+ }
26
24
  #endif
27
25
 
28
26
  float bottomInset{0};
@@ -38,4 +36,4 @@ class TrueSheetFooterViewState final {
38
36
  #endif
39
37
  };
40
38
 
41
- } // namespace facebook::react
39
+ } // namespace facebook::react
@@ -15,4 +15,4 @@ float TrueSheetInsets::bottomSafeArea() {
15
15
  return gBottomSafeArea.load(std::memory_order_relaxed);
16
16
  }
17
17
 
18
- } // namespace facebook::react
18
+ } // namespace facebook::react
@@ -13,4 +13,4 @@ class TrueSheetInsets {
13
13
  static float bottomSafeArea();
14
14
  };
15
15
 
16
- } // namespace facebook::react
16
+ } // namespace facebook::react