@lodev09/react-native-true-sheet 4.0.0-beta.6 → 4.0.0-beta.8

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 (52) hide show
  1. package/README.md +1 -0
  2. package/android/build.gradle +22 -1
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +3 -25
  8. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
  9. package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -0
  10. package/android/src/main/jni/TrueSheetSpec.h +1 -0
  11. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
  12. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
  13. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
  14. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
  15. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
  16. package/ios/TrueSheetContentView.h +3 -1
  17. package/ios/TrueSheetContentView.mm +11 -0
  18. package/ios/TrueSheetModule.h +2 -7
  19. package/ios/TrueSheetModule.mm +7 -16
  20. package/ios/TrueSheetOverlayView.h +32 -0
  21. package/ios/TrueSheetOverlayView.mm +161 -0
  22. package/ios/TrueSheetView.mm +76 -5
  23. package/ios/TrueSheetViewController.mm +27 -13
  24. package/ios/core/TrueSheetOverlayContainerView.h +31 -0
  25. package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
  26. package/ios/tvos/TrueSheetStubs.mm +12 -0
  27. package/lib/module/TrueSheetOverlay.js +30 -0
  28. package/lib/module/TrueSheetOverlay.js.map +1 -0
  29. package/lib/module/TrueSheetOverlay.web.js +35 -0
  30. package/lib/module/TrueSheetOverlay.web.js.map +1 -0
  31. package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  32. package/lib/module/index.js +1 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/mocks/index.js +10 -0
  35. package/lib/module/mocks/index.js.map +1 -1
  36. package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
  37. package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
  38. package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
  39. package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
  40. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
  41. package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
  42. package/lib/typescript/src/index.d.ts +1 -0
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/mocks/index.d.ts +4 -0
  45. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  46. package/package.json +9 -1
  47. package/react-native.config.js +1 -0
  48. package/src/TrueSheetOverlay.tsx +24 -0
  49. package/src/TrueSheetOverlay.web.tsx +33 -0
  50. package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
  51. package/src/index.ts +1 -0
  52. package/src/mocks/index.ts +7 -0
@@ -27,16 +27,11 @@ NS_ASSUME_NONNULL_BEGIN
27
27
 
28
28
  /**
29
29
  * Register a sheet component view with its React tag
30
- * Called automatically by TrueSheetView during initialization
30
+ * Called automatically by TrueSheetView when it attaches to a window.
31
+ * The registry holds views weakly, so no unregistration is needed.
31
32
  */
32
33
  + (void)registerView:(TrueSheetView *)view withTag:(NSNumber *)tag;
33
34
 
34
- /**
35
- * Unregister a sheet component view
36
- * Called automatically by TrueSheetView during dealloc
37
- */
38
- + (void)unregisterViewWithTag:(NSNumber *)tag;
39
-
40
35
  @end
41
36
 
42
37
  NS_ASSUME_NONNULL_END
@@ -18,8 +18,9 @@
18
18
 
19
19
  #import <TrueSheetSpec/TrueSheetSpec.h>
20
20
 
21
- // Static registry to store view references by tag
22
- static NSMutableDictionary<NSNumber *, TrueSheetView *> *viewRegistry;
21
+ // Static registry of live hosts by tag — weak so an unmounted host deallocates
22
+ // (RN zeroes the tag before teardown, so hosts can't unregister themselves)
23
+ static NSMapTable<NSNumber *, TrueSheetView *> *viewRegistry;
23
24
 
24
25
  @interface TrueSheetModule () <NativeTrueSheetModuleSpec>
25
26
  @end
@@ -30,7 +31,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
30
31
 
31
32
  + (void)initialize {
32
33
  if (self == [TrueSheetModule class]) {
33
- viewRegistry = [NSMutableDictionary new];
34
+ viewRegistry = [NSMapTable strongToWeakObjectsMapTable];
34
35
  }
35
36
  }
36
37
 
@@ -151,7 +152,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
151
152
  // Find the root presented sheet (one without a parent TrueSheet)
