@lodev09/react-native-true-sheet 4.0.0-beta.2 → 4.0.0-beta.21

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 (205) 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 +303 -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 +124 -50
  12. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +369 -163
  13. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +59 -12
  14. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +126 -0
  15. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
  16. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
  17. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
  18. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
  19. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  20. package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
  21. package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
  22. package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
  23. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
  24. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  25. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
  26. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
  27. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
  28. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
  29. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
  30. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
  31. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
  32. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
  33. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
  34. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
  35. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
  36. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
  37. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
  38. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  39. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  40. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
  41. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
  42. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
  43. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
  44. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
  45. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
  46. package/ios/TrueSheetContainerView.h +27 -7
  47. package/ios/TrueSheetContainerView.mm +31 -11
  48. package/ios/TrueSheetContentView.h +43 -15
  49. package/ios/TrueSheetContentView.mm +211 -189
  50. package/ios/TrueSheetFooterView.h +6 -0
  51. package/ios/TrueSheetFooterView.mm +93 -17
  52. package/ios/TrueSheetModule.h +2 -7
  53. package/ios/TrueSheetModule.mm +7 -16
  54. package/ios/TrueSheetOverlayView.h +32 -0
  55. package/ios/TrueSheetOverlayView.mm +161 -0
  56. package/ios/TrueSheetView.mm +275 -56
  57. package/ios/TrueSheetViewController.h +31 -3
  58. package/ios/TrueSheetViewController.mm +393 -94
  59. package/ios/core/TrueSheetBlurView.h +0 -2
  60. package/ios/core/TrueSheetBlurView.mm +3 -36
  61. package/ios/core/TrueSheetDetentCalculator.h +7 -1
  62. package/ios/core/TrueSheetDetentCalculator.mm +27 -4
  63. package/ios/core/TrueSheetGrabberView.mm +13 -3
  64. package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
  65. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  66. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  67. package/ios/events/TrueSheetLifecycleEvents.h +2 -0
  68. package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
  69. package/ios/tvos/TrueSheetStubs.mm +12 -0
  70. package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
  71. package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
  72. package/lib/module/TrueSheet.js +175 -39
  73. package/lib/module/TrueSheet.js.map +1 -1
  74. package/lib/module/TrueSheet.web.js +220 -243
  75. package/lib/module/TrueSheet.web.js.map +1 -1
  76. package/lib/module/TrueSheetBackground.js +15 -0
  77. package/lib/module/TrueSheetBackground.js.map +1 -0
  78. package/lib/module/TrueSheetOverlay.js +30 -0
  79. package/lib/module/TrueSheetOverlay.js.map +1 -0
  80. package/lib/module/TrueSheetOverlay.web.js +35 -0
  81. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  82. package/lib/module/TrueSheetPeek.web.js +20 -15
  83. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  84. package/lib/module/detentBackgrounds.js +21 -0
  85. package/lib/module/detentBackgrounds.js.map +1 -0
  86. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  87. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  88. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -10
  89. package/lib/module/index.js +1 -0
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/mocks/index.js +18 -2
  92. package/lib/module/mocks/index.js.map +1 -1
  93. package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
  94. package/lib/module/navigation/base-types.js +2 -0
  95. package/lib/module/navigation/base-types.js.map +1 -0
  96. package/lib/module/navigation/createTrueSheetRouter.js +0 -9
  97. package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
  98. package/lib/module/navigation/expo-router/base-types.js +2 -0
  99. package/lib/module/navigation/expo-router/base-types.js.map +1 -0
  100. package/lib/module/navigation/expo-router/index.js +10 -7
  101. package/lib/module/navigation/expo-router/index.js.map +1 -1
  102. package/lib/module/navigation/expo-router/types.js +4 -0
  103. package/lib/module/navigation/expo-router/types.js.map +1 -0
  104. package/lib/module/navigation/index.js.map +1 -1
  105. package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
  106. package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
  107. package/lib/module/web/constants.js +1 -1
  108. package/lib/module/web/constants.js.map +1 -1
  109. package/lib/module/web/layout.js +168 -0
  110. package/lib/module/web/layout.js.map +1 -0
  111. package/lib/module/web/vaul/constants.js +9 -0
  112. package/lib/module/web/vaul/constants.js.map +1 -1
  113. package/lib/module/web/vaul/context.js.map +1 -1
  114. package/lib/module/web/vaul/helpers.js +1 -0
  115. package/lib/module/web/vaul/helpers.js.map +1 -1
  116. package/lib/module/web/vaul/index.js +47 -7
  117. package/lib/module/web/vaul/index.js.map +1 -1
  118. package/lib/module/web/vaul/use-snap-points.js +21 -10
  119. package/lib/module/web/vaul/use-snap-points.js.map +1 -1
  120. package/lib/typescript/src/TrueSheet.d.ts +6 -0
  121. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  122. package/lib/typescript/src/TrueSheet.types.d.ts +157 -34
  123. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  124. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  125. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  126. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  127. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  128. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  129. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  130. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  131. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  132. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  133. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  134. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  135. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  136. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  137. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +12 -7
  138. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  139. package/lib/typescript/src/index.d.ts +1 -0
  140. package/lib/typescript/src/index.d.ts.map +1 -1
  141. package/lib/typescript/src/mocks/index.d.ts +4 -0
  142. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  143. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
  144. package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
  145. package/lib/typescript/src/navigation/base-types.d.ts +9 -0
  146. package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
  147. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
  148. package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
  149. package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
  150. package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
  151. package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
  152. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  153. package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
  154. package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
  155. package/lib/typescript/src/navigation/index.d.ts +3 -4
  156. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  157. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
  158. package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
  159. package/lib/typescript/src/navigation/types.d.ts +41 -5
  160. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  161. package/lib/typescript/src/web/constants.d.ts +1 -1
  162. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  163. package/lib/typescript/src/web/layout.d.ts +19 -0
  164. package/lib/typescript/src/web/layout.d.ts.map +1 -0
  165. package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
  166. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
  167. package/lib/typescript/src/web/vaul/context.d.ts +1 -0
  168. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
  169. package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
  170. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
  171. package/lib/typescript/src/web/vaul/index.d.ts +13 -1
  172. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  173. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +2 -1
  174. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
  175. package/package.json +17 -8
  176. package/react-native.config.js +1 -0
  177. package/src/TrueSheet.tsx +208 -48
  178. package/src/TrueSheet.types.ts +166 -35
  179. package/src/TrueSheet.web.tsx +272 -290
  180. package/src/TrueSheetBackground.tsx +17 -0
  181. package/src/TrueSheetOverlay.tsx +24 -0
  182. package/src/TrueSheetOverlay.web.tsx +33 -0
  183. package/src/TrueSheetPeek.web.tsx +25 -23
  184. package/src/detentBackgrounds.ts +24 -0
  185. package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
  186. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  187. package/src/fabric/TrueSheetViewNativeComponent.ts +15 -10
  188. package/src/index.ts +1 -0
  189. package/src/mocks/index.ts +17 -2
  190. package/src/mocks/navigationExpoRouter.ts +6 -5
  191. package/src/navigation/base-types.ts +20 -0
  192. package/src/navigation/createTrueSheetRouter.ts +5 -33
  193. package/src/navigation/expo-router/base-types.ts +18 -0
  194. package/src/navigation/expo-router/index.ts +28 -30
  195. package/src/navigation/expo-router/types.ts +116 -0
  196. package/src/navigation/index.ts +6 -7
  197. package/src/navigation/screen/useSheetScreenState.ts +2 -0
  198. package/src/navigation/types.ts +75 -22
  199. package/src/web/constants.ts +1 -1
  200. package/src/web/layout.ts +196 -0
  201. package/src/web/vaul/constants.ts +9 -0
  202. package/src/web/vaul/context.ts +1 -0
  203. package/src/web/vaul/helpers.ts +5 -0
  204. package/src/web/vaul/index.tsx +75 -5
  205. package/src/web/vaul/use-snap-points.ts +32 -15
