@lodev09/react-native-true-sheet 4.0.0-beta.3 → 4.0.0-beta.30

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 (244) hide show
  1. package/README.md +28 -5
  2. package/android/build.gradle +23 -1
  3. package/android/proguard-rules.pro +3 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +38 -109
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +127 -0
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  11. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +29 -3
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +234 -66
  14. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +491 -186
  15. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +64 -10
  16. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -14
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +135 -0
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +23 -23
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +130 -6
  20. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  21. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +14 -17
  22. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
  23. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +148 -0
  24. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +33 -40
  25. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  26. package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
  27. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  28. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  29. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  30. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  31. package/android/src/main/jni/TrueSheetStateUpdater.cpp +37 -6
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +114 -30
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +14 -3
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +5 -6
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +10 -6
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  46. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  47. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  48. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  49. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  50. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  51. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  52. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  53. package/ios/TrueSheetContainerView.h +23 -7
  54. package/ios/TrueSheetContainerView.mm +45 -25
  55. package/ios/TrueSheetContentView.h +44 -15
  56. package/ios/TrueSheetContentView.mm +221 -190
  57. package/ios/TrueSheetFooterView.mm +133 -28
  58. package/ios/TrueSheetModule.h +2 -7
  59. package/ios/TrueSheetModule.mm +18 -18
  60. package/ios/TrueSheetOverlayView.h +42 -0
  61. package/ios/TrueSheetOverlayView.mm +175 -0
  62. package/ios/TrueSheetView.h +2 -0
  63. package/ios/TrueSheetView.mm +268 -52
  64. package/ios/TrueSheetViewController.h +36 -3
  65. package/ios/TrueSheetViewController.mm +797 -102
  66. package/ios/core/RNScreensEventObserver.mm +31 -28
  67. package/ios/core/TrueSheetBlurView.h +0 -2
  68. package/ios/core/TrueSheetBlurView.mm +3 -36
  69. package/ios/core/TrueSheetDetentCalculator.h +18 -1
  70. package/ios/core/TrueSheetDetentCalculator.mm +44 -9
  71. package/ios/core/TrueSheetDimView.h +27 -0
  72. package/ios/core/TrueSheetDimView.mm +41 -0
  73. package/ios/core/TrueSheetGrabberView.mm +13 -3
  74. package/ios/core/TrueSheetKeyboardObserver.h +7 -0
  75. package/ios/core/TrueSheetKeyboardObserver.mm +107 -15
  76. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  77. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  78. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  79. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  80. package/ios/tvos/TrueSheetStubs.mm +12 -0
  81. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  82. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  83. package/lib/module/TrueSheet.js +222 -54
  84. package/lib/module/TrueSheet.js.map +1 -1
  85. package/lib/module/TrueSheet.web.js +364 -275
  86. package/lib/module/TrueSheet.web.js.map +1 -1
  87. package/lib/module/TrueSheetBackground.js +15 -0
  88. package/lib/module/TrueSheetBackground.js.map +1 -0
  89. package/lib/module/TrueSheetOverlay.js +30 -0
  90. package/lib/module/TrueSheetOverlay.js.map +1 -0
  91. package/lib/module/TrueSheetOverlay.web.js +35 -0
  92. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  93. package/lib/module/TrueSheetPeek.web.js +20 -15
  94. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  95. package/lib/module/TrueSheetProvider.web.js +13 -9
  96. package/lib/module/TrueSheetProvider.web.js.map +1 -1
  97. package/lib/module/detentBackgrounds.js +21 -0
  98. package/lib/module/detentBackgrounds.js.map +1 -0
  99. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  100. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  101. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  102. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +89 -73
  103. package/lib/module/index.js +1 -0
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/mocks/index.js +18 -2
  106. package/lib/module/mocks/index.js.map +1 -1
  107. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  108. package/lib/module/navigation/base-types.js +2 -0
  109. package/lib/module/navigation/base-types.js.map +1 -0
  110. package/lib/module/navigation/createTrueSheetRouter.js +158 -100
  111. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  112. package/lib/module/navigation/expo-router/base-types.js +2 -0
  113. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  114. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +71 -0
  115. package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
  116. package/lib/module/navigation/expo-router/index.js +18 -7
  117. package/lib/module/navigation/expo-router/index.js.map +1 -1
  118. package/lib/module/navigation/expo-router/types.js +4 -0
  119. package/lib/module/navigation/expo-router/types.js.map +1 -0
  120. package/lib/module/navigation/index.js.map +1 -1
  121. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  122. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  123. package/lib/module/scrollable.js +28 -0
  124. package/lib/module/scrollable.js.map +1 -0
  125. package/lib/module/web/constants.js +1 -1
  126. package/lib/module/web/constants.js.map +1 -1
  127. package/lib/module/web/keyboard.js +69 -0
  128. package/lib/module/web/keyboard.js.map +1 -0
  129. package/lib/module/web/layout.js +168 -0
  130. package/lib/module/web/layout.js.map +1 -0
  131. package/lib/module/web/vaul/constants.js +9 -0
  132. package/lib/module/web/vaul/constants.js.map +1 -1
  133. package/lib/module/web/vaul/context.js +4 -0
  134. package/lib/module/web/vaul/context.js.map +1 -1
  135. package/lib/module/web/vaul/helpers.js +1 -0
  136. package/lib/module/web/vaul/helpers.js.map +1 -1
  137. package/lib/module/web/vaul/index.js +83 -21
  138. package/lib/module/web/vaul/index.js.map +1 -1
  139. package/lib/module/web/vaul/use-snap-points.js +57 -28
  140. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  141. package/lib/typescript/src/TrueSheet.d.ts +18 -1
  142. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  143. package/lib/typescript/src/TrueSheet.types.d.ts +209 -35
  144. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  145. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  146. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  147. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  148. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  149. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  150. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  151. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  152. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  153. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  154. package/lib/typescript/src/TrueSheetProvider.web.d.ts +5 -1
  155. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
  156. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  157. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  158. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -3
  159. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  160. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  161. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  162. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +76 -65
  163. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  164. package/lib/typescript/src/index.d.ts +1 -0
  165. package/lib/typescript/src/index.d.ts.map +1 -1
  166. package/lib/typescript/src/mocks/index.d.ts +4 -0
  167. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  168. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  169. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  170. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  171. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  172. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +36 -10
  173. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  174. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  175. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  176. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +33 -0
  177. package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
  178. package/lib/typescript/src/navigation/expo-router/index.d.ts +9 -6
  179. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  180. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  181. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  182. package/lib/typescript/src/navigation/index.d.ts +3 -4
  183. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  184. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  185. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  186. package/lib/typescript/src/navigation/types.d.ts +43 -6
  187. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  188. package/lib/typescript/src/scrollable.d.ts +2 -0
  189. package/lib/typescript/src/scrollable.d.ts.map +1 -0
  190. package/lib/typescript/src/web/constants.d.ts +1 -1
  191. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  192. package/lib/typescript/src/web/keyboard.d.ts +15 -0
  193. package/lib/typescript/src/web/keyboard.d.ts.map +1 -0
  194. package/lib/typescript/src/web/layout.d.ts +20 -0
  195. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  196. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  197. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  198. package/lib/typescript/src/web/vaul/context.d.ts +5 -1
  199. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  200. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  201. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  202. package/lib/typescript/src/web/vaul/index.d.ts +20 -1
  203. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  204. package/lib/typescript/src/web/vaul/types.d.ts +9 -0
  205. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -1
  206. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +6 -4
  207. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  208. package/package.json +18 -8
  209. package/react-native.config.js +1 -0
  210. package/src/TrueSheet.tsx +257 -62
  211. package/src/TrueSheet.types.ts +225 -35
  212. package/src/TrueSheet.web.tsx +445 -333
  213. package/src/TrueSheetBackground.tsx +17 -0
  214. package/src/TrueSheetOverlay.tsx +24 -0
  215. package/src/TrueSheetOverlay.web.tsx +33 -0
  216. package/src/TrueSheetPeek.web.tsx +25 -23
  217. package/src/TrueSheetProvider.web.tsx +22 -13
  218. package/src/detentBackgrounds.ts +24 -0
  219. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  220. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
  221. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  222. package/src/fabric/TrueSheetViewNativeComponent.ts +89 -73
  223. package/src/index.ts +1 -0
  224. package/src/mocks/index.ts +17 -2
  225. package/src/mocks/navigationExpoRouter.ts +6 -5
  226. package/src/navigation/base-types.ts +20 -0
  227. package/src/navigation/createTrueSheetRouter.ts +220 -144
  228. package/src/navigation/expo-router/base-types.ts +18 -0
  229. package/src/navigation/expo-router/extendTrueSheetRouter.ts +99 -0
  230. package/src/navigation/expo-router/index.ts +41 -30
  231. package/src/navigation/expo-router/types.ts +116 -0
  232. package/src/navigation/index.ts +6 -7
  233. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  234. package/src/navigation/types.ts +81 -23
  235. package/src/scrollable.ts +25 -0
  236. package/src/web/constants.ts +1 -1
  237. package/src/web/keyboard.ts +79 -0
  238. package/src/web/layout.ts +196 -0
  239. package/src/web/vaul/constants.ts +9 -0
  240. package/src/web/vaul/context.ts +7 -1
  241. package/src/web/vaul/helpers.ts +5 -0
  242. package/src/web/vaul/index.tsx +113 -16
  243. package/src/web/vaul/types.ts +10 -0
  244. package/src/web/vaul/use-snap-points.ts +86 -33