152
153
  TrueSheetView *rootSheet = nil;
153
154
 
154
- for (TrueSheetView *view in viewRegistry.allValues) {
155
+ for (TrueSheetView *view in viewRegistry.objectEnumerator) {
155
156
  if (!view.viewController.isPresented) {
156
157
  continue;
157
158
  }
@@ -191,7 +192,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
191
192
  }
192
193
 
193
194
  @synchronized(viewRegistry) {
194
- return viewRegistry[reactTag];
195
+ return [viewRegistry objectForKey:reactTag];
195
196
  }
196
197
  }
197
198
 
@@ -201,17 +202,7 @@ RCT_EXPORT_MODULE(TrueSheetModule)
201
202
  }
202
203
 
203
204
  @synchronized(viewRegistry) {
204
- viewRegistry[tag] = view;
205
- }
206
- }
207
-
208
- + (void)unregisterViewWithTag:(NSNumber *)tag {
209
- if (!tag) {
210
- return;
211
- }
212
-
213
- @synchronized(viewRegistry) {
214
- [viewRegistry removeObjectForKey:tag];
205
+ [viewRegistry setObject:view forKey:tag];
215
206
  }
216
207
  }
217
208
 
@@ -0,0 +1,32 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import <React/RCTViewComponentView.h>
12
+ #import <UIKit/UIKit.h>
13
+
14
+ NS_ASSUME_NONNULL_BEGIN
15
+
16
+ /**
17
+ * Hidden host that renders its children in a container attached directly to
18
+ * the window, above every presented sheet.
19
+ */
20
+ @interface TrueSheetOverlayView : RCTViewComponentView
21
+
22
+ /**
23
+ * Re-stacks every overlay container above the window's other subviews.
24
+ * Called when a sheet presents, since presenting adds its transition view on top.
25
+ */
26
+ + (void)bringOverlaysToFrontInWindow:(nullable UIWindow *)window;
27
+
28
+ @end
29
+
30
+ NS_ASSUME_NONNULL_END
31
+
32
+ #endif
@@ -0,0 +1,161 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import "TrueSheetOverlayView.h"
12
+ #import "core/TrueSheetOverlayContainerView.h"
13
+
14
+ #import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
15
+ #import <react/renderer/components/TrueSheetSpec/Props.h>
16
+ #import <react/renderer/components/TrueSheetSpec/RCTComponentViewHelpers.h>
17
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h>
18
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h>
19
+
20
+ #import <React/RCTSurfaceTouchHandler.h>
21
+
22
+ using namespace facebook::react;
23
+
24
+ static NSHashTable<TrueSheetOverlayView *> *gOverlayViews;
25
+
26
+ @interface TrueSheetOverlayView () <TrueSheetOverlayContainerViewDelegate>
27
+ @end
28
+
29
+ @implementation TrueSheetOverlayView {
30
+ TrueSheetOverlayContainerView *_containerView;
31
+ // Owns `_containerView` so it has a view controller in its responder chain. The
32
+ // container is a sibling of `rootViewController.view`, and a window's responder chain
33
+ // skips its root view controller — without an owner, `-[UIView reactViewController]`
34
+ // finds nothing and content that installs a view controller cannot attach.
35
+ UIViewController *_containerController;
36
+ RCTSurfaceTouchHandler *_touchHandler;
37
+ TrueSheetOverlayViewShadowNode::ConcreteState::Shared _state;
38
+ CGSize _lastStateSize;
39
+ }
40
+
41
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
42
+ return concreteComponentDescriptorProvider<TrueSheetOverlayViewComponentDescriptor>();
43
+ }
44
+
45
+ + (void)bringOverlaysToFrontInWindow:(nullable UIWindow *)window {
46
+ if (!window)
47
+ return;
48
+
49
+ for (TrueSheetOverlayView *overlay in gOverlayViews) {
50
+ if (overlay->_containerView.superview == window) {
51
+ [window bringSubviewToFront:overlay->_containerView];
52
+ }
53
+ }
54
+ }
55
+
56
+ - (instancetype)initWithFrame:(CGRect)frame {
57
+ if (self = [super initWithFrame:frame]) {
58
+ static const auto defaultProps = std::make_shared<const TrueSheetOverlayViewProps>();
59
+ _props = defaultProps;
60
+
61
+ _containerView = [[TrueSheetOverlayContainerView alloc] initWithFrame:CGRectZero];
62
+ _containerView.delegate = self;
63
+
64
+ // Assigning the container as its view is what puts the controller in the responder
65
+ // chain; the containment below is for lifecycle only.
66
+ _containerController = [UIViewController new];
67
+ _containerController.view = _containerView;
68
+
69
+ _touchHandler = [[RCTSurfaceTouchHandler alloc] init];
70
+ [_touchHandler attachToView:_containerView];
71
+
72
+ _lastStateSize = CGSizeZero;
73
+
74
+ // The host stays in the React tree for layout only — children render in the container
75
+ self.hidden = YES;
76
+
77
+ if (!gOverlayViews) {
78
+ gOverlayViews = [NSHashTable weakObjectsHashTable];
79
+ }
80
+ [gOverlayViews addObject:self];
81
+ }
82
+ return self;
83
+ }
84
+
85
+ - (void)didMoveToWindow {
86
+ [super didMoveToWindow];
87
+
88
+ UIWindow *window = self.window;
89
+ if (window) {
90
+ _containerView.frame = window.bounds;
91
+ [window addSubview:_containerView];
92
+
93
+ UIViewController *root = window.rootViewController;
94
+ if (root != nil && _containerController.parentViewController != root) {
95
+ [root addChildViewController:_containerController];
96
+ [_containerController didMoveToParentViewController:root];
97
+ }
98
+ } else {
99
+ [_containerController willMoveToParentViewController:nil];
100
+ [_containerView removeFromSuperview];
101
+ [_containerController removeFromParentViewController];
102
+ }
103
+ }
104
+
105
+ #pragma mark - State
106
+
107
+ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
108
+ _state = std::static_pointer_cast<TrueSheetOverlayViewShadowNode::ConcreteState const>(state);
109
+
110
+ if (self.window) {
111
+ [self updateStateWithSize:self.window.bounds.size];
112
+ }
113
+ }
114
+
115
+ - (void)overlayContainerViewDidLayout:(CGSize)size {
116
+ [self updateStateWithSize:size];
117
+ }
118
+
119
+ - (void)updateStateWithSize:(CGSize)size {
120
+ if (!_state)
121
+ return;
122
+
123
+ if (fabs(size.width - _lastStateSize.width) < 0.5 && fabs(size.height - _lastStateSize.height) < 0.5)
124
+ return;
125
+
126
+ _lastStateSize = size;
127
+
128
+ auto stateData = _state->getData();
129
+ stateData.containerWidth = static_cast<float>(size.width);
130
+ stateData.containerHeight = static_cast<float>(size.height);
131
+ _state->updateState(std::move(stateData), EventQueue::UpdateMode::unstable_Immediate);
132
+ }
133
+
134
+ #pragma mark - Child Component Mounting
135
+
136
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
137
+ [_containerView insertSubview:childComponentView atIndex:index];
138
+ }
139
+
140
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
141
+ [childComponentView removeFromSuperview];
142
+ }
143
+
144
+ - (void)prepareForRecycle {
145
+ [super prepareForRecycle];
146
+
147
+ [_containerController willMoveToParentViewController:nil];
148
+ [_containerView removeFromSuperview];
149
+ [_containerController removeFromParentViewController];
150
+ _lastStateSize = CGSizeZero;
151
+ _state.reset();
152
+ self.hidden = YES;
153
+ }
154
+
155
+ @end
156
+
157
+ Class<RCTComponentViewProtocol> TrueSheetOverlayViewCls(void) {
158
+ return TrueSheetOverlayView.class;
159
+ }
160
+
161
+ #endif
@@ -13,6 +13,7 @@
13
13
  #import "TrueSheetContentView.h"
