@lodev09/react-native-true-sheet 4.0.0-beta.1 → 4.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -4
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +24 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +211 -182
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +14 -19
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +82 -39
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +275 -88
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +26 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +75 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +56 -34
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +1 -1
- package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +0 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +54 -29
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +6 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +6 -4
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +7 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
- package/ios/TrueSheetContainerView.h +14 -7
- package/ios/TrueSheetContainerView.mm +10 -7
- package/ios/TrueSheetContentView.h +31 -15
- package/ios/TrueSheetContentView.mm +133 -179
- package/ios/TrueSheetFooterView.h +6 -0
- package/ios/TrueSheetFooterView.mm +48 -2
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +7 -16
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetView.mm +171 -29
- package/ios/TrueSheetViewController.h +10 -0
- package/ios/TrueSheetViewController.mm +256 -74
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetLifecycleEvents.h +2 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +149 -34
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +122 -53
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +3 -3
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +10 -0
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigation.js +31 -21
- package/lib/module/mocks/navigation.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js +21 -0
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
- package/lib/module/mocks/reanimated.js +1 -1
- package/lib/module/mocks/reanimated.js.map +1 -1
- package/lib/module/navigation/{TrueSheetView.js → TrueSheetNavigatorContent.js} +14 -12
- package/lib/module/navigation/TrueSheetNavigatorContent.js.map +1 -0
- package/lib/module/navigation/actions.js +50 -0
- package/lib/module/navigation/actions.js.map +1 -0
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/{TrueSheetRouter.js → createTrueSheetRouter.js} +11 -24
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -0
- package/lib/module/navigation/expo-router/base-types.js +2 -0
- package/lib/module/navigation/expo-router/base-types.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +50 -0
- package/lib/module/navigation/expo-router/index.js.map +1 -0
- package/lib/module/navigation/expo-router/types.js +4 -0
- package/lib/module/navigation/expo-router/types.js.map +1 -0
- package/lib/module/navigation/index.js +43 -2
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/navigator.js +10 -0
- package/lib/module/navigation/navigator.js.map +1 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +2 -2
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/TrueSheetScreen.js +2 -2
- package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +5 -4
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/navigation/useTrueSheetNavigation.js +1 -1
- package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -1
- package/lib/module/web/dom.js +10 -0
- package/lib/module/web/dom.js.map +1 -0
- package/lib/module/web/vaul/constants.js +4 -0
- package/lib/module/web/vaul/constants.js.map +1 -1
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/helpers.js +10 -4
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +35 -2
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +6 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +72 -1
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigation.d.ts +5 -1
- package/lib/typescript/src/mocks/navigation.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +22 -0
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -0
- package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -1
- package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts +5 -0
- package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts.map +1 -0
- package/lib/typescript/src/navigation/actions.d.ts +71 -0
- package/lib/typescript/src/navigation/actions.d.ts.map +1 -0
- package/lib/typescript/src/navigation/base-types.d.ts +9 -0
- package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +3 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +37 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +42 -3
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/navigator.d.ts +7 -0
- package/lib/typescript/src/navigation/navigator.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +1 -1
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +1 -1
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/types.d.ts +3 -4
- package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +5 -6
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +65 -5
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +1 -1
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -1
- package/lib/typescript/src/web/dom.d.ts +7 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +7 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/package.json +32 -9
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +184 -46
- package/src/TrueSheet.types.ts +77 -1
- package/src/TrueSheet.web.tsx +135 -64
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +4 -3
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +7 -0
- package/src/mocks/navigation.ts +32 -25
- package/src/mocks/navigationExpoRouter.ts +41 -0
- package/src/mocks/reanimated.ts +8 -9
- package/src/navigation/{TrueSheetView.tsx → TrueSheetNavigatorContent.tsx} +24 -17
- package/src/navigation/actions.ts +78 -0
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +194 -0
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/index.ts +90 -0
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +68 -2
- package/src/navigation/navigator.ts +18 -0
- package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +2 -2
- package/src/navigation/screen/TrueSheetScreen.tsx +2 -2
- package/src/navigation/screen/types.ts +4 -6
- package/src/navigation/screen/useSheetScreenState.ts +10 -15
- package/src/navigation/types.ts +99 -16
- package/src/navigation/useTrueSheetNavigation.ts +1 -1
- package/src/web/dom.ts +9 -0
- package/src/web/vaul/constants.ts +4 -0
- package/src/web/vaul/context.ts +1 -0
- package/src/web/vaul/helpers.ts +13 -6
- package/src/web/vaul/index.tsx +46 -1
- package/lib/module/navigation/TrueSheetRouter.js.map +0 -1
- package/lib/module/navigation/TrueSheetView.js.map +0 -1
- package/lib/module/navigation/createTrueSheetNavigator.js +0 -63
- package/lib/module/navigation/createTrueSheetNavigator.js.map +0 -1
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +0 -57
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +0 -1
- package/lib/typescript/src/navigation/TrueSheetView.d.ts +0 -10
- package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +0 -1
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +0 -35
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +0 -1
- package/src/navigation/TrueSheetRouter.ts +0 -234
- package/src/navigation/createTrueSheetNavigator.tsx +0 -93
|
@@ -45,6 +45,7 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
45
45
|
- (void)restoreWindowAccessibilityElements;
|
|
46
46
|
- (void)setSheetAccessibilityElementsHidden:(BOOL)hidden;
|
|
47
47
|
- (void)setAccessibilityContentElement:(UIView *)contentView;
|
|
48
|
+
- (NSArray *)accessibilityGrabberElements;
|
|
48
49
|
- (void)endInteractiveDismissState;
|
|
49
50
|
- (void)animateInteractiveContainerToTransform:(CGAffineTransform)transform
|
|
50
51
|
duration:(NSTimeInterval)duration
|
|
@@ -53,6 +54,20 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
53
54
|
|
|
54
55
|
@end
|
|
55
56
|
|
|
57
|
+
// VoiceOver delivers the two-finger Z (escape) gesture by walking up the view
|
|
58
|
+
// hierarchy from the focused element, so the sheet's root view handles it.
|
|
59
|
+
@interface TrueSheetControllerView : UIView
|
|
60
|
+
@property (nonatomic, weak) TrueSheetViewController *controller;
|
|
61
|
+
@end
|
|
62
|
+
|
|
63
|
+
@implementation TrueSheetControllerView
|
|
64
|
+
|
|
65
|
+
- (BOOL)accessibilityPerformEscape {
|
|
66
|
+
return [self.controller accessibilityPerformEscape];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@end
|
|
70
|
+
|
|
56
71
|
@implementation TrueSheetViewController {
|
|
57
72
|
TrueSheetPositionState _lastEmittedPositionState;
|
|
58
73
|
CGSize _lastReportedSize;
|
|
@@ -70,6 +85,11 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
70
85
|
BOOL _isDragging;
|
|
71
86
|
BOOL _isWillDismissEmitted;
|
|
72
87
|
|
|
88
|
+
// Last bottom safe area observed while presented — reused as the estimate
|
|
89
|
+
// for the next present (beats the float-threshold guess for re-presents).
|
|
90
|
+
BOOL _hasObservedBottomInset;
|
|
91
|
+
CGFloat _observedBottomInset;
|
|
92
|
+
|
|
73
93
|
BOOL _isInteractiveDismiss;
|
|
74
94
|
CGFloat _interactiveStartPosition;
|
|
75
95
|
UIView *_interactiveContainerView;
|
|
@@ -202,10 +222,19 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
- (CGFloat)screenHeight {
|
|
205
|
-
|
|
225
|
+
// In a windowed iPad app (Stage Manager, iPadOS 26 windowing) the window can be shorter than
|
|
226
|
+
// UIScreen.mainScreen, so prefer the window's height. Some callers run before this controller's
|
|
227
|
+
// view is attached (e.g. mid-presentation detent resolution), so fall back to the presenter's.
|
|
228
|
+
UIWindow *window = self.view.window ?: self.presentingViewController.view.window;
|
|
206
229
|
return window ? window.bounds.size.height : UIScreen.mainScreen.bounds.size.height;
|
|
207
230
|
}
|
|
208
231
|
|
|
232
|
+
// Only phone sheets absorb the bottom safe-area inset — non-phone idioms
|
|
233
|
+
// (iPad form sheets) are inset from the screen edge by UIKit.
|
|
234
|
+
static BOOL TrueSheetIsPhoneIdiom(void) {
|
|
235
|
+
return UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone;
|
|
236
|
+
}
|
|
237
|
+
|
|
209
238
|
- (CGFloat)detentBottomAdjustmentForHeight:(CGFloat)height {
|
|
210
239
|
if (_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic) {
|
|
211
240
|
return 0;
|
|
@@ -215,7 +244,7 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
215
244
|
}
|
|
216
245
|
|
|
217
246
|
- (CGFloat)bottomSafeAreaForHeight:(CGFloat)height {
|
|
218
|
-
if (
|
|
247
|
+
if (!TrueSheetIsPhoneIdiom()) {
|
|
219
248
|
return 0;
|
|
220
249
|
}
|
|
221
250
|
|
|
@@ -232,49 +261,101 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
232
261
|
}
|
|
233
262
|
|
|
234
263
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
264
|
+
* Largest height the sheet can reach across its configured detents, excluding
|
|
265
|
+
* the inset currently baked into the footer's height. Basis for the footer's
|
|
266
|
+
* inset absorption so it follows the same rule as bottomSafeAreaForHeight: —
|
|
267
|
+
* a sheet that only ever floats (small detents on iOS 26) keeps its footer
|
|
268
|
+
* unpadded.
|
|
238
269
|
*/
|
|
239
|
-
- (CGFloat)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
270
|
+
- (CGFloat)maxNaturalDetentHeight {
|
|
271
|
+
CGFloat applied = [self.footerHeight floatValue] > 0 ? _appliedFooterBottomInset : 0;
|
|
272
|
+
CGFloat maxHeight = 0;
|
|
273
|
+
|
|
274
|
+
for (id detent in self.detents) {
|
|
275
|
+
if (![detent isKindOfClass:[NSNumber class]]) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
CGFloat value = [detent doubleValue];
|
|
280
|
+
CGFloat height = 0;
|
|
281
|
+
if (value == -1) {
|
|
282
|
+
// Auto counts a relative footer's (padded) height
|
|
283
|
+
height = [_detentCalculator autoHeight] - (_absoluteFooter ? 0 : applied);
|
|
284
|
+
} else if (value == -2) {
|
|
285
|
+
// Peek counts an absolute footer's (padded) height
|
|
286
|
+
height = [_detentCalculator peekHeight] - (_absoluteFooter ? applied : 0);
|
|
287
|
+
} else if (value > 0 && value <= 1) {
|
|
288
|
+
height = value * self.screenHeight;
|
|
289
|
+
}
|
|
290
|
+
maxHeight = fmax(maxHeight, height);
|
|
243
291
|
}
|
|
244
292
|
|
|
245
|
-
if (
|
|
246
|
-
|
|
293
|
+
if (self.maxContentHeight) {
|
|
294
|
+
maxHeight = fmin(maxHeight, [self.maxContentHeight floatValue]);
|
|
247
295
|
}
|
|
248
296
|
|
|
249
|
-
return
|
|
297
|
+
return maxHeight > 0 ? maxHeight : self.screenHeight;
|
|
250
298
|
}
|
|
251
299
|
|
|
252
|
-
// The inset the footer absorbs as padding.
|
|
253
|
-
// available so it matches the auto detent's inset exclusion above.
|
|
300
|
+
// The inset the footer absorbs as padding.
|
|
254
301
|
- (CGFloat)footerBottomInset {
|
|
255
302
|
if (_insetAdjustment != TrueSheetViewInsetAdjustment::Automatic) {
|
|
256
303
|
return 0;
|
|
257
304
|
}
|
|
258
305
|
|
|
259
|
-
|
|
306
|
+
// Early out so the maxNaturalDetentHeight fallback below doesn't run per
|
|
307
|
+
// layout pass on iPad only to be zeroed by bottomSafeAreaForHeight:.
|
|
308
|
+
if (!TrueSheetIsPhoneIdiom()) {
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Once laid out, the sheet's own safe area is the authority — it reflects
|
|
313
|
+
// UIKit's actual float/anchored decision (a floating sheet gets 0), where
|
|
314
|
+
// bottomSafeAreaForHeight: can only guess the float threshold. Guessing
|
|
315
|
+
// wrong leaves the safe-area region unfilled (gap) or double-padded.
|
|
316
|
+
UIView *view = self.viewIfLoaded;
|
|
317
|
+
if (view.window) {
|
|
318
|
+
_hasObservedBottomInset = YES;
|
|
319
|
+
_observedBottomInset = view.safeAreaInsets.bottom;
|
|
320
|
+
return _observedBottomInset;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Off-window while presented (e.g. hidden behind a native screen): the last
|
|
324
|
+
// observed value is this sheet's own truth — prefer it over the
|
|
325
|
+
// float-threshold guess. Cleared on dismiss so a re-present observes fresh
|
|
326
|
+
// geometry (rotation or detent changes while dismissed would stale it).
|
|
327
|
+
if (_hasObservedBottomInset) {
|
|
328
|
+
return _observedBottomInset;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return [self bottomSafeAreaForHeight:[self maxNaturalDetentHeight]];
|
|
260
332
|
}
|
|
261
333
|
|
|
262
334
|
/**
|
|
263
|
-
* Bottom inset excluded from the
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
335
|
+
* Bottom inset excluded from the auto detent. A relative footer owns the
|
|
336
|
+
* sheet's bottom edge and carries the inset in its height (see
|
|
337
|
+
* footerBottomInset) — subtract exactly what was baked in so UIKit's own
|
|
338
|
+
* safe-area layout doesn't double it.
|
|
267
339
|
*/
|
|
268
|
-
- (CGFloat)
|
|
269
|
-
if (
|
|
340
|
+
- (CGFloat)autoDetentBottomInset {
|
|
341
|
+
if (_absoluteFooter || [self.footerHeight floatValue] <= 0) {
|
|
270
342
|
return 0;
|
|
271
343
|
}
|
|
272
344
|
|
|
345
|
+
return _appliedFooterBottomInset;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Bottom inset excluded from the peek detent. An absolute footer counts
|
|
350
|
+
* toward the peek height and carries the inset in its height — subtract
|
|
351
|
+
* exactly what was baked in so it isn't doubled.
|
|
352
|
+
*/
|
|
353
|
+
- (CGFloat)peekDetentBottomInset {
|
|
273
354
|
if (!_absoluteFooter || [self.footerHeight floatValue] <= 0) {
|
|
274
355
|
return 0;
|
|
275
356
|
}
|
|
276
357
|
|
|
277
|
-
return
|
|
358
|
+
return _appliedFooterBottomInset;
|
|
278
359
|
}
|
|
279
360
|
|
|
280
361
|
- (BOOL)isDesignCompatibilityMode {
|
|
@@ -314,6 +395,12 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
314
395
|
|
|
315
396
|
#pragma mark - View Lifecycle
|
|
316
397
|
|
|
398
|
+
- (void)loadView {
|
|
399
|
+
TrueSheetControllerView *view = [[TrueSheetControllerView alloc] init];
|
|
400
|
+
view.controller = self;
|
|
401
|
+
self.view = view;
|
|
402
|
+
}
|
|
403
|
+
|
|
317
404
|
- (void)viewDidLoad {
|
|
318
405
|
[super viewDidLoad];
|
|
319
406
|
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
|
@@ -421,6 +508,37 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
421
508
|
}
|
|
422
509
|
}
|
|
423
510
|
|
|
511
|
+
- (NSArray *)accessibilityGrabberElements {
|
|
512
|
+
if (_grabberView && !_grabberView.hidden) {
|
|
513
|
+
return @[ _grabberView ];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// The system grabber is an accessibility element UIKit adds to the presented view
|
|
517
|
+
// (a sibling of our root view). Expose it so VoiceOver can resize or dismiss with it.
|
|
518
|
+
// UIKit also keeps a transparent bottom grabber there, so skip invisible views.
|
|
519
|
+
NSMutableArray *elements = [NSMutableArray array];
|
|
520
|
+
for (UIView *subview in self.presentationController.presentedView.subviews) {
|
|
521
|
+
if (subview != self.view && subview.isAccessibilityElement && !subview.hidden && subview.alpha > 0) {
|
|
522
|
+
[elements addObject:subview];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return elements;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
- (BOOL)accessibilityPerformEscape {
|
|
529
|
+
if (!_isPresented) {
|
|
530
|
+
return NO;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!self.dismissible) {
|
|
534
|
+
[self.delegate viewControllerDidAttemptDismiss];
|
|
535
|
+
return NO;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
|
539
|
+
return YES;
|
|
540
|
+
}
|
|
541
|
+
|
|
424
542
|
- (void)setAccessibilityContentElement:(UIView *)contentView {
|
|
425
543
|
BOOL hasAccessibilityFooterElements = [contentView isKindOfClass:[TrueSheetContainerView class]] &&
|
|
426
544
|
[(TrueSheetContainerView *)contentView hasAccessibilityFooterElements];
|
|
@@ -433,12 +551,12 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
433
551
|
// its accessibilityElements getter recomputes live, so a footer/content subtree
|
|
434
552
|
// that remounts (e.g. swapping a footer button on a state change) stays
|
|
435
553
|
// discoverable instead of leaving this snapshot pointing at destroyed views.
|
|
436
|
-
|
|
554
|
+
NSMutableArray *accessibilityElements = [[self accessibilityGrabberElements] mutableCopy];
|
|
437
555
|
if (isAccessibilityModal) {
|
|
438
556
|
NSArray *contentElements = contentView.accessibilityElements;
|
|
439
|
-
accessibilityElements
|
|
557
|
+
[accessibilityElements addObjectsFromArray:contentElements.count > 0 ? contentElements : @[ contentView ]];
|
|
440
558
|
} else {
|
|
441
|
-
accessibilityElements
|
|
559
|
+
[accessibilityElements addObject:contentView];
|
|
442
560
|
}
|
|
443
561
|
|
|
444
562
|
self.view.isAccessibilityElement = NO;
|
|
@@ -451,6 +569,15 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
451
569
|
presentedView.accessibilityViewIsModal = isAccessibilityModal;
|
|
452
570
|
presentedView.accessibilityElements = accessibilityElements;
|
|
453
571
|
|
|
572
|
+
// The system grabber is a sibling of our root view, so an escape performed while it
|
|
573
|
+
// is focused walks presentedView → window and never reaches TrueSheetControllerView.
|
|
574
|
+
if (@available(iOS 17.0, *)) {
|
|
575
|
+
__weak __typeof(self) weakSelf = self;
|
|
576
|
+
presentedView.accessibilityPerformEscapeBlock = ^BOOL {
|
|
577
|
+
return [weakSelf accessibilityPerformEscape];
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
454
581
|
UIWindow *window = self.view.window;
|
|
455
582
|
UIViewController *presentingViewController = [self accessibilityPresentingViewController];
|
|
456
583
|
if (window && presentingViewController.view) {
|
|
@@ -520,6 +647,7 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
520
647
|
[self restoreWindowAccessibilityElements];
|
|
521
648
|
_isPresented = NO;
|
|
522
649
|
_isWillDismissEmitted = NO;
|
|
650
|
+
_hasObservedBottomInset = NO;
|
|
523
651
|
|
|
524
652
|
[_anchorView removeFromSuperview];
|
|
525
653
|
_anchorView = nil;
|
|
@@ -575,24 +703,26 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
575
703
|
return;
|
|
576
704
|
}
|
|
577
705
|
|
|
578
|
-
|
|
706
|
+
// Mid-drag the frame tracks the finger — never settle or learn from it;
|
|
707
|
+
// pending changes keep their flags and settle on the first post-drag layout.
|
|
708
|
+
if ((_pendingContentSizeChange || _pendingDetentsChange) && !_isDragging) {
|
|
579
709
|
_pendingContentSizeChange = NO;
|
|
580
710
|
_pendingDetentsChange = NO;
|
|
581
711
|
[self settleAtDetentIndex:self.currentDetentIndex debug:@"layout"];
|
|
582
|
-
} else
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
//
|
|
712
|
+
} else {
|
|
713
|
+
// A child on top moves this sheet two ways: an animated collapse/restore
|
|
714
|
+
// step (presenting/dismissing) — emit the target non-realtime so JS
|
|
715
|
+
// animates to it — or direct per-frame re-layouts while the child drags,
|
|
716
|
+
// which stay realtime. Mid-drag no child is on top, so drags emit realtime.
|
|
587
717
|
BOOL realtime = self.presentedViewController == nil || !self.isPresentedViewAnimating;
|
|
588
718
|
|
|
589
719
|
// A layout pass at rest can nudge the frame by a sub-pixel amount
|
|
590
720
|
// (pixel-grid snapping after present), leaving the interpolated index
|
|
591
721
|
// slightly off the whole number. Re-learn when the frame is effectively
|
|
592
722
|
// at the current detent so the drift is absorbed instead of emitted.
|
|
593
|
-
// The tight threshold keeps real movement (e.g. scroll-driven expansion
|
|
594
|
-
// from being absorbed.
|
|
595
|
-
if (self.presentedViewController == nil && !self.isPresentedViewAnimating) {
|
|
723
|
+
// The tight threshold keeps real movement (e.g. scroll-driven expansion
|
|
724
|
+
// or a drag hovering near the detent) from being absorbed.
|
|
725
|
+
if (!_isDragging && self.presentedViewController == nil && !self.isPresentedViewAnimating) {
|
|
596
726
|
NSInteger index = self.currentDetentIndex;
|
|
597
727
|
CGFloat expectedHeight = [_detentCalculator resolvedHeightForIndex:index];
|
|
598
728
|
CGFloat actualHeight = self.screenHeight - self.currentPosition;
|
|
@@ -605,9 +735,46 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
605
735
|
}
|
|
606
736
|
}
|
|
607
737
|
|
|
738
|
+
// Rotation or an iPad window resize changes the height fractional detents
|
|
739
|
+
// resolve against — re-resolve alongside the transition so the sheet lands on
|
|
740
|
+
// the new height instead of waiting for a content-driven rebuild.
|
|
741
|
+
- (void)viewWillTransitionToSize:(CGSize)size
|
|
742
|
+
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
|
|
743
|
+
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
744
|
+
|
|
745
|
+
if (@available(iOS 16.0, *)) {
|
|
746
|
+
[coordinator
|
|
747
|
+
animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
|
748
|
+
[self.sheet invalidateDetents];
|
|
749
|
+
}
|
|
750
|
+
completion:nil];
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Fires when UIKit resolves the sheet's float/anchored placement (present,
|
|
755
|
+
// detent resize crossing the float threshold, rotation). The footer's
|
|
756
|
+
// absorbed inset follows the observed safe area — repush it right away so
|
|
757
|
+
// the footer pads/unpads in the same layout pass.
|
|
758
|
+
- (void)viewSafeAreaInsetsDidChange {
|
|
759
|
+
[super viewSafeAreaInsetsDidChange];
|
|
760
|
+
|
|
761
|
+
// Mid-dismiss the frame moves off-screen and reports transient insets —
|
|
762
|
+
// don't let them pollute the observed value (matches viewDidLayoutSubviews).
|
|
763
|
+
if (!self.isBeingDismissed) {
|
|
764
|
+
[self.delegate viewControllerSafeAreaInsetsDidChange];
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
608
768
|
- (void)viewDidLayoutSubviews {
|
|
609
769
|
[super viewDidLayoutSubviews];
|
|
610
770
|
|
|
771
|
+
// viewSafeAreaInsetsDidChange doesn't fire on re-present when the observed
|
|
772
|
+
// inset matches the previous presentation's — refresh every layout pass
|
|
773
|
+
// instead (deduped downstream, so unchanged values are free).
|
|
774
|
+
if (!self.isBeingDismissed) {
|
|
775
|
+
[self.delegate viewControllerSafeAreaInsetsDidChange];
|
|
776
|
+
}
|
|
777
|
+
|
|
611
778
|
// Skip size reports while backgrounded behind a stacked child — the push-back
|
|
612
779
|
// scaling changes the frame and would needlessly resize content.
|
|
613
780
|
// _lastReportedSize stays stale so the restore pass re-reports any real change.
|
|
@@ -695,7 +862,14 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
695
862
|
_isDragging = YES;
|
|
696
863
|
break;
|
|
697
864
|
case UIGestureRecognizerStateChanged:
|
|
698
|
-
|
|
865
|
+
// Layout owns the emit while the sheet resizes with the finger (a
|
|
866
|
+
// layout pass is already queued), and the transition tick owns it
|
|
867
|
+
// during an interactive dismiss. Translation-only movement (e.g.
|
|
868
|
+
// dragging down to the first detent) triggers neither — emit here as
|
|
869
|
+
// the backup.
|
|
870
|
+
if (!_isTransitioning && !self.presentedView.layer.needsLayout && !self.viewIfLoaded.layer.needsLayout) {
|
|
871
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:YES debug:@"drag change"];
|
|
872
|
+
}
|
|
699
873
|
break;
|
|
700
874
|
case UIGestureRecognizerStateEnded:
|
|
701
875
|
case UIGestureRecognizerStateCancelled: {
|
|
@@ -780,27 +954,26 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
780
954
|
}
|
|
781
955
|
|
|
782
956
|
- (void)handleTransitionTick {
|
|
783
|
-
//
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (self.isBeingDismissed) {
|
|
788
|
-
// Emit only once the dismiss is committed: on release UIKit moves the
|
|
789
|
-
// model frame off-screen, while a cancelled drag rewinds it back to the
|
|
790
|
-
// detent (isBeingDismissed stays YES during the rewind).
|
|
791
|
-
if (self.currentPosition >= self.screenHeight) {
|
|
792
|
-
[self emitWillDismissEvents];
|
|
793
|
-
}
|
|
957
|
+
// Also covers the interactive dismiss phase (finger down) — layout passes
|
|
958
|
+
// are skipped while transitioning, so the tick owns position there too.
|
|
959
|
+
CGFloat position = self.livePosition;
|
|
794
960
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
961
|
+
if (self.isBeingDismissed) {
|
|
962
|
+
// Emit only once the dismiss is committed: on release UIKit moves the
|
|
963
|
+
// model frame off-screen, while a cancelled drag rewinds it back to the
|
|
964
|
+
// detent (isBeingDismissed stays YES during the rewind).
|
|
965
|
+
if (self.currentPosition >= self.screenHeight) {
|
|
966
|
+
[self emitWillDismissEvents];
|
|
800
967
|
}
|
|
801
968
|
|
|
802
|
-
|
|
969
|
+
// Hide blur at the end of dismiss to prevent UIVisualEffectView
|
|
970
|
+
// from causing a flicker/flash at the bottom edge of the sheet.
|
|
971
|
+
if (self.screenHeight - position < 1) {
|
|
972
|
+
_blurView.alpha = 0;
|
|
973
|
+
}
|
|
803
974
|
}
|
|
975
|
+
|
|
976
|
+
[self emitChangePositionDelegateWithPosition:position realtime:YES debug:@"transition"];
|
|
804
977
|
}
|
|
805
978
|
|
|
806
979
|
/**
|
|
@@ -1079,8 +1252,7 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1079
1252
|
return [self customDetentWithIdentifier:@"custom-auto"
|
|
1080
1253
|
atIndex:index
|
|
1081
1254
|
heightBlock:^CGFloat {
|
|
1082
|
-
|
|
1083
|
-
return height - [self autoDetentBottomInsetForHeight:height];
|
|
1255
|
+
return self->_autoDetentHeight - [self autoDetentBottomInset];
|
|
1084
1256
|
}];
|
|
1085
1257
|
} else {
|
|
1086
1258
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
@@ -1092,8 +1264,7 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1092
1264
|
return [self customDetentWithIdentifier:@"custom-peek"
|
|
1093
1265
|
atIndex:index
|
|
1094
1266
|
heightBlock:^CGFloat {
|
|
1095
|
-
|
|
1096
|
-
return height - [self peekDetentBottomInsetForHeight:height];
|
|
1267
|
+
return self->_peekDetentHeight - [self peekDetentBottomInset];
|
|
1097
1268
|
}];
|
|
1098
1269
|
} else {
|
|
1099
1270
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
@@ -1107,8 +1278,13 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1107
1278
|
|
|
1108
1279
|
if (@available(iOS 16.0, *)) {
|
|
1109
1280
|
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
1110
|
-
|
|
1111
|
-
|
|
1281
|
+
// Resolve lazily — detents are built before presentation (no window yet), and
|
|
1282
|
+
// UIKit re-resolves on container size changes (rotation, windowed iPad resize).
|
|
1283
|
+
return [self customDetentWithIdentifier:detentId
|
|
1284
|
+
atIndex:index
|
|
1285
|
+
heightBlock:^CGFloat {
|
|
1286
|
+
return value * self.screenHeight;
|
|
1287
|
+
}];
|
|
1112
1288
|
} else if (value >= 0.5) {
|
|
1113
1289
|
return [UISheetPresentationControllerDetent largeDetent];
|
|
1114
1290
|
} else {
|
|
@@ -1116,16 +1292,6 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1116
1292
|
}
|
|
1117
1293
|
}
|
|
1118
1294
|
|
|
1119
|
-
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
1120
|
-
height:(CGFloat)height
|
|
1121
|
-
atIndex:(NSInteger)index API_AVAILABLE(ios(16.0)) {
|
|
1122
|
-
return [self customDetentWithIdentifier:identifier
|
|
1123
|
-
atIndex:index
|
|
1124
|
-
heightBlock:^CGFloat {
|
|
1125
|
-
return height;
|
|
1126
|
-
}];
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
1295
|
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
1130
1296
|
atIndex:(NSInteger)index
|
|
1131
1297
|
heightBlock:(CGFloat (^)(void))heightBlock
|
|
@@ -1246,14 +1412,11 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1246
1412
|
#if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_1) && !TARGET_OS_MACCATALYST
|
|
1247
1413
|
if (@available(iOS 26.1, *)) {
|
|
1248
1414
|
if (!self.isDesignCompatibilityMode) {
|
|
1249
|
-
if (self.backgroundColor) {
|
|
1250
|
-
self.sheet.backgroundEffect = [UIColorEffect effectWithColor:self.backgroundColor];
|
|
1251
|
-
} else if (hasBlur) {
|
|
1415
|
+
if (self.backgroundColor || hasBlur) {
|
|
1252
1416
|
self.sheet.backgroundEffect = [UIColorEffect effectWithColor:[UIColor clearColor]];
|
|
1253
1417
|
} else {
|
|
1254
1418
|
self.sheet.backgroundEffect = nil;
|
|
1255
1419
|
}
|
|
1256
|
-
return;
|
|
1257
1420
|
}
|
|
1258
1421
|
}
|
|
1259
1422
|
#endif
|
|
@@ -1305,6 +1468,8 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1305
1468
|
}];
|
|
1306
1469
|
} else if (strongSelf.dismissible) {
|
|
1307
1470
|
[strongSelf.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
|
1471
|
+
} else {
|
|
1472
|
+
[strongSelf.delegate viewControllerDidAttemptDismiss];
|
|
1308
1473
|
}
|
|
1309
1474
|
};
|
|
1310
1475
|
|
|
@@ -1316,6 +1481,12 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1316
1481
|
_grabberView.onIncrement = nil;
|
|
1317
1482
|
_grabberView.onDecrement = nil;
|
|
1318
1483
|
}
|
|
1484
|
+
|
|
1485
|
+
// Only the topmost sheet may refresh, or a parent's prop update would steal the
|
|
1486
|
+
// window accessibility override from the child presented over it.
|
|
1487
|
+
if (_isPresented && self.isTopmostPresentedController) {
|
|
1488
|
+
[self setupAccessibilityContainer];
|
|
1489
|
+
}
|
|
1319
1490
|
}
|
|
1320
1491
|
|
|
1321
1492
|
- (void)handleGrabberTap {
|
|
@@ -1328,8 +1499,12 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1328
1499
|
return;
|
|
1329
1500
|
|
|
1330
1501
|
NSInteger nextIndex = (currentIndex + 1) % detentCount;
|
|
1331
|
-
if (nextIndex == 0 && detentCount == 1
|
|
1332
|
-
|
|
1502
|
+
if (nextIndex == 0 && detentCount == 1) {
|
|
1503
|
+
if (self.dismissible) {
|
|
1504
|
+
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
|
1505
|
+
} else {
|
|
1506
|
+
[self.delegate viewControllerDidAttemptDismiss];
|
|
1507
|
+
}
|
|
1333
1508
|
} else {
|
|
1334
1509
|
[self.sheet animateChanges:^{
|
|
1335
1510
|
[self resizeToDetentIndex:nextIndex];
|
|
@@ -1421,6 +1596,13 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
|
|
|
1421
1596
|
return self.dismissible;
|
|
1422
1597
|
}
|
|
1423
1598
|
|
|
1599
|
+
// UIKit calls this after shouldDismiss returns NO — the sheet has already
|
|
1600
|
+
// bounced back, so JS can confirm and dismiss programmatically (Apple's
|
|
1601
|
+
// unsaved-changes pattern).
|
|
1602
|
+
- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController {
|
|
1603
|
+
[self.delegate viewControllerDidAttemptDismiss];
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1424
1606
|
- (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
|
|
1425
1607
|
(UISheetPresentationController *)sheetPresentationController {
|
|
1426
1608
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -91,6 +91,13 @@ static const CGFloat kHitPaddingVertical = 10.0;
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Pad the pill evenly like the system grabber. The view frame itself stays inside
|
|
95
|
+
// the sheet, since the reveal check reads the frame rather than this rect.
|
|
96
|
+
- (CGRect)accessibilityFrame {
|
|
97
|
+
CGRect rect = CGRectInset(_vibrancyView.frame, -kHitPaddingHorizontal, -kHitPaddingVertical);
|
|
98
|
+
return UIAccessibilityConvertFrameToScreenCoordinates(rect, self);
|
|
99
|
+
}
|
|
100
|
+
|
|
94
101
|
- (void)accessibilityIncrement {
|
|
95
102
|
if (_onIncrement) {
|
|
96
103
|
_onIncrement();
|
|
@@ -144,14 +151,17 @@ static const CGFloat kHitPaddingVertical = 10.0;
|
|
|
144
151
|
CGFloat topMargin = [self effectiveTopMargin];
|
|
145
152
|
CGFloat parentWidth = self.superview ? self.superview.bounds.size.width : UIScreen.mainScreen.bounds.size.width;
|
|
146
153
|
|
|
154
|
+
// Keep the frame inside the sheet. A frame above the top edge reads as a clipped element
|
|
155
|
+
// to VoiceOver, and the sheet reveals it on focus by moving to the next detent.
|
|
147
156
|
CGFloat frameWidth = pillWidth + kHitPaddingHorizontal * 2;
|
|
148
|
-
CGFloat
|
|
149
|
-
CGFloat
|
|
157
|
+
CGFloat frameY = MAX(0, topMargin - kHitPaddingVertical);
|
|
158
|
+
CGFloat pillY = topMargin - frameY;
|
|
159
|
+
CGFloat frameHeight = pillY + pillHeight + kHitPaddingVertical;
|
|
150
160
|
|
|
151
161
|
self.frame = CGRectMake((parentWidth - frameWidth) / 2.0, frameY, frameWidth, frameHeight);
|
|
152
162
|
self.backgroundColor = UIColor.clearColor;
|
|
153
163
|
|
|
154
|
-
CGRect pillRect = CGRectMake(kHitPaddingHorizontal,
|
|
164
|
+
CGRect pillRect = CGRectMake(kHitPaddingHorizontal, pillY, pillWidth, pillHeight);
|
|
155
165
|
_vibrancyView.frame = pillRect;
|
|
156
166
|
_vibrancyView.layer.cornerRadius = [self effectiveCornerRadius];
|
|
157
167
|
_vibrancyView.clipsToBounds = YES;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by Jovanni Lo (@lodev09)
|
|
3
|
+
// Copyright (c) 2024-present. All rights reserved.
|
|
4
|
+
//
|
|
5
|
+
// This source code is licensed under the MIT license found in the
|
|
6
|
+
// LICENSE file in the root directory of this source tree.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
10
|
+
|
|
11
|
+
#import <UIKit/UIKit.h>
|
|
12
|
+
|
|
13
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
14
|
+
|
|
15
|
+
@protocol TrueSheetOverlayContainerViewDelegate <NSObject>
|
|
16
|
+
- (void)overlayContainerViewDidLayout:(CGSize)size;
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Window-level view hosting an overlay's children.
|
|
21
|
+
* Touches that miss every child fall through to the views beneath.
|
|
22
|
+
*/
|
|
23
|
+
@interface TrueSheetOverlayContainerView : UIView
|
|
24
|
+
|
|
25
|
+
@property (nonatomic, weak, nullable) id<TrueSheetOverlayContainerViewDelegate> delegate;
|
|
26
|
+
|
|
27
|
+
@end
|
|
28
|
+
|
|
29
|
+
NS_ASSUME_NONNULL_END
|
|
30
|
+
|
|
31
|
+
#endif
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by Jovanni Lo (@lodev09)
|
|
3
|
+
// Copyright (c) 2024-present. All rights reserved.
|
|
4
|
+
//
|
|
5
|
+
// This source code is licensed under the MIT license found in the
|
|
6
|
+
// LICENSE file in the root directory of this source tree.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
10
|
+
|
|
11
|
+
#import "TrueSheetOverlayContainerView.h"
|
|
12
|
+
|
|
13
|
+
@implementation TrueSheetOverlayContainerView
|
|
14
|
+
|
|
15
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
16
|
+
if (self = [super initWithFrame:frame]) {
|
|
17
|
+
self.backgroundColor = UIColor.clearColor;
|
|
18
|
+
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
19
|
+
}
|
|
20
|
+
return self;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event {
|
|
24
|
+
UIView *hit = [super hitTest:point withEvent:event];
|
|
25
|
+
return hit == self ? nil : hit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (void)layoutSubviews {
|
|
29
|
+
[super layoutSubviews];
|
|
30
|
+
[self.delegate overlayContainerViewDidLayout:self.bounds.size];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
#endif
|
|
@@ -33,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
33
33
|
|
|
34
34
|
+ (void)emitDidDismiss:(std::shared_ptr<const facebook::react::EventEmitter>)eventEmitter;
|
|
35
35
|
|
|
36
|
+
+ (void)emitDismissAttempt:(std::shared_ptr<const facebook::react::EventEmitter>)eventEmitter;
|
|
37
|
+
|
|
36
38
|
@end
|
|
37
39
|
|
|
38
40
|
NS_ASSUME_NONNULL_END
|
|
@@ -66,6 +66,14 @@
|
|
|
66
66
|
emitter->onDidDismiss({});
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
+ (void)emitDismissAttempt:(std::shared_ptr<const facebook::react::EventEmitter>)eventEmitter {
|
|
70
|
+
if (!eventEmitter)
|
|
71
|
+
return;
|
|
72
|
+
|
|
73
|
+
auto emitter = std::static_pointer_cast<TrueSheetViewEventEmitter const>(eventEmitter);
|
|
74
|
+
emitter->onDismissAttempt({});
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
@end
|
|
70
78
|
|
|
71
79
|
#endif // RCT_NEW_ARCH_ENABLED
|