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

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 +52 -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 +148 -188
  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
@@ -17,25 +17,20 @@
17
17
  #import <react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h>
18
18
  #import "TrueSheetContainerView.h"
19
19
  #import "TrueSheetFooterView.h"
20
- #import "TrueSheetView.h"
21
20
  #import "TrueSheetViewController.h"
22
21
  #import "utils/PlatformUtil.h"
23
22
  #import "utils/UIView+FirstResponder.h"
24
23
 
25
24
  using namespace facebook::react;
26
25
 
27
- static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
28
-
29
26
  @implementation TrueSheetContentView {
30
27
  TrueSheetContentViewShadowNode::ConcreteState::Shared _state;
31
- RCTScrollViewComponentView *_pinnedScrollView;
32
- UIScrollView *_observedScrollView;
33
- CGSize _lastSize;
34
- CGFloat _bottomInset;
35
- CGFloat _originalIndicatorBottomInset;
28
+ RCTScrollViewComponentView *_detectedScrollView;
29
+ CGFloat _lastReportedNaturalHeight;
30
+ CGFloat _appliedKeyboardOffset;
36
31
  BOOL _observingTextChanges;
37
- BOOL _scrollableBounded;
38
- BOOL _isReportPending;
32
+ UIScrollViewContentInsetAdjustmentBehavior _originalInsetAdjustmentBehavior;
33
+ BOOL _appliedContentInsetAdjustment;
39
34
  }
40
35
 
41
36
  + (ComponentDescriptorProvider)componentDescriptorProvider {
@@ -52,37 +47,51 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
52
47
 
53
48
  - (void)dealloc {
54
49
  [self stopObservingTextChanges];
55
- [self unobserveScrollViewContentSize];
56
50
  }
57
51
 
58
52
  - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
59
53
  _state = std::static_pointer_cast<TrueSheetContentViewShadowNode::ConcreteState const>(state);
54
+
55
+ CGFloat naturalHeight = _state->getData().naturalHeight;
56
+ if (naturalHeight != _lastReportedNaturalHeight) {
57
+ _lastReportedNaturalHeight = naturalHeight;
58
+ [self.delegate contentViewDidChangeSize:CGSizeMake(self.frame.size.width, naturalHeight)];
59
+ }
60
60
  }
61
61
 
