react-native-screens 3.35.0-rc.0 → 4.0.0-beta.0

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 (115) hide show
  1. package/android/build.gradle +2 -2
  2. package/android/src/main/java/com/swmansion/rnscreens/InsetsObserverProxy.kt +67 -0
  3. package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +101 -4
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapper.kt +38 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapperManager.kt +25 -0
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenFooter.kt +287 -0
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenFooterManager.kt +25 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +11 -19
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +4 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenModalFragment.kt +281 -0
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +62 -19
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +403 -41
  14. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt +4 -1
  15. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +2 -2
  16. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +95 -11
  17. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +39 -28
  18. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetDialogRootView.kt +104 -0
  19. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetDialogScreen.kt +26 -0
  20. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingFragment.kt +488 -0
  21. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingView.kt +66 -0
  22. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/GestureTransparentViewGroup.kt +24 -0
  23. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +127 -0
  24. package/android/src/main/java/com/swmansion/rnscreens/events/SheetDetentChangedEvent.kt +27 -0
  25. package/android/src/main/java/com/swmansion/rnscreens/ext/NumericExt.kt +12 -0
  26. package/android/src/main/java/com/swmansion/rnscreens/ext/ViewExt.kt +32 -0
  27. package/android/src/main/java/com/swmansion/rnscreens/utils/ScreenDummyLayoutHelper.kt +45 -8
  28. package/android/src/main/res/base/drawable/rns_rounder_top_corners_shape.xml +8 -0
  29. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenContentWrapperManagerDelegate.java +25 -0
  30. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenContentWrapperManagerInterface.java +16 -0
  31. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenFooterManagerDelegate.java +25 -0
  32. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenFooterManagerInterface.java +16 -0
  33. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +9 -2
  34. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +5 -2
  35. package/ios/RNSConvert.h +5 -3
  36. package/ios/RNSConvert.mm +14 -20
  37. package/ios/RNSScreen.h +3 -2
  38. package/ios/RNSScreen.mm +433 -49
  39. package/ios/RNSScreenContentWrapper.h +44 -0
  40. package/ios/RNSScreenContentWrapper.mm +61 -0
  41. package/ios/RNSScreenFooter.h +30 -0
  42. package/ios/RNSScreenFooter.mm +137 -0
  43. package/lib/commonjs/components/Screen.js +6 -2
  44. package/lib/commonjs/components/Screen.js.map +1 -1
  45. package/lib/commonjs/components/ScreenContentWrapper.js +19 -0
  46. package/lib/commonjs/components/ScreenContentWrapper.js.map +1 -0
  47. package/lib/commonjs/components/ScreenFooter.js +23 -0
  48. package/lib/commonjs/components/ScreenFooter.js.map +1 -0
  49. package/lib/commonjs/fabric/ModalScreenNativeComponent.js.map +1 -1
  50. package/lib/commonjs/fabric/ScreenContentWrapperNativeComponent.js +10 -0
  51. package/lib/commonjs/fabric/ScreenContentWrapperNativeComponent.js.map +1 -0
  52. package/lib/commonjs/fabric/ScreenFooterNativeComponent.js +10 -0
  53. package/lib/commonjs/fabric/ScreenFooterNativeComponent.js.map +1 -0
  54. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  55. package/lib/commonjs/index.js +30 -0
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/commonjs/native-stack/views/FooterComponent.js +18 -0
  58. package/lib/commonjs/native-stack/views/FooterComponent.js.map +1 -0
  59. package/lib/commonjs/native-stack/views/NativeStackView.js +59 -14
  60. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  61. package/lib/module/components/Screen.js +6 -2
  62. package/lib/module/components/Screen.js.map +1 -1
  63. package/lib/module/components/ScreenContentWrapper.js +12 -0
  64. package/lib/module/components/ScreenContentWrapper.js.map +1 -0
  65. package/lib/module/components/ScreenFooter.js +17 -0
  66. package/lib/module/components/ScreenFooter.js.map +1 -0
  67. package/lib/module/fabric/ModalScreenNativeComponent.js.map +1 -1
  68. package/lib/module/fabric/ScreenContentWrapperNativeComponent.js +3 -0
  69. package/lib/module/fabric/ScreenContentWrapperNativeComponent.js.map +1 -0
  70. package/lib/module/fabric/ScreenFooterNativeComponent.js +3 -0
  71. package/lib/module/fabric/ScreenFooterNativeComponent.js.map +1 -0
  72. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  73. package/lib/module/index.js +2 -0
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/native-stack/views/FooterComponent.js +11 -0
  76. package/lib/module/native-stack/views/FooterComponent.js.map +1 -0
  77. package/lib/module/native-stack/views/NativeStackView.js +61 -15
  78. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  79. package/lib/typescript/components/Screen.d.ts.map +1 -1
  80. package/lib/typescript/components/ScreenContentWrapper.d.ts +6 -0
  81. package/lib/typescript/components/ScreenContentWrapper.d.ts.map +1 -0
  82. package/lib/typescript/components/ScreenFooter.d.ts +12 -0
  83. package/lib/typescript/components/ScreenFooter.d.ts.map +1 -0
  84. package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts +2 -3
  85. package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts.map +1 -1
  86. package/lib/typescript/fabric/ScreenContentWrapperNativeComponent.d.ts +7 -0
  87. package/lib/typescript/fabric/ScreenContentWrapperNativeComponent.d.ts.map +1 -0
  88. package/lib/typescript/fabric/ScreenFooterNativeComponent.d.ts +7 -0
  89. package/lib/typescript/fabric/ScreenFooterNativeComponent.d.ts.map +1 -0
  90. package/lib/typescript/fabric/ScreenNativeComponent.d.ts +9 -3
  91. package/lib/typescript/fabric/ScreenNativeComponent.d.ts.map +1 -1
  92. package/lib/typescript/index.d.ts +2 -0
  93. package/lib/typescript/index.d.ts.map +1 -1
  94. package/lib/typescript/native-stack/types.d.ts +63 -23
  95. package/lib/typescript/native-stack/types.d.ts.map +1 -1
  96. package/lib/typescript/native-stack/views/FooterComponent.d.ts +7 -0
  97. package/lib/typescript/native-stack/views/FooterComponent.d.ts.map +1 -0
  98. package/lib/typescript/native-stack/views/NativeStackView.d.ts.map +1 -1
  99. package/lib/typescript/types.d.ts +42 -17
  100. package/lib/typescript/types.d.ts.map +1 -1
  101. package/native-stack/README.md +16 -14
  102. package/package.json +1 -1
  103. package/react-native.config.js +18 -16
  104. package/src/components/Screen.tsx +6 -2
  105. package/src/components/ScreenContentWrapper.tsx +12 -0
  106. package/src/components/ScreenFooter.tsx +18 -0
  107. package/src/fabric/ModalScreenNativeComponent.ts +2 -4
  108. package/src/fabric/ScreenContentWrapperNativeComponent.ts +9 -0
  109. package/src/fabric/ScreenFooterNativeComponent.ts +6 -0
  110. package/src/fabric/ScreenNativeComponent.ts +10 -4
  111. package/src/index.tsx +10 -0
  112. package/src/native-stack/types.tsx +57 -23
  113. package/src/native-stack/views/FooterComponent.tsx +10 -0
  114. package/src/native-stack/views/NativeStackView.tsx +74 -11
  115. package/src/types.tsx +41 -16
