@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
@@ -25,6 +25,8 @@ using namespace facebook::react;
25
25
  CGFloat _currentKeyboardOffset;
26
26
  CGFloat _bottomInset;
27
27
  BOOL _keyboardVisible;
28
+ BOOL _didPushBottomInset;
29
+ CGFloat _appliedBottomInset;
28
30
  }
29
31
 
30
32
  + (ComponentDescriptorProvider)componentDescriptorProvider {
@@ -43,6 +45,8 @@ using namespace facebook::react;
43
45
  _currentKeyboardOffset = 0;
44
46
  _bottomInset = 0;
45
47
  _keyboardVisible = NO;
48
+ _didPushBottomInset = NO;
49
+ _appliedBottomInset = 0;
46
50
  }
47
51
  return self;
48
52
  }
@@ -55,7 +59,11 @@ using namespace facebook::react;
55
59
  // bottom safe-area inset — the footer owns the sheet's bottom edge, so it
56
60
  // absorbs the inset and its background fills it.
57
61
  - (void)setBottomInset:(CGFloat)bottomInset {
58
- if (_bottomInset == bottomInset) {
62
+ // The first push must go through even when the value matches the ivar
63
+ // default (0) — it marks the state initialized so the shadow node stops
64
+ // seeding from the app-wide precalculated inset, which may belong to
65
+ // another sheet (e.g. a floating sheet whose real inset is 0).
66
+ if (_didPushBottomInset && _bottomInset == bottomInset) {
59
67
  return;
60
68
  }
61
69
  _bottomInset = bottomInset;
@@ -85,6 +93,14 @@ using namespace facebook::react;
85
93
  // them an inset stale (same reason Android bridges through
86
94
  // TrueSheetStateUpdater).
87
95
  _state->updateState(std::move(newState), EventQueue::UpdateMode::unstable_Immediate);
96
+ _didPushBottomInset = YES;
97
+ }
98
+
99
+ // The inset baked into the current layout by the shadow node (pushed or
100
+ // seeded — see TrueSheetFooterViewShadowNode). Refreshed in
101
+ // updateLayoutMetrics so it always pairs with the measured height.
102
+ - (CGFloat)appliedBottomInset {
103
+ return _appliedBottomInset;
88
104
  }
89
105
 
90
106
  // Height the footer occupies above the keyboard — its layout height minus the
@@ -123,11 +139,24 @@ using namespace facebook::react;
123
139
  oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics {
124
140
  [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
125
141
 
142
+ // State lands before layout metrics within the same mount transaction, so
143
+ // this reflects the inset the shadow node baked into this frame — seeded
144
+ // values are recorded back into the state at layout time (see
145
+ // TrueSheetFooterViewShadowNode).
146
+ CGFloat previousAppliedBottomInset = _appliedBottomInset;
147
+ if (_state) {
148
+ _appliedBottomInset = _state->getData().bottomInset;
149
+ }
150
+
126
151
  CGFloat height = layoutMetrics.frame.size.height;
127
- if (height != _lastHeight) {
152
+ // An inset change alone (fixed-height footer: padding moves, frame doesn't)
153
+ // still changes what's baked in — report so the controller re-pairs.
154
+ if (height != _lastHeight || _appliedBottomInset != previousAppliedBottomInset) {
128
155
  _lastHeight = height;
129
156
  [self.delegate footerViewDidChangeSize:CGSizeMake(layoutMetrics.frame.size.width, height)];
130
157
  }
158
+
159
+ [self applyKeyboardOffset];
131
160
  }
132
161
 
133
162
  - (void)prepareForRecycle {
@@ -143,32 +172,56 @@ using namespace facebook::react;
143
172
  _currentKeyboardOffset = 0;
144
173
  _bottomInset = 0;
145
174
  _keyboardVisible = NO;
175
+ _didPushBottomInset = NO;
176
+ _appliedBottomInset = 0;
146
177
  }
147
178
 
148
179
  #pragma mark - TrueSheetKeyboardObserverDelegate
149
180
 
181
+ // Only an absolute footer floats above the keyboard — a relative footer stays
182
+ // in the layout flow behind it, and an absolute one opts out via
183
+ // footerOptions.avoidKeyboard.
184
+ - (BOOL)floatsAboveKeyboard {
185
+ TrueSheetViewController *controller = self.keyboardObserver.viewController;
186
+ return controller.absoluteFooter && controller.footerAvoidsKeyboard;
187
+ }
188
+
189
+ - (CGFloat)keyboardSlideForHeight:(CGFloat)height {
190
+ UIWindow *window = self.window;
191
+ if (!window) {
192
+ return 0;
193
+ }
194
+
195
+ // Measure in the transform's coordinate space, excluding the footer's current slide.
196
+ CGFloat footerBottom = self.center.y + self.bounds.size.height / 2;
197
+ CGPoint keyboardTopInWindow = CGPointMake(CGRectGetMidX(window.bounds), CGRectGetMaxY(window.bounds) - height);
198
+ CGFloat keyboardTop = [self.superview convertPoint:keyboardTopInWindow fromView:window].y;
199
+ CGFloat keyboardOffset = self.keyboardObserver.viewController.footerKeyboardOffset;
200
+ return MAX(0, footerBottom - keyboardTop + keyboardOffset);
201
+ }
202
+
150
203
  // Yoga owns the footer's frame (pinned to the container's bottom edge), so the
151
- // keyboard slide is carried by a transform instead of layout. Only an absolute
152
- // footer floats above the keyboard — a relative footer stays in the layout
153
- // flow behind it.
204
+ // keyboard slide is carried by a transform instead of layout.
154
205
  - (void)keyboardWillShow:(CGFloat)height duration:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
155
- if (!self.keyboardObserver.viewController.absoluteFooter) {
206
+ if (![self floatsAboveKeyboard]) {
156
207
  return;
157
208
  }
158
209
 
159
210
  [self setKeyboardVisible:YES];
160
211
 
161
- CGFloat keyboardOffset = self.keyboardObserver.viewController.footerKeyboardOffset;
162
- CGFloat slide = MAX(0, height + keyboardOffset);
212
+ CGFloat slide = [self keyboardSlideForHeight:height];
163
213
  _currentKeyboardOffset = slide;
164
214
 
165
215
  [UIView animateWithDuration:duration
166
- delay:0
167
- options:curve | UIViewAnimationOptionBeginFromCurrentState
168
- animations:^{
169
- self.transform = CGAffineTransformMakeTranslation(0, -slide);
170
- }
171
- completion:nil];
216
+ delay:0
217
+ options:curve | UIViewAnimationOptionBeginFromCurrentState
218
+ animations:^{
219
+ self.transform = CGAffineTransformMakeTranslation(0, -slide);
220
+ [self syncEdgeEffect];
221
+ }
222
+ completion:^(BOOL finished) {
223
+ [self applyKeyboardOffset];
224
+ }];
172
225
  }
173
226
 
174
227
  - (void)keyboardWillHide:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
@@ -180,20 +233,43 @@ using namespace facebook::react;
180
233
  options:curve | UIViewAnimationOptionBeginFromCurrentState
181
234
  animations:^{
182
235
  self.transform = CGAffineTransformIdentity;
236
+ [self syncEdgeEffect];
183
237
  }
184
238
  completion:nil];
185
239
  }
