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