62
- // Tells the shadow node to fill the container (flexGrow/flexShrink) so the
63
- // pinned ScrollView's viewport is bounded to the visible space. Only applied
64
- // for auto detents — otherwise content lays out naturally like a regular view.
65
- - (void)setScrollableBounded:(BOOL)bounded {
66
- if (_scrollableBounded == bounded) {
62
+ - (void)setScrollableHandle:(NSInteger)scrollableHandle {
63
+ if (_scrollableHandle == scrollableHandle) {
67
64
  return;
68
65
  }
69
- _scrollableBounded = bounded;
66
+ _scrollableHandle = scrollableHandle;
70
67
 
71
- if (_state) {
72
- TrueSheetContentViewState newState;
73
- newState.scrollableBounded = bounded;
74
- _state->updateState(std::move(newState));
68
+ // Release the previously resolved ScrollView — setupScrollable re-resolves
69
+ [self clearScrollable];
70
+ }
71
+
72
+ - (void)setContentInsetAdjustment:(BOOL)contentInsetAdjustment {
73
+ if (_contentInsetAdjustment == contentInsetAdjustment) {
74
+ return;
75
75
  }
76
+ _contentInsetAdjustment = contentInsetAdjustment;
77
+ [self applyContentInsetAdjustment];
76
78
  }
77
79
 
78
- - (void)setHasAutoDetent:(BOOL)hasAutoDetent {
79
- if (_hasAutoDetent == hasAutoDetent) {
80
+ // Hands the bottom safe-area inset to UIKit — `automatic` only insets while
81
+ // the content can scroll, which the Android counterpart mirrors manually.
82
+ - (void)applyContentInsetAdjustment {
83
+ if (!_detectedScrollView) {
80
84
  return;
81
85
  }
82
- _hasAutoDetent = hasAutoDetent;
83
86
 
84
- if (_pinnedScrollView) {
85
- [self setScrollableBounded:hasAutoDetent];
87
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
88
+ if (_contentInsetAdjustment && !_appliedContentInsetAdjustment) {
89
+ _originalInsetAdjustmentBehavior = scrollView.contentInsetAdjustmentBehavior;
90
+ scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
91
+ _appliedContentInsetAdjustment = YES;
92
+ } else if (!_contentInsetAdjustment && _appliedContentInsetAdjustment) {
93
+ scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
94
+ _appliedContentInsetAdjustment = NO;
86
95
  }
87
96
  }
88
97
 
@@ -111,58 +120,25 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
111
120
  #pragma mark - Layout
112
121
 
113
122
  - (CGFloat)naturalHeight {
114
- CGFloat height = self.frame.size.height;
115
- if (_pinnedScrollView) {
116
- height += _pinnedScrollView.scrollView.contentSize.height - _pinnedScrollView.frame.size.height;
117
- }
118
- return MAX(0, height);
119
- }
120
-
121
- - (void)reportSizeIfChanged {
122
- // A deep ScrollView unmount doesn't pass through this view's mount hooks —
123
- // detect the stale pin here so it (and its contentSize observation) is
124
- // released instead of lingering until the next explicit setup or recycle.
125
- if (_pinnedScrollView && ![_pinnedScrollView isDescendantOfView:self]) {
126
- [self.delegate contentViewScrollViewDidChange];
127
- }
128
-
129
- // naturalHeight mixes frames from different views; mid-transaction they're
130
- // momentarily inconsistent (parent updates before the ScrollView), which
131
- // feeds back into the auto detent and oscillates. Coalesce to the next
132
- // main-queue tick so frames are settled before measuring.
133
- if (_pinnedScrollView) {
134
- if (_isReportPending) {
135
- return;
123
+ if (_state) {
124
+ CGFloat height = _state->getData().naturalHeight;
125
+ if (height > 0) {
126
+ return height;
136
127
  }
137
- _isReportPending = YES;
138
-
139
- __weak __typeof(self) weakSelf = self;
140
- dispatch_async(dispatch_get_main_queue(), ^{
141
- __typeof(self) strongSelf = weakSelf;
142
- if (!strongSelf) {
143
- return;
144
- }
145
- strongSelf->_isReportPending = NO;
146
- [strongSelf reportSizeNow];
147
- });
148
- return;
149
- }
150
-
151
- [self reportSizeNow];
152
- }
153
-
154
- - (void)reportSizeNow {
155
- CGSize newSize = CGSizeMake(self.frame.size.width, self.naturalHeight);
156
- if (!CGSizeEqualToSize(newSize, _lastSize)) {
157
- _lastSize = newSize;
158
- [self.delegate contentViewDidChangeSize:newSize];
159
128
  }
129
+ return self.frame.size.height;
160
130
  }
161
131
 
162
132
  - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
163
133
  oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
164
134
  [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
165
- [self reportSizeIfChanged];
135
+
136
+ // A deep ScrollView unmount doesn't pass through this view's mount hooks —
137
+ // detect the stale reference here so it's released instead of lingering until the
138
+ // next explicit setup or recycle.
139
+ if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
140
+ [self.delegate contentViewScrollViewDidChange];
141
+ }
166
142
  }
167
143
 
168
144
  #pragma mark - Child Mounting
@@ -178,46 +154,61 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
178
154
  }
179
155
 
180
156
  - (void)checkScrollViewChanged {
181
- if (!_pinnedScrollView || ![_pinnedScrollView isDescendantOfView:self]) {
157
+ if (!_detectedScrollView || ![_detectedScrollView isDescendantOfView:self]) {
182
158
  [self.delegate contentViewScrollViewDidChange];
183
159
  }
184
160
  }
185
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
+ [self updateKeyboardInset];
173
+ }
174
+
186
175
  #pragma mark - Scrollable
187
176
 
188
- - (void)setScrollViewContentInset:(CGFloat)contentBottom indicatorInset:(CGFloat)indicatorBottom {
189
- if (!_pinnedScrollView)
177
+ // The scroll indicator follows automatically — UIKit derives its insets from
178
+ // the content inset while `automaticallyAdjustsScrollIndicatorInsets` is set.
179
+ - (void)setKeyboardInset:(CGFloat)inset {
180
+ if (!_detectedScrollView)
190
181
  return;
191
182
 
192
- UIEdgeInsets contentInset = _pinnedScrollView.scrollView.contentInset;
193
- contentInset.bottom = contentBottom;
194
- _pinnedScrollView.scrollView.contentInset = contentInset;
183
+ UIEdgeInsets contentInset = _detectedScrollView.scrollView.contentInset;
184
+ contentInset.bottom = inset;
185
+ _detectedScrollView.scrollView.contentInset = contentInset;
186
+ }
195
187
 
196
- UIEdgeInsets indicatorInsets = _pinnedScrollView.scrollView.verticalScrollIndicatorInsets;
197
- indicatorInsets.bottom = indicatorBottom;
198
- _pinnedScrollView.scrollView.verticalScrollIndicatorInsets = indicatorInsets;
188
+ - (void)updateKeyboardInset {
189
+ CGFloat keyboardHeight = _keyboardObserver.currentHeight;
190
+ if (_detectedScrollView && keyboardHeight > 0) {
191
+ [self setKeyboardInset:[self keyboardInsetWithHeight:keyboardHeight]];
192
+ }
199
193
  }
200
194
 
201
195
  - (void)clearScrollable {
202
- if (_pinnedScrollView) {
203
- [self setScrollViewContentInset:0 indicatorInset:_originalIndicatorBottomInset];
204
- }
205
- [self unobserveScrollViewContentSize];
206
- [self setScrollableBounded:NO];
207
- _pinnedScrollView = nil;
208
- _bottomInset = 0;
209
- _originalIndicatorBottomInset = 0;
210
- [self reportSizeIfChanged];
196
+ [self setKeyboardInset:0];
197
+ _appliedKeyboardOffset = 0;
198
+ if (_appliedContentInsetAdjustment) {
199
+ _detectedScrollView.scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
200
+ _appliedContentInsetAdjustment = NO;
201
+ }
202
+ _detectedScrollView = nil;
211
203
  }
212
204
 
213
- - (void)setupScrollableWithBottomInset:(CGFloat)bottomInset {
214
- // Check if pinned scroll view is still valid (still in view hierarchy)
215
- if (_pinnedScrollView && ![_pinnedScrollView isDescendantOfView:self]) {
205
+ - (void)setupScrollable {
206
+ // Check if the detected scroll view is still valid (still in view hierarchy)
207
+ if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
216
208
  [self clearScrollable];
217
209
  }
218
210
 
219
- // Already set up with same inset and valid scroll view
220
- if (_pinnedScrollView && _bottomInset == bottomInset) {
211
+ if (_detectedScrollView) {
221
212
  return;
222
213
  }
223
214
 
@@ -226,104 +217,69 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
226
217
  return;
227
218
  }
228
219
 
229
- // Only capture originals on first pin
230
- if (!_pinnedScrollView) {
231
- _originalIndicatorBottomInset = scrollView.scrollView.verticalScrollIndicatorInsets.bottom;
232
- _pinnedScrollView = scrollView;
233
-
234
- [self observeScrollViewContentSize];
235
- [self setScrollableBounded:_hasAutoDetent];
236
- [self reportSizeIfChanged];
237
- }
238
-
239
- _bottomInset = bottomInset;
220
+ _detectedScrollView = scrollView;
240
221
 
241
- [self setScrollViewContentInset:_bottomInset indicatorInset:_originalIndicatorBottomInset];
222
+ [self applyContentInsetAdjustment];
242
223
 
243
224
  // If keyboard is currently showing, re-apply the keyboard inset to the new ScrollView
244
- CGFloat keyboardHeight = _keyboardObserver ? _keyboardObserver.currentHeight : 0;
245
- if (keyboardHeight > 0) {
246
- CGFloat inset = [self keyboardInsetWithHeight:keyboardHeight];
247
- [self setScrollViewContentInset:inset indicatorInset:_originalIndicatorBottomInset + inset];
248
- }
225
+ [self updateKeyboardInset];
249
226
  }
250
227
 
251
- // An absolute footer rises above the keyboard and covers the content's bottom
252
- // edge include it so the caret clears the footer, not just the keyboard.
253
- // A relative footer stays behind the keyboard below the content, so its
254
- // height shields that much of the keyboard's overlap.
228
+ // Short or nested scroll views can end above the sheet's bottom edge.
229
+ // Measure their actual keyboard overlap, including space occupied by a relative footer.
255
230
  - (CGFloat)keyboardInsetWithHeight:(CGFloat)height {
256
- if (!self.footerView) {
257
- return height;
258
- }
259
- if (_keyboardObserver.viewController.absoluteFooter) {
260
- return height + [self.footerView keyboardOcclusionHeight];
261
- }
262
- return MAX(0, height - self.footerView.frame.size.height);
263
- }
231
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
232
+ UIWindow *window = scrollView.window;
233
+ CGRect scrollFrame = [scrollView convertRect:scrollView.bounds toView:window];
234
+ CGFloat keyboardTop = CGRectGetMaxY(window.bounds) - height;
235
+ CGFloat inset = MAX(0, CGRectGetMaxY(scrollFrame) - keyboardTop);
264
236
 
265
- // Content growth is invisible to layout once the viewport is bounded, so track
266
- // the scroll content size directly to keep the auto detent height in sync.
267
- - (void)observeScrollViewContentSize {
268
- UIScrollView *scrollView = _pinnedScrollView.scrollView;
269
- if (_observedScrollView == scrollView) {
270
- return;
237
+ // UIKit stacks the safe area on top of contentInset while the adjustment
238
+ // behavior is automatic take it out so the total lands on the keyboard edge.
239
+ if (_appliedContentInsetAdjustment) {
240
+ inset = MAX(0, inset - scrollView.safeAreaInsets.bottom);
271
241
  }
272
- [self unobserveScrollViewContentSize];
273
- _observedScrollView = scrollView;
274
- [scrollView addObserver:self
275
- forKeyPath:@"contentSize"
276
- options:NSKeyValueObservingOptionNew
277
- context:TrueSheetContentSizeContext];
278
- }
279
242
 
280
- - (void)unobserveScrollViewContentSize {
281
- if (_observedScrollView) {
282
- [_observedScrollView removeObserver:self forKeyPath:@"contentSize" context:TrueSheetContentSizeContext];
283
- _observedScrollView = nil;
243
+ // Track how much of keyboardOffset actually lands in the inset so the caret
244
+ // reveal can compensate — the offset shifts the inset, not the keyboard edge.
245
+ CGFloat adjustedInset = MAX(0, inset + self.keyboardOffset);
246
+ _appliedKeyboardOffset = adjustedInset - inset;
247
+
248
+ // Content that already fits above the keyboard has nothing to reveal — the
249
+ // inset would only open blank scroll range below it (huge gap at the bottom).
250
+ if (scrollView.contentSize.height <= scrollView.bounds.size.height - adjustedInset) {
251
+ _appliedKeyboardOffset = 0;
252
+ return 0;
284
253
  }
254
+
255
+ return adjustedInset;
285
256
  }
286
257
 
287
- - (void)observeValueForKeyPath:(NSString *)keyPath
288
- ofObject:(id)object
289
- change:(NSDictionary *)change
290
- context:(void *)context {
291
- if (context == TrueSheetContentSizeContext) {
292
- [self reportSizeIfChanged];
293
- return;
258
+ // An absolute footer floats over the viewport's bottom edge — extend the
259
+ // caret target so it clears the footer, not just the keyboard.
260
+ - (CGFloat)footerOcclusion {
261
+ if (self.footerView && _keyboardObserver.viewController.absoluteFooter) {
262
+ return [self.footerView keyboardOcclusionHeight];
294
263
  }
295
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
264
+ return 0;
296
265
  }
297
266
 
267
+ // Resolves the user-provided `scrollableHandle` within the content subtree —
268
+ // Fabric component views carry their React tag as `UIView.tag`.
298
269
  - (RCTScrollViewComponentView *)findScrollView {
299
- if (_pinnedScrollView) {
300
- return _pinnedScrollView;
270
+ if (_detectedScrollView) {
271
+ return _detectedScrollView;
301
272
  }
302
273
 
303
- if (self.subviews.count == 0) {
274
+ if (_scrollableHandle <= 0) {
304
275
  return nil;
305
276
  }
306
277
 
307
- RCTScrollViewComponentView *scrollView = [self findScrollViewInSubviews:self.subviews];
308
-
309
- if (!scrollView) {
310
- for (UIView *subview in self.subviews) {
311
- scrollView = [self findScrollViewInSubviews:subview.subviews];
312
- if (scrollView) {
313
- break;
314
- }
315
- }
278
+ UIView *view = [self viewWithTag:_scrollableHandle];
279
+ if ([view isKindOfClass:RCTScrollViewComponentView.class]) {
280
+ return (RCTScrollViewComponentView *)view;
316
281
  }
317
282
 
318
- return scrollView;
319
- }
320
-
321
- - (RCTScrollViewComponentView *)findScrollViewInSubviews:(NSArray<UIView *> *)subviews {
322
- for (UIView *subview in subviews) {
323
- if ([subview isKindOfClass:RCTScrollViewComponentView.class] && ![subview isKindOfClass:TrueSheetView.class]) {
324
- return (RCTScrollViewComponentView *)subview;
325
- }
326
- }
327
283
  return nil;
328
284
  }
329
285
 
@@ -331,11 +287,11 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
331
287
 
332
288
  - (void)applyScrollEdgeEffects:(nullable ScrollableOptions *)options {
333
289
  #if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_0)
334
- if (!_pinnedScrollView)
290
+ if (!_detectedScrollView)
335
291
  return;
336
292
 
337
293
  if (@available(iOS 26.0, *)) {
338
- UIScrollView *scrollView = _pinnedScrollView.scrollView;
294
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
339
295
  auto topEffect = options ? options.topScrollEdgeEffect : TrueSheetViewTopScrollEdgeEffect::Hidden;
340
296
  auto bottomEffect = options ? options.bottomScrollEdgeEffect : TrueSheetViewBottomScrollEdgeEffect::Hidden;
341
297
 
@@ -371,7 +327,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
371
327
  #pragma mark - TrueSheetKeyboardObserverDelegate
372
328
 
373
329
  - (void)keyboardWillShow:(CGFloat)height duration:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
374
- if (!_pinnedScrollView) {
330
+ if (!_detectedScrollView) {
375
331
  return;
376
332
  }
377
333
 
@@ -382,12 +338,14 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
382
338
 
383
339
  CGFloat inset = [self keyboardInsetWithHeight:height];
384
340
  [UIView animateWithDuration:duration
385
- delay:0
386
- options:curve | UIViewAnimationOptionBeginFromCurrentState
387
- animations:^{
388
- [self setScrollViewContentInset:inset indicatorInset:self->_originalIndicatorBottomInset + inset];
389
- }
390
- completion:nil];
341
+ delay:0
342
+ options:curve | UIViewAnimationOptionBeginFromCurrentState
343
+ animations:^{
344
+ [self setKeyboardInset:inset];
345
+ }
346
+ completion:^(BOOL finished) {
347
+ [self updateKeyboardInset];
348
+ }];
391
349
 
392
350
  // Defer scroll until the next run loop so content insets are applied first
393
351
  if (firstResponder) {
@@ -398,7 +356,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
398
356
  }
399
357
 
400
358
  - (void)focusedInputTextDidChange:(NSNotification *)notification {
401
- if (!_pinnedScrollView || !_keyboardObserver || _keyboardObserver.currentHeight <= 0) {
359
+ if (!_detectedScrollView || !_keyboardObserver || _keyboardObserver.currentHeight <= 0) {
402
360
  return;
403
361
  }
404
362
 
@@ -409,12 +367,15 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
409
367
  }
410
368
 
411
369
  dispatch_async(dispatch_get_main_queue(), ^{
370
+ // Typing can grow the content (e.g. a multiline input) past the fits-above-
371
+ // the-keyboard threshold — keep the inset in sync before revealing the caret.
372
+ [self updateKeyboardInset];
412
373
  [self scrollToFocusedCaretAnimated:YES];
413
374
  });
414
375
  }
415
376
 
416
377
  - (void)scrollToFocusedCaretAnimated:(BOOL)animated {
417
- if (!_pinnedScrollView) {
378
+ if (!_detectedScrollView) {
418
379
  return;
419
380
  }
420
381
 
@@ -424,7 +385,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
424
385
  return;
425
386
  }
426
387
 
427
- UIScrollView *scrollView = _pinnedScrollView.scrollView;
388
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
428
389
  CGRect targetRect = [firstResponder convertRect:firstResponder.bounds toView:scrollView];
429
390
 
430
391
  if ([firstResponder conformsToProtocol:@protocol(UITextInput)]) {
@@ -442,14 +403,14 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
442
403
  }
443
404
  }
444
405
 
445
- targetRect.size.height += self.keyboardScrollOffset;
406
+ targetRect.size.height += self.keyboardScrollOffset + [self footerOcclusion] - _appliedKeyboardOffset;
446
407
  [scrollView scrollRectToVisible:targetRect animated:animated];
447
408
  }
448
409
 
449
410
  - (void)keyboardWillHide:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
450
411
  [self stopObservingTextChanges];
451
412
 
452
- if (!_pinnedScrollView) {
413
+ if (!_detectedScrollView) {
453
414
  return;
454
415
  }
455
416
 
@@ -457,8 +418,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
457
418
  delay:0
458
419
  options:curve | UIViewAnimationOptionBeginFromCurrentState
459
420
  animations:^{
460
- [self setScrollViewContentInset:self->_bottomInset
461
- indicatorInset:self->_originalIndicatorBottomInset];
421
+ [self setKeyboardInset:0];
462
422
  }
463
423
  completion:nil];
464
424
  }
@@ -470,9 +430,9 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
470
430
  [self stopObservingTextChanges];
471
431
  [self clearScrollable];
472
432
  _state.reset();
473
- _scrollableBounded = NO;
474
- _hasAutoDetent = NO;
475
- _lastSize = CGSizeZero;
433
+ _scrollableHandle = 0;
434
+ _contentInsetAdjustment = NO;
435
+ _lastReportedNaturalHeight = 0;
476
436
  }
477
437
 
478
438
  @end
@@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN
27
27
  @property (nonatomic, weak, nullable) TrueSheetKeyboardObserver *keyboardObserver;
28
28
  @property (nonatomic, weak, nullable) id<TrueSheetFooterViewDelegate> delegate;
29
29
 
30
+ /**
31
+ * Bottom inset currently baked into the footer's layout height — pairs with
32
+ * the measured frame so detent math can subtract exactly what's baked in.
33
+ */
34
+ @property (nonatomic, readonly) CGFloat appliedBottomInset;
35
+
30
36
  /**
31
37
  * Re-applies the footer's keyboard slide using the current keyboard height.
32
38
  * No-op when the keyboard is hidden. Used to reflect live `keyboardOffset` changes.
@@ -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,8 +139,19 @@ 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
  }
@@ -143,6 +170,8 @@ using namespace facebook::react;
143
170
  _currentKeyboardOffset = 0;
144
171
  _bottomInset = 0;
145
172
  _keyboardVisible = NO;
173
+ _didPushBottomInset = NO;
174
+ _appliedBottomInset = 0;
146
175
  }
147
176
 
148
177
  #pragma mark - TrueSheetKeyboardObserverDelegate
@@ -167,6 +196,7 @@ using namespace facebook::react;
167
196
  options:curve | UIViewAnimationOptionBeginFromCurrentState
168
197
  animations:^{
169
198
  self.transform = CGAffineTransformMakeTranslation(0, -slide);
199
+ [self syncEdgeEffect];
170
200
  }
171
201
  completion:nil];
172
202
  }
@@ -180,10 +210,26 @@ using namespace facebook::react;
180
210
  options:curve | UIViewAnimationOptionBeginFromCurrentState
181
211
  animations:^{
182
212
  self.transform = CGAffineTransformIdentity;
213
+ [self syncEdgeEffect];
183
214
  }
184
215
  completion:nil];
185
216
  }
186
217
 
218
+ // UIKit derives the edge effect region from model geometry at commit time,
219
+ // outside our animation context — the blur snaps to the footer's final
220
+ // position while the footer is still animating. Re-evaluating the scroll
221
+ // view's layout inside the animation block makes the region change inherit
222
+ // the keyboard curve.
223
+ - (void)syncEdgeEffect {
224
+ if (@available(iOS 26.0, *)) {
225
+ UIScrollView *scrollView = [self edgeInteractionScrollView];
226
+ if (scrollView) {
227
+ [scrollView setNeedsLayout];
228
+ [scrollView layoutIfNeeded];
229
+ }
230
+ }
231
+ }
232
+
187
233
  - (void)applyKeyboardOffset {
188
234
  CGFloat height = self.keyboardObserver.currentHeight;
189
235
  if (height <= 0 || !self.keyboardObserver.viewController.absoluteFooter) {
@@ -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