@lodev09/react-native-true-sheet 4.0.0-beta.6 → 4.0.0-beta.7

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 (48) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -0
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +3 -25
  6. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  7. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -0
  8. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  9. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
  10. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  11. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  12. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
  13. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
  14. package/ios/TrueSheetContentView.h +3 -1
  15. package/ios/TrueSheetContentView.mm +11 -0
  16. package/ios/TrueSheetOverlayView.h +32 -0
  17. package/ios/TrueSheetOverlayView.mm +141 -0
  18. package/ios/TrueSheetView.mm +17 -0
  19. package/ios/TrueSheetViewController.mm +27 -13
  20. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  21. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  22. package/ios/tvos/TrueSheetStubs.mm +12 -0
  23. package/lib/module/TrueSheetOverlay.js +30 -0
  24. package/lib/module/TrueSheetOverlay.js.map +1 -0
  25. package/lib/module/TrueSheetOverlay.web.js +35 -0
  26. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  27. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  28. package/lib/module/index.js +1 -0
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/mocks/index.js +10 -0
  31. package/lib/module/mocks/index.js.map +1 -1
  32. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  33. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  34. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  35. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  36. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  37. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  38. package/lib/typescript/src/index.d.ts +1 -0
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/mocks/index.d.ts +4 -0
  41. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  42. package/package.json +9 -1
  43. package/react-native.config.js +1 -0
  44. package/src/TrueSheetOverlay.tsx +24 -0
  45. package/src/TrueSheetOverlay.web.tsx +33 -0
  46. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  47. package/src/index.ts +1 -0
  48. package/src/mocks/index.ts +7 -0