package/ios/RNSScreen.mm CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  #import "RNSScreen.h"
4
4
  #import "RNSScreenContainer.h"
5
+ #import "RNSScreenContentWrapper.h"
5
6
  #import "RNSScreenWindowTraits.h"
6
7
 
7
8
  #ifdef RCT_NEW_ARCH_ENABLED
8
9
  #import <React/RCTConversions.h>
9
10
  #import <React/RCTFabricComponentsPlugins.h>
10
11
  #import <React/RCTRootComponentView.h>
12
+ #import <React/RCTScrollViewComponentView.h>
11
13
  #import <React/RCTSurfaceTouchHandler.h>
12
14
  #import <react/renderer/components/rnscreens/EventEmitters.h>
13
15
  #import <react/renderer/components/rnscreens/Props.h>
@@ -17,11 +19,15 @@
17
19
  #import "RNSHeaderHeightChangeEvent.h"
18
20
  #import "RNSScreenViewEvent.h"
19
21
  #else
22
+ #import <React/RCTScrollView.h>
20
23
  #import <React/RCTTouchHandler.h>
21
- #endif
24
+ #endif // RCT_NEW_ARCH_ENABLED
22
25
 
23
26
  #import <React/RCTShadowView.h>
24
27
  #import <React/RCTUIManager.h>
28
+ #import <React/RCTUIManagerUtils.h>
29
+
30
+ #import "RNSScreenFooter.h"
25
31
  #import "RNSScreenStack.h"
26
32
  #import "RNSScreenStackHeaderConfig.h"
27
33
 
@@ -29,22 +35,38 @@
29
35
 
30
36
  #ifdef RCT_NEW_ARCH_ENABLED
31
37
  namespace react = facebook::react;
38
+ #define ReactScrollViewBase RCTScrollViewComponentView
39
+ #else
40
+ #define ReactScrollViewBase RCTScrollView
32
41
  #endif // RCT_NEW_ARCH_ENABLED
33
42
 
34
- @interface RNSScreenView ()
43
+ constexpr NSInteger SHEET_FIT_TO_CONTENTS = -1;
44
+ constexpr NSInteger SHEET_LARGEST_UNDIMMED_DETENT_NONE = -1;
45
+
46
+ @interface RNSScreenView () <
47
+ UIAdaptivePresentationControllerDelegate,
48
+ RNSScreenContentWrapperDelegate,
49
+ #if !TARGET_OS_TV
50
+ UISheetPresentationControllerDelegate,
51
+ #endif
35
52
  #ifdef RCT_NEW_ARCH_ENABLED
36
- <RCTRNSScreenViewProtocol, UIAdaptivePresentationControllerDelegate>
53
+ RCTRNSScreenViewProtocol,
54
+ CAAnimationDelegate>
37
55
  #else
38
- <UIAdaptivePresentationControllerDelegate, RCTInvalidating>
56
+ RCTInvalidating>
39
57
  #endif
58
+
40
59
  @end
41
60
 
