@lodev09/react-native-true-sheet 3.1.0-beta.0 → 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.
Files changed (61) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +12 -10
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +19 -7
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +119 -208
  4. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +17 -5
  5. package/android/src/main/jni/CMakeLists.txt +6 -30
  6. package/android/src/main/res/anim/true_sheet_slide_in.xml +4 -4
  7. package/android/src/main/res/anim/true_sheet_slide_out.xml +4 -3
  8. package/ios/TrueSheetContainerView.mm +4 -0
  9. package/ios/TrueSheetContentView.mm +4 -0
  10. package/ios/TrueSheetFooterView.mm +4 -0
  11. package/ios/TrueSheetHeaderView.mm +4 -0
  12. package/ios/TrueSheetModule.mm +24 -5
  13. package/ios/TrueSheetView.h +2 -0
  14. package/ios/TrueSheetView.mm +17 -5
  15. package/ios/TrueSheetViewController.h +3 -2
  16. package/ios/TrueSheetViewController.mm +204 -95
  17. package/lib/module/TrueSheet.js +12 -8
  18. package/lib/module/TrueSheet.js.map +1 -1
  19. package/lib/module/navigation/TrueSheetRouter.js +119 -0
  20. package/lib/module/navigation/TrueSheetRouter.js.map +1 -0
  21. package/lib/module/navigation/TrueSheetView.js +169 -0
  22. package/lib/module/navigation/TrueSheetView.js.map +1 -0
  23. package/lib/module/navigation/createTrueSheetNavigator.js +59 -0
  24. package/lib/module/navigation/createTrueSheetNavigator.js.map +1 -0
  25. package/lib/module/navigation/index.js +6 -0
  26. package/lib/module/navigation/index.js.map +1 -0
  27. package/lib/module/navigation/types.js +4 -0
  28. package/lib/module/navigation/types.js.map +1 -0
  29. package/lib/module/navigation/useTrueSheetNavigation.js +26 -0
  30. package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -0
  31. package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +3 -3
  32. package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
  33. package/lib/module/specs/NativeTrueSheetModule.js.map +1 -1
  34. package/lib/typescript/src/TrueSheet.d.ts +8 -4
  35. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  36. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +57 -0
  37. package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +1 -0
  38. package/lib/typescript/src/navigation/TrueSheetView.d.ts +10 -0
  39. package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +1 -0
  40. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +35 -0
  41. package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +1 -0
  42. package/lib/typescript/src/navigation/index.d.ts +6 -0
  43. package/lib/typescript/src/navigation/index.d.ts.map +1 -0
  44. package/lib/typescript/src/navigation/types.d.ts +125 -0
  45. package/lib/typescript/src/navigation/types.d.ts.map +1 -0
  46. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +23 -0
  47. package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -0
  48. package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
  49. package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts +4 -2
  50. package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts.map +1 -1
  51. package/package.json +13 -2
  52. package/src/TrueSheet.tsx +16 -8
  53. package/src/__mocks__/index.js +6 -5
  54. package/src/navigation/TrueSheetRouter.ts +172 -0
  55. package/src/navigation/TrueSheetView.tsx +271 -0
  56. package/src/navigation/createTrueSheetNavigator.tsx +89 -0
  57. package/src/navigation/index.ts +14 -0
  58. package/src/navigation/types.ts +176 -0
  59. package/src/navigation/useTrueSheetNavigation.ts +28 -0
  60. package/src/reanimated/ReanimatedTrueSheetProvider.tsx +6 -9
  61. 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
- // Capture parent sheet reference if presented from another TrueSheet
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
- if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
151
- dispatch_async(dispatch_get_main_queue(), ^{
152
- NSInteger index = [self currentDetentIndex];
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
- if ([self.delegate respondsToSelector:@selector(viewControllerDidPresentAtIndex:position:detent:)]) {
174
- dispatch_async(dispatch_get_main_queue(), ^{
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 emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
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
- // Notify the parent sheet (if any) that it regained focus
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)updateDraggable {
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 = [self currentDetentIndex];
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
- [self emitChangePositionDelegateWithPosition:self.currentPosition realtime:YES];
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
- _isDragging = NO;
353
- dispatch_async(dispatch_get_main_queue(), ^{
354
- // Store resolved position when drag ends
355
- [self storeResolvedPositionForIndex:[self currentDetentIndex]];
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
- [self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
358
- });
438
+ _isDragging = NO;
359
439
  break;
360
440
  }
361
441
  default:
@@ -363,10 +443,61 @@
363
443
  }
364
444
  }
365
445
 
366
- #pragma mark - Position Tracking
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 = [self currentDetentIndex];
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]]) {
@@ -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();