14
14
  #import "TrueSheetFooterView.h"
15
15
  #import "TrueSheetModule.h"
16
+ #import "TrueSheetOverlayView.h"
16
17
  #import "TrueSheetViewController.h"
17
18
  #import "core/RNScreensEventObserver.h"
18
19
  #import "events/TrueSheetDragEvents.h"
@@ -124,13 +125,38 @@ using namespace facebook::react;
124
125
 
125
126
  UIViewController *vc = [self findPresentingViewController];
126
127
 
127
- // Only present if the view controller is in the same window and not being dismissed
128
- if (!vc || vc.view.window != self.window || _controller.isBeingDismissed) {
128
+ // Only present if the view controller is in the same window
129
+ if (!vc || vc.view.window != self.window) {
129
130
  // Animate next time when sheet finally moves to the correct window
130
131
  _initialDetentAnimated = YES;
131
132
  return;
132
133
  }
133
134
 
135
+ // A sheet still animating out of the presenter (e.g. our own controller,
136
+ // recycled mid-dismissal) blocks presentation — retry once its transition ends.
137
+ UIViewController *dismissing = vc.presentedViewController;
138
+ if (dismissing && dismissing.isBeingDismissed) {
139
+ _initialDetentAnimated = YES;
140
+
141
+ __weak __typeof(self) weakSelf = self;
142
+ void (^retry)(void) = ^{
143
+ dispatch_async(dispatch_get_main_queue(), ^{
144
+ [weakSelf presentInitialIfNeeded];
145
+ });
146
+ };
147
+
148
+ id<UIViewControllerTransitionCoordinator> coordinator = dismissing.transitionCoordinator;
149
+ if (coordinator) {
150
+ [coordinator animateAlongsideTransition:nil
151
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
152
+ retry();
153
+ }];
154
+ } else {
155
+ retry();
156
+ }
157
+ return;
158
+ }
159
+
134
160
  _didInitiallyPresent = YES;