@@ -13,6 +13,7 @@
13
13
  #import "TrueSheetContentView.h"
14
14
  #import "TrueSheetFooterView.h"
15
15
  #import "TrueSheetModule.h"
16
+ #import "TrueSheetOverlayView.h"
16
17
  #import "TrueSheetViewController.h"
17
18
  #import "core/RNScreensEventObserver.h"
18
19
  #import "events/TrueSheetDragEvents.h"
@@ -53,7 +54,7 @@ using namespace facebook::react;
53
54
  NSInteger _initialDetentIndex;
54
55
  TrueSheetViewInsetAdjustment _insetAdjustment;
55
56
  ScrollableOptions *_scrollableOptions;
56
- BOOL _hasAutoDetent;
57
+ NSInteger _scrollableHandle;
57
58
  BOOL _initialDetentAnimated;
58
59
  BOOL _isSheetUpdatePending;
59
60
  BOOL _pendingLayoutUpdate;
@@ -63,6 +64,9 @@ using namespace facebook::react;
63
64
  BOOL _pendingMountEvent;
64
65
  BOOL _pendingSizeChange;
65
66
  BOOL _pendingPropsUpdate;
67
+ BOOL _isPresentPending;
68
+ NSUInteger _pendingPresentGeneration;
69
+ TrueSheetCompletionBlock _pendingPresentCompletion;
66
70
  NSArray *_pendingDetents;
67
71
  RNScreensEventObserver *_screensEventObserver;
68
72
  }
@@ -110,24 +114,80 @@ using namespace facebook::react;
110
114
  return;
111
115
  }
112
116
 