186
240
 
241
+ // UIKit derives the edge effect region from model geometry at commit time,
242
+ // outside our animation context — the blur snaps to the footer's final
243
+ // position while the footer is still animating. Re-evaluating the scroll
244
+ // view's layout inside the animation block makes the region change inherit
245
+ // the keyboard curve.
246
+ - (void)syncEdgeEffect {
247
+ if (@available(iOS 26.0, *)) {
248
+ UIScrollView *scrollView = [self edgeInteractionScrollView];
249
+ if (scrollView) {
250
+ [scrollView setNeedsLayout];
251
+ [scrollView layoutIfNeeded];
252
+ }
253
+ }
254
+ }
255
+
187
256
  - (void)applyKeyboardOffset {
188
257
  CGFloat height = self.keyboardObserver.currentHeight;
189
- if (height <= 0 || !self.keyboardObserver.viewController.absoluteFooter) {
258
+ if (height <= 0) {
259
+ return;
260
+ }
261
+
262
+ // avoidKeyboard flipped off mid-keyboard — drop the slide already applied
263
+ if (![self floatsAboveKeyboard]) {
264
+ [self setKeyboardVisible:NO];
265
+ _currentKeyboardOffset = 0;
266
+ self.transform = CGAffineTransformIdentity;
190
267
  return;
191
268
  }
192
269
 
193
270
  [self setKeyboardVisible:YES];
194
271
 
195
- CGFloat keyboardOffset = self.keyboardObserver.viewController.footerKeyboardOffset;
196
- CGFloat slide = MAX(0, height + keyboardOffset);
272
+ CGFloat slide = [self keyboardSlideForHeight:height];
197
273
  _currentKeyboardOffset = slide;
198
274
  self.transform = CGAffineTransformMakeTranslation(0, -slide);
199
275
  }
@@ -27,16 +27,11 @@ NS_ASSUME_NONNULL_BEGIN
27
27
 
28
28
  /**
29
29
  * Register a sheet component view with its React tag
30
- * Called automatically by TrueSheetView during initialization
30
+ * Called automatically by TrueSheetView when it attaches to a window.
31
+ * The registry holds views weakly, so no unregistration is needed.
31
32
  */
32
33
  + (void)registerView:(TrueSheetView *)view withTag:(NSNumber *)tag;
33
34
 
34
- /**
35
- * Unregister a sheet component view
36
- * Called automatically by TrueSheetView during dealloc
37
- */
38
- + (void)unregisterViewWithTag:(NSNumber *)tag;
39
-
40
35
  @end
41
36
 
42
37
  NS_ASSUME_NONNULL_END
@@ -18,8 +18,9 @@
18
18
 
19
19
  #import <TrueSheetSpec/TrueSheetSpec.h>
20
20
 
21
- // Static registry to store view references by tag
22
- static NSMutableDictionary<NSNumber *, TrueSheetView *> *viewRegistry;
21
+ // Static registry of live hosts by tag — weak so an unmounted host deallocates
22
+ // (RN zeroes the tag before teardown, so hosts can't unregister themselves)
23
+ static NSMapTable<NSNumber *, TrueSheetView *> *viewRegistry;
23
24
 
24
25
  @interface TrueSheetModule () <NativeTrueSheetModuleSpec>
25
26
  @end
@@ -30,7 +31,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
30
31
 
31
32
  + (void)initialize {
32
33
  if (self == [TrueSheetModule class]) {
33
- viewRegistry = [NSMutableDictionary new];
34
+ viewRegistry = [NSMapTable strongToWeakObjectsMapTable];
34
35
  }
35
36
  }
36
37
 
@@ -151,7 +152,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
151
152
  // Find the root presented sheet (one without a parent TrueSheet)
152
153
  TrueSheetView *rootSheet = nil;
153
154
 
154
- for (TrueSheetView *view in viewRegistry.allValues) {
155
+ for (TrueSheetView *view in viewRegistry.objectEnumerator) {
155
156
  if (!view.viewController.isPresented) {
156
157
  continue;
157
158
  }
@@ -191,7 +192,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
191
192
  }
192
193
 
193
194
  @synchronized(viewRegistry) {
194
- return viewRegistry[reactTag];
195
+ return [viewRegistry objectForKey:reactTag];
195
196
  }
196
197
  }
197
198
 
@@ -201,17 +202,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
201
202
  }