@@ -0,0 +1,106 @@
1
+ package com.lodev09.truesheet
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.view.View
5
+ import android.view.ViewGroup
6
+ import android.view.accessibility.AccessibilityEvent
7
+ import com.facebook.react.uimanager.StateWrapper
8
+ import com.facebook.react.uimanager.ThemedReactContext
9
+ import com.facebook.react.uimanager.events.EventDispatcher
10
+ import com.facebook.react.views.view.ReactViewGroup
11
+ import com.lodev09.truesheet.core.TrueSheetOverlayRootView
12
+ import com.lodev09.truesheet.core.TrueSheetOverlayRootViewDelegate
13
+ import com.lodev09.truesheet.utils.findRootContainerView
14
+
15
+ /**
16
+ * Hidden host that renders its children in a root view attached to the
17
+ * activity's content view, above every presented sheet.
18
+ */
19
+ @SuppressLint("ViewConstructor")
20
+ class TrueSheetOverlayView(private val reactContext: ThemedReactContext) :
21
+ ReactViewGroup(reactContext),
22
+ TrueSheetOverlayRootViewDelegate {
23
+
24
+ private val rootView = TrueSheetOverlayRootView(reactContext).apply {
25
+ delegate = this@TrueSheetOverlayView
26
+ }
27
+
28
+ private var rootContainerView: ViewGroup? = null
29
+
30
+ override var eventDispatcher: EventDispatcher? = null
31
+
32
+ private var lastWidth = 0
33
+ private var lastHeight = 0
34
+
35
+ var stateWrapper: StateWrapper? = null
36
+ set(value) {
37
+ field = value
38
+ pushState()
39
+ }
40
+
41
+ init {
42
+ // The host stays in the React tree for layout only — children render in the root view
43
+ visibility = GONE
44
+ }
45
+
46
+ // ==================== View Hierarchy Management ====================
47
+
48
+ override fun onAttachedToWindow() {
49
+ super.onAttachedToWindow()
50
+ attachRootView()
51
+ }
52
+
53
+ override fun onDetachedFromWindow() {
54
+ detachRootView()
55
+ super.onDetachedFromWindow()
56
+ }
57
+
58
+ private fun attachRootView() {
59
+ if (rootView.parent != null) return
60
+
61
+ val container = findRootContainerView(reactContext) ?: return
62
+ container.addView(rootView)
63
+ rootContainerView = container
64
+ }
65
+
66
+ private fun detachRootView() {
67
+ rootContainerView?.removeView(rootView)
68
+ rootContainerView = null
69
+ }
70
+
71
+ fun onDropInstance() {
72
+ detachRootView()
73
+ rootView.delegate = null
74
+ }
75
+
76
+ override fun addView(child: View?, index: Int) {
77
+ rootView.addView(child, index)
78
+ }
79
+
80
+ override fun getChildCount(): Int = rootView.childCount
81
+
82
+ override fun getChildAt(index: Int): View? = rootView.getChildAt(index)
83
+
84
+ override fun removeViewAt(index: Int) {
85
+ rootView.removeViewAt(index)
86
+ }
87
+
88
+ // Accessibility: children live in the root view since this view is hidden
89
+ override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
90
+ override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
91
+
92
+ // ==================== State ====================
93
+
94
+ override fun overlayRootViewDidChangeSize(width: Int, height: Int) {
95
+ if (width == lastWidth && height == lastHeight) return
96
+
97
+ lastWidth = width
98
+ lastHeight = height
99
+ pushState()
100
+ }
101
+
102
+ private fun pushState() {
103
+ if (lastWidth == 0 && lastHeight == 0) return
104
+ stateWrapper?.let { TrueSheetStateUpdater.updateContainerSize(it, lastWidth, lastHeight) }
105
+ }
106
+ }
@@ -0,0 +1,45 @@
1
+ package com.lodev09.truesheet
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.PointerEvents
5
+ import com.facebook.react.uimanager.ReactStylesDiffMap
6
+ import com.facebook.react.uimanager.StateWrapper
7
+ import com.facebook.react.uimanager.ThemedReactContext
8
+ import com.facebook.react.uimanager.UIManagerHelper
9
+ import com.facebook.react.uimanager.ViewGroupManager
10
+ import com.facebook.react.uimanager.annotations.ReactProp
11
+
12
+ /**
13
+ * ViewManager for TrueSheetOverlayView
14
+ * Renders content above every presented sheet
15
+ */
16
+ @ReactModule(name = TrueSheetOverlayViewManager.REACT_CLASS)
17
+ class TrueSheetOverlayViewManager : ViewGroupManager<TrueSheetOverlayView>() {
18
+
19
+ override fun getName(): String = REACT_CLASS
20
+
21
+ override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetOverlayView = TrueSheetOverlayView(reactContext)
22
+
23
+ override fun onDropViewInstance(view: TrueSheetOverlayView) {
24
+ super.onDropViewInstance(view)
25
+ view.onDropInstance()
26
+ }
27
+
28
+ override fun addEventEmitters(reactContext: ThemedReactContext, view: TrueSheetOverlayView) {
29
+ view.eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
30
+ }
31
+
32
+ override fun updateState(view: TrueSheetOverlayView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? {
33
+ view.stateWrapper = stateWrapper
34
+ return null
35
+ }
36
+
37
+ @ReactProp(name = "pointerEvents")
38
+ fun setPointerEvents(view: TrueSheetOverlayView, pointerEventsStr: String?) {
39
+ view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
40
+ }
41
+
42
+ companion object {
43
+ const val REACT_CLASS = "TrueSheetOverlayView"
44
+ }
45
+ }
@@ -40,6 +40,7 @@ class TrueSheetPackage : BaseReactPackage() {
40
40
  TrueSheetContentViewManager(),
41
41
  TrueSheetHeaderViewManager(),
42
42
  TrueSheetFooterViewManager(),
43
- TrueSheetPeekViewManager()
43
+ TrueSheetPeekViewManager(),
44
+ TrueSheetOverlayViewManager()
44
45
  )
45
46
  }
@@ -1,5 +1,7 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
+ import com.facebook.react.bridge.WritableNativeMap
4
+ import com.facebook.react.uimanager.PixelUtil.pxToDp
3
5
  import com.facebook.react.uimanager.StateWrapper
4
6
 
5
7
  /**
@@ -22,6 +24,22 @@ object TrueSheetStateUpdater {
22
24
  fun updateState(stateWrapper: StateWrapper, widthDp: Float, heightDp: Float): Boolean =
23
25
  isAvailable && nativeUpdateState(stateWrapper, widthDp, heightDp)
24
26
 
27
+ /**
28
+ * Pushes the container size (px) into the Fabric state — synchronously when
29
+ * the bridge is available, async otherwise.
30
+ */
31
+ fun updateContainerSize(stateWrapper: StateWrapper, widthPx: Int, heightPx: Int) {
32
+ val widthDp = widthPx.toFloat().pxToDp()
33
+ val heightDp = heightPx.toFloat().pxToDp()
34
+
35
+ if (updateState(stateWrapper, widthDp, heightDp)) return
36
+
37
+ val newStateData = WritableNativeMap()
38
+ newStateData.putDouble("containerWidth", widthDp.toDouble())
39
+ newStateData.putDouble("containerHeight", heightDp.toDouble())
40
+ stateWrapper.updateState(newStateData)
41
+ }
42
+
25
43
  /** Returns false when unavailable — caller should fall back to async updateState. */