113
- if (_initialDetentIndex >= 0 && !_didInitiallyPresent) {
114
- UIViewController *vc = [self findPresentingViewController];
117
+ [self presentInitialIfNeeded];
118
+ }
119
+
120
+ // JS holds initialDetentIndex back one commit so effect-driven content (e.g. a
121
+ // navigation footer set via setOptions) commits together with it — by the time
122
+ // the prop lands the tree is final and the sheet presents at its final height.
123
+ // Called from didMoveToWindow (re-attach) and finalizeUpdates (the prop
124
+ // arriving after attach, which didMoveToWindow won't re-fire for).
125
+ - (void)presentInitialIfNeeded {
126
+ if (_initialDetentIndex < 0 || _didInitiallyPresent)
127
+ return;
115
128
 
116
- // Only present if the view controller is in the same window and not being dismissed
117
- if (vc && vc.view.window == self.window && !_controller.isBeingDismissed) {
118
- _didInitiallyPresent = YES;
119
- [self presentAtIndex:_initialDetentIndex animated:_initialDetentAnimated completion:nil];
129
+ UIViewController *vc = [self findPresentingViewController];
130
+
131
+ // Only present if the view controller is in the same window
132
+ if (!vc || vc.view.window != self.window) {
133
+ // Animate next time when sheet finally moves to the correct window
134
+ _initialDetentAnimated = YES;
135
+ return;
136
+ }
137
+
138
+ // A sheet still animating out of the presenter (e.g. our own controller,
139
+ // recycled mid-dismissal) blocks presentation — retry once its transition ends.
140
+ UIViewController *dismissing = vc.presentedViewController;
141
+ if (dismissing && dismissing.isBeingDismissed) {
142
+ _initialDetentAnimated = YES;
143
+
144
+ __weak __typeof(self) weakSelf = self;
145
+ void (^retry)(void) = ^{
146
+ dispatch_async(dispatch_get_main_queue(), ^{
147
+ [weakSelf presentInitialIfNeeded];
148
+ });
149
+ };
150
+
151
+ id<UIViewControllerTransitionCoordinator> coordinator = dismissing.transitionCoordinator;
152
+ if (coordinator) {
153
+ [coordinator animateAlongsideTransition:nil
154
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
155
+ retry();
156
+ }];
120
157
  } else {
121
- // Animate next time when sheet finally moves to the correct window
122
- _initialDetentAnimated = YES;
158
+ retry();
123
159
  }
160
+ return;
124
161
  }
162
+
163
+ _didInitiallyPresent = YES;
164
+
165
+ // Deferred a runloop turn so sibling mounts from the current transaction
166
+ // (e.g. a footer committed alongside the prop) land before measuring.
167
+ __weak __typeof(self) weakSelf = self;
168
+ dispatch_async(dispatch_get_main_queue(), ^{
169
+ __typeof(self) strongSelf = weakSelf;
170
+ if (!strongSelf || strongSelf->_controller.isPresented || strongSelf->_controller.isBeingPresented)
171
+ return;
172
+
173
+ [strongSelf presentAtIndex:strongSelf->_initialDetentIndex
174
+ animated:strongSelf->_initialDetentAnimated
175
+ completion:^(BOOL success, NSError *_Nullable error) {
176
+ // Present can fail if the view detached during this turn —
177
+ // reset so the next attach/prop update retries.
178
+ if (!success) {
179
+ strongSelf->_didInitiallyPresent = NO;
180
+ }
181
+ }];
182
+ });
125
183
  }
126
184
 