@@ -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 _appliedSafeAreaInsetAdjustment;
39
34
  }
40
35
 
41
36
  + (ComponentDescriptorProvider)componentDescriptorProvider {
@@ -52,37 +47,60 @@ 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)setSafeAreaInsetAdjustment:(BOOL)safeAreaInsetAdjustment {
73
+ if (_safeAreaInsetAdjustment == safeAreaInsetAdjustment) {
74
+ return;
75
75
  }
76
+ _safeAreaInsetAdjustment = safeAreaInsetAdjustment;
77
+ [self applySafeAreaInsetAdjustment];
78
+ [self updateBottomInset];
76
79
  }
77
80
 
78
- - (void)setHasAutoDetent:(BOOL)hasAutoDetent {
79
- if (_hasAutoDetent == hasAutoDetent) {
81
+ - (void)setFooterInsetAdjustment:(BOOL)footerInsetAdjustment {
82
+ if (_footerInsetAdjustment == footerInsetAdjustment) {
80
83
  return;
81
84
  }
82
- _hasAutoDetent = hasAutoDetent;
85
+ _footerInsetAdjustment = footerInsetAdjustment;
86
+ [self updateBottomInset];
87
+ }
83
88
 
84
- if (_pinnedScrollView) {
85
- [self setScrollableBounded:hasAutoDetent];
89
+ // Hands the bottom safe-area inset to UIKit — `automatic` only insets while
90
+ // the content can scroll, which the Android counterpart mirrors manually.
91
+ - (void)applySafeAreaInsetAdjustment {
92
+ if (!_detectedScrollView) {
93
+ return;
94
+ }
95
+
96
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
97
+ if (_safeAreaInsetAdjustment && !_appliedSafeAreaInsetAdjustment) {
98
+ _originalInsetAdjustmentBehavior = scrollView.contentInsetAdjustmentBehavior;
99
+ scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
100
+ _appliedSafeAreaInsetAdjustment = YES;
101
+ } else if (!_safeAreaInsetAdjustment && _appliedSafeAreaInsetAdjustment) {
102
+ scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
103
+ _appliedSafeAreaInsetAdjustment = NO;
86
104
  }
87
105
  }
88
106
 
@@ -111,58 +129,25 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
111
129
  #pragma mark - Layout
112
130
 
113
131
  - (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;
132
+ if (_state) {
133
+ CGFloat height = _state->getData().naturalHeight;
134
+ if (height > 0) {
135
+ return height;
136
136
  }
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
137
  }
138
+ return self.frame.size.height;
160
139
  }
161
140
 
162
141
  - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
163
142
  oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
164
143
  [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
165
- [self reportSizeIfChanged];
144
+
145
+ // A deep ScrollView unmount doesn't pass through this view's mount hooks —
146
+ // detect the stale reference here so it's released instead of lingering until the
147
+ // next explicit setup or recycle.
148
+ if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
149
+ [self.delegate contentViewScrollViewDidChange];
150
+ }
166
151
  }
167
152
 
168
153
  #pragma mark - Child Mounting
@@ -178,46 +163,107 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
178
163
  }
179
164
 
180
165
  - (void)checkScrollViewChanged {
181
- if (!_pinnedScrollView || ![_pinnedScrollView isDescendantOfView:self]) {
166
+ if (!_detectedScrollView || ![_detectedScrollView isDescendantOfView:self]) {
167
+ [self.delegate contentViewScrollViewDidChange];
168
+ }
169
+ }
170
+
171
+ // A handle that commits in the same transaction as the ScrollView's insertion
172
+ // resolves to nothing — prop updates process before mount instructions, and a
173
+ // deep insertion doesn't pass through this view's mount hooks. Retry once the
174
+ // whole transaction has mounted.
175
+ - (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
176
+ withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry {
177
+ if (_scrollableHandle > 0 && !_detectedScrollView) {
182
178
  [self.delegate contentViewScrollViewDidChange];
183
179
  }
180
+
181
+ // Layout has settled for the whole tree — the footer overlap (sheet height,
182
+ // scroll view frame) and the keyboard inset are current here
183
+ [self updateBottomInset];
184
+
185
+ [self.delegate contentViewDidMountTransaction];
184
186
  }
185
187
 
186
188
  #pragma mark - Scrollable
187
189
 
188
- - (void)setScrollViewContentInset:(CGFloat)contentBottom indicatorInset:(CGFloat)indicatorBottom {
189
- if (!_pinnedScrollView)
190
+ // The scroll indicator follows automatically — UIKit derives its insets from
191
+ // the content inset while `automaticallyAdjustsScrollIndicatorInsets` is set.
192
+ - (void)setBottomInset:(CGFloat)inset {
193
+ if (!_detectedScrollView)
194
+ return;
195
+
196
+ UIEdgeInsets contentInset = _detectedScrollView.scrollView.contentInset;
197
+ if (fabs(contentInset.bottom - inset) < 0.001) {
190
198
  return;
199
+ }
200
+
201
+ contentInset.bottom = inset;
202
+ _detectedScrollView.scrollView.contentInset = contentInset;
203
+ }
191
204
 
192
- UIEdgeInsets contentInset = _pinnedScrollView.scrollView.contentInset;
193
- contentInset.bottom = contentBottom;
194
- _pinnedScrollView.scrollView.contentInset = contentInset;
205
+ // How much of the absolute footer covers the scroll view — measured from the
206
+ // footer's layout position (pinned to the container's bottom edge; its
207
+ // keyboard rise is a transform) so a short scroll view ending above the footer
208
+ // gets no inset. UIKit stacks the safe area on top of contentInset while the
209
+ // adjustment behavior is automatic — take out the inset baked into the
210
+ // footer's height so the total lands on the footer's top edge. Stays constant
211
+ // across keyboard transitions: the footer drops its inset as it rises, and
212
+ // the baked value drops with it. A pinned footer adds only the overlap not
213
+ // already covered by the keyboard inset.
214
+ - (CGFloat)footerInsetAbove:(CGFloat)keyboardInset {
215
+ if (!_footerInsetAdjustment || !self.footerView || !_detectedScrollView) {
216
+ return 0;
217
+ }
218
+
219
+ UIView *container = self.footerView.superview;
220
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
221
+ CGRect scrollFrame = [scrollView convertRect:scrollView.bounds toView:container];
222
+ CGFloat footerTop = container.bounds.size.height - self.footerView.bounds.size.height;
195
223
 
196
- UIEdgeInsets indicatorInsets = _pinnedScrollView.scrollView.verticalScrollIndicatorInsets;
197
- indicatorInsets.bottom = indicatorBottom;
198
- _pinnedScrollView.scrollView.verticalScrollIndicatorInsets = indicatorInsets;
224
+ CGFloat inset = CGRectGetMaxY(scrollFrame) - footerTop;
225
+ if (_appliedSafeAreaInsetAdjustment) {
226
+ inset -= self.footerView.appliedBottomInset;
227
+ }
228
+ if (!_keyboardObserver.viewController.footerAvoidsKeyboard) {
229
+ inset -= keyboardInset;
230
+ }
231
+ return MAX(0, inset);
232
+ }
233
+
234
+ - (CGFloat)keyboardInset {
235
+ CGFloat keyboardHeight = _keyboardObserver.currentHeight;
236
+ return keyboardHeight > 0 ? [self keyboardInsetWithHeight:keyboardHeight] : 0;
237
+ }
238
+
239
+ // Safe to call mid keyboard animation: the target matches the animation's
240
+ // model value (the footer inset doesn't change with the keyboard), so the
241
+ // early-out in setBottomInset: leaves the animation alone.
242
+ - (void)updateBottomInset {
243
+ if (!_detectedScrollView) {
244
+ return;
245
+ }
246
+ CGFloat keyboardInset = [self keyboardInset];
247
+ [self setBottomInset:[self footerInsetAbove:keyboardInset] + keyboardInset];
199
248
  }
200
249
 
201
250
  - (void)clearScrollable {
202
- if (_pinnedScrollView) {
203
- [self setScrollViewContentInset:0 indicatorInset:_originalIndicatorBottomInset];
251
+ [self setBottomInset:0];
252
+ _appliedKeyboardOffset = 0;
253
+ if (_appliedSafeAreaInsetAdjustment) {
254
+ _detectedScrollView.scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
255
+ _appliedSafeAreaInsetAdjustment = NO;
204
256
  }
205
- [self unobserveScrollViewContentSize];
206
- [self setScrollableBounded:NO];
207
- _pinnedScrollView = nil;
208
- _bottomInset = 0;
209
- _originalIndicatorBottomInset = 0;
210
- [self reportSizeIfChanged];
257
+ _detectedScrollView = nil;
211
258
  }
212
259
 
213
- - (void)setupScrollableWithBottomInset:(CGFloat)bottomInset {
214
- // Check if pinned scroll view is still valid (still in view hierarchy)
215
- if (_pinnedScrollView && ![_pinnedScrollView isDescendantOfView:self]) {
260
+ - (void)setupScrollable {
261
+ // Check if the detected scroll view is still valid (still in view hierarchy)
262
+ if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
216
263
  [self clearScrollable];
217
264
  }
218
265
 
219
- // Already set up with same inset and valid scroll view
220
- if (_pinnedScrollView && _bottomInset == bottomInset) {
266
+ if (_detectedScrollView) {
221
267
  return;
222
268
  }
223
269
 
@@ -226,104 +272,76 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
226
272
  return;
227
273
  }
228
274
 
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;
275
+ _detectedScrollView = scrollView;
240
276
 
241
- [self setScrollViewContentInset:_bottomInset indicatorInset:_originalIndicatorBottomInset];
277
+ [self applySafeAreaInsetAdjustment];
242
278
 
243
- // 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
- }
279
+ // Apply the footer inset, and the keyboard inset if it's currently showing
280
+ [self updateBottomInset];
249
281
  }
250
282
 
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.
283
+ // Short or nested scroll views can end above the sheet's bottom edge.
284
+ // Their frame also accounts for space occupied by a relative footer.
255
285
  - (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
- }
286
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
287
+ UIWindow *window = scrollView.window;
288
+ CGRect scrollFrame = [scrollView convertRect:scrollView.bounds toView:window];
289
+ CGFloat keyboardTop = CGRectGetMaxY(window.bounds) - height;
290
+ CGFloat inset = MAX(0, CGRectGetMaxY(scrollFrame) - keyboardTop);
264
291
 
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;
292
+ // UIKit stacks the safe area on top of contentInset while the adjustment
293
+ // behavior is automatic — take it out so the total lands on the keyboard edge.
294
+ if (_appliedSafeAreaInsetAdjustment) {
295
+ inset = MAX(0, inset - scrollView.safeAreaInsets.bottom);
271
296
  }
272
- [self unobserveScrollViewContentSize];
273
- _observedScrollView = scrollView;
274
- [scrollView addObserver:self
275
- forKeyPath:@"contentSize"
276
- options:NSKeyValueObservingOptionNew
277
- context:TrueSheetContentSizeContext];
278
- }
279
297
 
280
- - (void)unobserveScrollViewContentSize {
281
- if (_observedScrollView) {
282
- [_observedScrollView removeObserver:self forKeyPath:@"contentSize" context:TrueSheetContentSizeContext];
283
- _observedScrollView = nil;
298
+ // Track how much of keyboardOffset actually lands in the inset so the caret
299
+ // reveal can compensate — the offset shifts the inset, not the keyboard edge.
300
+ CGFloat adjustedInset = MAX(0, inset + self.keyboardOffset);
301
+ _appliedKeyboardOffset =
302
+ adjustedInset + [self footerInsetAbove:adjustedInset] - (inset + [self footerInsetAbove:inset]);
303
+
304
+ // Content that already fits above the keyboard (and footer) has nothing to
305
+ // reveal — the inset would only open blank scroll range below it (huge gap
306
+ // at the bottom).
307
+ if (scrollView.contentSize.height + [self footerInsetAbove:adjustedInset] <=
308
+ scrollView.bounds.size.height - adjustedInset) {
309
+ _appliedKeyboardOffset = 0;
310
+ return 0;
284
311
  }
312
+
313
+ return adjustedInset;
285
314
  }
286
315
 
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;
316
+ // An absolute footer floats over the viewport's bottom edge — extend the
317
+ // caret target so it clears the footer, not just the keyboard. Not needed
318
+ // while the footer inset is applied (the visible rect already ends above it)
319
+ // or when the footer stays behind the keyboard.
320
+ - (CGFloat)footerOcclusion {
321
+ TrueSheetViewController *controller = _keyboardObserver.viewController;
322
+ if (self.footerView && controller.absoluteFooter && controller.footerAvoidsKeyboard &&
323
+ [self footerInsetAbove:0] <= 0) {
324
+ return [self.footerView keyboardOcclusionHeight];
294
325
  }
295
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
326
+ return 0;
296
327
  }
297
328
 
329
+ // Resolves the user-provided `scrollableHandle` within the content subtree —
330
+ // Fabric component views carry their React tag as `UIView.tag`.
298
331
  - (RCTScrollViewComponentView *)findScrollView {
299
- if (_pinnedScrollView) {
300
- return _pinnedScrollView;
332
+ if (_detectedScrollView) {
333
+ return _detectedScrollView;
301
334
  }
302
335
 
303
- if (self.subviews.count == 0) {
336
+ if (_scrollableHandle <= 0) {
304
337
  return nil;
305
338
  }
306
339
 
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
- }
340
+ UIView *view = [self viewWithTag:_scrollableHandle];
341
+ if ([view isKindOfClass:RCTScrollViewComponentView.class]) {
342
+ return (RCTScrollViewComponentView *)view;
316
343
  }
317
344
 
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
345
  return nil;
328
346
  }
329
347
 
@@ -331,11 +349,11 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
331
349
 
332
350
  - (void)applyScrollEdgeEffects:(nullable ScrollableOptions *)options {
333
351
  #if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_0)
334
- if (!_pinnedScrollView)
352
+ if (!_detectedScrollView)
335
353
  return;
336
354
 
337
355
  if (@available(iOS 26.0, *)) {
338
- UIScrollView *scrollView = _pinnedScrollView.scrollView;
356
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
339
357
  auto topEffect = options ? options.topScrollEdgeEffect : TrueSheetViewTopScrollEdgeEffect::Hidden;
340
358
  auto bottomEffect = options ? options.bottomScrollEdgeEffect : TrueSheetViewBottomScrollEdgeEffect::Hidden;
341
359
 
@@ -370,8 +388,14 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
370
388
 
371
389
  #pragma mark - TrueSheetKeyboardObserverDelegate
372
390
 
391
+ - (void)setKeyboardObserver:(TrueSheetKeyboardObserver *)keyboardObserver {
392
+ [_keyboardObserver removeDelegate:self];
393
+ _keyboardObserver = keyboardObserver;
394
+ [keyboardObserver addDelegate:self];
395
+ }
396
+
373
397
  - (void)keyboardWillShow:(CGFloat)height duration:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
374
- if (!_pinnedScrollView) {
398
+ if (!_detectedScrollView) {
375
399
  return;
376
400
  }
377
401
 
@@ -380,14 +404,17 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
380
404
  TrueSheetViewController *sheetController = _keyboardObserver.viewController;
381
405
  UIView *firstResponder = sheetController ? [sheetController.view findFirstResponder] : nil;
382
406
 
383
- CGFloat inset = [self keyboardInsetWithHeight:height];
407
+ CGFloat keyboardInset = [self keyboardInsetWithHeight:height];
408
+ CGFloat inset = [self footerInsetAbove:keyboardInset] + keyboardInset;
384
409
  [UIView animateWithDuration:duration
385
- delay:0
386
- options:curve | UIViewAnimationOptionBeginFromCurrentState
387
- animations:^{
388
- [self setScrollViewContentInset:inset indicatorInset:self->_originalIndicatorBottomInset + inset];
389
- }
390
- completion:nil];
410
+ delay:0
411
+ options:curve | UIViewAnimationOptionBeginFromCurrentState
412
+ animations:^{
413
+ [self setBottomInset:inset];
414
+ }
415
+ completion:^(BOOL finished) {
416
+ [self updateBottomInset];
417
+ }];
391
418
 
392
419
  // Defer scroll until the next run loop so content insets are applied first
393
420
  if (firstResponder) {
@@ -398,7 +425,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
398
425
  }
399
426
 
400
427
  - (void)focusedInputTextDidChange:(NSNotification *)notification {
401
- if (!_pinnedScrollView || !_keyboardObserver || _keyboardObserver.currentHeight <= 0) {
428
+ if (!_detectedScrollView || !_keyboardObserver || _keyboardObserver.currentHeight <= 0) {
402
429
  return;
403
430
  }
404
431
 
@@ -409,22 +436,26 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
409
436
  }
410
437
 
411
438
  dispatch_async(dispatch_get_main_queue(), ^{
439
+ // Typing can grow the content (e.g. a multiline input) past the fits-above-
440
+ // the-keyboard threshold — keep the inset in sync before revealing the caret.
441
+ [self updateBottomInset];
412
442
  [self scrollToFocusedCaretAnimated:YES];
413
443
  });
414
444
  }
415
445
 
416
446
  - (void)scrollToFocusedCaretAnimated:(BOOL)animated {
417
- if (!_pinnedScrollView) {
447
+ if (!_detectedScrollView) {
418
448
  return;
419
449
  }
420
450
 
421
451
  TrueSheetViewController *sheetController = _keyboardObserver.viewController;
422
452
  UIView *firstResponder = sheetController ? [sheetController.view findFirstResponder] : nil;
423
- if (!firstResponder) {
453
+ UIScrollView *scrollView = _detectedScrollView.scrollView;
454
+ // An input outside the scrollable (e.g. in the footer) can't be revealed by scrolling it
455
+ if (!firstResponder || ![firstResponder isDescendantOfView:scrollView]) {
424
456
  return;
425
457
  }
426
458
 
427
- UIScrollView *scrollView = _pinnedScrollView.scrollView;
428
459
  CGRect targetRect = [firstResponder convertRect:firstResponder.bounds toView:scrollView];
429
460
 
430
461
  if ([firstResponder conformsToProtocol:@protocol(UITextInput)]) {
@@ -442,14 +473,14 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
442
473
  }
443
474
  }
444
475
 
445
- targetRect.size.height += self.keyboardScrollOffset;
476
+ targetRect.size.height += self.keyboardScrollOffset + [self footerOcclusion] - _appliedKeyboardOffset;
446
477
  [scrollView scrollRectToVisible:targetRect animated:animated];
447
478
  }
448
479
 
449
480
  - (void)keyboardWillHide:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
450
481
  [self stopObservingTextChanges];
451
482
 
452
- if (!_pinnedScrollView) {
483
+ if (!_detectedScrollView) {
453
484
  return;
454
485
  }
455
486
 
@@ -457,8 +488,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
457
488
  delay:0
458
489
  options:curve | UIViewAnimationOptionBeginFromCurrentState
459
490
  animations:^{
460
- [self setScrollViewContentInset:self->_bottomInset
461
- indicatorInset:self->_originalIndicatorBottomInset];
491
+ [self setBottomInset:[self footerInsetAbove:0]];
462
492
  }
463
493
  completion:nil];
464
494
  }
@@ -470,9 +500,10 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
470
500
  [self stopObservingTextChanges];
471
501
  [self clearScrollable];
472
502
  _state.reset();
473
- _scrollableBounded = NO;
474
- _hasAutoDetent = NO;
475
- _lastSize = CGSizeZero;
503
+ _scrollableHandle = 0;
504
+ _safeAreaInsetAdjustment = NO;
505
+ _footerInsetAdjustment = NO;
506
+ _lastReportedNaturalHeight = 0;
476
507
  }
477
508
 
478
509
  @end