135
161
 
136
162
  // Deferred a runloop turn so sibling mounts from the current transaction
@@ -175,8 +201,6 @@ using namespace facebook::react;
175
201
 
176
202
  [_snapshotView removeFromSuperview];
177
203
  _snapshotView = nil;
178
-
179
- [TrueSheetModule unregisterViewWithTag:@(self.tag)];
180
204
  }
181
205
 
182
206
  #pragma mark - RCTComponentViewProtocol
@@ -382,7 +406,38 @@ using namespace facebook::react;
382
406
  - (void)prepareForRecycle {
383
407
  [super prepareForRecycle];
384
408
 
385
- [TrueSheetModule unregisterViewWithTag:@(self.tag)];
409
+ // Pooled mid-dismissal: the next mount can reuse this host — and its
410
+ // controller — while the previous sheet is still animating out. Detach until
411
+ // that transition ends so its tail (didBlur, didDismiss, position) can't reach
412
+ // the new mount, then hand the controller back and let auto-present retry.
413
+ if (_controller.isBeingDismissed) {
414
+ _controller.delegate = nil;
415
+
416
+ __weak __typeof(self) weakSelf = self;
417
+ void (^reattach)(void) = ^{
418
+ dispatch_async(dispatch_get_main_queue(), ^{
419
+ __typeof(self) strongSelf = weakSelf;
420
+ if (!strongSelf)
421
+ return;
422
+
423
+ strongSelf->_controller.delegate = strongSelf;
424
+ strongSelf->_controller.activeDetentIndex = -1;
425
+ if (strongSelf.window) {
426
+ [strongSelf presentInitialIfNeeded];
427
+ }
428
+ });
429
+ };
430
+
431
+ id<UIViewControllerTransitionCoordinator> coordinator = _controller.transitionCoordinator;
432
+ if (coordinator) {
433
+ [coordinator animateAlongsideTransition:nil
434
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
435
+ reattach();
436
+ }];
437
+ } else {
438
+ reattach();
439
+ }
440
+ }
386
441
 