127
185
  - (void)dealloc {
128
186
  [_screensEventObserver stopObserving];
129
187
  _screensEventObserver = nil;
130
188
 
189
+ [self cancelPendingPresent];
190
+
131
191
  if (_controller && _controller.presentingViewController) {
132
192
  // Find the root presenting controller to dismiss the entire stack
133
193
  UIViewController *root = _controller.presentingViewController;
@@ -146,8 +206,6 @@ using namespace facebook::react;
146
206
 
147
207
  [_snapshotView removeFromSuperview];
148
208
  _snapshotView = nil;
149
-
150
- [TrueSheetModule unregisterViewWithTag:@(self.tag)];
151
209
  }
152
210
 
153
211
  #pragma mark - RCTComponentViewProtocol
@@ -163,12 +221,8 @@ using namespace facebook::react;
163
221
 
164
222
  // Detents (-1 represents "auto")
165
223
  NSMutableArray *detents = [NSMutableArray new];
166
- _hasAutoDetent = NO;
167
224
  for (const auto &detent : newProps.detents) {
168
225
  [detents addObject:@(detent)];
169
- if (detent == -1) {
170
- _hasAutoDetent = YES;
171
- }
172
226
  }
173
227
 
174
228
  if (oldProps) {
@@ -187,14 +241,29 @@ using namespace facebook::react;
187
241
  // Background color
188
242
  _controller.backgroundColor = RCTUIColorFromSharedColor(newProps.backgroundColor);
189
243
 
244
+ NSMutableArray<NSDictionary *> *detentBackgrounds = [NSMutableArray new];
245
+ ContextContainer contextContainer;
246
+ PropsParserContext parserContext{-1, contextContainer};
247
+ for (const auto &entry : newProps.detentBackgrounds) {
248
+ NSMutableDictionary *background = [NSMutableDictionary new];
249
+ UIColor *color = RCTUIColorFromSharedColor(entry.color);
250
+ if (color) {
251
+ background[@"color"] = color;
252
+ }
253
+ if (!entry.blur.empty()) {
254
+ auto blur = TrueSheetViewBackgroundBlur::None;
255
+ fromRawValue(parserContext, RawValue(entry.blur), blur);
256
+ background[@"blur"] = @(static_cast<int>(blur));
257
+ }
258
+ [detentBackgrounds addObject:background];
259
+ }
260
+ if (![_controller.detentBackgrounds isEqualToArray:detentBackgrounds]) {
261
+ _controller.detentBackgrounds = detentBackgrounds;
262
+ }
263
+
190
264
  // Blur tint
191
265
  _controller.backgroundBlur = newProps.backgroundBlur;
192
266
 
193
- // Blur options
194
- const auto &blurOpts = newProps.blurOptions;
195
- _controller.blurIntensity = blurOpts.intensity >= 0 ? @(blurOpts.intensity) : nil;
196
- _controller.blurInteraction = blurOpts.interaction;
197
-
198
267
  // Corner radius
199
268
  _controller.cornerRadius = newProps.cornerRadius < 0 ? nil : @(newProps.cornerRadius);
200
269
 
@@ -204,8 +273,7 @@ using namespace facebook::react;
204
273
  // Content width
205
274
  _controller.maxContentWidth = newProps.maxContentWidth != 0.0 ? @(newProps.maxContentWidth) : nil;
206
275
 
207
- // Anchor
208
- _controller.anchor = newProps.anchor;
276
+ _controller.placement = newProps.placement;
209
277
 
210
278
  _controller.grabber = newProps.grabber;
211
279
 
@@ -248,33 +316,38 @@ using namespace facebook::react;
248
316
  _initialDetentIndex = newProps.initialDetentIndex;
249
317
  _initialDetentAnimated = newProps.initialDetentAnimated;
250
318
 
251
- const auto &scrollableOpts = newProps.scrollableOptions;
252
- BOOL scrollingExpandsSheet = scrollableOpts.scrollingExpandsSheet;
253
- auto topEdgeEffect = scrollableOpts.topScrollEdgeEffect;
254
- auto bottomEdgeEffect = scrollableOpts.bottomScrollEdgeEffect;
255
- BOOL hasScrollableOptions = scrollableOpts.keyboardScrollOffset > 0 || !scrollingExpandsSheet ||
256
- topEdgeEffect != TrueSheetViewTopScrollEdgeEffect::Hidden ||
257
- bottomEdgeEffect != TrueSheetViewBottomScrollEdgeEffect::Hidden;
258
-
259
- if (hasScrollableOptions) {
260
- ScrollableOptions *options = [[ScrollableOptions alloc] init];
261
- options.keyboardScrollOffset = scrollableOpts.keyboardScrollOffset;
262
- options.scrollingExpandsSheet = scrollingExpandsSheet;
263
- options.topScrollEdgeEffect = topEdgeEffect;
264
- options.bottomScrollEdgeEffect = bottomEdgeEffect;
265
- _scrollableOptions = options;
266
- } else {
267
- _scrollableOptions = nil;
268
- }
319
+ _scrollableHandle = newProps.scrollableHandle;
269
320
 
270
- _controller.scrollingExpandsSheet = scrollingExpandsSheet;
321
+ const auto &scrollableOpts = newProps.scrollableOptions;
322
+ ScrollableOptions *options = [[ScrollableOptions alloc] init];
323
+ options.contentInsetAdjustment = scrollableOpts.contentInsetAdjustment;
324
+ options.keyboardScrollOffset = scrollableOpts.keyboardScrollOffset;
325
+ options.keyboardOffset = scrollableOpts.keyboardOffset;
326
+ options.scrollingExpandsSheet = scrollableOpts.scrollingExpandsSheet;
327
+ options.topScrollEdgeEffect = scrollableOpts.topScrollEdgeEffect;
328
+ options.bottomScrollEdgeEffect = scrollableOpts.bottomScrollEdgeEffect;
329
+
330
+ // nil while every option is at its default
331
+ BOOL hasScrollableOptions = options.keyboardScrollOffset > 0 || options.keyboardOffset != 0 ||
332
+ !options.scrollingExpandsSheet ||
333
+ options.contentInsetAdjustment != TrueSheetViewContentInsetAdjustment::Automatic ||
334
+ options.topScrollEdgeEffect != TrueSheetViewTopScrollEdgeEffect::Hidden ||
335
+ options.bottomScrollEdgeEffect != TrueSheetViewBottomScrollEdgeEffect::Hidden;
336
+ _scrollableOptions = hasScrollableOptions ? options : nil;
337
+
338
+ _controller.scrollingExpandsSheet = options.scrollingExpandsSheet;
271
339
 
272
340
  _controller.absoluteHeader = newProps.headerOptions.position == TrueSheetViewPosition::Absolute;
273
341
  _controller.absoluteFooter = newProps.footerOptions.footerPosition == TrueSheetViewFooterPosition::Absolute;
342
+ _controller.footerInsetAdjustment =
343
+ options.footerInsetAdjustment && _controller.absoluteFooter && _scrollableHandle > 0;
274
344
 
275
345
  CGFloat footerKeyboardOffset = newProps.footerOptions.keyboardOffset;
276
- if (_controller.footerKeyboardOffset != footerKeyboardOffset) {
346
+ BOOL footerAvoidsKeyboard = newProps.footerOptions.avoidKeyboard;
347
+ if (_controller.footerKeyboardOffset != footerKeyboardOffset ||
348
+ _controller.footerAvoidsKeyboard != footerAvoidsKeyboard) {
277
349
  _controller.footerKeyboardOffset = footerKeyboardOffset;
350
+ _controller.footerAvoidsKeyboard = footerAvoidsKeyboard;
278
351
  [_containerView updateFooterKeyboardOffset];
279
352
  }
280
353
 
@@ -342,13 +415,49 @@ using namespace facebook::react;
342
415
  _pendingPropsUpdate = YES;
343
416
  } else if (_initialDetentIndex >= 0) {
344
417
  _pendingLayoutUpdate = NO;
418
+ if (self.window) {
419
+ [self presentInitialIfNeeded];
420
+ }
345
421
  }
346
422
  }
347
423
 
348
424
  - (void)prepareForRecycle {
349
425
  [super prepareForRecycle];
350
426
 
351
- [TrueSheetModule unregisterViewWithTag:@(self.tag)];
427
+ // Pooled mid-dismissal: the next mount can reuse this host — and its
428
+ // controller — while the previous sheet is still animating out. Detach until
429
+ // that transition ends so its tail (didBlur, didDismiss, position) can't reach
430
+ // the new mount, then hand the controller back and let auto-present retry.
431
+ if (_controller.isBeingDismissed) {
432
+ _controller.delegate = nil;
433
+
434
+ __weak __typeof(self) weakSelf = self;
435
+ void (^reattach)(void) = ^{
436
+ dispatch_async(dispatch_get_main_queue(), ^{
437
+ __typeof(self) strongSelf = weakSelf;
438
+ if (!strongSelf)
439
+ return;
440
+
441
+ strongSelf->_controller.delegate = strongSelf;
442
+ strongSelf->_controller.activeDetentIndex = -1;
443
+ if (strongSelf.window) {
444
+ [strongSelf presentInitialIfNeeded];
445
+ }
446
+ });
447
+ };
448
+
449
+ id<UIViewControllerTransitionCoordinator> coordinator = _controller.transitionCoordinator;
450
+ if (coordinator) {
451
+ [coordinator animateAlongsideTransition:nil
452
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
453
+ reattach();
454
+ }];
455
+ } else {
456
+ reattach();
457
+ }
458
+ }
459
+
460
+ [self cancelPendingPresent];
352
461
 
353
462
  _lastStateSize = CGSizeZero;
354
463
  _didInitiallyPresent = NO;
@@ -389,7 +498,6 @@ using namespace facebook::react;
389
498
  [_touchHandler attachToView:_containerView];
390
499
  [_controller.view addSubview:_containerView];
391
500
  [_controller.view bringSubviewToFront:_containerView];
392
- _containerView.accessibilityViewIsModal = YES;
393
501
  _controller.accessibilityContentView = _containerView;
394
502
  [_controller setupAccessibilityContainer];
395
503
 
@@ -403,9 +511,8 @@ using namespace facebook::react;
403
511
  _controller.headerHeight = @(headerHeight);
404
512
  }