26
44
  fun updateFooterState(stateWrapper: StateWrapper, bottomInsetDp: Float): Boolean =
27
45
  isAvailable && nativeUpdateFooterState(stateWrapper, bottomInsetDp)
@@ -6,7 +6,6 @@ import android.view.ViewGroup
6
6
  import android.view.accessibility.AccessibilityEvent
7
7
  import androidx.annotation.UiThread
8
8
  import com.facebook.react.bridge.LifecycleEventListener
9
- import com.facebook.react.bridge.WritableNativeMap
10
9
  import com.facebook.react.uimanager.PixelUtil.dpToPx
11
10
  import com.facebook.react.uimanager.PixelUtil.pxToDp
12
11
  import com.facebook.react.uimanager.StateWrapper
@@ -22,6 +21,7 @@ import com.lodev09.truesheet.core.RNScreensEventObserverDelegate
22
21
  import com.lodev09.truesheet.core.TrueSheetStackManager
23
22
  import com.lodev09.truesheet.events.*
24
23
  import com.lodev09.truesheet.utils.KeyboardUtils
24
+ import com.lodev09.truesheet.utils.findRootContainerView
25
25
 
26
26
  /**
27
27
  * Main TrueSheet host view that manages the sheet and dispatches events to JavaScript.
@@ -376,18 +376,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
376
376
  lastContainerWidth = width
377
377
  lastContainerHeight = height
378
378
 
379
- val sw = stateWrapper ?: return
380
- val widthDp = width.toFloat().pxToDp()
381
- val heightDp = height.toFloat().pxToDp()
382
-
383
- // Synchronous update — commits and mounts within the same UI-thread frame
384
- if (TrueSheetStateUpdater.updateState(sw, widthDp, heightDp)) return
385
-
386
- // Fallback: async state update
387
- val newStateData = WritableNativeMap()
388
- newStateData.putDouble("containerWidth", widthDp.toDouble())
389
- newStateData.putDouble("containerHeight", heightDp.toDouble())
390
- sw.updateState(newStateData)
379
+ stateWrapper?.let { TrueSheetStateUpdater.updateContainerSize(it, width, height) }
391
380
  }
392
381
 
393
382
  // ==================== Sheet Actions ====================
@@ -731,16 +720,5 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
731
720
  * of whichever window this view is in - whether that's the activity's window or a
732
721
  * Modal's dialog window.
733
722
  */
734
- override fun findRootContainerView(): ViewGroup? {
735
- var current: android.view.ViewParent? = parent
736
-
737
- while (current != null) {
738
- if (current is ViewGroup && current.id == android.R.id.content) {
739
- return current
740
- }
741
- current = current.parent
742
- }
743
-
744
- return reactContext.currentActivity?.findViewById(android.R.id.content)
745
- }
723
+ override fun findRootContainerView(): ViewGroup? = findRootContainerView(reactContext)
746
724
  }
@@ -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
+ }
@@ -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
@@ -21,3 +23,20 @@ fun ViewGroup.smoothScrollTo(x: Int, y: Int) {
21
23
  is NestedScrollView -> smoothScrollTo(x, y)
22
24
  }
