@lodev09/react-native-true-sheet 4.0.0-beta.3 → 4.0.0-beta.5
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 +21 -2
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +24 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +195 -174
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +6 -17
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +50 -8
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +101 -18
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +24 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +3 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +14 -24
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +0 -7
- 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/ios/TrueSheetContainerView.h +9 -7
- package/ios/TrueSheetContainerView.mm +6 -7
- package/ios/TrueSheetContentView.h +28 -14
- package/ios/TrueSheetContentView.mm +122 -179
- package/ios/TrueSheetFooterView.mm +17 -0
- package/ios/TrueSheetView.mm +13 -10
- package/ios/TrueSheetViewController.mm +35 -27
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +47 -14
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +26 -24
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +4 -0
- package/lib/typescript/src/TrueSheet.d.ts +2 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +64 -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/fabric/TrueSheetViewNativeComponent.d.ts +3 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +1 -1
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +48 -13
- package/src/TrueSheet.types.ts +68 -1
- package/src/TrueSheet.web.tsx +39 -24
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetViewNativeComponent.ts +4 -0
- package/src/navigation/types.ts +1 -0
|
@@ -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,41 @@ 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
|
|
|
186
162
|
#pragma mark - Scrollable
|
|
187
163
|
|
|
188
|
-
|
|
189
|
-
|
|
164
|
+
// The scroll indicator follows automatically — UIKit derives its insets from
|
|
165
|
+
// the content inset while `automaticallyAdjustsScrollIndicatorInsets` is set.
|
|
166
|
+
- (void)setKeyboardInset:(CGFloat)inset {
|
|
167
|
+
if (!_detectedScrollView)
|
|
190
168
|
return;
|
|
191
169
|
|
|
192
|
-
UIEdgeInsets contentInset =
|
|
193
|
-
contentInset.bottom =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
UIEdgeInsets indicatorInsets = _pinnedScrollView.scrollView.verticalScrollIndicatorInsets;
|
|
197
|
-
indicatorInsets.bottom = indicatorBottom;
|
|
198
|
-
_pinnedScrollView.scrollView.verticalScrollIndicatorInsets = indicatorInsets;
|
|
170
|
+
UIEdgeInsets contentInset = _detectedScrollView.scrollView.contentInset;
|
|
171
|
+
contentInset.bottom = inset;
|
|
172
|
+
_detectedScrollView.scrollView.contentInset = contentInset;
|
|
199
173
|
}
|
|
200
174
|
|
|
201
175
|
- (void)clearScrollable {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
_originalIndicatorBottomInset = 0;
|
|
210
|
-
[self reportSizeIfChanged];
|
|
176
|
+
[self setKeyboardInset:0];
|
|
177
|
+
_appliedKeyboardOffset = 0;
|
|
178
|
+
if (_appliedContentInsetAdjustment) {
|
|
179
|
+
_detectedScrollView.scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
|
|
180
|
+
_appliedContentInsetAdjustment = NO;
|
|
181
|
+
}
|
|
182
|
+
_detectedScrollView = nil;
|
|
211
183
|
}
|
|
212
184
|
|
|
213
|
-
- (void)
|
|
214
|
-
// Check if
|
|
215
|
-
if (
|
|
185
|
+
- (void)setupScrollable {
|
|
186
|
+
// Check if the detected scroll view is still valid (still in view hierarchy)
|
|
187
|
+
if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
|
|
216
188
|
[self clearScrollable];
|
|
217
189
|
}
|
|
218
190
|
|
|
219
|
-
|
|
220
|
-
if (_pinnedScrollView && _bottomInset == bottomInset) {
|
|
191
|
+
if (_detectedScrollView) {
|
|
221
192
|
return;
|
|
222
193
|
}
|
|
223
194
|
|
|
@@ -226,104 +197,74 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
226
197
|
return;
|
|
227
198
|
}
|
|
228
199
|
|
|
229
|
-
|
|
230
|
-
if (!_pinnedScrollView) {
|
|
231
|
-
_originalIndicatorBottomInset = scrollView.scrollView.verticalScrollIndicatorInsets.bottom;
|
|
232
|
-
_pinnedScrollView = scrollView;
|
|
200
|
+
_detectedScrollView = scrollView;
|
|
233
201
|
|
|
234
|
-
|
|
235
|
-
[self setScrollableBounded:_hasAutoDetent];
|
|
236
|
-
[self reportSizeIfChanged];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
_bottomInset = bottomInset;
|
|
240
|
-
|
|
241
|
-
[self setScrollViewContentInset:_bottomInset indicatorInset:_originalIndicatorBottomInset];
|
|
202
|
+
[self applyContentInsetAdjustment];
|
|
242
203
|
|
|
243
204
|
// If keyboard is currently showing, re-apply the keyboard inset to the new ScrollView
|
|
244
205
|
CGFloat keyboardHeight = _keyboardObserver ? _keyboardObserver.currentHeight : 0;
|
|
245
206
|
if (keyboardHeight > 0) {
|
|
246
|
-
|
|
247
|
-
[self setScrollViewContentInset:inset indicatorInset:_originalIndicatorBottomInset + inset];
|
|
207
|
+
[self setKeyboardInset:[self keyboardInsetWithHeight:keyboardHeight]];
|
|
248
208
|
}
|
|
249
209
|
}
|
|
250
210
|
|
|
251
|
-
// An absolute footer rises above the keyboard and covers the content's bottom
|
|
252
|
-
// edge — include it so the caret clears the footer, not just the keyboard.
|
|
253
211
|
// A relative footer stays behind the keyboard below the content, so its
|
|
254
|
-
// height shields that much of the keyboard's overlap.
|
|
212
|
+
// height shields that much of the keyboard's overlap. An absolute footer
|
|
213
|
+
// floats within the viewport — its clearance is the content padding's job
|
|
214
|
+
// (the caret reveal accounts for it, see footerOcclusion).
|
|
255
215
|
- (CGFloat)keyboardInsetWithHeight:(CGFloat)height {
|
|
256
|
-
|
|
257
|
-
|
|
216
|
+
CGFloat inset = height;
|
|
217
|
+
if (self.footerView && !_keyboardObserver.viewController.absoluteFooter) {
|
|
218
|
+
inset = MAX(0, height - self.footerView.frame.size.height);
|
|
258
219
|
}
|
|
259
|
-
if (_keyboardObserver.viewController.absoluteFooter) {
|
|
260
|
-
return height + [self.footerView keyboardOcclusionHeight];
|
|
261
|
-
}
|
|
262
|
-
return MAX(0, height - self.footerView.frame.size.height);
|
|
263
|
-
}
|
|
264
220
|
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (_observedScrollView == scrollView) {
|
|
270
|
-
return;
|
|
221
|
+
// UIKit stacks the safe area on top of contentInset while the adjustment
|
|
222
|
+
// behavior is automatic — take it out so the total lands on the keyboard edge.
|
|
223
|
+
if (_appliedContentInsetAdjustment) {
|
|
224
|
+
inset = MAX(0, inset - _detectedScrollView.scrollView.safeAreaInsets.bottom);
|
|
271
225
|
}
|
|
272
|
-
[self unobserveScrollViewContentSize];
|
|
273
|
-
_observedScrollView = scrollView;
|
|
274
|
-
[scrollView addObserver:self
|
|
275
|
-
forKeyPath:@"contentSize"
|
|
276
|
-
options:NSKeyValueObservingOptionNew
|
|
277
|
-
context:TrueSheetContentSizeContext];
|
|
278
|
-
}
|
|
279
226
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
227
|
+
// Track how much of keyboardOffset actually lands in the inset so the caret
|
|
228
|
+
// reveal can compensate — the offset shifts the inset, not the keyboard edge.
|
|
229
|
+
CGFloat adjustedInset = MAX(0, inset + self.keyboardOffset);
|
|
230
|
+
_appliedKeyboardOffset = adjustedInset - inset;
|
|
231
|
+
|
|
232
|
+
// Content that already fits above the keyboard has nothing to reveal — the
|
|
233
|
+
// inset would only open blank scroll range below it (huge gap at the bottom).
|
|
234
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
235
|
+
if (scrollView.contentSize.height <= scrollView.bounds.size.height - adjustedInset) {
|
|
236
|
+
_appliedKeyboardOffset = 0;
|
|
237
|
+
return 0;
|
|
284
238
|
}
|
|
239
|
+
|
|
240
|
+
return adjustedInset;
|
|
285
241
|
}
|
|
286
242
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
[self reportSizeIfChanged];
|
|
293
|
-
return;
|
|
243
|
+
// An absolute footer floats over the viewport's bottom edge — extend the
|
|
244
|
+
// caret target so it clears the footer, not just the keyboard.
|
|
245
|
+
- (CGFloat)footerOcclusion {
|
|
246
|
+
if (self.footerView && _keyboardObserver.viewController.absoluteFooter) {
|
|
247
|
+
return [self.footerView keyboardOcclusionHeight];
|
|
294
248
|
}
|
|
295
|
-
|
|
249
|
+
return 0;
|
|
296
250
|
}
|
|
297
251
|
|
|
252
|
+
// Resolves the user-provided `scrollableHandle` within the content subtree —
|
|
253
|
+
// Fabric component views carry their React tag as `UIView.tag`.
|
|
298
254
|
- (RCTScrollViewComponentView *)findScrollView {
|
|
299
|
-
if (
|
|
300
|
-
return
|
|
255
|
+
if (_detectedScrollView) {
|
|
256
|
+
return _detectedScrollView;
|
|
301
257
|
}
|
|
302
258
|
|
|
303
|
-
if (
|
|
259
|
+
if (_scrollableHandle <= 0) {
|
|
304
260
|
return nil;
|
|
305
261
|
}
|
|
306
262
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
for (UIView *subview in self.subviews) {
|
|
311
|
-
scrollView = [self findScrollViewInSubviews:subview.subviews];
|
|
312
|
-
if (scrollView) {
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
263
|
+
UIView *view = [self viewWithTag:_scrollableHandle];
|
|
264
|
+
if ([view isKindOfClass:RCTScrollViewComponentView.class]) {
|
|
265
|
+
return (RCTScrollViewComponentView *)view;
|
|
316
266
|
}
|
|
317
267
|
|
|
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
268
|
return nil;
|
|
328
269
|
}
|
|
329
270
|
|
|
@@ -331,11 +272,11 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
331
272
|
|
|
332
273
|
- (void)applyScrollEdgeEffects:(nullable ScrollableOptions *)options {
|
|
333
274
|
#if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
|
334
|
-
if (!
|
|
275
|
+
if (!_detectedScrollView)
|
|
335
276
|
return;
|
|
336
277
|
|
|
337
278
|
if (@available(iOS 26.0, *)) {
|
|
338
|
-
UIScrollView *scrollView =
|
|
279
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
339
280
|
auto topEffect = options ? options.topScrollEdgeEffect : TrueSheetViewTopScrollEdgeEffect::Hidden;
|
|
340
281
|
auto bottomEffect = options ? options.bottomScrollEdgeEffect : TrueSheetViewBottomScrollEdgeEffect::Hidden;
|
|
341
282
|
|
|
@@ -371,7 +312,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
371
312
|
#pragma mark - TrueSheetKeyboardObserverDelegate
|
|
372
313
|
|
|
373
314
|
- (void)keyboardWillShow:(CGFloat)height duration:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
|
|
374
|
-
if (!
|
|
315
|
+
if (!_detectedScrollView) {
|
|
375
316
|
return;
|
|
376
317
|
}
|
|
377
318
|
|
|
@@ -385,7 +326,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
385
326
|
delay:0
|
|
386
327
|
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
|
387
328
|
animations:^{
|
|
388
|
-
[self
|
|
329
|
+
[self setKeyboardInset:inset];
|
|
389
330
|
}
|
|
390
331
|
completion:nil];
|
|
391
332
|
|
|
@@ -398,7 +339,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
398
339
|
}
|
|
399
340
|
|
|
400
341
|
- (void)focusedInputTextDidChange:(NSNotification *)notification {
|
|
401
|
-
if (!
|
|
342
|
+
if (!_detectedScrollView || !_keyboardObserver || _keyboardObserver.currentHeight <= 0) {
|
|
402
343
|
return;
|
|
403
344
|
}
|
|
404
345
|
|
|
@@ -409,12 +350,15 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
409
350
|
}
|
|
410
351
|
|
|
411
352
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
353
|
+
// Typing can grow the content (e.g. a multiline input) past the fits-above-
|
|
354
|
+
// the-keyboard threshold — keep the inset in sync before revealing the caret.
|
|
355
|
+
[self setKeyboardInset:[self keyboardInsetWithHeight:self->_keyboardObserver.currentHeight]];
|
|
412
356
|
[self scrollToFocusedCaretAnimated:YES];
|
|
413
357
|
});
|
|
414
358
|
}
|
|
415
359
|
|
|
416
360
|
- (void)scrollToFocusedCaretAnimated:(BOOL)animated {
|
|
417
|
-
if (!
|
|
361
|
+
if (!_detectedScrollView) {
|
|
418
362
|
return;
|
|
419
363
|
}
|
|
420
364
|
|
|
@@ -424,7 +368,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
424
368
|
return;
|
|
425
369
|
}
|
|
426
370
|
|
|
427
|
-
UIScrollView *scrollView =
|
|
371
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
428
372
|
CGRect targetRect = [firstResponder convertRect:firstResponder.bounds toView:scrollView];
|
|
429
373
|
|
|
430
374
|
if ([firstResponder conformsToProtocol:@protocol(UITextInput)]) {
|
|
@@ -442,14 +386,14 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
442
386
|
}
|
|
443
387
|
}
|
|
444
388
|
|
|
445
|
-
targetRect.size.height += self.keyboardScrollOffset;
|
|
389
|
+
targetRect.size.height += self.keyboardScrollOffset + [self footerOcclusion] - _appliedKeyboardOffset;
|
|
446
390
|
[scrollView scrollRectToVisible:targetRect animated:animated];
|
|
447
391
|
}
|
|
448
392
|
|
|
449
393
|
- (void)keyboardWillHide:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
|
|
450
394
|
[self stopObservingTextChanges];
|
|
451
395
|
|
|
452
|
-
if (!
|
|
396
|
+
if (!_detectedScrollView) {
|
|
453
397
|
return;
|
|
454
398
|
}
|
|
455
399
|
|
|
@@ -457,8 +401,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
457
401
|
delay:0
|
|
458
402
|
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
|
459
403
|
animations:^{
|
|
460
|
-
[self
|
|
461
|
-
indicatorInset:self->_originalIndicatorBottomInset];
|
|
404
|
+
[self setKeyboardInset:0];
|
|
462
405
|
}
|
|
463
406
|
completion:nil];
|
|
464
407
|
}
|
|
@@ -470,9 +413,9 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
470
413
|
[self stopObservingTextChanges];
|
|
471
414
|
[self clearScrollable];
|
|
472
415
|
_state.reset();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
416
|
+
_scrollableHandle = 0;
|
|
417
|
+
_contentInsetAdjustment = NO;
|
|
418
|
+
_lastReportedNaturalHeight = 0;
|
|
476
419
|
}
|
|
477
420
|
|
|
478
421
|
@end
|
|
@@ -196,6 +196,7 @@ using namespace facebook::react;
|
|
|
196
196
|
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
|
197
197
|
animations:^{
|
|
198
198
|
self.transform = CGAffineTransformMakeTranslation(0, -slide);
|
|
199
|
+
[self syncEdgeEffect];
|
|
199
200
|
}
|
|
200
201
|
completion:nil];
|
|
201
202
|
}
|
|
@@ -209,10 +210,26 @@ using namespace facebook::react;
|
|
|
209
210
|
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
|
210
211
|
animations:^{
|
|
211
212
|
self.transform = CGAffineTransformIdentity;
|
|
213
|
+
[self syncEdgeEffect];
|
|
212
214
|
}
|
|
213
215
|
completion:nil];
|
|
214
216
|
}
|
|
215
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
|
+
|
|
216
233
|
- (void)applyKeyboardOffset {
|
|
217
234
|
CGFloat height = self.keyboardObserver.currentHeight;
|
|
218
235
|
if (height <= 0 || !self.keyboardObserver.viewController.absoluteFooter) {
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -53,7 +53,7 @@ using namespace facebook::react;
|
|
|
53
53
|
NSInteger _initialDetentIndex;
|
|
54
54
|
TrueSheetViewInsetAdjustment _insetAdjustment;
|
|
55
55
|
ScrollableOptions *_scrollableOptions;
|
|
56
|
-
|
|
56
|
+
NSInteger _scrollableHandle;
|
|
57
57
|
BOOL _initialDetentAnimated;
|
|
58
58
|
BOOL _isSheetUpdatePending;
|
|
59
59
|
BOOL _pendingLayoutUpdate;
|
|
@@ -192,12 +192,8 @@ using namespace facebook::react;
|
|
|
192
192
|
|
|
193
193
|
// Detents (-1 represents "auto")
|
|
194
194
|
NSMutableArray *detents = [NSMutableArray new];
|
|
195
|
-
_hasAutoDetent = NO;
|
|
196
195
|
for (const auto &detent : newProps.detents) {
|
|
197
196
|
[detents addObject:@(detent)];
|
|
198
|
-
if (detent == -1) {
|
|
199
|
-
_hasAutoDetent = YES;
|
|
200
|
-
}
|
|
201
197
|
}
|
|
202
198
|
|
|
203
199
|
if (oldProps) {
|
|
@@ -277,17 +273,23 @@ using namespace facebook::react;
|
|
|
277
273
|
_initialDetentIndex = newProps.initialDetentIndex;
|
|
278
274
|
_initialDetentAnimated = newProps.initialDetentAnimated;
|
|
279
275
|
|
|
276
|
+
_scrollableHandle = newProps.scrollableHandle;
|
|
277
|
+
|
|
280
278
|
const auto &scrollableOpts = newProps.scrollableOptions;
|
|
281
279
|
BOOL scrollingExpandsSheet = scrollableOpts.scrollingExpandsSheet;
|
|
280
|
+
BOOL contentInsetAdjustment = scrollableOpts.contentInsetAdjustmentBehavior;
|
|
282
281
|
auto topEdgeEffect = scrollableOpts.topScrollEdgeEffect;
|
|
283
282
|
auto bottomEdgeEffect = scrollableOpts.bottomScrollEdgeEffect;
|
|
284
|
-
BOOL hasScrollableOptions = scrollableOpts.keyboardScrollOffset > 0 ||
|
|
283
|
+
BOOL hasScrollableOptions = scrollableOpts.keyboardScrollOffset > 0 || scrollableOpts.keyboardOffset != 0 ||
|
|
284
|
+
!scrollingExpandsSheet || !contentInsetAdjustment ||
|
|
285
285
|
topEdgeEffect != TrueSheetViewTopScrollEdgeEffect::Hidden ||
|
|
286
286
|
bottomEdgeEffect != TrueSheetViewBottomScrollEdgeEffect::Hidden;
|
|
287
287
|
|
|
288
288
|
if (hasScrollableOptions) {
|
|
289
289
|
ScrollableOptions *options = [[ScrollableOptions alloc] init];
|
|
290
|
+
options.contentInsetAdjustment = contentInsetAdjustment;
|
|
290
291
|
options.keyboardScrollOffset = scrollableOpts.keyboardScrollOffset;
|
|
292
|
+
options.keyboardOffset = scrollableOpts.keyboardOffset;
|
|
291
293
|
options.scrollingExpandsSheet = scrollingExpandsSheet;
|
|
292
294
|
options.topScrollEdgeEffect = topEdgeEffect;
|
|
293
295
|
options.bottomScrollEdgeEffect = bottomEdgeEffect;
|
|
@@ -502,7 +504,7 @@ using namespace facebook::react;
|
|
|
502
504
|
[_controller setupSheetSizing];
|
|
503
505
|
[_controller setupSheetProps];
|
|
504
506
|
|
|
505
|
-
//
|
|
507
|
+
// Detect before measuring so a first-time detection doesn't shift sizes mid-animation.
|
|
506
508
|
[self setupScrollable];
|
|
507
509
|
|
|
508
510
|
// Measure synchronously so the presentation starts at the right height —
|
|
@@ -671,7 +673,7 @@ using namespace facebook::react;
|
|
|
671
673
|
[self setupSheetDetentsForSizeChange];
|
|
672
674
|
}
|
|
673
675
|
|
|
674
|
-
// When the ScrollView changes (e.g. conditional remount), re-
|
|
676
|
+
// When the ScrollView changes (e.g. conditional remount), re-detect the new ScrollView.
|
|
675
677
|
- (void)containerViewScrollViewDidChange {
|
|
676
678
|
[self setupScrollable];
|
|
677
679
|
}
|
|
@@ -822,9 +824,10 @@ using namespace facebook::react;
|
|
|
822
824
|
if (!_containerView)
|
|
823
825
|
return;
|
|
824
826
|
|
|
825
|
-
_containerView.insetAdjustment = _insetAdjustment;
|
|
826
827
|
_containerView.scrollableOptions = _scrollableOptions;
|
|
827
|
-
_containerView.
|
|
828
|
+
_containerView.scrollableHandle = _scrollableHandle;
|
|
829
|
+
_containerView.contentInsetAdjustment = (_scrollableOptions ? _scrollableOptions.contentInsetAdjustment : YES) &&
|
|
830
|
+
_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic;
|
|
828
831
|
[self refreshFooterBottomInset];
|
|
829
832
|
[_containerView setupScrollable];
|
|
830
833
|
}
|