405
513
 
406
- CGFloat footerHeight = [_containerView footerHeight];
407
- if (footerHeight > 0) {
408
- _controller.footerHeight = @(footerHeight);
514
+ if ([_containerView footerHeight] > 0) {
515
+ [self syncFooterMetrics];
409
516
  }
410
517
 
411
518
  CGFloat peekContentHeight = [_containerView peekContentHeight];
@@ -445,7 +552,7 @@ using namespace facebook::react;
445
552
  - (void)presentAtIndex:(NSInteger)index
446
553
  animated:(BOOL)animated
447
554
  completion:(nullable TrueSheetCompletionBlock)completion {
448
- if (_controller.isBeingPresented || _controller.isPresented) {
555
+ if (_controller.isBeingPresented || _controller.isPresented || _isPresentPending) {
449
556
  RCTLogWarn(@"TrueSheet: sheet is already presented. Use resize() to change detent.");
450
557
  if (completion) {
451
558
  completion(YES, nil);
@@ -467,11 +574,41 @@ using namespace facebook::react;
467
574
  return;
468
575
  }
469
576
 
577
+ // A controller still animating out of the presenter (e.g. a navigation modal
578
+ // closing) makes UIKit queue this presentation, so everything measured below
579
+ // goes stale before it starts. Present once that transition ends.
580
+ UIViewController *dismissing = presentingViewController.presentedViewController;
581
+ id<UIViewControllerTransitionCoordinator> dismissCoordinator =
582
+ dismissing.isBeingDismissed ? dismissing.transitionCoordinator : nil;
583
+ if (dismissCoordinator) {
584
+ _isPresentPending = YES;
585
+ _pendingPresentCompletion = completion;
586
+ NSUInteger generation = ++_pendingPresentGeneration;
587
+
588
+ __weak __typeof(self) weakSelf = self;
589
+ [dismissCoordinator
590
+ animateAlongsideTransition:nil
591
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
592
+ dispatch_async(dispatch_get_main_queue(), ^{
593
+ __typeof(self) strongSelf = weakSelf;
594
+ if (!strongSelf || !strongSelf->_isPresentPending ||
595
+ strongSelf->_pendingPresentGeneration != generation)
596
+ return;
597
+
598
+ TrueSheetCompletionBlock pendingCompletion = strongSelf->_pendingPresentCompletion;
599
+ strongSelf->_isPresentPending = NO;
600
+ strongSelf->_pendingPresentCompletion = nil;
601
+ [strongSelf presentAtIndex:index animated:animated completion:pendingCompletion];
602
+ });
603
+ }];
604
+ return;
605
+ }
606
+
470
607
  [_controller setupAnchorViewInView:presentingViewController.view];
471
608
  [_controller setupSheetSizing];
472
609
  [_controller setupSheetProps];
473
610
 
474
- // Pin before measuring so a first-time pin doesn't shift sizes mid-animation.
611
+ // Detect before measuring so a first-time detection doesn't shift sizes mid-animation.
475
612
  [self setupScrollable];
476
613
 
477
614
  // Measure synchronously so the presentation starts at the right height —
@@ -480,24 +617,41 @@ using namespace facebook::react;
480
617
  if (_containerView) {
481
618
  _controller.contentHeight = @([_containerView contentHeight]);
482
619
  _controller.headerHeight = @([_containerView headerHeight]);
483
- _controller.footerHeight = @([_containerView footerHeight]);
620
+ [self syncFooterMetrics];
484
621
  _controller.peekContentHeight = @([_containerView peekContentHeight]);
485
622
  }
486
623
  _pendingSizeChange = NO;
487
624
 
625
+ [self refreshFooterBottomInset];
488
626
  [_controller setupSheetDetents];
489
627
  [_controller setupActiveDetentWithIndex:index];
490
628
 
491
629
  [_screensEventObserver capturePresenterScreenFromView:self];
492
630
  [_screensEventObserver startObservingWithState:_state.get()->getData()];
493
631
 
632
+ UIWindow *window = presentingViewController.view.window;
494
633
  [presentingViewController presentViewController:_controller
495
634
  animated:animated
496
635
  completion:^{
636
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
497
637
  if (completion) {
498
638
  completion(YES, nil);
499
639
  }
500
640
  }];
641
+
642
+ // Presenting adds the sheet's transition view on top of the window once the
643
+ // transition starts — re-front overlays alongside it so the first frame is
644
+ // already ordered (and again on completion in case the transition was queued)
645
+ id<UIViewControllerTransitionCoordinator> coordinator = _controller.transitionCoordinator;
646
+ if (coordinator) {
647
+ [coordinator
648
+ animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
649
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
650
+ }
651
+ completion:nil];
652
+ } else {
653
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
654
+ }
501
655
  }
502
656
 
503
657
  - (void)resizeToIndex:(NSInteger)index completion:(nullable TrueSheetCompletionBlock)completion {
@@ -530,7 +684,32 @@ using namespace facebook::react;
530
684
  realtime:NO];
531
685
  }
