@lodev09/react-native-true-sheet 4.0.0-beta.1 → 4.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) 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 +24 -7
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +211 -182
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +14 -19
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -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 +82 -39
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +275 -88
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +26 -1
  14. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +75 -3
  15. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +56 -34
  16. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  17. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  18. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +1 -1
  19. package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
  20. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  21. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  22. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +0 -8
  23. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +54 -29
  24. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +6 -1
  25. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +1 -1
  26. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +6 -4
  27. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +7 -0
  28. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
  29. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  30. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  31. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
  33. package/ios/TrueSheetContainerView.h +14 -7
  34. package/ios/TrueSheetContainerView.mm +10 -7
  35. package/ios/TrueSheetContentView.h +31 -15
  36. package/ios/TrueSheetContentView.mm +133 -179
  37. package/ios/TrueSheetFooterView.h +6 -0
  38. package/ios/TrueSheetFooterView.mm +48 -2
  39. package/ios/TrueSheetModule.h +2 -7
  40. package/ios/TrueSheetModule.mm +7 -16
  41. package/ios/TrueSheetOverlayView.h +32 -0
  42. package/ios/TrueSheetOverlayView.mm +161 -0
  43. package/ios/TrueSheetView.mm +171 -29
  44. package/ios/TrueSheetViewController.h +10 -0
  45. package/ios/TrueSheetViewController.mm +256 -74
  46. package/ios/core/TrueSheetGrabberView.mm +13 -3
  47. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  48. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  49. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  50. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  51. package/ios/tvos/TrueSheetStubs.mm +12 -0
  52. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  53. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  54. package/lib/module/TrueSheet.js +149 -34
  55. package/lib/module/TrueSheet.js.map +1 -1
  56. package/lib/module/TrueSheet.web.js +122 -53
  57. package/lib/module/TrueSheet.web.js.map +1 -1
  58. package/lib/module/TrueSheetOverlay.js +30 -0
  59. package/lib/module/TrueSheetOverlay.js.map +1 -0
  60. package/lib/module/TrueSheetOverlay.web.js +35 -0
  61. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  62. package/lib/module/TrueSheetPeek.web.js +3 -3
  63. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  64. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  65. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  66. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -0
  67. package/lib/module/index.js +1 -0
  68. package/lib/module/index.js.map +1 -1
  69. package/lib/module/mocks/index.js +10 -0
  70. package/lib/module/mocks/index.js.map +1 -1
  71. package/lib/module/mocks/navigation.js +31 -21
  72. package/lib/module/mocks/navigation.js.map +1 -1
  73. package/lib/module/mocks/navigationExpoRouter.js +21 -0
  74. package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
  75. package/lib/module/mocks/reanimated.js +1 -1
  76. package/lib/module/mocks/reanimated.js.map +1 -1
  77. package/lib/module/navigation/{TrueSheetView.js → TrueSheetNavigatorContent.js} +14 -12
  78. package/lib/module/navigation/TrueSheetNavigatorContent.js.map +1 -0
  79. package/lib/module/navigation/actions.js +50 -0
  80. package/lib/module/navigation/actions.js.map +1 -0
  81. package/lib/module/navigation/base-types.js +2 -0
  82. package/lib/module/navigation/base-types.js.map +1 -0
  83. package/lib/module/navigation/{TrueSheetRouter.js → createTrueSheetRouter.js} +11 -24
  84. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -0
  85. package/lib/module/navigation/expo-router/base-types.js +2 -0
  86. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  87. package/lib/module/navigation/expo-router/index.js +50 -0
  88. package/lib/module/navigation/expo-router/index.js.map +1 -0
  89. package/lib/module/navigation/expo-router/types.js +4 -0
  90. package/lib/module/navigation/expo-router/types.js.map +1 -0
  91. package/lib/module/navigation/index.js +43 -2
  92. package/lib/module/navigation/index.js.map +1 -1
  93. package/lib/module/navigation/navigator.js +10 -0
  94. package/lib/module/navigation/navigator.js.map +1 -0
  95. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +2 -2
  96. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
  97. package/lib/module/navigation/screen/TrueSheetScreen.js +2 -2
  98. package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
  99. package/lib/module/navigation/screen/useSheetScreenState.js +5 -4
  100. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  101. package/lib/module/navigation/useTrueSheetNavigation.js +1 -1
  102. package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -1
  103. package/lib/module/web/dom.js +10 -0
  104. package/lib/module/web/dom.js.map +1 -0
  105. package/lib/module/web/vaul/constants.js +4 -0
  106. package/lib/module/web/vaul/constants.js.map +1 -1
  107. package/lib/module/web/vaul/context.js.map +1 -1
  108. package/lib/module/web/vaul/helpers.js +10 -4
  109. package/lib/module/web/vaul/helpers.js.map +1 -1
  110. package/lib/module/web/vaul/index.js +35 -2
  111. package/lib/module/web/vaul/index.js.map +1 -1
  112. package/lib/typescript/src/TrueSheet.d.ts +6 -0
  113. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  114. package/lib/typescript/src/TrueSheet.types.d.ts +72 -1
  115. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  116. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  117. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  118. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  119. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  120. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  121. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  122. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  123. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  124. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +4 -0
  125. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  126. package/lib/typescript/src/index.d.ts +1 -0
  127. package/lib/typescript/src/index.d.ts.map +1 -1
  128. package/lib/typescript/src/mocks/index.d.ts +4 -0
  129. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  130. package/lib/typescript/src/mocks/navigation.d.ts +5 -1
  131. package/lib/typescript/src/mocks/navigation.d.ts.map +1 -1
  132. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +22 -0
  133. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -0
  134. package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -1
  135. package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts +5 -0
  136. package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts.map +1 -0
  137. package/lib/typescript/src/navigation/actions.d.ts +71 -0
  138. package/lib/typescript/src/navigation/actions.d.ts.map +1 -0
  139. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  140. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  141. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +3 -0
  142. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -0
  143. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  144. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  145. package/lib/typescript/src/navigation/expo-router/index.d.ts +37 -0
  146. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -0
  147. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  148. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  149. package/lib/typescript/src/navigation/index.d.ts +42 -3
  150. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  151. package/lib/typescript/src/navigation/navigator.d.ts +7 -0
  152. package/lib/typescript/src/navigation/navigator.d.ts.map +1 -0
  153. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +1 -1
  154. package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -1
  155. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +1 -1
  156. package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -1
  157. package/lib/typescript/src/navigation/screen/types.d.ts +3 -4
  158. package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -1
  159. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +5 -6
  160. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  161. package/lib/typescript/src/navigation/types.d.ts +65 -5
  162. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  163. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +1 -1
  164. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -1
  165. package/lib/typescript/src/web/dom.d.ts +7 -0
  166. package/lib/typescript/src/web/dom.d.ts.map +1 -0
  167. package/lib/typescript/src/web/vaul/constants.d.ts +1 -0
  168. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  169. package/lib/typescript/src/web/vaul/context.d.ts +1 -0
  170. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  171. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  172. package/lib/typescript/src/web/vaul/index.d.ts +7 -1
  173. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  174. package/package.json +32 -9
  175. package/react-native.config.js +1 -0
  176. package/src/TrueSheet.tsx +184 -46
  177. package/src/TrueSheet.types.ts +77 -1
  178. package/src/TrueSheet.web.tsx +135 -64
  179. package/src/TrueSheetOverlay.tsx +24 -0
  180. package/src/TrueSheetOverlay.web.tsx +33 -0
  181. package/src/TrueSheetPeek.web.tsx +4 -3
  182. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  183. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  184. package/src/fabric/TrueSheetViewNativeComponent.ts +5 -0
  185. package/src/index.ts +1 -0
  186. package/src/mocks/index.ts +7 -0
  187. package/src/mocks/navigation.ts +32 -25
  188. package/src/mocks/navigationExpoRouter.ts +41 -0
  189. package/src/mocks/reanimated.ts +8 -9
  190. package/src/navigation/{TrueSheetView.tsx → TrueSheetNavigatorContent.tsx} +24 -17
  191. package/src/navigation/actions.ts +78 -0
  192. package/src/navigation/base-types.ts +20 -0
  193. package/src/navigation/createTrueSheetRouter.ts +194 -0
  194. package/src/navigation/expo-router/base-types.ts +18 -0
  195. package/src/navigation/expo-router/index.ts +90 -0
  196. package/src/navigation/expo-router/types.ts +116 -0
  197. package/src/navigation/index.ts +68 -2
  198. package/src/navigation/navigator.ts +18 -0
  199. package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +2 -2
  200. package/src/navigation/screen/TrueSheetScreen.tsx +2 -2
  201. package/src/navigation/screen/types.ts +4 -6
  202. package/src/navigation/screen/useSheetScreenState.ts +10 -15
  203. package/src/navigation/types.ts +99 -16
  204. package/src/navigation/useTrueSheetNavigation.ts +1 -1
  205. package/src/web/dom.ts +9 -0
  206. package/src/web/vaul/constants.ts +4 -0
  207. package/src/web/vaul/context.ts +1 -0
  208. package/src/web/vaul/helpers.ts +13 -6
  209. package/src/web/vaul/index.tsx +46 -1
  210. package/lib/module/navigation/TrueSheetRouter.js.map +0 -1
  211. package/lib/module/navigation/TrueSheetView.js.map +0 -1
  212. package/lib/module/navigation/createTrueSheetNavigator.js +0 -63
  213. package/lib/module/navigation/createTrueSheetNavigator.js.map +0 -1
  214. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +0 -57
  215. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +0 -1
  216. package/lib/typescript/src/navigation/TrueSheetView.d.ts +0 -10
  217. package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +0 -1
  218. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +0 -35
  219. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +0 -1
  220. package/src/navigation/TrueSheetRouter.ts +0 -234
  221. package/src/navigation/createTrueSheetNavigator.tsx +0 -93