387
442
  _lastStateSize = CGSizeZero;
388
443
  _didInitiallyPresent = NO;
@@ -525,13 +580,29 @@ using namespace facebook::react;
525
580
  [_screensEventObserver capturePresenterScreenFromView:self];
526
581
  [_screensEventObserver startObservingWithState:_state.get()->getData()];
527
582
 
583
+ UIWindow *window = presentingViewController.view.window;
528
584
  [presentingViewController presentViewController:_controller
529
585
  animated:animated
530
586
  completion:^{
587
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
531
588
  if (completion) {
532
589
  completion(YES, nil);
533
590
  }
534
591
  }];
592
+
593
+ // Presenting adds the sheet's transition view on top of the window once the
594
+ // transition starts — re-front overlays alongside it so the first frame is
595
+ // already ordered (and again on completion in case the transition was queued)
596
+ id<UIViewControllerTransitionCoordinator> coordinator = _controller.transitionCoordinator;
597
+ if (coordinator) {
598
+ [coordinator
599
+ animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
600
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
601
+ }
602
+ completion:nil];
603
+ } else {
604
+ [TrueSheetOverlayView bringOverlaysToFrontInWindow:window];
605
+ }
535
606
  }
536
607
 
537
608
  - (void)resizeToIndex:(NSInteger)index completion:(nullable TrueSheetCompletionBlock)completion {
@@ -207,7 +207,10 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
207
207
  }
208
208
 
209
209
  - (CGFloat)screenHeight {
210
- UIWindow *window = self.view.window;
210
+ // In a windowed iPad app (Stage Manager, iPadOS 26 windowing) the window can be shorter than
211
+ // UIScreen.mainScreen, so prefer the window's height. Some callers run before this controller's
212
+ // view is attached (e.g. mid-presentation detent resolution), so fall back to the presenter's.
213
+ UIWindow *window = self.view.window ?: self.presentingViewController.view.window;
211
214
  return window ? window.bounds.size.height : UIScreen.mainScreen.bounds.size.height;
212
215
  }
213
216
 
@@ -671,6 +674,22 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
671
674
  }
672
675
  }
673
676
 
677
+ // Rotation or an iPad window resize changes the height fractional detents
678
+ // resolve against — re-resolve alongside the transition so the sheet lands on
679
+ // the new height instead of waiting for a content-driven rebuild.
680
+ - (void)viewWillTransitionToSize:(CGSize)size
681
+ withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
682
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
683
+
684
+ if (@available(iOS 16.0, *)) {
685
+ [coordinator
686
+ animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
687
+ [self.sheet invalidateDetents];
688
+ }
689
+ completion:nil];
690
+ }
691
+ }
692
+
674
693
  // Fires when UIKit resolves the sheet's float/anchored placement (present,
675
694
  // detent resize crossing the float threshold, rotation). The footer's
676
695
  // absorbed inset follows the observed safe area — repush it right away so
@@ -1198,8 +1217,13 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1198
1217
 
1199
1218
  if (@available(iOS 16.0, *)) {
1200
1219
  NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
1201
- CGFloat sheetHeight = value * self.screenHeight;
1202
- return [self customDetentWithIdentifier:detentId height:sheetHeight atIndex:index];
1220
+ // Resolve lazily detents are built before presentation (no window yet), and
1221
+ // UIKit re-resolves on container size changes (rotation, windowed iPad resize).
1222
+ return [self customDetentWithIdentifier:detentId
1223
+ atIndex:index
1224
+ heightBlock:^CGFloat {
1225
+ return value * self.screenHeight;
1226
+ }];
1203
1227
  } else if (value >= 0.5) {
1204
1228
  return [UISheetPresentationControllerDetent largeDetent];
1205
1229
  } else {
@@ -1207,16 +1231,6 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1207
1231
  }
1208
1232
  }
1209
1233
 
