@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.
- package/README.md +1 -0
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +3 -25
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -0
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +36 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
- package/ios/TrueSheetContentView.h +3 -1
- package/ios/TrueSheetContentView.mm +11 -0
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +7 -16
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetView.mm +76 -5
- package/ios/TrueSheetViewController.mm +27 -13
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +10 -0
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/package.json +9 -1
- package/react-native.config.js +1 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +7 -0
package/ios/TrueSheetModule.h
CHANGED
|
@@ -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
|
|
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
|
package/ios/TrueSheetModule.mm
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
#import <TrueSheetSpec/TrueSheetSpec.h>
|
|
20
20
|
|
|
21
|
-
// Static registry
|
|
22
|
-
|
|
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 = [
|
|
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.
|
|
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
|
|
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
|
|
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
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -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
|
|
128
|
-
if (!vc || vc.view.window != self.window
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1202
|
-
|
|
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
|
+
});
|
package/lib/module/index.js
CHANGED
|
@@ -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
|
package/lib/module/index.js.map
CHANGED
|
@@ -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
|
*/
|