532
686
 
687
+ /**
688
+ * Drops a deferred present and resolves its caller. Returns whether one was pending.
689
+ */
690
+ - (BOOL)cancelPendingPresent {
691
+ if (!_isPresentPending) {
692
+ return NO;
693
+ }
694
+
695
+ TrueSheetCompletionBlock pendingCompletion = _pendingPresentCompletion;
696
+ _isPresentPending = NO;
697
+ _pendingPresentCompletion = nil;
698
+ if (pendingCompletion) {
699
+ pendingCompletion(YES, nil);
700
+ }
701
+ return YES;
702
+ }
703
+
533
704
  - (void)dismissAnimated:(BOOL)animated completion:(nullable TrueSheetCompletionBlock)completion {
705
+ // Dismissing before a deferred present starts cancels it — nothing was presented.
706
+ if ([self cancelPendingPresent]) {
707
+ if (completion) {
708
+ completion(YES, nil);
709
+ }
710
+ return;
711
+ }
712
+
534
713
  if (_controller.isBeingDismissed || !_controller.isPresented) {
535
714
  RCTLogWarn(@"TrueSheet: sheet is already dismissed. No need to dismiss it again.");
536
715
 
@@ -583,6 +762,10 @@ using namespace facebook::react;
583
762
  * Debounced sheet update to handle rapid content/header size changes.
584
763
  */
585
764
  - (void)setupSheetDetentsForSizeChange {
765
+ // Keep the footer's absorbed inset in sync with the latest measured heights
766
+ // even before presenting, so the sheet presents at its final size.
767
+ [self refreshFooterBottomInset];
768
+
586
769
  // Retargeting the in-flight presentation makes UIKit snap the whole stack
587
770
  // (including the sheet behind) instead of animating. Defer to didPresent —
588
771
  // presentAtIndex measured synchronously, so this is usually a no-op.
@@ -610,6 +793,7 @@ using namespace facebook::react;
610
793
  // Refresh here (not just on peek size events) since the peek's offset
611
794
  // within the content can change without its own size changing.
612
795
  self->_controller.peekContentHeight = @([self->_containerView peekContentHeight]);
796
+ [self refreshFooterBottomInset];
613
797
  [self->_controller setupSheetDetentsForSizeChange];
614
798
  });
615
799
  }
@@ -625,7 +809,7 @@ using namespace facebook::react;
625
809
  }
626
810
 