42
61
  @implementation RNSScreenView {
62
+ __weak ReactScrollViewBase *_sheetsScrollView;
63
+ BOOL _didSetSheetAllowedDetentsOnController;
43
64
  #ifdef RCT_NEW_ARCH_ENABLED
44
65
  RCTSurfaceTouchHandler *_touchHandler;
45
66
  react::RNSScreenShadowNode::ConcreteState::Shared _state;
46
67
  // on fabric, they are not available by default so we need them exposed here too
47
68
  NSMutableArray<UIView *> *_reactSubviews;
69
+ __weak RNSScreenContentWrapper *_contentWrapper;
48
70
  #else
49
71
  __weak RCTBridge *_bridge;
50
72
  RCTTouchHandler *_touchHandler;
@@ -59,6 +81,7 @@ namespace react = facebook::react;
59
81
  static const auto defaultProps = std::make_shared<const react::RNSScreenProps>();
60
82
  _props = defaultProps;
61
83
  _reactSubviews = [NSMutableArray new];
84
+ _contentWrapper = nil;
62
85
  [self initCommonProps];
63
86
  }
64
87
  return self;
@@ -91,6 +114,8 @@ namespace react = facebook::react;
91
114
  #if !TARGET_OS_TV
92
115
  _sheetExpandsWhenScrolledToEdge = YES;
93
116
  #endif // !TARGET_OS_TV
117
+ _sheetsScrollView = nil;
118
+ _didSetSheetAllowedDetentsOnController = NO;
94
119
  }
95
120
 
96
121
  - (UIViewController *)reactViewController
@@ -121,7 +146,49 @@ namespace react = facebook::react;
121
146
  }
122
147
  #else
123
148
  [_bridge.uiManager setSize:self.bounds.size forView:self];
124
- #endif
149
+ #endif // RCT_NEW_ARCH_ENABLED
150
+
151
+ if (_stackPresentation != RNSScreenStackPresentationFormSheet) {
152
+ return;
153
+ }
154
+
155
+ // In case of formSheet stack presentation, to mitigate view flickering
156
+ // (see PR with description of this problem: https://github.com/software-mansion/react-native-screens/pull/1870)
157
+ // we do not set `bottom: 0` in JS for wrapper of the screen content, causing React to not set
158
+ // strict frame every time the sheet size is updated by the code above. This approach leads however to
159
+ // situation where (if present) scrollview does not know its view port size resulting in buggy behaviour.
160
+ // That's exactly the issue we are handling below. We look for a scroll view down the view hierarchy (only going
161
+ // through first subviews, as the OS does something similar e.g. when looking for scrollview for large header
162
+ // interaction) and we set its frame to the sheet size. **This is not perfect**, as the content might jump when items
163
+ // are added/removed to/from the scroll view, but it's the best we got rn. See
164
+ // https://github.com/software-mansion/react-native-screens/pull/1852
165
+
166
+ // TODO: Consider adding a prop to control whether we want to look for a scroll view here.
167
+ // It might be necessary in case someone doesn't want its scroll view to span over whole
168
+ // height of the sheet.
169
+ ReactScrollViewBase *scrollView = [self findDirectLineDescendantReactScrollView];
170
+ if (_sheetsScrollView != scrollView) {
171
+ [_sheetsScrollView removeObserver:self forKeyPath:@"bounds" context:nil];
172
+ _sheetsScrollView = scrollView;
173
+
174
+ // We pass 0 as options, as we are not interested in receiving updated bounds value,
175
+ // we are going to overwrite it anyway.
176
+ [scrollView addObserver:self forKeyPath:@"bounds" options:0 context:nil];
177
+ }
178
+ if (scrollView != nil) {
179
+ [scrollView setFrame:self.frame];
180
+ }
181
+ }
182
+
183
+ - (void)observeValueForKeyPath:(NSString *)keyPath
184
+ ofObject:(id)object
185
+ change:(NSDictionary<NSKeyValueChangeKey, id> *)change
186
+ context:(void *)context
187
+ {
188
+ UIView *scrollview = (UIView *)object;
189
+ if (!CGRectEqualToRect(scrollview.frame, self.frame)) {
190
+ [scrollview setFrame:self.frame];
191
+ }
125
192
  }
126
193
 
127
194
  - (void)setStackPresentation:(RNSScreenStackPresentation)stackPresentation
@@ -286,8 +353,41 @@ namespace react = facebook::react;
286
353
  return _reactSuperview;
287
354
  }
288
355
 
356
+ /// This is RNSScreenContentWrapperDelegate method, where we do get notified when React did update frame of our child.
357
+ - (void)reactDidSetFrame:(CGRect)reactFrame forContentWrapper:(RNSScreenContentWrapper *)contentWrapepr
358
+ {
359
+ if (self.stackPresentation != RNSScreenStackPresentationFormSheet || _didSetSheetAllowedDetentsOnController == YES) {
360
+ return;
361
+ }
362
+
363
+ _didSetSheetAllowedDetentsOnController = YES;
364
+
365
+ #if !TARGET_OS_TV && !TARGET_OS_VISION && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_16_0) && \
366
+ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
367
+ if (@available(iOS 16.0, *)) {
368
+ UISheetPresentationController *sheetController = _controller.sheetPresentationController;
369
+ if (sheetController == nil) {
370
+ RCTLogError(@"[RNScreens] sheetPresentationController is null when attempting to set allowed detents");
371
+ return;
372
+ }
373
+
374
+ if (_sheetAllowedDetents.count > 0 && _sheetAllowedDetents[0].intValue == SHEET_FIT_TO_CONTENTS) {
375
+ auto detents = [self detentsFromMaxHeights:@[ [NSNumber numberWithFloat:reactFrame.size.height] ]];
376
+ [self setAllowedDetentsForSheet:sheetController to:detents animate:YES];
377
+ }
378
+ }
379
+ #endif // Check for iOS >= 16 && !TARGET_OS_TV
380
+ }
381
+
289
382
  - (void)addSubview:(UIView *)view