@@ -1,6 +1,10 @@
1
1
  #include "TrueSheetContentViewShadowNode.h"
2
2
 
3
+ #include <limits>
4
+
3
5
  #include <react/renderer/components/view/conversions.h>
6
+ #include <react/renderer/core/LayoutConstraints.h>
7
+ #include <react/renderer/core/LayoutContext.h>
4
8
 
5
9
  namespace facebook::react {
6
10
 
@@ -8,35 +12,56 @@ using namespace yoga;
8
12
 
9
13
  extern const char TrueSheetContentViewComponentName[] = "TrueSheetContentView";
10
14
 
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);
15
+ // measure() lays out a clone of this node, which re-enters layout() on the
16
+ // clone — guard so the measurement pass doesn't measure again recursively.
17
+ static thread_local bool gIsMeasuringNaturalHeight = false;
18
+
19
+ void TrueSheetContentViewShadowNode::layout(LayoutContext layoutContext) {
20
+ ConcreteViewShadowNode::layout(layoutContext);
21
+ updateNaturalHeightIfNeeded(layoutContext);
22
+ }
23
+
24
+ // The content's committed height follows the container instead of its own
25
+ // content when it can grow/shrink along the main axis or uses a percent
26
+ // height. Reporting that height as the natural height would echo the sheet's
27
+ // own size back as a content size change e.g. flex: 1 content tracking a
28
+ // drag frame by frame.
29
+ static bool isHeightContainerDerived(const yoga::Style &style) {
30
+ return style.flexGrow().unwrapOrDefault(0) > 0 ||
31
+ style.flexShrink().unwrapOrDefault(0) > 0 ||
32
+ style.dimension(Dimension::Height).isPercent();
33
+ }
34
+
35
+ // The natural height is the height the content wants when unbounded — the
36
+ // source for the auto detent. When the content's height derives from the
37
+ // container, the committed layout doesn't reflect it — lay out a clone of the
38
+ // subtree with an unconstrained height instead. Yoga respects the user's
39
+ // styles: an explicit-height ScrollView keeps its height while a flexible one
40
+ // expands to its content — no ScrollView discovery involved.
41
+ void TrueSheetContentViewShadowNode::updateNaturalHeightIfNeeded(
42
+ const LayoutContext &layoutContext) {
43
+ if (gIsMeasuringNaturalHeight) {
44
+ return;
45
+ }
46
+
47
+ auto stateData = getStateData();
48
+ auto size = getLayoutMetrics().frame.size;
49
+
50
+ Float naturalHeight = size.height;
51
+ if (isHeightContainerDerived(getConcreteProps().yogaStyle)) {
52
+ auto constraints = LayoutConstraints{};
53
+ constraints.minimumSize = {size.width, 0};
54
+ constraints.maximumSize = {size.width, std::numeric_limits<Float>::infinity()};
55
+ constraints.layoutDirection = getLayoutMetrics().layoutDirection;
56
+
57
+ gIsMeasuringNaturalHeight = true;
58
+ naturalHeight = measure(layoutContext, constraints).height;
59
+ gIsMeasuringNaturalHeight = false;
60
+ }
61
+
62
+ if (stateData.naturalHeight != naturalHeight) {
63
+ stateData.naturalHeight = naturalHeight;
64
+ setStateData(std::move(stateData));
40
65
  }
41
66
  }