627
811
  - (void)containerViewFooterDidChangeSize:(CGSize)newSize {
628
- _controller.footerHeight = @(newSize.height);
812
+ [self syncFooterMetrics];
629
813
  [self setupSheetDetentsForSizeChange];
630
814
  [_controller setupAccessibilityContainer];
631
815
  }
@@ -634,7 +818,7 @@ using namespace facebook::react;
634
818
  [self setupSheetDetentsForSizeChange];
635
819
  }
636
820
 
637
- // When the ScrollView changes (e.g. conditional remount), re-pin the new ScrollView.
821
+ // When the ScrollView changes (e.g. conditional remount), re-detect the new ScrollView.
638
822
  - (void)containerViewScrollViewDidChange {
639
823
  [self setupScrollable];
640
824
  }
@@ -687,6 +871,10 @@ using namespace facebook::react;
687
871
  }
688
872
  }
689
873
 
874
+ - (void)viewControllerDidAttemptDismiss {
875
+ [TrueSheetLifecycleEvents emitDismissAttempt:_eventEmitter];
876
+ }
877
+
690
878
  - (void)viewControllerDidDismiss {
691
879
  [_containerView cleanupKeyboardObserver];
692
880
  if (!_dismissedByNavigation) {
@@ -716,6 +904,11 @@ using namespace facebook::react;
716
904
  [self updateStateWithSize:size];
717
905
  }
718
906
 
907
+ - (void)viewControllerSafeAreaInsetsDidChange {
908
+ [self refreshFooterBottomInset];
909
+ [_containerView updateFooterKeyboardOffset];
910
+ }
911
+
719
912
  - (void)viewControllerWillFocus {
720
913
  [TrueSheetFocusEvents emitWillFocus:_eventEmitter];
721
914
  }
@@ -781,16 +974,41 @@ using namespace facebook::react;
781
974
  if (!_containerView)
782
975
  return;
783
976
 
784
- _containerView.insetAdjustment = _insetAdjustment;
785
977
  _containerView.scrollableOptions = _scrollableOptions;
786
- _containerView.hasAutoDetent = _hasAutoDetent;
787
- _containerView.footerBottomInset = _controller.footerBottomInset;
978
+ _containerView.scrollableHandle = _scrollableHandle;
979
+ _containerView.safeAreaInsetAdjustment = (_scrollableOptions ? _scrollableOptions.safeAreaInsetAdjustment : YES) &&
980
+ _insetAdjustment == TrueSheetViewInsetAdjustment::Automatic;
981
+ _containerView.footerInsetAdjustment = _controller.footerInsetAdjustment;
982
+ [self refreshFooterBottomInset];
788
983
  [_containerView setupScrollable];
984
+ }
985
+
986
+ // footerHeight and appliedFooterBottomInset must stay paired — detent math
987
+ // subtracts exactly the inset baked into the measured footer height (see
988
+ // autoDetentBottomInset/peekDetentBottomInset).
989
+ - (void)syncFooterMetrics {
990
+ _controller.footerHeight = @([_containerView footerHeight]);
991
+ _controller.appliedFooterBottomInset = [_containerView footerAppliedBottomInset];
992
+ }
993
+
994
+ // Recomputes the inset the footer absorbs and pushes it down. The inset
995
+ // depends on measured heights and detents (see maxNaturalDetentHeight), so
996
+ // this runs on size and detent changes — not just prop updates.
997
+ - (void)refreshFooterBottomInset {
998
+ if (!_containerView)
999
+ return;
1000
+
1001
+ // appliedFooterBottomInset is NOT set here — it tracks what's actually
1002
+ // baked into the footer's measured height (reported with its layout), not
1003
+ // what was just pushed. Setting it at push time desyncs the pair and lets
1004
+ // a seeded inset read as natural content height (see maxNaturalDetentHeight).
1005
+ CGFloat inset = _controller.footerBottomInset;
1006
+ _containerView.footerBottomInset = inset;
789
1007
 
790
1008
  // Publish the inset so a footer set later (e.g. navigation setOptions) is
791
1009
  // padded on its very first layout instead of resizing the sheet twice.
792
1010
  if (_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic) {
793
- TrueSheetInsets::setBottomSafeArea(_controller.footerBottomInset);
1011
+ TrueSheetInsets::setBottomSafeArea(inset);
794
1012
  }
795
1013
  }
796
1014
 
@@ -801,6 +1019,7 @@ using namespace facebook::react;
801
1019
  if (_pendingDetents) {
802
1020
  _controller.detents = _pendingDetents;
803
1021
  _pendingDetents = nil;
1022
+ [self refreshFooterBottomInset];
804
1023
  }
805
1024
 
806
1025
  UIView *presenterView = _controller.presentingViewController.view;
@@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
26
26
  - (void)viewControllerDidPresentAtIndex:(NSInteger)index position:(CGFloat)position detent:(CGFloat)detent;
27
27
  - (void)viewControllerWillDismiss;
28
28
  - (void)viewControllerDidDismiss;
29
+ - (void)viewControllerDidAttemptDismiss;
29
30
  - (void)viewControllerDidChangeDetent:(NSInteger)index position:(CGFloat)position detent:(CGFloat)detent;
30
31
  - (void)viewControllerDidDrag:(UIGestureRecognizerState)state
