@lodev09/react-native-true-sheet 3.1.0-beta.1 → 3.1.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/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +12 -10
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +19 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +119 -208
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +17 -5
- package/android/src/main/jni/CMakeLists.txt +6 -30
- package/android/src/main/res/anim/true_sheet_slide_in.xml +4 -4
- package/android/src/main/res/anim/true_sheet_slide_out.xml +4 -3
- package/ios/TrueSheetContainerView.mm +4 -4
- package/ios/TrueSheetContentView.mm +4 -4
- package/ios/TrueSheetFooterView.mm +4 -4
- package/ios/TrueSheetHeaderView.mm +4 -4
- package/ios/TrueSheetModule.mm +24 -5
- package/ios/TrueSheetView.h +2 -0
- package/ios/TrueSheetView.mm +17 -5
- package/ios/TrueSheetViewController.h +3 -2
- package/ios/TrueSheetViewController.mm +204 -95
- package/lib/module/TrueSheet.js +12 -8
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/navigation/TrueSheetRouter.js +119 -0
- package/lib/module/navigation/TrueSheetRouter.js.map +1 -0
- package/lib/module/navigation/TrueSheetView.js +169 -0
- package/lib/module/navigation/TrueSheetView.js.map +1 -0
- package/lib/module/navigation/createTrueSheetNavigator.js +59 -0
- package/lib/module/navigation/createTrueSheetNavigator.js.map +1 -0
- package/lib/module/navigation/index.js +6 -0
- package/lib/module/navigation/index.js.map +1 -0
- package/lib/module/navigation/types.js +4 -0
- package/lib/module/navigation/types.js.map +1 -0
- package/lib/module/navigation/useTrueSheetNavigation.js +26 -0
- package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -0
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +3 -3
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/module/specs/NativeTrueSheetModule.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +8 -4
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +57 -0
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +1 -0
- package/lib/typescript/src/navigation/TrueSheetView.d.ts +10 -0
- package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +35 -0
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +6 -0
- package/lib/typescript/src/navigation/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/types.d.ts +125 -0
- package/lib/typescript/src/navigation/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +23 -0
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts +4 -2
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts.map +1 -1
- package/package.json +13 -2
- package/src/TrueSheet.tsx +16 -8
- package/src/__mocks__/index.js +6 -5
- package/src/navigation/TrueSheetRouter.ts +172 -0
- package/src/navigation/TrueSheetView.tsx +271 -0
- package/src/navigation/createTrueSheetNavigator.tsx +89 -0
- package/src/navigation/index.ts +14 -0
- package/src/navigation/types.ts +176 -0
- package/src/navigation/useTrueSheetNavigation.ts +28 -0
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +6 -9
- package/src/specs/NativeTrueSheetModule.ts +4 -2
|
@@ -22,20 +22,22 @@
|
|
|
22
22
|
|
|
23
23
|
@implementation TrueSheetViewController {
|
|
24
24
|
CGFloat _lastPosition;
|
|
25
|
-
BOOL _isDragging;
|
|
26
25
|
NSInteger _pendingDetentIndex;
|
|
26
|
+
BOOL _pendingContentSizeChange;
|
|
27
|
+
|
|
28
|
+
CADisplayLink *_transitioningTimer;
|
|
29
|
+
UIView *_transitionFakeView;
|
|
30
|
+
BOOL _isDragging;
|
|
31
|
+
BOOL _isTransitioning;
|
|
32
|
+
BOOL _isTrackingPositionFromLayout;
|
|
27
33
|
|
|
28
|
-
// Reference to parent TrueSheetViewController (if presented from another sheet)
|
|
29
34
|
__weak TrueSheetViewController *_parentSheetController;
|
|
30
35
|
|
|
31
|
-
// Blur effect view
|
|
32
36
|
TrueSheetBlurView *_blurView;
|
|
33
|
-
|
|
34
|
-
// Custom grabber view
|
|
35
37
|
TrueSheetGrabberView *_grabberView;
|
|
36
38
|
|
|
37
|
-
// Resolved detent positions (Y coordinate when sheet rests at each detent)
|
|
38
39
|
NSMutableArray<NSNumber *> *_resolvedDetentPositions;
|
|
40
|
+
BOOL _hasPresentedController;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
#pragma mark - Initialization
|
|
@@ -53,11 +55,17 @@
|
|
|
53
55
|
_lastPosition = 0;
|
|
54
56
|
_isDragging = NO;
|
|
55
57
|
_isPresented = NO;
|
|
58
|
+
_pendingContentSizeChange = NO;
|
|
56
59
|
_activeDetentIndex = -1;
|
|
57
60
|
_pendingDetentIndex = -1;
|
|
58
61
|
|
|
62
|
+
_isTransitioning = NO;
|
|
63
|
+
_transitionFakeView = [UIView new];
|
|
64
|
+
_isTrackingPositionFromLayout = NO;
|
|
65
|
+
|
|
59
66
|
_blurInteraction = YES;
|
|
60
67
|
_resolvedDetentPositions = [NSMutableArray array];
|
|
68
|
+
_hasPresentedController = NO;
|
|
61
69
|
}
|
|
62
70
|
return self;
|
|
63
71
|
}
|
|
@@ -75,10 +83,6 @@
|
|
|
75
83
|
return self.presentedViewController == nil;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
- (BOOL)isActiveAndVisible {
|
|
79
|
-
return self.isViewLoaded && self.view.window != nil;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
86
|
- (UIView *)presentedView {
|
|
83
87
|
return self.sheetPresentationController.presentedView;
|
|
84
88
|
}
|
|
@@ -126,7 +130,6 @@
|
|
|
126
130
|
[super viewDidLoad];
|
|
127
131
|
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
|
128
132
|
|
|
129
|
-
// Create custom grabber view (hidden by default, shown when grabberOptions is set)
|
|
130
133
|
_grabberView = [[TrueSheetGrabberView alloc] init];
|
|
131
134
|
_grabberView.hidden = YES;
|
|
132
135
|
[_grabberView addToView:self.view];
|
|
@@ -135,48 +138,61 @@
|
|
|
135
138
|
- (void)viewWillAppear:(BOOL)animated {
|
|
136
139
|
[super viewWillAppear:animated];
|
|
137
140
|
|
|
138
|
-
// Only trigger on initial presentation, not repositioning
|
|
139
141
|
if (!_isPresented) {
|
|
140
|
-
|
|
142
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
143
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
144
|
+
});
|
|
145
|
+
|
|
141
146
|
UIViewController *presenter = self.presentingViewController;
|
|
142
147
|
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
143
148
|
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
144
|
-
// Notify parent that it is about to lose focus
|
|
145
149
|
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
|
|
146
150
|
[_parentSheetController.delegate viewControllerWillBlur];
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
NSInteger index =
|
|
154
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
155
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
|
|
156
|
+
NSInteger index = self.currentDetentIndex;
|
|
153
157
|
CGFloat position = self.currentPosition;
|
|
154
158
|
CGFloat detent = [self detentValueForIndex:index];
|
|
155
159
|
|
|
156
160
|
[self.delegate viewControllerWillPresentAtIndex:index position:position detent:detent];
|
|
157
|
-
}
|
|
158
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
164
|
+
[self.delegate viewControllerWillFocus];
|
|
165
|
+
}
|
|
166
|
+
});
|
|
159
167
|
}
|
|
168
|
+
|
|
169
|
+
[self setupTransitionTracker];
|
|
160
170
|
}
|
|
161
171
|
|
|
162
172
|
- (void)viewDidAppear:(BOOL)animated {
|
|
163
173
|
[super viewDidAppear:animated];
|
|
164
174
|
|
|
165
175
|
if (!_isPresented) {
|
|
166
|
-
// Notify parent that it has lost focus (after the child sheet appeared)
|
|
167
176
|
if (_parentSheetController) {
|
|
168
177
|
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
169
178
|
[_parentSheetController.delegate viewControllerDidBlur];
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
183
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidPresentAtIndex:position:detent:)]) {
|
|
175
184
|
NSInteger index = [self currentDetentIndex];
|
|
176
185
|
CGFloat detent = [self detentValueForIndex:index];
|
|
177
186
|
[self.delegate viewControllerDidPresentAtIndex:index position:self.currentPosition detent:detent];
|
|
178
|
-
}
|
|
179
|
-
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
190
|
+
[self.delegate viewControllerDidFocus];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO debug:@"did present"];
|
|
194
|
+
});
|
|
195
|
+
|
|
180
196
|
[self setupGestureRecognizer];
|
|
181
197
|
_isPresented = YES;
|
|
182
198
|
}
|
|
@@ -190,32 +206,33 @@
|
|
|
190
206
|
[super viewWillDisappear:animated];
|
|
191
207
|
|
|
192
208
|
if (self.isDismissing) {
|
|
193
|
-
_isPresented = NO;
|
|
194
|
-
_activeDetentIndex = -1;
|
|
195
|
-
|
|
196
|
-
if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
|
|
197
|
-
[self.delegate viewControllerWillDismiss];
|
|
198
|
-
}
|
|
199
|
-
|
|
200
209
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
201
|
-
[self
|
|
210
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
|
|
211
|
+
[self.delegate viewControllerWillBlur];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
|
|
215
|
+
[self.delegate viewControllerWillDismiss];
|
|
216
|
+
}
|
|
202
217
|
});
|
|
203
218
|
|
|
204
|
-
// Notify the parent sheet (if any) that it is about to regain focus
|
|
205
219
|
if (_parentSheetController) {
|
|
206
220
|
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
207
221
|
[_parentSheetController.delegate viewControllerWillFocus];
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
224
|
}
|
|
225
|
+
|
|
226
|
+
[self setupTransitionTracker];
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
214
230
|
[super viewDidDisappear:animated];
|
|
215
231
|
|
|
216
|
-
// Only dispatch didDismiss when actually dismissing (not when another modal is presented on top)
|
|
217
232
|
if (self.isDismissing) {
|
|
218
|
-
|
|
233
|
+
_isPresented = NO;
|
|
234
|
+
_activeDetentIndex = -1;
|
|
235
|
+
|
|
219
236
|
if (_parentSheetController) {
|
|
220
237
|
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
221
238
|
[_parentSheetController.delegate viewControllerDidFocus];
|
|
@@ -223,12 +240,36 @@
|
|
|
223
240
|
_parentSheetController = nil;
|
|
224
241
|
}
|
|
225
242
|
|
|
243
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
244
|
+
[self.delegate viewControllerDidBlur];
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
if ([self.delegate respondsToSelector:@selector(viewControllerDidDismiss)]) {
|
|
227
248
|
[self.delegate viewControllerDidDismiss];
|
|
228
249
|
}
|
|
229
250
|
}
|
|
230
251
|
}
|
|
231
252
|
|
|
253
|
+
- (void)viewWillLayoutSubviews {
|
|
254
|
+
[super viewWillLayoutSubviews];
|
|
255
|
+
|
|
256
|
+
if (!_isTransitioning) {
|
|
257
|
+
_isTrackingPositionFromLayout = YES;
|
|
258
|
+
|
|
259
|
+
UIViewController *presented = self.presentedViewController;
|
|
260
|
+
BOOL hasPresentedController = presented != nil && !presented.isBeingPresented && !presented.isBeingDismissed;
|
|
261
|
+
BOOL realtime = !hasPresentedController;
|
|
262
|
+
|
|
263
|
+
if (_pendingContentSizeChange) {
|
|
264
|
+
_pendingContentSizeChange = NO;
|
|
265
|
+
realtime = NO;
|
|
266
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:realtime debug:@"layout"];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
232
273
|
- (void)viewDidLayoutSubviews {
|
|
233
274
|
[super viewDidLayoutSubviews];
|
|
234
275
|
|
|
@@ -236,20 +277,6 @@
|
|
|
236
277
|
[self.delegate viewControllerDidChangeSize:self.view.frame.size];
|
|
237
278
|
}
|
|
238
279
|
|
|
239
|
-
// Check if there's an active presented controller that has settled (not being presented/dismissed)
|
|
240
|
-
UIViewController *presented = self.presentedViewController;
|
|
241
|
-
BOOL hasPresentedController = presented != nil && !presented.isBeingPresented && !presented.isBeingDismissed;
|
|
242
|
-
|
|
243
|
-
if (!_isDragging) {
|
|
244
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
245
|
-
// Update stored position for current detent (handles content size changes)
|
|
246
|
-
[self storeResolvedPositionForIndex:[self currentDetentIndex]];
|
|
247
|
-
|
|
248
|
-
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:hasPresentedController];
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Emit pending detent change after programmatic resize settles
|
|
253
280
|
if (_pendingDetentIndex >= 0) {
|
|
254
281
|
NSInteger pendingIndex = _pendingDetentIndex;
|
|
255
282
|
_pendingDetentIndex = -1;
|
|
@@ -258,12 +285,67 @@
|
|
|
258
285
|
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
259
286
|
CGFloat detent = [self detentValueForIndex:pendingIndex];
|
|
260
287
|
[self.delegate viewControllerDidChangeDetent:pendingIndex position:self.currentPosition detent:detent];
|
|
288
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO debug:@"pending detent change"];
|
|
261
289
|
}
|
|
262
290
|
});
|
|
263
291
|
}
|
|
292
|
+
|
|
293
|
+
_isTrackingPositionFromLayout = NO;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#pragma mark - Presentation Tracking (RN Screens)
|
|
297
|
+
|
|
298
|
+
- (void)presentViewController:(UIViewController *)viewControllerToPresent
|
|
299
|
+
animated:(BOOL)flag
|
|
300
|
+
completion:(void (^)(void))completion {
|
|
301
|
+
BOOL isExternalController = ![viewControllerToPresent isKindOfClass:[TrueSheetViewController class]];
|
|
302
|
+
|
|
303
|
+
if (isExternalController && !_hasPresentedController) {
|
|
304
|
+
_hasPresentedController = YES;
|
|
305
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
|
|
306
|
+
[self.delegate viewControllerWillBlur];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
[super presentViewController:viewControllerToPresent
|
|
311
|
+
animated:flag
|
|
312
|
+
completion:^{
|
|
313
|
+
if (isExternalController && self->_hasPresentedController) {
|
|
314
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
315
|
+
[self.delegate viewControllerDidBlur];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (completion) {
|
|
319
|
+
completion();
|
|
320
|
+
}
|
|
321
|
+
}];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
|
|
325
|
+
UIViewController *presented = self.presentedViewController;
|
|
326
|
+
BOOL isExternalController = presented && ![presented isKindOfClass:[TrueSheetViewController class]];
|
|
327
|
+
|
|
328
|
+
if (isExternalController && _hasPresentedController) {
|
|
329
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
330
|
+
[self.delegate viewControllerWillFocus];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
[super dismissViewControllerAnimated:flag
|
|
335
|
+
completion:^{
|
|
336
|
+
if (isExternalController && self->_hasPresentedController) {
|
|
337
|
+
self->_hasPresentedController = NO;
|
|
338
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
339
|
+
[self.delegate viewControllerDidFocus];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (completion) {
|
|
343
|
+
completion();
|
|
344
|
+
}
|
|
345
|
+
}];
|
|
264
346
|
}
|
|
265
347
|
|
|
266
|
-
#pragma mark - Gesture Handling
|
|
348
|
+
#pragma mark - Position & Gesture Handling
|
|
267
349
|
|
|
268
350
|
- (TrueSheetContentView *)findContentView:(UIView *)view {
|
|
269
351
|
if ([view isKindOfClass:[TrueSheetContentView class]]) {
|
|
@@ -285,11 +367,9 @@
|
|
|
285
367
|
if (!presentedView)
|
|
286
368
|
return;
|
|
287
369
|
|
|
288
|
-
// Disable pan gestures if draggable is NO
|
|
289
370
|
if (!self.draggable) {
|
|
290
371
|
[GestureUtil setPanGesturesEnabled:NO forView:presentedView];
|
|
291
372
|
|
|
292
|
-
// Also disable ScrollView's pan gesture if present
|
|
293
373
|
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
294
374
|
if (contentView) {
|
|
295
375
|
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
@@ -300,10 +380,8 @@
|
|
|
300
380
|
return;
|
|
301
381
|
}
|
|
302
382
|
|
|
303
|
-
// Attach to presented view's pan gesture (sheet's drag gesture from UIKit)
|
|
304
383
|
[GestureUtil attachPanGestureHandler:presentedView target:self selector:@selector(handlePanGesture:)];
|
|
305
384
|
|
|
306
|
-
// Also attach to ScrollView's pan gesture if present
|
|
307
385
|
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
308
386
|
if (contentView) {
|
|
309
387
|
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
@@ -315,14 +393,13 @@
|
|
|
315
393
|
}
|
|
316
394
|
}
|
|
317
395
|
|
|
318
|
-
- (void)
|
|
396
|
+
- (void)setupDraggable {
|
|
319
397
|
UIView *presentedView = self.presentedView;
|
|
320
398
|
if (!presentedView)
|
|
321
399
|
return;
|
|
322
400
|
|
|
323
401
|
[GestureUtil setPanGesturesEnabled:self.draggable forView:presentedView];
|
|
324
402
|
|
|
325
|
-
// Also update ScrollView's pan gesture if present
|
|
326
403
|
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
327
404
|
if (contentView) {
|
|
328
405
|
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
@@ -333,7 +410,7 @@
|
|
|
333
410
|
}
|
|
334
411
|
|
|
335
412
|
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
|
|
336
|
-
NSInteger index =
|
|
413
|
+
NSInteger index = self.currentDetentIndex;
|
|
337
414
|
CGFloat detent = [self detentValueForIndex:index];
|
|
338
415
|
|
|
339
416
|
if ([self.delegate respondsToSelector:@selector(viewControllerDidDrag:index:position:detent:)]) {
|
|
@@ -345,17 +422,20 @@
|
|
|
345
422
|
_isDragging = YES;
|
|
346
423
|
break;
|
|
347
424
|
case UIGestureRecognizerStateChanged:
|
|
348
|
-
|
|
425
|
+
if (!_isTrackingPositionFromLayout) {
|
|
426
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:YES debug:@"drag change"];
|
|
427
|
+
}
|
|
349
428
|
break;
|
|
350
429
|
case UIGestureRecognizerStateEnded:
|
|
351
430
|
case UIGestureRecognizerStateCancelled: {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
431
|
+
if (!_isTransitioning) {
|
|
432
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
433
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
434
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO debug:@"drag end"];
|
|
435
|
+
});
|
|
436
|
+
}
|
|
356
437
|
|
|
357
|
-
|
|
358
|
-
});
|
|
438
|
+
_isDragging = NO;
|
|
359
439
|
break;
|
|
360
440
|
}
|
|
361
441
|
default:
|
|
@@ -363,10 +443,61 @@
|
|
|
363
443
|
}
|
|
364
444
|
}
|
|
365
445
|
|
|
366
|
-
|
|
446
|
+
- (void)setupTransitionTracker {
|
|
447
|
+
if (!self.transitionCoordinator)
|
|
448
|
+
return;
|
|
449
|
+
|
|
450
|
+
_isTransitioning = YES;
|
|
451
|
+
|
|
452
|
+
CGRect dismissedFrame = CGRectMake(0, self.screenHeight, 0, 0);
|
|
453
|
+
CGRect presentedFrame = CGRectMake(0, self.currentPosition, 0, 0);
|
|
454
|
+
|
|
455
|
+
_transitionFakeView.frame = self.isDismissing ? presentedFrame : dismissedFrame;
|
|
456
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
457
|
+
|
|
458
|
+
auto animation = ^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
459
|
+
[[context containerView] addSubview:self->_transitionFakeView];
|
|
460
|
+
self->_transitionFakeView.frame = self.isDismissing ? dismissedFrame : presentedFrame;
|
|
461
|
+
|
|
462
|
+
self->_transitioningTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleTransitionTracker)];
|
|
463
|
+
[self->_transitioningTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
[self.transitionCoordinator
|
|
467
|
+
animateAlongsideTransition:animation
|
|
468
|
+
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
469
|
+
[self->_transitioningTimer setPaused:YES];
|
|
470
|
+
[self->_transitioningTimer invalidate];
|
|
471
|
+
[self->_transitionFakeView removeFromSuperview];
|
|
472
|
+
self->_isTransitioning = NO;
|
|
473
|
+
}];
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
- (void)handleTransitionTracker {
|
|
477
|
+
if (!_isDragging && _transitionFakeView.layer) {
|
|
478
|
+
CALayer *layer = _transitionFakeView.layer;
|
|
479
|
+
CGFloat layerPosition = layer.presentationLayer.frame.origin.y;
|
|
480
|
+
|
|
481
|
+
if (self.currentPosition >= self.screenHeight) {
|
|
482
|
+
CGFloat position = fmax(_lastPosition, layerPosition);
|
|
483
|
+
[self emitChangePositionDelegateWithPosition:position realtime:YES debug:@"transition out"];
|
|
484
|
+
} else {
|
|
485
|
+
CGFloat position = fmax(self.currentPosition, layerPosition);
|
|
486
|
+
[self emitChangePositionDelegateWithPosition:position realtime:YES debug:@"transition in"];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position realtime:(BOOL)realtime debug:(NSString *)debug {
|
|
492
|
+
UIViewController *presented = self.presentedViewController;
|
|
493
|
+
if (presented) {
|
|
494
|
+
UIModalPresentationStyle style = presented.modalPresentationStyle;
|
|
495
|
+
if (style == UIModalPresentationFullScreen || style == UIModalPresentationOverFullScreen ||
|
|
496
|
+
style == UIModalPresentationCurrentContext || style == UIModalPresentationOverCurrentContext) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
367
500
|
|
|
368
|
-
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position realtime:(BOOL)realtime {
|
|
369
|
-
// Use epsilon comparison to avoid missing updates due to floating point precision
|
|
370
501
|
if (fabs(_lastPosition - position) > 0.01) {
|
|
371
502
|
_lastPosition = position;
|
|
372
503
|
|
|
@@ -378,14 +509,12 @@
|
|
|
378
509
|
}
|
|
379
510
|
}
|
|
380
511
|
|
|
381
|
-
/// Stores the current position for the given detent index
|
|
382
512
|
- (void)storeResolvedPositionForIndex:(NSInteger)index {
|
|
383
513
|
if (index >= 0 && index < (NSInteger)_resolvedDetentPositions.count) {
|
|
384
514
|
_resolvedDetentPositions[index] = @(self.currentPosition);
|
|
385
515
|
}
|
|
386
516
|
}
|
|
387
517
|
|
|
388
|
-
/// Returns the estimated Y position for a detent index, using stored positions when available
|
|
389
518
|
- (CGFloat)estimatedPositionForIndex:(NSInteger)index {
|
|
390
519
|
if (index < 0 || index >= (NSInteger)_resolvedDetentPositions.count)
|
|
391
520
|
return 0;
|
|
@@ -395,11 +524,9 @@
|
|
|
395
524
|
return storedPos;
|
|
396
525
|
}
|
|
397
526
|
|
|
398
|
-
// Estimate based on detent value and known offset from first resolved position
|
|
399
527
|
CGFloat detentValue = [self detentValueForIndex:index];
|
|
400
528
|
CGFloat basePosition = self.screenHeight - (detentValue * self.screenHeight);
|
|
401
529
|
|
|
402
|
-
// Find a resolved position to calculate offset
|
|
403
530
|
for (NSInteger i = 0; i < (NSInteger)_resolvedDetentPositions.count; i++) {
|
|
404
531
|
CGFloat pos = [_resolvedDetentPositions[i] doubleValue];
|
|
405
532
|
if (pos > 0) {
|
|
@@ -413,8 +540,6 @@
|
|
|
413
540
|
return basePosition;
|
|
414
541
|
}
|
|
415
542
|
|
|
416
|
-
/// Finds the segment containing the given position and returns the lower index and progress within that segment.
|
|
417
|
-
/// Returns YES if a segment was found, NO otherwise. When NO, `outIndex` contains the boundary index.
|
|
418
543
|
- (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
|
|
419
544
|
NSInteger count = _resolvedDetentPositions.count;
|
|
420
545
|
if (count == 0) {
|
|
@@ -432,7 +557,6 @@
|
|
|
432
557
|
CGFloat firstPos = [self estimatedPositionForIndex:0];
|
|
433
558
|
CGFloat lastPos = [self estimatedPositionForIndex:count - 1];
|
|
434
559
|
|
|
435
|
-
// Below first detent (position > firstPos means sheet is smaller)
|
|
436
560
|
if (position > firstPos) {
|
|
437
561
|
CGFloat range = self.screenHeight - firstPos;
|
|
438
562
|
*outIndex = -1;
|
|
@@ -440,14 +564,12 @@
|
|
|
440
564
|
return NO;
|
|
441
565
|
}
|
|
442
566
|
|
|
443
|
-
// Above last detent
|
|
444
567
|
if (position < lastPos) {
|
|
445
568
|
*outIndex = count - 1;
|
|
446
569
|
*outProgress = 0;
|
|
447
570
|
return NO;
|
|
448
571
|
}
|
|
449
572
|
|
|
450
|
-
// Find segment (positions decrease as index increases)
|
|
451
573
|
for (NSInteger i = 0; i < count - 1; i++) {
|
|
452
574
|
CGFloat pos = [self estimatedPositionForIndex:i];
|
|
453
575
|
CGFloat nextPos = [self estimatedPositionForIndex:i + 1];
|
|
@@ -472,14 +594,11 @@
|
|
|
472
594
|
|
|
473
595
|
if (!found) {
|
|
474
596
|
if (index == -1) {
|
|
475
|
-
// Below first detent - return negative progress
|
|
476
597
|
return -progress;
|
|
477
598
|
}
|
|
478
|
-
// At or beyond boundary
|
|
479
599
|
return index;
|
|
480
600
|
}
|
|
481
601
|
|
|
482
|
-
// Within a segment - interpolate
|
|
483
602
|
return index + fmax(0, fmin(1, progress));
|
|
484
603
|
}
|
|
485
604
|
|
|
@@ -490,15 +609,12 @@
|
|
|
490
609
|
|
|
491
610
|
if (!found) {
|
|
492
611
|
if (index == -1) {
|
|
493
|
-
// Below first detent
|
|
494
612
|
CGFloat firstDetent = [self detentValueForIndex:0];
|
|
495
613
|
return fmax(0, firstDetent * (1 - progress));
|
|
496
614
|
}
|
|
497
|
-
// At or beyond boundary
|
|
498
615
|
return [self detentValueForIndex:index];
|
|
499
616
|
}
|
|
500
617
|
|
|
501
|
-
// Within a segment - interpolate between detent values
|
|
502
618
|
CGFloat detent = [self detentValueForIndex:index];
|
|
503
619
|
CGFloat nextDetent = [self detentValueForIndex:index + 1];
|
|
504
620
|
return detent + progress * (nextDetent - detent);
|
|
@@ -507,7 +623,6 @@
|
|
|
507
623
|
- (CGFloat)detentValueForIndex:(NSInteger)index {
|
|
508
624
|
if (index >= 0 && index < (NSInteger)_detents.count) {
|
|
509
625
|
CGFloat value = [_detents[index] doubleValue];
|
|
510
|
-
// For auto (-1), calculate actual fraction from content + header height
|
|
511
626
|
if (value == -1) {
|
|
512
627
|
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue];
|
|
513
628
|
return autoHeight / self.screenHeight;
|
|
@@ -519,6 +634,11 @@
|
|
|
519
634
|
|
|
520
635
|
#pragma mark - Sheet Configuration
|
|
521
636
|
|
|
637
|
+
- (void)setupSheetDetentsForSizeChange {
|
|
638
|
+
_pendingContentSizeChange = YES;
|
|
639
|
+
[self setupSheetDetents];
|
|
640
|
+
}
|
|
641
|
+
|
|
522
642
|
- (void)setupSheetDetents {
|
|
523
643
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
524
644
|
if (!sheet)
|
|
@@ -535,13 +655,11 @@
|
|
|
535
655
|
withAutoHeight:autoHeight
|
|
536
656
|
atIndex:index];
|
|
537
657
|
[detents addObject:sheetDetent];
|
|
538
|
-
// Initialize with placeholder - will be updated when sheet settles at each detent
|
|
539
658
|
[_resolvedDetentPositions addObject:@(0)];
|
|
540
659
|
}
|
|
541
660
|
|
|
542
661
|
sheet.detents = detents;
|
|
543
662
|
|
|
544
|
-
// Setup dimmed background
|
|
545
663
|
if (self.dimmed && [self.dimmedDetentIndex integerValue] == 0) {
|
|
546
664
|
sheet.largestUndimmedDetentIdentifier = nil;
|
|
547
665
|
} else {
|
|
@@ -571,7 +689,6 @@
|
|
|
571
689
|
|
|
572
690
|
CGFloat value = [detent doubleValue];
|
|
573
691
|
|
|
574
|
-
// -1 represents "auto" (fit content height)
|
|
575
692
|
if (value == -1) {
|
|
576
693
|
if (@available(iOS 16.0, *)) {
|
|
577
694
|
return [self customDetentWithIdentifier:@"custom-auto" height:autoHeight];
|
|
@@ -637,7 +754,6 @@
|
|
|
637
754
|
if (detentCount == 0)
|
|
638
755
|
return;
|
|
639
756
|
|
|
640
|
-
// Clamp index to valid range
|
|
641
757
|
NSInteger clampedIndex = _activeDetentIndex;
|
|
642
758
|
if (clampedIndex < 0) {
|
|
643
759
|
clampedIndex = 0;
|
|
@@ -694,7 +810,6 @@
|
|
|
694
810
|
|
|
695
811
|
self.view.backgroundColor = self.backgroundColor;
|
|
696
812
|
|
|
697
|
-
// Setup blur effect view - recreate only when blurTint changes
|
|
698
813
|
BOOL blurTintChanged = ![_blurView.blurTint isEqualToString:self.blurTint];
|
|
699
814
|
|
|
700
815
|
if (_blurView && blurTintChanged) {
|
|
@@ -713,11 +828,9 @@
|
|
|
713
828
|
[_blurView applyBlurEffect];
|
|
714
829
|
}
|
|
715
830
|
|
|
716
|
-
// Setup grabber
|
|
717
831
|
BOOL showGrabber = self.grabber && self.draggable;
|
|
718
832
|
|
|
719
833
|
if (self.grabberOptions) {
|
|
720
|
-
// Use custom grabber view when options are provided
|
|
721
834
|
sheet.prefersGrabberVisible = NO;
|
|
722
835
|
|
|
723
836
|
NSDictionary *options = self.grabberOptions;
|
|
@@ -729,7 +842,6 @@
|
|
|
729
842
|
[_grabberView applyConfiguration];
|
|
730
843
|
_grabberView.hidden = !showGrabber;
|
|
731
844
|
} else {
|
|
732
|
-
// Use system default grabber when no options provided
|
|
733
845
|
sheet.prefersGrabberVisible = showGrabber;
|
|
734
846
|
_grabberView.hidden = YES;
|
|
735
847
|
}
|
|
@@ -741,7 +853,7 @@
|
|
|
741
853
|
(UISheetPresentationController *)sheetPresentationController {
|
|
742
854
|
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
743
855
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
744
|
-
NSInteger index =
|
|
856
|
+
NSInteger index = self.currentDetentIndex;
|
|
745
857
|
if (index >= 0) {
|
|
746
858
|
CGFloat detent = [self detentValueForIndex:index];
|
|
747
859
|
[self.delegate viewControllerDidChangeDetent:index position:self.currentPosition detent:detent];
|
|
@@ -754,13 +866,10 @@
|
|
|
754
866
|
|
|
755
867
|
#if RNS_DISMISSIBLE_MODAL_PROTOCOL_AVAILABLE
|
|
756
868
|
- (BOOL)isDismissible {
|
|
757
|
-
// Prevent react-native-screens from dismissing this sheet when presenting a modal
|
|
758
869
|
return NO;
|
|
759
870
|
}
|
|
760
871
|
|
|
761
872
|
- (UIViewController *)newPresentingViewController {
|
|
762
|
-
// Find the topmost TrueSheetViewController in the chain
|
|
763
|
-
// This handles cases where this sheet is presenting another sheet (child sheet)
|
|
764
873
|
UIViewController *topmost = self;
|
|
765
874
|
while (topmost.presentedViewController != nil && !topmost.presentedViewController.isBeingDismissed &&
|
|
766
875
|
[topmost.presentedViewController isKindOfClass:[TrueSheetViewController class]]) {
|
package/lib/module/TrueSheet.js
CHANGED
|
@@ -106,29 +106,31 @@ export class TrueSheet extends PureComponent {
|
|
|
106
106
|
* Present the sheet by given `name` (Promise-based)
|
|
107
107
|
* @param name - Sheet name (must match sheet's name prop)
|
|
108
108
|
* @param index - Detent index (default: 0)
|
|
109
|
+
* @param animated - Whether to animate the presentation (default: true)
|
|
109
110
|
* @returns Promise that resolves when sheet is fully presented
|
|
110
111
|
* @throws Error if sheet not found or presentation fails
|
|
111
112
|
*/
|
|
112
|
-
static async present(name, index = 0) {
|
|
113
|
+
static async present(name, index = 0, animated = true) {
|
|
113
114
|
const instance = TrueSheet.getInstance(name);
|
|
114
115
|
if (!instance) {
|
|
115
116
|
throw new Error(`Sheet with name "${name}" not found`);
|
|
116
117
|
}
|
|
117
|
-
return instance.present(index);
|
|
118
|
+
return instance.present(index, animated);
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
/**
|
|
121
122
|
* Dismiss the sheet by given `name` (Promise-based)
|
|
122
123
|
* @param name - Sheet name
|
|
124
|
+
* @param animated - Whether to animate the dismissal (default: true)
|
|
123
125
|
* @returns Promise that resolves when sheet is fully dismissed
|
|
124
126
|
* @throws Error if sheet not found or dismissal fails
|
|
125
127
|
*/
|
|
126
|
-
static async dismiss(name) {
|
|
128
|
+
static async dismiss(name, animated = true) {
|
|
127
129
|
const instance = TrueSheet.getInstance(name);
|
|
128
130
|
if (!instance) {
|
|
129
131
|
throw new Error(`Sheet with name "${name}" not found`);
|
|
130
132
|
}
|
|
131
|
-
return instance.dismiss();
|
|
133
|
+
return instance.dismiss(animated);
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
/**
|
|
@@ -213,8 +215,9 @@ export class TrueSheet extends PureComponent {
|
|
|
213
215
|
/**
|
|
214
216
|
* Present the Sheet by `index` (Promise-based)
|
|
215
217
|
* @param index - Detent index (default: 0)
|
|
218
|
+
* @param animated - Whether to animate the presentation (default: true)
|
|
216
219
|
*/
|
|
217
|
-
async present(index = 0) {
|
|
220
|
+
async present(index = 0, animated = true) {
|
|
218
221
|
const detentsLength = Math.min(this.props.detents?.length ?? 2, 3); // Max 3 detents
|
|
219
222
|
if (index < 0 || index >= detentsLength) {
|
|
220
223
|
throw new Error(`TrueSheet: present index (${index}) is out of bounds. detents array has ${detentsLength} item(s)`);
|
|
@@ -229,7 +232,7 @@ export class TrueSheet extends PureComponent {
|
|
|
229
232
|
});
|
|
230
233
|
});
|
|
231
234
|
}
|
|
232
|
-
return TrueSheetModule?.presentByRef(this.handle, index);
|
|
235
|
+
return TrueSheetModule?.presentByRef(this.handle, index, animated);
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
/**
|
|
@@ -242,9 +245,10 @@ export class TrueSheet extends PureComponent {
|
|
|
242
245
|
|
|
243
246
|
/**
|
|
244
247
|
* Dismisses the Sheet
|
|
248
|
+
* @param animated - Whether to animate the dismissal (default: true)
|
|
245
249
|
*/
|
|
246
|
-
async dismiss() {
|
|
247
|
-
return TrueSheetModule?.dismissByRef(this.handle);
|
|
250
|
+
async dismiss(animated = true) {
|
|
251
|
+
return TrueSheetModule?.dismissByRef(this.handle, animated);
|
|
248
252
|
}
|
|
249
253
|
componentDidMount() {
|
|
250
254
|
this.registerInstance();
|