202
203
 
203
204
  @synchronized(viewRegistry) {
204
- viewRegistry[tag] = view;
205
- }
206
- }
207
-
208
- + (void)unregisterViewWithTag:(NSNumber *)tag {
209
- if (!tag) {
210
- return;
211
- }
212
-
213
- @synchronized(viewRegistry) {
214
- [viewRegistry removeObjectForKey:tag];
205
+ [viewRegistry setObject:view forKey:tag];
215
206
  }
216
207
  }
217
208
 
@@ -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
@@ -0,0 +1,161 @@
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 "TrueSheetOverlayView.h"
12
+ #import "core/TrueSheetOverlayContainerView.h"
13
+
14
+ #import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
15
+ #import <react/renderer/components/TrueSheetSpec/Props.h>
16
+ #import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
17
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h>
18
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h>
19
+
20
+ #import <React/RCTSurfaceTouchHandler.h>
21
+
22
+ using namespace facebook::react;
23
+
24
+ static NSHashTable<TrueSheetOverlayView *> *gOverlayViews;
25
+
26
+ @interface TrueSheetOverlayView () <TrueSheetOverlayContainerViewDelegate>
27
+ @end
28
+
29
+ @implementation TrueSheetOverlayView {
30
+ TrueSheetOverlayContainerView *_containerView;
31
+ // Owns `_containerView` so it has a view controller in its responder chain. The
32
+ // container is a sibling of `rootViewController.view`, and a window's responder chain
33
+ // skips its root view controller — without an owner, `-[UIView reactViewController]`
34
+ // finds nothing and content that installs a view controller cannot attach.
35
+ UIViewController *_containerController;
36
+ RCTSurfaceTouchHandler *_touchHandler;
37
+ TrueSheetOverlayViewShadowNode::ConcreteState::Shared _state;
38
+ CGSize _lastStateSize;
39
+ }
40
+
41
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
42
+ return concreteComponentDescriptorProvider<TrueSheetOverlayViewComponentDescriptor>();
43
+ }
44
+
45
+ + (void)bringOverlaysToFrontInWindow:(nullable UIWindow *)window {
46
+ if (!window)
47
+ return;
48
+
49
+ for (TrueSheetOverlayView *overlay in gOverlayViews) {
50
+ if (overlay->_containerView.superview == window) {
51
+ [window bringSubviewToFront:overlay->_containerView];
52
+ }
53
+ }
54
+ }
55
+
56
+ - (instancetype)initWithFrame:(CGRect)frame {
57
+ if (self = [super initWithFrame:frame]) {
58
+ static const auto defaultProps = std::make_shared<const TrueSheetOverlayViewProps>();
59
+ _props = defaultProps;
60
+
61
+ _containerView = [[TrueSheetOverlayContainerView alloc] initWithFrame:CGRectZero];
62
+ _containerView.delegate = self;
63
+
64
+ // Assigning the container as its view is what puts the controller in the responder
65
+ // chain; the containment below is for lifecycle only.
66
+ _containerController = [UIViewController new];
67
+ _containerController.view = _containerView;
68
+
69
+ _touchHandler = [[RCTSurfaceTouchHandler alloc] init];
70
+ [_touchHandler attachToView:_containerView];
71
+
72
+ _lastStateSize = CGSizeZero;
73
+
74
+ // The host stays in the React tree for layout only — children render in the container
75
+ self.hidden = YES;
76
+
77
+ if (!gOverlayViews) {
78
+ gOverlayViews = [NSHashTable weakObjectsHashTable];
79
+ }
80
+ [gOverlayViews addObject:self];
81
+ }
82
+ return self;
83
+ }
84
+
85
+ - (void)didMoveToWindow {
86
+ [super didMoveToWindow];
87
+
88
+ UIWindow *window = self.window;
89
+ if (window) {
90
+ _containerView.frame = window.bounds;
91
+ [window addSubview:_containerView];
92
+
93
+ UIViewController *root = window.rootViewController;
94
+ if (root != nil && _containerController.parentViewController != root) {
95
+ [root addChildViewController:_containerController];
96
+ [_containerController didMoveToParentViewController:root];
97
+ }
98
+ } else {
99
+ [_containerController willMoveToParentViewController:nil];
100
+ [_containerView removeFromSuperview];
101
+ [_containerController removeFromParentViewController];
102
+ }
103
+ }
104
+
105
+ #pragma mark - State
106
+
107
+ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
108
+ _state = std::static_pointer_cast<TrueSheetOverlayViewShadowNode::ConcreteState const>(state);
109
+
110
+ if (self.window) {
111
+ [self updateStateWithSize:self.window.bounds.size];
112
+ }
113
+ }
114
+
115
+ - (void)overlayContainerViewDidLayout:(CGSize)size {
116
+ [self updateStateWithSize:size];
117
+ }
118
+
119
+ - (void)updateStateWithSize:(CGSize)size {
120
+ if (!_state)
121
+ return;
122
+
123
+ if (fabs(size.width - _lastStateSize.width) < 0.5 && fabs(size.height - _lastStateSize.height) < 0.5)
124
+ return;
125
+
126
+ _lastStateSize = size;
127
+
128
+ auto stateData = _state->getData();
129
+ stateData.containerWidth = static_cast<float>(size.width);
130
+ stateData.containerHeight = static_cast<float>(size.height);
131
+ _state->updateState(std::move(stateData), EventQueue::UpdateMode::unstable_Immediate);
132
+ }
133
+
134
+ #pragma mark - Child Component Mounting
135
+
136
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
137
+ [_containerView insertSubview:childComponentView atIndex:index];
138
+ }
139
+
140
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
141
+ [childComponentView removeFromSuperview];
142
+ }
143
+
144
+ - (void)prepareForRecycle {
145
+ [super prepareForRecycle];
146
+
147
+ [_containerController willMoveToParentViewController:nil];
148
+ [_containerView removeFromSuperview];
149
+ [_containerController removeFromParentViewController];
150
+ _lastStateSize = CGSizeZero;
151
+ _state.reset();
152
+ self.hidden = YES;
153
+ }
154
+
155
+ @end
156
+
157
+ Class<RCTComponentViewProtocol> TrueSheetOverlayViewCls(void) {
158
+ return TrueSheetOverlayView.class;
159
+ }
160
+
161
+ #endif