31
32
  index:(NSInteger)index
@@ -36,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
36
37
  detent:(CGFloat)detent
37
38
  realtime:(BOOL)realtime;
38
39
  - (void)viewControllerDidChangeSize:(CGSize)size;
40
+ - (void)viewControllerSafeAreaInsetsDidChange;
39
41
  - (void)viewControllerWillFocus;
40
42
  - (void)viewControllerDidFocus;
41
43
  - (void)viewControllerWillBlur;
@@ -53,6 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
53
55
 
54
56
  @property (nonatomic, weak, nullable) id<TrueSheetViewControllerDelegate> delegate;
55
57
  @property (nonatomic, strong) NSArray<NSNumber *> *detents;
58
+ @property (nonatomic, copy) NSArray<NSDictionary *> *detentBackgrounds;
56
59
  @property (nonatomic, strong, nullable) NSNumber *maxContentHeight;
57
60
  @property (nonatomic, strong, nullable) NSNumber *maxContentWidth;
58
61
  @property (nonatomic, strong, nullable) NSNumber *contentHeight;
@@ -60,6 +63,13 @@ NS_ASSUME_NONNULL_BEGIN
60
63
  @property (nonatomic, assign) BOOL absoluteHeader;
61
64
  @property (nonatomic, strong, nullable) NSNumber *footerHeight;
62
65
  @property (nonatomic, assign) BOOL absoluteFooter;
66
+
67
+ /**
68
+ * YES while the plugged scrollable is padded by the absolute footer's height
69
+ * (see ScrollableOptions.contentInsetAdjustment) — the footer then counts
70
+ * toward the auto detent like a relative one, so the content ends above it.
71
+ */
72
+ @property (nonatomic, assign) BOOL footerInsetAdjustment;
63
73
  @property (nonatomic, strong, nullable) NSNumber *peekContentHeight;
64
74
  @property (nonatomic, strong, nullable) UIColor *backgroundColor;
65
75
  @property (nonatomic, strong, nullable) NSNumber *cornerRadius;
@@ -70,13 +80,17 @@ NS_ASSUME_NONNULL_BEGIN
70
80
  @property (nonatomic, assign) BOOL dimmed;
71
81
  @property (nonatomic, strong, nullable) NSNumber *dimmedDetentIndex;
72
82
  @property (nonatomic, assign) facebook::react::TrueSheetViewBackgroundBlur backgroundBlur;
73
- @property (nonatomic, strong, nullable) NSNumber *blurIntensity;
74
- @property (nonatomic, assign) BOOL blurInteraction;
75
83
  @property (nonatomic, assign) facebook::react::TrueSheetViewPresentation presentation;
76
- @property (nonatomic, assign) facebook::react::TrueSheetViewAnchor anchor;
84
+ @property (nonatomic, assign) facebook::react::TrueSheetViewPlacement placement;
77
85
  @property (nonatomic, assign) facebook::react::TrueSheetViewInsetAdjustment insetAdjustment;
78
86
  @property (nonatomic, assign) BOOL scrollingExpandsSheet;
79
87
  @property (nonatomic, assign) CGFloat footerKeyboardOffset;
88
+
89
+ /**
90
+ * NO keeps an absolute footer pinned to the sheet's bottom edge behind the
91
+ * keyboard instead of rising above it.
92
+ */
93
+ @property (nonatomic, assign) BOOL footerAvoidsKeyboard;
80
94
  @property (nonatomic, assign) BOOL dismissible;
81
95
  @property (nonatomic, assign) BOOL isPresented;
82
96
  @property (nonatomic, assign) NSInteger activeDetentIndex;
@@ -87,6 +101,12 @@ NS_ASSUME_NONNULL_BEGIN
87
101
  * ~keyboardHeight into the learned offset.
88
102
  */
89
103
  @property (nonatomic, assign) BOOL keyboardSheetGrown;
104
+
105
+ /**
106
+ * Target keyboard height, set as soon as the keyboard frame change is
107
+ * announced — ahead of the grow/shrink animation, unlike keyboardSheetGrown.
108
+ */
109
+ @property (nonatomic, assign) CGFloat keyboardHeight;
90
110
  @property (nonatomic, readonly) BOOL isTopmostPresentedController;
91
111
  @property (nonatomic, readonly) BOOL isStackedBehindChild;
92
112
  @property (nonatomic, weak, nullable) UIView *accessibilityContentView;
@@ -99,6 +119,14 @@ NS_ASSUME_NONNULL_BEGIN
99
119
  */
100
120
  @property (nonatomic, readonly) CGFloat footerBottomInset;
101
121
 
122
+ /**
123
+ * The inset currently baked into the footer's measured height, reported
124
+ * alongside its layout (see TrueSheetFooterView.appliedBottomInset). Detent
125
+ * resolution subtracts this exact value so the baked inset is never
126
+ * double-counted.
127
+ */
128
+ @property (nonatomic, assign) CGFloat appliedFooterBottomInset;
129
+
102
130
  - (void)setupAccessibilityContainer;
103
131
  - (void)applyActiveDetent;
104
132
  - (void)setupActiveDetentWithIndex:(NSInteger)index;