23
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 {
@@ -0,0 +1,46 @@
1
+ #pragma once
2
+
3
+ #include <react/renderer/components/view/conversions.h>
4
+ #include <yoga/node/Node.h>
5
+ #include <yoga/style/Style.h>
6
+
7
+ namespace facebook::react {
8
+
9
+ /*
10
+ * Overrides a node's Yoga dimensions with the container size reported from
11
+ * native. A zero dimension leaves that axis to Yoga.
12
+ */
13
+ inline void applyContainerSizeToYogaNode(
14
+ yoga::Node &node,
15
+ const yoga::Style &baseStyle,
16
+ float containerWidth,
17
+ float containerHeight) {
18
+ if (containerWidth <= 0 && containerHeight <= 0) {
19
+ return;
20
+ }
21
+
22
+ yoga::Style adjustedStyle = baseStyle;
23
+ const auto &currentStyle = node.style();
24
+ bool needsUpdate = false;
25
+
26
+ if (containerWidth > 0) {
27
+ adjustedStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(containerWidth));
28
+ if (adjustedStyle.dimension(yoga::Dimension::Width) != currentStyle.dimension(yoga::Dimension::Width)) {
29
+ needsUpdate = true;
30
+ }
31
+ }
32
+
33
+ if (containerHeight > 0) {
34
+ adjustedStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(containerHeight));
35
+ if (adjustedStyle.dimension(yoga::Dimension::Height) != currentStyle.dimension(yoga::Dimension::Height)) {
36
+ needsUpdate = true;
37
+ }
38
+ }
39
+
40
+ if (needsUpdate) {
41
+ node.setStyle(adjustedStyle);
42
+ node.setDirty(true);
43
+ }
44
+ }
45
+
46
+ } // namespace facebook::react
@@ -0,0 +1,21 @@
1
+ #pragma once
2
+
3
+ #include <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h>
4
+ #include <react/renderer/core/ConcreteComponentDescriptor.h>
5
+
6
+ namespace facebook::react {
7
+
8
+ /*
9
+ * Descriptor for <TrueSheetOverlayView> component.
10
+ */
11
+ class TrueSheetOverlayViewComponentDescriptor final
12
+ : public ConcreteComponentDescriptor<TrueSheetOverlayViewShadowNode> {
13
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
14
+
15
+ void adopt(ShadowNode &shadowNode) const override {
16
+ static_cast<TrueSheetOverlayViewShadowNode &>(shadowNode).adjustLayoutWithState();
17
+ ConcreteComponentDescriptor::adopt(shadowNode);
18
+ }
19
+ };
20
+
21
+ } // namespace facebook::react
@@ -0,0 +1,17 @@
1
+ #include "TrueSheetOverlayViewShadowNode.h"
2
+
3
+ #include "TrueSheetLayoutUtils.h"
4
+
5
+ namespace facebook::react {
6
+
7
+ extern const char TrueSheetOverlayViewComponentName[] = "TrueSheetOverlayView";
8
+
9
+ void TrueSheetOverlayViewShadowNode::adjustLayoutWithState() {
10
+ ensureUnsealed();
11
+
12
+ const auto &stateData = getStateData();
13
+ applyContainerSizeToYogaNode(
14
+ yogaNode_, getConcreteProps().yogaStyle, stateData.containerWidth, stateData.containerHeight);
15
+ }
16
+
17
+ } // namespace facebook::react
@@ -0,0 +1,36 @@
1
+ #pragma once
2
+
3
+ #include <jsi/jsi.h>
4
+ #include <react/renderer/components/TrueSheetSpec/EventEmitters.h>
5
+ #include <react/renderer/components/TrueSheetSpec/Props.h>
6
+ #include <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
7
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
8
+
9
+ namespace facebook::react {
10
+
11
+ JSI_EXPORT extern const char TrueSheetOverlayViewComponentName[];
12
+
13
+ /*
14
+ * `ShadowNode` for <TrueSheetOverlayView> component.
15
+ * Sized to the window from native (via TrueSheetViewState) so its children
16
+ * lay out against the full window like the sheet's container.
17
+ */
18
+ class JSI_EXPORT TrueSheetOverlayViewShadowNode final
19
+ : public ConcreteViewShadowNode<
20
+ TrueSheetOverlayViewComponentName,
21
+ TrueSheetOverlayViewProps,
22
+ TrueSheetOverlayViewEventEmitter,
23
+ TrueSheetViewState> {
24
+ using ConcreteViewShadowNode::ConcreteViewShadowNode;
25
+
26
+ public:
27
+ static ShadowNodeTraits BaseTraits() {
28
+ auto traits = ConcreteViewShadowNode::BaseTraits();
29
+ traits.set(ShadowNodeTraits::Trait::RootNodeKind);
30
+ return traits;
31
+ }
32
+
33
+ void adjustLayoutWithState();
34
+ };
35
+
36
+ } // namespace facebook::react
@@ -1,48 +1,17 @@
1
1
  #include "TrueSheetViewShadowNode.h"
2
2
 
3
- #include <react/renderer/components/view/conversions.h>
3
+ #include "TrueSheetLayoutUtils.h"
4
4
 