42
67
 
@@ -22,7 +22,12 @@ class JSI_EXPORT TrueSheetContentViewShadowNode final
22
22
  using ConcreteViewShadowNode::ConcreteViewShadowNode;
23
23
 
24
24
  public:
25
- void adjustLayoutWithState();
25
+ #pragma mark - LayoutableShadowNode
26
+
27
+ void layout(LayoutContext layoutContext) override;
28
+
29
+ private:
30
+ void updateNaturalHeightIfNeeded(const LayoutContext &layoutContext);
26
31
  };
27
32
 
28
33
  } // namespace facebook::react
@@ -4,7 +4,7 @@ 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
 
@@ -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,8 +12,8 @@ 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:
@@ -21,10 +23,10 @@ class TrueSheetContentViewState final {
21
23
  TrueSheetContentViewState(
22
24
  TrueSheetContentViewState const &previousState,
23
25
  folly::dynamic data)
24
- : scrollableBounded(data["scrollableBounded"].getBool()) {}
26
+ : naturalHeight(previousState.naturalHeight) {}
25
27
  #endif
26
28
 
27
- bool scrollableBounded{false};
29
+ Float naturalHeight{0};
28
30
 
29
31
  #ifdef ANDROID
30
32
  folly::dynamic getDynamic() const;
@@ -25,6 +25,13 @@ void TrueSheetFooterViewShadowNode::adjustLayoutWithState() {
25
25
  float bottomInset = stateData.bottomInset;
26
26
  if (!stateData.initialized && props.autoBottomInset) {
27
27
  bottomInset = TrueSheetInsets::bottomSafeArea();
28
+ if (bottomInset != stateData.bottomInset) {
29
+ // Record the seeded value so the mounted view reads exactly the inset
30
+ // baked into this layout — the global may change before mount.
31
+ auto seededData = stateData;
32
+ seededData.bottomInset = bottomInset;
33
+ setStateData(std::move(seededData));
34
+ }
28
35
  }
29
36
 
30
37
  // A footer pinned to the sheet's bottom edge owns the bottom safe-area
@@ -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)
@@ -16,7 +16,9 @@ NS_ASSUME_NONNULL_BEGIN
16
16
 
17
17
  @interface ScrollableOptions : NSObject
18
18
 
19
+ @property (nonatomic, assign) BOOL contentInsetAdjustment;
19
20
  @property (nonatomic, assign) CGFloat keyboardScrollOffset;
21
+ @property (nonatomic, assign) CGFloat keyboardOffset;
20
22
  @property (nonatomic, assign) BOOL scrollingExpandsSheet;
21
23
  @property (nonatomic, assign) facebook::react::TrueSheetViewTopScrollEdgeEffect topScrollEdgeEffect;
22
24
  @property (nonatomic, assign) facebook::react::TrueSheetViewBottomScrollEdgeEffect bottomScrollEdgeEffect;
@@ -46,20 +48,20 @@ NS_ASSUME_NONNULL_BEGIN
46
48
  @property (nonatomic, weak, nullable) id<TrueSheetContainerViewDelegate> delegate;
47
49
 
48
50
  /**
49
- * Inset adjustment mode for scrollable content
51
+ * Options for scrollable behavior
50
52
  */
51
- @property (nonatomic, assign) facebook::react::TrueSheetViewInsetAdjustment insetAdjustment;
53
+ @property (nonatomic, strong, nullable) ScrollableOptions *scrollableOptions;
52
54
 
53
55
  /**
54
- * Options for scrollable behavior
56
+ * React tag of the user-provided scrollable within the content (see the `scrollableRef` prop)
55
57
  */
56
- @property (nonatomic, strong, nullable) ScrollableOptions *scrollableOptions;
58
+ @property (nonatomic, assign) NSInteger scrollableHandle;
57
59
 
58
60
  /**
59
- * Whether the sheet has an `auto` detent bounds the pinned scrollable's
60
- * viewport to the container so the sheet height can derive from its content size
61
+ * Whether the resolved scrollable's `contentInsetAdjustmentBehavior` is set to
62
+ * `automatic` already gated by the sheet's `insetAdjustment`
61
63
  */
62
- @property (nonatomic, assign) BOOL hasAutoDetent;
64
+ @property (nonatomic, assign) BOOL contentInsetAdjustment;
63
65
 
64
66
  /**
65
67
  * Bottom safe-area inset the footer absorbs as padding — the footer
@@ -82,6 +84,11 @@ NS_ASSUME_NONNULL_BEGIN
82
84
  */
83
85
  - (CGFloat)footerHeight;
84
86
 
87
+ /**
88
+ * Returns the bottom inset currently baked into the footer's height
89
+ */
90
+ - (CGFloat)footerAppliedBottomInset;
91
+
85
92
  /**
86
93
  * Returns the current height of the peek view within the content
87
94
  */
@@ -17,7 +17,6 @@
17
17
  #import "TrueSheetViewController.h"
18
18
  #import "core/TrueSheetKeyboardObserver.h"
19
19
  #import "utils/UIView+ScrollEdgeInteraction.h"
20
- #import "utils/WindowUtil.h"
21
20
 
22
21
  #import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
23
22
  #import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
@@ -33,7 +32,9 @@ using namespace facebook::react;
33
32
 
34
33
  - (instancetype)init {
35
34
  if (self = [super init]) {
35
+ _contentInsetAdjustment = YES;
36
36
  _keyboardScrollOffset = 0;
37
+ _keyboardOffset = 0;
37
38
  _scrollingExpandsSheet = YES;
38
39
  _topScrollEdgeEffect = TrueSheetViewTopScrollEdgeEffect::Hidden;
39
40
  _bottomScrollEdgeEffect = TrueSheetViewBottomScrollEdgeEffect::Hidden;
@@ -118,6 +119,10 @@ using namespace facebook::react;
118
119
  return _footerView ? _footerView.frame.size.height : 0;
119
120
  }
120
121
 
122
+ - (CGFloat)footerAppliedBottomInset {
123
+ return _footerView ? _footerView.appliedBottomInset : 0;
124
+ }
125
+
121
126
  // Distance from the top of the content view to the bottom of the peek view.
122
127
  // Includes the peek view's offset within the content (padding, views above it)
123
128
  // so the peek detent reveals everything down to the peek content's bottom edge.
@@ -145,16 +150,14 @@ using namespace facebook::react;
145
150
  - (void)setScrollableOptions:(ScrollableOptions *)scrollableOptions {
146
151
  _scrollableOptions = scrollableOptions;
147
152
  _contentView.keyboardScrollOffset = scrollableOptions ? scrollableOptions.keyboardScrollOffset : 0;
153
+ _contentView.keyboardOffset = scrollableOptions ? scrollableOptions.keyboardOffset : 0;
148
154
  }
149
155
 
150
156
  - (void)setupScrollable {
151
157
  if (_contentView) {
152
- CGFloat bottomInset = 0;
153
- if (_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic) {
154
- bottomInset = [WindowUtil keyWindow].safeAreaInsets.bottom;
155
- }
156
- _contentView.hasAutoDetent = _hasAutoDetent;
157
- [_contentView setupScrollableWithBottomInset:bottomInset];
158
+ _contentView.scrollableHandle = _scrollableHandle;
159
+ _contentView.contentInsetAdjustment = _contentInsetAdjustment;
160
+ [_contentView setupScrollable];
158
161
  [_contentView applyScrollEdgeEffects:_scrollableOptions];
159
162
  if (@available(iOS 26.0, *)) {
160
163
  [self setupEdgeInteractions];
@@ -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,42 +29,57 @@ 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;
37
+
38
+ /**
39
+ * Adjustment added to the keyboard bottom inset applied to the detected
40
+ * scrollable — negative values reduce the inset (e.g. to cancel out safe-area
41
+ * padding already baked into the content's paddingBottom).
42
+ */
43
+ @property (nonatomic, assign) CGFloat keyboardOffset;
44
+
35
45
  @property (nonatomic, weak, nullable) TrueSheetKeyboardObserver *keyboardObserver;
36
46
 
37
47
  /**
38
- * Sibling footer view — an absolute footer rises above the keyboard and
39
- * occludes the content's bottom edge, so it counts toward the keyboard inset.
48
+ * Sibling footer view — a relative footer shields part of the keyboard's
49
+ * overlap from the inset; an absolute footer extends the caret reveal target.
40
50
  */
41
51
  @property (nonatomic, weak, nullable) TrueSheetFooterView *footerView;
42
52
 
43
53
  /**
44
- * Whether the sheet has an `auto` detent. Deriving the sheet height from the
45
- * scroll content is circular with natural layout, so the pinned ScrollView's
46
- * viewport is force-bounded to the container only in this case.
54
+ * Content height measured unconstrained by the shadow node the height the
55
+ * content wants regardless of container bounds (see
56
+ * TrueSheetContentViewShadowNode). Falls back to the frame height before the
57
+ * first state update.
47
58
  */
48
- @property (nonatomic, assign) BOOL hasAutoDetent;
59
+ @property (nonatomic, readonly) CGFloat naturalHeight;
49
60
 
50
61
  /**
51
- * Content height with the pinned ScrollView's viewport replaced by its content
52
- * size the height the content wants regardless of container bounds.
53
- * Falls back to the frame height when no ScrollView is pinned.
62
+ * React tag of the user-provided scrollable (see the `scrollableRef` prop).
63
+ * Setting a new handle clears the currently resolved ScrollView.
54
64
  */
55
- @property (nonatomic, readonly) CGFloat naturalHeight;
65
+ @property (nonatomic, assign) NSInteger scrollableHandle;
66
+
67
+ /**
68
+ * Sets the resolved ScrollView's `contentInsetAdjustmentBehavior` to
69
+ * `automatic` while plugged so UIKit applies the bottom safe-area inset to
70
+ * the scroll content. The previous behavior is restored on clear.
71
+ */
72
+ @property (nonatomic, assign) BOOL contentInsetAdjustment;
56
73
 
57
74
  - (RCTScrollViewComponentView *_Nullable)findScrollView;
58
75
 
59
76
  /**
60
- * Pin the first ScrollView found in the content, wiring insets and keyboard handling
61
- * @param bottomInset Bottom content inset for the scroll view
77
+ * Resolve the ScrollView from `scrollableHandle`, wiring keyboard handling
62
78
  */
63
- - (void)setupScrollableWithBottomInset:(CGFloat)bottomInset;
79
+ - (void)setupScrollable;
64
80
 
65
81
  /**
66
- * Apply scroll edge effects to the pinned scroll view (iOS 26+)
82
+ * Apply scroll edge effects to the detected scroll view (iOS 26+)
67
83
  */
68
84
  - (void)applyScrollEdgeEffects:(nullable ScrollableOptions *)options;
69
85