290
383
  {
384
+ /// This system method is called on Paper only. Fabric uses `-[insertSubview:atIndex:]`.
385
+ if ([view isKindOfClass:RNSScreenContentWrapper.class] &&
386
+ self.stackPresentation == RNSScreenStackPresentationFormSheet) {
387
+ auto contentWrapper = (RNSScreenContentWrapper *)view;
388
+ contentWrapper.delegate = self;
389
+ }
390
+
291
391
  if (![view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
292
392
  [super addSubview:view];
293
393
  } else {
@@ -403,13 +503,31 @@ namespace react = facebook::react;
403
503
  #endif
404
504
  }
405
505
 
506
+ - (void)notifySheetDetentChangeToIndex:(NSInteger)newDetentIndex isStable:(BOOL)isStable
507
+ {
508
+ #ifdef RCT_NEW_ARCH_ENABLED
509
+ if (_eventEmitter != nullptr) {
510
+ int index = newDetentIndex;
511
+ std::dynamic_pointer_cast<const react::RNSScreenEventEmitter>(_eventEmitter)
512
+ ->onSheetDetentChanged(
513
+ react::RNSScreenEventEmitter::OnSheetDetentChanged{.index = index, .isStable = isStable});
514
+ }
515
+ #else
516
+ if (self.onSheetDetentChanged) {
517
+ self.onSheetDetentChanged(@{
518
+ @"index" : @(newDetentIndex),
519
+ @"isStable" : @(YES),
520
+ });
521
+ }
522
+ #endif
523
+ }
524
+
406
525
  - (void)notifyHeaderHeightChange:(double)headerHeight
407
526
  {
408
527
  #ifdef RCT_NEW_ARCH_ENABLED
409
528
  if (_eventEmitter != nullptr) {
410
- std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
411
- ->onHeaderHeightChange(
412
- facebook::react::RNSScreenEventEmitter::OnHeaderHeightChange{.headerHeight = headerHeight});
529
+ std::dynamic_pointer_cast<const react::RNSScreenEventEmitter>(_eventEmitter)
530
+ ->onHeaderHeightChange(react::RNSScreenEventEmitter::OnHeaderHeightChange{.headerHeight = headerHeight});
413
531
  }
414
532
 
415
533
  RNSHeaderHeightChangeEvent *event =
@@ -579,6 +697,19 @@ namespace react = facebook::react;
579
697
  return nil;
580
698
  }
581
699
 
700
+ /// Looks for RCTScrollView in direct line - goes through the subviews at index 0 down the view hierarchy.
701
+ - (nullable ReactScrollViewBase *)findDirectLineDescendantReactScrollView
702
+ {
703
+ UIView *firstSubview = self;
704
+ while (firstSubview.subviews.count > 0) {
705
+ firstSubview = firstSubview.subviews[0];
706
+ if ([firstSubview isKindOfClass:ReactScrollViewBase.class]) {
707
+ return (ReactScrollViewBase *)firstSubview;
708
+ }
709
+ }
710
+ return nil;
711
+ }
712
+
582
713
  - (BOOL)isModal
583
714
  {
584
715
  return self.stackPresentation != RNSScreenStackPresentationPush;
@@ -608,58 +739,295 @@ namespace react = facebook::react;
608
739
  }
609
740
 
610
741
  #if !TARGET_OS_TV && !TARGET_OS_VISION
742
+
743
+ - (void)setPropertyForSheet:(UISheetPresentationController *)sheet
744
+ withBlock:(void (^)(void))block
745
+ animate:(BOOL)animate API_AVAILABLE(ios(15.0))
746
+ {
747
+ if (animate) {
748
+ [sheet animateChanges:block];
749
+ } else {
750
+ block();
751
+ }
752
+ }
753
+
754
+ - (void)setAllowedDetentsForSheet:(UISheetPresentationController *)sheet
755
+ to:(NSArray<UISheetPresentationControllerDetent *> *)detents
756
+ animate:(BOOL)animate API_AVAILABLE(ios(15.0))
757
+ {
758
+ [self setPropertyForSheet:sheet
759
+ withBlock:^{
760
+ sheet.detents = detents;
761
+ }
762
+ animate:animate];
763
+ }
764
+
765
+ - (void)setSelectedDetentForSheet:(UISheetPresentationController *)sheet
766
+ to:(UISheetPresentationControllerDetentIdentifier)detent
767
+ animate:(BOOL)animate API_AVAILABLE(ios(15.0))
768
+ {
769
+ if (sheet.selectedDetentIdentifier != detent) {
770
+ [self setPropertyForSheet:sheet
771
+ withBlock:^{
772
+ sheet.selectedDetentIdentifier = detent;
773
+ }
774
+ animate:animate];
775
+ }
776
+ }
777
+
778
+ - (void)setCornerRadiusForSheet:(UISheetPresentationController *)sheet
779
+ to:(CGFloat)radius
780
+ animate:(BOOL)animate API_AVAILABLE(ios(15.0))
781
+ {
782
+ if (sheet.preferredCornerRadius != radius) {
783
+ [self setPropertyForSheet:sheet
784
+ withBlock:^{
785
+ sheet.preferredCornerRadius =
786
+ radius < 0 ? UISheetPresentationControllerAutomaticDimension : radius;
787
+ }
788
+ animate:animate];
789
+ }
790
+ }
791
+
792
+ - (void)setGrabberVisibleForSheet:(UISheetPresentationController *)sheet
793
+ to:(BOOL)visible
794
+ animate:(BOOL)animate API_AVAILABLE(ios(15.0))
795
+ {
796
+ if (sheet.prefersGrabberVisible != visible) {
797
+ [self setPropertyForSheet:sheet
798
+ withBlock:^{
799
+ sheet.prefersGrabberVisible = visible;
800
+ }
801
+ animate:animate];
802
+ }
803
+ }
804
+
805
+ - (void)setLargestUndimmedDetentForSheet:(UISheetPresentationController *)sheet
806
+ to:(UISheetPresentationControllerDetentIdentifier)detent
807
+ animate:(BOOL)animate API_AVAILABLE(ios(15.0))
808
+ {
809
+ if (sheet.largestUndimmedDetentIdentifier != detent) {
810
+ [self setPropertyForSheet:sheet
811
+ withBlock:^{
812
+ sheet.largestUndimmedDetentIdentifier = detent;
813
+ }
814
+ animate:animate];
815
+ }
816
+ }
817
+
818
+ #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_15_0) && \
819
+ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0
820
+ - (NSInteger)detentIndexFromDetentIdentifier:(UISheetPresentationControllerDetentIdentifier)identifier
821
+ API_AVAILABLE(ios(15.0))
822
+ {
823
+ // We first check if we are running on iOS 16+ as the API is different
824
+ #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_16_0) && \
825
+ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
826
+ if (_sheetAllowedDetents.count > 0) {
827
+ // We should be running on custom detents in this case, thus identifier should be a stringified number.
828
+ return identifier.integerValue;
829
+ } else
830
+ #endif // iOS 16 check
831
+ {
832
+ // We're using system defined identifiers.
833
+ if (_sheetAllowedDetents.count >= 2 || _sheetAllowedDetents.count == 0) {
834
+ if (identifier == UISheetPresentationControllerDetentIdentifierMedium) {
835
+ return 0;
836
+ } else if (identifier == UISheetPresentationControllerDetentIdentifierLarge) {
837
+ return 1;
838
+ } else {
839
+ RCTLogError(@"[RNScreens] Unexpected detent identifier %@", identifier);
840
+ }
841
+ } else {
842
+ // There is only single option.
843
+ return 0;
844
+ }
845
+ }
846
+ return 0;
847
+ }
848
+
849
+ - (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
850
+ (UISheetPresentationController *)sheetPresentationController API_AVAILABLE(ios(15.0))
851
+ {
852
+ UISheetPresentationControllerDetentIdentifier ident = sheetPresentationController.selectedDetentIdentifier;
853
+ [self notifySheetDetentChangeToIndex:[self detentIndexFromDetentIdentifier:ident] isStable:YES];
854
+ }
855
+ #endif // iOS 15 check
856
+
611
857
  /**
612
858
  * Updates settings for sheet presentation controller.
613
859
  * Note that this method should not be called inside `stackPresentation` setter, because on Paper we don't have
614
- * guarantee that values of all related props had been updated earlier.
860
+ * guarantee that values of all related props had been updated earlier. It should be invoked from `didSetProps`.
861
+ * On Fabric we have control over prop-setting process but it might be reasonable to run it from `finalizeUpdates`.
615
862
  */
616
- - (void)updatePresentationStyle
863
+ - (void)updateFormSheetPresentationStyle
617
864
  {
865
+ if (_stackPresentation != RNSScreenStackPresentationFormSheet) {
866
+ return;
867
+ }
618
868
  #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_15_0) && \
619
869
  __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0
870
+ int firstDimmedDetentIndex = _sheetAllowedDetents.count;
871
+
872
+ // Whether we use system (iOS 15) detents or custom (iOS 16+).
873
+ // Custom detents are in use if we are on iOS 16+ and we have at least single detent
874
+ // defined in the detents array. In any other case we do use system defined detents.
875
+ bool systemDetentsInUse = false;
876
+
620
877
  if (@available(iOS 15.0, *)) {
621
878
  UISheetPresentationController *sheet = _controller.sheetPresentationController;
622
- if (_stackPresentation == RNSScreenStackPresentationFormSheet && sheet != nil) {
623
- sheet.prefersScrollingExpandsWhenScrolledToEdge = _sheetExpandsWhenScrolledToEdge;
624
- sheet.prefersGrabberVisible = _sheetGrabberVisible;
625
- sheet.preferredCornerRadius =
626
- _sheetCornerRadius < 0 ? UISheetPresentationControllerAutomaticDimension : _sheetCornerRadius;
627
-
628
- if (_sheetLargestUndimmedDetent == RNSScreenDetentTypeMedium) {
629
- sheet.largestUndimmedDetentIdentifier = UISheetPresentationControllerDetentIdentifierMedium;
630
- } else if (_sheetLargestUndimmedDetent == RNSScreenDetentTypeLarge) {
631
- sheet.largestUndimmedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
632
- } else if (_sheetLargestUndimmedDetent == RNSScreenDetentTypeAll) {
633
- sheet.largestUndimmedDetentIdentifier = nil;
879
+ if (sheet == nil) {
880
+ return;
881
+ }
882
+ sheet.delegate = self;
883
+ #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_16_0) && \
884
+ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
885
+ if (_sheetAllowedDetents.count > 0) {
886
+ if (@available(iOS 16.0, *)) {
887
+ if (_sheetAllowedDetents.count == 1 && [_sheetAllowedDetents[0] integerValue] == SHEET_FIT_TO_CONTENTS) {
888
+ // This is `fitToContents` case, where sheet should be just high to display its contents.
889
+ // Paper: we do not set anything here, we will set once React computed layout of our React's children, namely
890
+ // RNSScreenContentWrapper, which in case of formSheet presentation style does have exactly the same frame as
891
+ // actual content. The update will be triggered once our child is mounted and laid out by React.
892
+ // Fabric: in this very moment our children are already mounted & laid out. In the very end of this method,
893
+ // after all other configuration is applied we trigger content wrapper to send us update on its frame.
894
+ } else {
895
+ [self setAllowedDetentsForSheet:sheet
896
+ to:[self detentsFromMaxHeightFractions:_sheetAllowedDetents]
897
+ animate:NO];
898
+ }
899
+ }
900
+ } else
901
+ #endif // Check for iOS >= 16
902
+ {
903
+ systemDetentsInUse = true;
904
+ if (_sheetAllowedDetents.count == 0) {
905
+ [self setAllowedDetentsForSheet:sheet
906
+ to:@[
907
+ UISheetPresentationControllerDetent.mediumDetent,
908
+ UISheetPresentationControllerDetent.largeDetent
909
+ ]
910
+ animate:YES];
911
+ } else if (_sheetAllowedDetents.count >= 2) {
912
+ float firstDetentFraction = _sheetAllowedDetents[0].floatValue;
913
+ float secondDetentFraction = _sheetAllowedDetents[1].floatValue;
914
+ firstDimmedDetentIndex = 2;
915
+
916
+ if (firstDetentFraction < secondDetentFraction) {
917
+ [self setAllowedDetentsForSheet:sheet
918
+ to:@[
919
+ UISheetPresentationControllerDetent.mediumDetent,
920
+ UISheetPresentationControllerDetent.largeDetent
921
+ ]
922
+ animate:YES];
923
+ } else {
924
+ RCTLogError(@"[RNScreens] The values in sheetAllowedDetents array must be sorted");
925
+ }
634
926
  } else {
635
- RCTLogError(@"Unhandled value of sheetLargestUndimmedDetent passed");
927
+ float firstDetentFraction = _sheetAllowedDetents[0].floatValue;
928
+ if (firstDetentFraction == SHEET_FIT_TO_CONTENTS) {
929
+ RCTLogError(@"[RNScreens] Unsupported on iOS versions below 16");
930
+ } else if (firstDetentFraction < 1.0) {
931
+ [self setAllowedDetentsForSheet:sheet to:@[ UISheetPresentationControllerDetent.mediumDetent ] animate:YES];
932
+ [self setSelectedDetentForSheet:sheet to:UISheetPresentationControllerDetentIdentifierMedium animate:YES];
933
+ } else {
934
+ [self setAllowedDetentsForSheet:sheet to:@[ UISheetPresentationControllerDetent.largeDetent ] animate:YES];
935
+ [self setSelectedDetentForSheet:sheet to:UISheetPresentationControllerDetentIdentifierLarge animate:YES];
936
+ }
636
937
  }
938
+ }
637
939
 
638
- if (_sheetAllowedDetents == RNSScreenDetentTypeMedium) {
639
- sheet.detents = @[ UISheetPresentationControllerDetent.mediumDetent ];
640
- if (sheet.selectedDetentIdentifier != UISheetPresentationControllerDetentIdentifierMedium) {
641
- [sheet animateChanges:^{
642
- sheet.selectedDetentIdentifier = UISheetPresentationControllerDetentIdentifierMedium;
643
- }];
644
- }
645
- } else if (_sheetAllowedDetents == RNSScreenDetentTypeLarge) {
646
- sheet.detents = @[ UISheetPresentationControllerDetent.largeDetent ];
647
- if (sheet.selectedDetentIdentifier != UISheetPresentationControllerDetentIdentifierLarge) {
648
- [sheet animateChanges:^{
649
- sheet.selectedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
650
- }];
940
+ sheet.prefersScrollingExpandsWhenScrolledToEdge = _sheetExpandsWhenScrolledToEdge;
941
+ [self setGrabberVisibleForSheet:sheet to:_sheetGrabberVisible animate:YES];
942
+ [self setCornerRadiusForSheet:sheet to:_sheetCornerRadius animate:YES];
943
+
944
+ // lud - largest undimmed detent
945
+ // First we try to take value from the prop or default.
946
+ int ludIndex = _sheetLargestUndimmedDetent != nil ? _sheetLargestUndimmedDetent.intValue : -1;
947
+ // Rationalize the value in case the user set something that did not make sense.
948
+ ludIndex = ludIndex >= firstDimmedDetentIndex ? firstDimmedDetentIndex - 1 : ludIndex;
949
+ if (ludIndex == SHEET_LARGEST_UNDIMMED_DETENT_NONE) {
950
+ [self setLargestUndimmedDetentForSheet:sheet to:nil animate:YES];
951
+ } else if (ludIndex >= 0) {
952
+ if (systemDetentsInUse) {
953
+ // We're on iOS 15 or do not have custom detents specified by the user.
954
+ if (firstDimmedDetentIndex == 0 || (firstDimmedDetentIndex == 1 && _sheetAllowedDetents[0].floatValue < 1.0)) {
955
+ // There are no detents specified or there is exactly one & it is less than 1.0 we default to medium.
956
+ [self setLargestUndimmedDetentForSheet:sheet
957
+ to:UISheetPresentationControllerDetentIdentifierMedium
958
+ animate:YES];
959
+ } else {
960
+ [self setLargestUndimmedDetentForSheet:sheet
961
+ to:UISheetPresentationControllerDetentIdentifierLarge
962
+ animate:YES];
651
963
  }
652
- } else if (_sheetAllowedDetents == RNSScreenDetentTypeAll) {
653
- sheet.detents =
654
- @[ UISheetPresentationControllerDetent.mediumDetent, UISheetPresentationControllerDetent.largeDetent ];
655
964
  } else {
656
- RCTLogError(@"Unhandled value of sheetAllowedDetents passed");
965
+ // We're on iOS 16+ & have custom detents.
966
+ [self setLargestUndimmedDetentForSheet:sheet to:[NSNumber numberWithInt:ludIndex].stringValue animate:YES];
657
967
  }
968
+ } else {
969
+ RCTLogError(@"[RNScreens] Value of sheetLargestUndimmedDetent prop must be >= -1");
658
970
  }
659
971
  }
660
- #endif // Check for max allowed iOS version
972
+
973
+ #ifdef RCT_NEW_ARCH_ENABLED
974
+ // We trigger update from content wrapper, because on Fabric we update props after the children are mounted & laid
975
+ // out.
976
+ [self->_contentWrapper triggerDelegateUpdate];
977
+ #endif // RCT_NEW_ARCH_ENABLED
978
+ #endif // Check for iOS >= 15
661
979
  }
662
- #endif // !TARGET_OS_TV
980
+
981
+ #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_16_0) && \
982
+ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
983
+
984
+ /**
985
+ * Creates array of detent objects based on provided `values` & `resolver`. Since we need to name the detents to be able
986
+ * to later refer to them, this method names the detents by stringifying their indices, e.g. detent on index 2 will be
987
+ * named "2".
988
+ */
989
+ - (NSArray<UISheetPresentationControllerDetent *> *)
990
+ detentsFromValues:(NSArray<NSNumber *> *)values
991
+ withResolver:(CGFloat (^)(id<UISheetPresentationControllerDetentResolutionContext>, NSNumber *))resolver
992
+ API_AVAILABLE(ios(16.0))
993
+ {
994
+ NSMutableArray<UISheetPresentationControllerDetent *> *customDetents =
995
+ [NSMutableArray arrayWithCapacity:values.count];
996
+ [values enumerateObjectsUsingBlock:^(NSNumber *value, NSUInteger index, BOOL *stop) {
997
+ UISheetPresentationControllerDetentIdentifier ident = [[NSNumber numberWithInt:index] stringValue];
998
+ [customDetents addObject:[UISheetPresentationControllerDetent
999
+ customDetentWithIdentifier:ident
1000
+ resolver:^CGFloat(
1001
+ id<UISheetPresentationControllerDetentResolutionContext> ctx) {
1002
+ return resolver(ctx, value);
1003
+ }]];
1004
+ }];
1005
+ return customDetents;
1006
+ }
1007
+
1008
+ - (NSArray<UISheetPresentationControllerDetent *> *)detentsFromMaxHeightFractions:(NSArray<NSNumber *> *)fractions
1009
+ API_AVAILABLE(ios(16.0))
1010
+ {
1011
+ return [self
1012
+ detentsFromValues:fractions
1013
+ withResolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> ctx, NSNumber *fraction) {
1014
+ return MIN(ctx.maximumDetentValue, ctx.maximumDetentValue * fraction.floatValue);
1015
+ }];
1016
+ }
1017
+
1018
+ - (NSArray<UISheetPresentationControllerDetent *> *)detentsFromMaxHeights:(NSArray<NSNumber *> *)maxHeights
1019
+ API_AVAILABLE(ios(16.0))
1020
+ {
1021
+ return
1022
+ [self detentsFromValues:maxHeights
1023
+ withResolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> ctx, NSNumber *height) {
1024
+ return MIN(ctx.maximumDetentValue, height.floatValue);
1025
+ }];
1026
+ }
1027
+
1028
+ #endif // Check for iOS >= 16
1029
+
1030
+ #endif // !TARGET_OS_TV && !TARGET_OS_VISION
663
1031
 
664
1032
  #pragma mark - Fabric specific
665
1033
  #ifdef RCT_NEW_ARCH_ENABLED
@@ -689,6 +1057,11 @@ namespace react = facebook::react;
689
1057
 
690
1058
  - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
691
1059
  {
1060
+ if ([childComponentView isKindOfClass:RNSScreenContentWrapper.class]) {
1061
+ auto contentWrapper = (RNSScreenContentWrapper *)childComponentView;
1062
+ contentWrapper.delegate = self;
1063
+ _contentWrapper = contentWrapper;
1064
+ }
692
1065
  if ([childComponentView isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
693
1066
  _config = (RNSScreenStackHeaderConfig *)childComponentView;
694
1067
  _config.screenView = self;
@@ -702,6 +1075,10 @@ namespace react = facebook::react;
702
1075
  if ([childComponentView isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
703
1076
  _config = nil;
704
1077
  }
1078
+ if ([childComponentView isKindOfClass:[RNSScreenContentWrapper class]]) {
1079
+ _contentWrapper.delegate = nil;
1080
+ _contentWrapper = nil;
1081
+ }
705
1082
  [_reactSubviews removeObject:childComponentView];
706
1083
  [super unmountChildComponentView:childComponentView index:index];
707
1084
  }
@@ -764,12 +1141,11 @@ namespace react = facebook::react;
764
1141
  [self setSheetExpandsWhenScrolledToEdge:newScreenProps.sheetExpandsWhenScrolledToEdge];
765
1142
 
766
1143
  if (newScreenProps.sheetAllowedDetents != oldScreenProps.sheetAllowedDetents) {
767
- [self setSheetAllowedDetents:[RNSConvert RNSScreenDetentTypeFromAllowedDetents:newScreenProps.sheetAllowedDetents]];
1144
+ [self setSheetAllowedDetents:[RNSConvert detentFractionsArrayFromVector:newScreenProps.sheetAllowedDetents]];
768
1145
  }
769
1146
 
770
1147
  if (newScreenProps.sheetLargestUndimmedDetent != oldScreenProps.sheetLargestUndimmedDetent) {
771
- [self setSheetLargestUndimmedDetent:
772
- [RNSConvert RNSScreenDetentTypeFromLargestUndimmedDetent:newScreenProps.sheetLargestUndimmedDetent]];
1148
+ [self setSheetLargestUndimmedDetent:[NSNumber numberWithInt:newScreenProps.sheetLargestUndimmedDetent]];
773
1149
  }
774
1150
  #endif // !TARGET_OS_TV
775
1151
 
@@ -816,7 +1192,7 @@ namespace react = facebook::react;
816
1192
  {
817
1193
  [super finalizeUpdates:updateMask];
818
1194
  #if !TARGET_OS_TV && !TARGET_OS_VISION
819
- [self updatePresentationStyle];
1195
+ [self updateFormSheetPresentationStyle];
820
1196
  #endif // !TARGET_OS_TV
821
1197
  }
822
1198
 
@@ -827,7 +1203,9 @@ namespace react = facebook::react;
827
1203
  {
828
1204
  [super didSetProps:changedProps];
829
1205
  #if !TARGET_OS_TV && !TARGET_OS_VISION
830
- [self updatePresentationStyle];
1206
+ if (self.stackPresentation == RNSScreenStackPresentationFormSheet) {
1207
+ [self updateFormSheetPresentationStyle];
1208
+ }
831
1209
  #endif // !TARGET_OS_TV
832
1210
  }
833
1211
 
@@ -855,6 +1233,7 @@ namespace react = facebook::react;
855
1233
  - (void)invalidate
856
1234
  {
857
1235
  _controller = nil;
1236
+ [_sheetsScrollView removeObserver:self forKeyPath:@"bounds" context:nil];
858
1237
  }
859
1238
  #endif
860
1239
 
@@ -1465,6 +1844,7 @@ RCT_EXPORT_VIEW_PROPERTY(onTransitionProgress, RCTDirectEventBlock);
1465
1844
  RCT_EXPORT_VIEW_PROPERTY(onWillAppear, RCTDirectEventBlock);
1466
1845
  RCT_EXPORT_VIEW_PROPERTY(onWillDisappear, RCTDirectEventBlock);
1467
1846
  RCT_EXPORT_VIEW_PROPERTY(onGestureCancel, RCTDirectEventBlock);
1847
+ RCT_EXPORT_VIEW_PROPERTY(onSheetDetentChanged, RCTDirectEventBlock);
1468
1848
 
1469
1849
  #if !TARGET_OS_TV
1470
1850
  RCT_EXPORT_VIEW_PROPERTY(screenOrientation, UIInterfaceOrientationMask)
@@ -1473,8 +1853,8 @@ RCT_EXPORT_VIEW_PROPERTY(statusBarHidden, BOOL)
1473
1853
  RCT_EXPORT_VIEW_PROPERTY(statusBarStyle, RNSStatusBarStyle)
1474
1854
  RCT_EXPORT_VIEW_PROPERTY(homeIndicatorHidden, BOOL)
1475
1855
 
1476
- RCT_EXPORT_VIEW_PROPERTY(sheetAllowedDetents, RNSScreenDetentType);
1477
- RCT_EXPORT_VIEW_PROPERTY(sheetLargestUndimmedDetent, RNSScreenDetentType);
1856
+ RCT_EXPORT_VIEW_PROPERTY(sheetAllowedDetents, NSArray<NSNumber *> *);
1857
+ RCT_EXPORT_VIEW_PROPERTY(sheetLargestUndimmedDetent, NSNumber *);
1478
1858
  RCT_EXPORT_VIEW_PROPERTY(sheetGrabberVisible, BOOL);
1479
1859
  RCT_EXPORT_VIEW_PROPERTY(sheetCornerRadius, CGFloat);
1480
1860
  RCT_EXPORT_VIEW_PROPERTY(sheetExpandsWhenScrolledToEdge, BOOL);
@@ -1629,3 +2009,7 @@ RCT_ENUM_CONVERTER(
1629
2009
  #endif
1630
2010
 
1631
2011
  @end
2012
+
2013
+ // So that the define-macro is not leaked out of this file.
2014
+ // This one is defined in very top of the file depending on RN architecture.
2015
+ #undef ReactScrollViewBase