5
5
  namespace facebook::react {
6
6
 
7
- using namespace yoga;
8
-
9
7
  extern const char TrueSheetViewComponentName[] = "TrueSheetView";
10
8
 
11
9
  void TrueSheetViewShadowNode::adjustLayoutWithState() {
12
10
  ensureUnsealed();
13
11
 
14
- auto state = std::static_pointer_cast<
15
- const TrueSheetViewShadowNode::ConcreteState>(getState());
16
- auto stateData = state->getData();
17
-
18
- // If container dimensions are set from native, override Yoga's dimensions
19
- if (stateData.containerWidth > 0 || stateData.containerHeight > 0) {
20
- auto &props = getConcreteProps();
21
- yoga::Style adjustedStyle = props.yogaStyle;
22
- auto currentStyle = yogaNode_.style();
23
- bool needsUpdate = false;
24
-
25
- // Set width if provided
26
- if (stateData.containerWidth > 0) {
27
- adjustedStyle.setDimension(yoga::Dimension::Width, StyleSizeLength::points(stateData.containerWidth));
28
- if (adjustedStyle.dimension(yoga::Dimension::Width) != currentStyle.dimension(yoga::Dimension::Width)) {
29
- needsUpdate = true;
30
- }
31
- }
32
-
33
- // Set height if provided
34
- if (stateData.containerHeight > 0) {
35
- adjustedStyle.setDimension(yoga::Dimension::Height, StyleSizeLength::points(stateData.containerHeight));
36
- if (adjustedStyle.dimension(yoga::Dimension::Height) != currentStyle.dimension(yoga::Dimension::Height)) {
37
- needsUpdate = true;
38
- }
39
- }
40
-
41
- if (needsUpdate) {
42
- yogaNode_.setStyle(adjustedStyle);
43
- yogaNode_.setDirty(true);
44
- }
45
- }
12
+ const auto &stateData = getStateData();
13
+ applyContainerSizeToYogaNode(
14
+ yogaNode_, getConcreteProps().yogaStyle, stateData.containerWidth, stateData.containerHeight);
46
15
  }
47
16
 
48
17
  #if !defined(ANDROID)
@@ -8,6 +8,7 @@
8
8
 
9
9
  #ifdef RCT_NEW_ARCH_ENABLED
10
10
 
11
+ #import <React/RCTMountingTransactionObserving.h>
11
12
  #import <React/RCTSurfaceTouchHandler.h>
12
13
  #import <React/RCTViewComponentView.h>
13
14
  #import <UIKit/UIKit.h>
@@ -28,7 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
28
29
 
29
30
  @end
30
31
 
31
- @interface TrueSheetContentView : RCTViewComponentView <TrueSheetKeyboardObserverDelegate>
32
+ @interface TrueSheetContentView
33
+ : RCTViewComponentView <TrueSheetKeyboardObserverDelegate, RCTMountingTransactionObserving>
32
34
 
33
35
  @property (nonatomic, weak, nullable) id<TrueSheetContentViewDelegate> delegate;
34
36
  @property (nonatomic, assign) CGFloat keyboardScrollOffset;
@@ -159,6 +159,17 @@ using namespace facebook::react;
159
159
  }
160
160
  }
161
161
 
162
+ // A handle that commits in the same transaction as the ScrollView's insertion
163
+ // resolves to nothing — prop updates process before mount instructions, and a
164
+ // deep insertion doesn't pass through this view's mount hooks. Retry once the
165
+ // whole transaction has mounted.
166
+ - (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
167
+ withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry {
168
+ if (_scrollableHandle > 0 && !_detectedScrollView) {
169
+ [self.delegate contentViewScrollViewDidChange];
170
+ }
171
+ }
172
+
162
173
  #pragma mark - Scrollable
163
174
 
164
175
  // The scroll indicator follows automatically — UIKit derives its insets from
@@ -0,0 +1,32 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import <React/RCTViewComponentView.h>
12
+ #import <UIKit/UIKit.h>
13
+
14
+ NS_ASSUME_NONNULL_BEGIN
15
+
16
+ /**
17
+ * Hidden host that renders its children in a container attached directly to
18
+ * the window, above every presented sheet.
19
+ */
20
+ @interface TrueSheetOverlayView : RCTViewComponentView
21
+
22
+ /**
23
+ * Re-stacks every overlay container above the window's other subviews.
24
+ * Called when a sheet presents, since presenting adds its transition view on top.
25
+ */
26
+ + (void)bringOverlaysToFrontInWindow:(nullable UIWindow *)window;
27
+
28
+ @end
29
+
30
+ NS_ASSUME_NONNULL_END
31
+
32
+ #endif