1210
- - (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
1211
- height:(CGFloat)height
1212
- atIndex:(NSInteger)index API_AVAILABLE(ios(16.0)) {
1213
- return [self customDetentWithIdentifier:identifier
1214
- atIndex:index
1215
- heightBlock:^CGFloat {
1216
- return height;
1217
- }];
1218
- }
1219
-
1220
1234
  - (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
1221
1235
  atIndex:(NSInteger)index
1222
1236
  heightBlock:(CGFloat (^)(void))heightBlock
@@ -0,0 +1,31 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import <UIKit/UIKit.h>
12
+
13
+ NS_ASSUME_NONNULL_BEGIN
14
+
15
+ @protocol TrueSheetOverlayContainerViewDelegate <NSObject>
16
+ - (void)overlayContainerViewDidLayout:(CGSize)size;
17
+ @end
18
+
19
+ /**
20
+ * Window-level view hosting an overlay's children.
21
+ * Touches that miss every child fall through to the views beneath.
22
+ */
23
+ @interface TrueSheetOverlayContainerView : UIView
24
+
25
+ @property (nonatomic, weak, nullable) id<TrueSheetOverlayContainerViewDelegate> delegate;
26
+
27
+ @end
28
+
29
+ NS_ASSUME_NONNULL_END
30
+
31
+ #endif
@@ -0,0 +1,35 @@
1
+ //
2
+ // Created by Jovanni Lo (@lodev09)
3
+ // Copyright (c) 2024-present. All rights reserved.
4
+ //
5
+ // This source code is licensed under the MIT license found in the
6
+ // LICENSE file in the root directory of this source tree.
7
+ //
8
+
9
+ #ifdef RCT_NEW_ARCH_ENABLED
10
+
11
+ #import "TrueSheetOverlayContainerView.h"
12
+
13
+ @implementation TrueSheetOverlayContainerView
14
+
15
+ - (instancetype)initWithFrame:(CGRect)frame {
16
+ if (self = [super initWithFrame:frame]) {
17
+ self.backgroundColor = UIColor.clearColor;
18
+ self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
19
+ }
20
+ return self;
21
+ }
22
+
23
+ - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event {
24
+ UIView *hit = [super hitTest:point withEvent:event];
25
+ return hit == self ? nil : hit;
26
+ }
27
+
28
+ - (void)layoutSubviews {
29
+ [super layoutSubviews];
30
+ [self.delegate overlayContainerViewDidLayout:self.bounds.size];
31
+ }
32
+
33
+ @end
34
+
35
+ #endif
@@ -20,6 +20,7 @@
20
20
  #import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
21
21
  #import <react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h>
22
22
  #import <react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h>
23
+ #import <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h>
23
24
  #import <react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h>
24
25
 
25
26
  #import <TrueSheetSpec/TrueSheetSpec.h>
@@ -94,6 +95,17 @@ using namespace facebook::react;
94
95
 
95
96
  @end
96
97
 
98
+ @interface TrueSheetOverlayView : RCTViewComponentView
99
+ @end
100
+
101
+ @implementation TrueSheetOverlayView
102
+
103
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
104
+ return concreteComponentDescriptorProvider<TrueSheetOverlayViewComponentDescriptor>();
105
+ }
106
+
107
+ @end
108
+
97
109
  #pragma mark - Module
98
110
 
99
111
  @interface TrueSheetModule : NSObject <RCTBridgeModule, NativeTrueSheetModuleSpec>
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ import { StyleSheet } from 'react-native';
4
+ import TrueSheetOverlayViewNativeComponent from './fabric/TrueSheetOverlayViewNativeComponent';
5
+
6
+ /**
7
+ * Renders its children in a native layer above every presented sheet — for
8
+ * toasts, dialogs, and other content a sheet must not cover.
9
+ * Fills the window; touches that miss its children pass through to the views beneath.
10
+ */
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ export const TrueSheetOverlay = ({
13
+ style,
14
+ ...rest
15
+ }) => /*#__PURE__*/_jsx(TrueSheetOverlayViewNativeComponent, {
16
+ ...rest,
17
+ style: [styles.overlay, style]
18
+ });
19
+ const styles = StyleSheet.create({
20
+ // Sized to the window from native — absolute so it takes no layout space.
21
+ // Children render in a native container, so the host itself must never
22
+ // catch touches (Android forces it visible on layout).
23
+ overlay: {
24
+ position: 'absolute',
25
+ top: 0,
26
+ left: 0,
27
+ pointerEvents: 'none'
28
+ }
29
+ });
30
+ //# sourceMappingURL=TrueSheetOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["StyleSheet","TrueSheetOverlayViewNativeComponent","jsx","_jsx","TrueSheetOverlay","style","rest","styles","overlay","create","position","top","left","pointerEvents"],"sourceRoot":"../../src","sources":["TrueSheetOverlay.tsx"],"mappings":";;AAAA,SAASA,UAAU,QAAwB,cAAc;AAEzD,OAAOC,mCAAmC,MAAM,8CAA8C;;AAE9F;AACA;AACA;AACA;AACA;AAJA,SAAAC,GAAA,IAAAC,IAAA;AAKA,OAAO,MAAMC,gBAAgB,GAAGA,CAAC;EAAEC,KAAK;EAAE,GAAGC;AAAgB,CAAC,kBAC5DH,IAAA,CAACF,mCAAmC;EAAA,GAAKK,IAAI;EAAED,KAAK,EAAE,CAACE,MAAM,CAACC,OAAO,EAAEH,KAAK;AAAE,CAAE,CACjF;AAED,MAAME,MAAM,GAAGP,UAAU,CAACS,MAAM,CAAC;EAC/B;EACA;EACA;EACAD,OAAO,EAAE;IACPE,QAAQ,EAAE,UAAU;IACpBC,GAAG,EAAE,CAAC;IACNC,IAAI,EAAE,CAAC;IACPC,aAAa,EAAE;EACjB;AACF,CAAC,CAAC","ignoreList":[]}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ /// <reference lib="dom" />
4
+ import { useLayoutEffect, useState } from 'react';
5
+ import { createPortal } from 'react-dom';
6
+ import { StyleSheet, View } from 'react-native';
7
+
8
+ /**
9
+ * Renders its children in a layer above every presented sheet — for
10
+ * toasts, dialogs, and other content a sheet must not cover.
11
+ * Fills the window; touches that miss its children pass through to the views beneath.
12
+ */
13
+ import { jsx as _jsx } from "react/jsx-runtime";
14
+ export const TrueSheetOverlay = ({
15
+ style,
16
+ ...rest
17
+ }) => {
18
+ // Portal target appended to the body so it stacks above the sheets' portal
19
+ const [container] = useState(() => typeof document !== 'undefined' ? document.createElement('div') : null);
20
+ useLayoutEffect(() => {
21
+ if (!container) return;
22
+ container.style.cssText = 'position:fixed;inset:0;pointer-events:none';
23
+ document.body.appendChild(container);
24
+ return () => {
25
+ container.remove();
26
+ };
27
+ }, [container]);
28
+ if (!container) return null;
29
+ return /*#__PURE__*/createPortal(/*#__PURE__*/_jsx(View, {
30
+ pointerEvents: "box-none",
31
+ ...rest,
32
+ style: [StyleSheet.absoluteFill, style]
33
+ }), container);
34
+ };
35
+ //# sourceMappingURL=TrueSheetOverlay.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useLayoutEffect","useState","createPortal","StyleSheet","View","jsx","_jsx","TrueSheetOverlay","style","rest","container","document","createElement","cssText","body","appendChild","remove","pointerEvents","absoluteFill"],"sourceRoot":"../../src","sources":["TrueSheetOverlay.web.tsx"],"mappings":";;AAAA;AACA,SAASA,eAAe,EAAEC,QAAQ,QAAQ,OAAO;AACjD,SAASC,YAAY,QAAQ,WAAW;AACxC,SAASC,UAAU,EAAEC,IAAI,QAAwB,cAAc;;AAE/D;AACA;AACA;AACA;AACA;AAJA,SAAAC,GAAA,IAAAC,IAAA;AAKA,OAAO,MAAMC,gBAAgB,GAAGA,CAAC;EAAEC,KAAK;EAAE,GAAGC;AAAgB,CAAC,KAAK;EACjE;EACA,MAAM,CAACC,SAAS,CAAC,GAAGT,QAAQ,CAAC,MAC3B,OAAOU,QAAQ,KAAK,WAAW,GAAGA,QAAQ,CAACC,aAAa,CAAC,KAAK,CAAC,GAAG,IACpE,CAAC;EAEDZ,eAAe,CAAC,MAAM;IACpB,IAAI,CAACU,SAAS,EAAE;IAEhBA,SAAS,CAACF,KAAK,CAACK,OAAO,GAAG,4CAA4C;IACtEF,QAAQ,CAACG,IAAI,CAACC,WAAW,CAACL,SAAS,CAAC;IACpC,OAAO,MAAM;MACXA,SAAS,CAACM,MAAM,CAAC,CAAC;IACpB,CAAC;EACH,CAAC,EAAE,CAACN,SAAS,CAAC,CAAC;EAEf,IAAI,CAACA,SAAS,EAAE,OAAO,IAAI;EAE3B,oBAAOR,YAAY,cACjBI,IAAA,CAACF,IAAI;IAACa,aAAa,EAAC,UAAU;IAAA,GAAKR,IAAI;IAAED,KAAK,EAAE,CAACL,UAAU,CAACe,YAAY,EAAEV,KAAK;EAAE,CAAE,CAAC,EACpFE,SACF,CAAC;AACH,CAAC","ignoreList":[]}
@@ -0,0 +1,12 @@
1
+ import type { ViewProps } from 'react-native';
2
+ import { codegenNativeComponent } from 'react-native';
3
+
4
+ export interface NativeProps extends ViewProps {
5
+ // No props needed - sized to the window from native
6
+ }
7
+
8
+ // interfaceOnly: shadow node/state/descriptor are custom (common/cpp) so the
9
+ // overlay is sized to the window
10
+ export default codegenNativeComponent<NativeProps>('TrueSheetOverlayView', {
11
+ interfaceOnly: true,
12
+ });
@@ -3,5 +3,6 @@
3
3
  export * from './TrueSheet';
4
4
  export * from "./TrueSheet.types.js";
5
5
  export { TrueSheetPeek } from './TrueSheetPeek';
6
+ export { TrueSheetOverlay } from './TrueSheetOverlay';
6
7
  export { TrueSheetProvider, useTrueSheet } from './TrueSheetProvider';
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["TrueSheetPeek","TrueSheetProvider","useTrueSheet"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,cAAc,aAAa;AAC3B,cAAc,sBAAmB;AACjC,SAASA,aAAa,QAAQ,iBAAiB;AAC/C,SAASC,iBAAiB,EAAEC,YAAY,QAAQ,qBAAqB","ignoreList":[]}
1
+ {"version":3,"names":["TrueSheetPeek","TrueSheetOverlay","TrueSheetProvider","useTrueSheet"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,cAAc,aAAa;AAC3B,cAAc,sBAAmB;AACjC,SAASA,aAAa,QAAQ,iBAAiB;AAC/C,SAASC,gBAAgB,QAAQ,oBAAoB;AACrD,SAASC,iBAAiB,EAAEC,YAAY,QAAQ,qBAAqB","ignoreList":[]}
@@ -68,6 +68,16 @@ export function TrueSheetPeek({
68
68
  return /*#__PURE__*/React.createElement(View, rest, children);
69
69
  }
70
70
 
71
+ /**
72
+ * Mock TrueSheetOverlay component for testing.
73
+ */
74
+ export function TrueSheetOverlay({
75
+ children,
76
+ ...rest
77
+ }) {
78
+ return /*#__PURE__*/React.createElement(View, rest, children);
79
+ }
80
+
71
81
  /**
72
82
  * Mock TrueSheetProvider for testing.
73
83
  */