react-native-navigation 8.8.5 → 8.8.6-snapshot.2571
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativenavigation/NavigationActivity.java +14 -1
- package/android/src/main/java/com/reactnativenavigation/NavigationApplication.java +3 -0
- package/android/src/main/java/com/reactnativenavigation/NavigationPackage.kt +27 -8
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRow.kt +262 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowAttacher.kt +205 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowConfigStore.kt +32 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowLayout.kt +139 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowModule.kt +37 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowOptions.kt +68 -0
- package/android/src/main/java/com/reactnativenavigation/options/BottomTabOptions.java +4 -1
- package/android/src/main/java/com/reactnativenavigation/options/NavigationBarOptions.java +19 -1
- package/android/src/main/java/com/reactnativenavigation/react/ReactView.java +13 -0
- package/android/src/main/java/com/reactnativenavigation/react/events/ComponentType.java +2 -1
- package/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt +63 -9
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabPresenter.java +28 -0
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +77 -6
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +1 -0
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java +2 -5
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java +33 -13
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java +76 -0
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/CustomBottomTabItemView.kt +73 -0
- package/android/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java +14 -0
- package/android/src/test/java/com/reactnativenavigation/utils/SystemUiUtilsTest.kt +64 -1
- package/ios/ARCHITECTURE.md +5 -0
- package/ios/BottomTabPresenter.h +7 -0
- package/ios/BottomTabPresenter.mm +27 -0
- package/ios/RNNAppDelegate.h +16 -0
- package/ios/RNNAppDelegate.mm +73 -0
- package/ios/RNNBottomTabOptions.h +2 -0
- package/ios/RNNBottomTabOptions.mm +5 -1
- package/ios/RNNBottomTabsController.h +2 -0
- package/ios/RNNBottomTabsController.mm +209 -1
- package/ios/RNNBottomTabsCustomRow.h +57 -0
- package/ios/RNNBottomTabsCustomRow.mm +252 -0
- package/ios/RNNBottomTabsCustomRowOptions.h +42 -0
- package/ios/RNNBottomTabsCustomRowOptions.mm +37 -0
- package/ios/RNNBottomTabsOptions.h +2 -0
- package/ios/RNNBottomTabsOptions.mm +2 -0
- package/ios/RNNComponentViewCreator.h +2 -1
- package/ios/RNNCustomTabBarItemView.h +26 -0
- package/ios/RNNCustomTabBarItemView.mm +83 -0
- package/ios/RNNReactRootViewCreator.mm +1 -0
- package/ios/RNNViewControllerFactory.mm +1 -0
- package/ios/ReactNativeNavigation.xcodeproj/project.pbxproj +24 -0
- package/lib/module/ARCHITECTURE.md +30 -0
- package/lib/module/Navigation.js +34 -1
- package/lib/module/Navigation.js.map +1 -1
- package/lib/module/NavigationDelegate.js +21 -0
- package/lib/module/NavigationDelegate.js.map +1 -1
- package/lib/module/adapters/AndroidCustomRowForwarder.js +75 -0
- package/lib/module/adapters/AndroidCustomRowForwarder.js.map +1 -0
- package/lib/module/commands/Commands.js +8 -0
- package/lib/module/commands/Commands.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/interfaces/Options.js.map +1 -1
- package/lib/module/linking/DeferredLinkQueue.js +52 -0
- package/lib/module/linking/DeferredLinkQueue.js.map +1 -0
- package/lib/module/linking/DeferredLinkQueue.test.js +54 -0
- package/lib/module/linking/DeferredLinkQueue.test.js.map +1 -0
- package/lib/module/linking/LinkingHandler.js +139 -0
- package/lib/module/linking/LinkingHandler.js.map +1 -0
- package/lib/module/linking/LinkingHandler.test.js +384 -0
- package/lib/module/linking/LinkingHandler.test.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.js +56 -0
- package/lib/module/linking/ModalLayoutBuilder.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js +154 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js.map +1 -0
- package/lib/module/linking/RouteMatcher.js +104 -0
- package/lib/module/linking/RouteMatcher.js.map +1 -0
- package/lib/module/linking/RouteMatcher.test.js +164 -0
- package/lib/module/linking/RouteMatcher.test.js.map +1 -0
- package/lib/module/linking/URLParser.js +56 -0
- package/lib/module/linking/URLParser.js.map +1 -0
- package/lib/module/linking/URLParser.test.js +100 -0
- package/lib/module/linking/URLParser.test.js.map +1 -0
- package/lib/module/linking/types.js +4 -0
- package/lib/module/linking/types.js.map +1 -0
- package/lib/typescript/Navigation.d.ts +22 -0
- package/lib/typescript/Navigation.d.ts.map +1 -1
- package/lib/typescript/NavigationDelegate.d.ts +13 -0
- package/lib/typescript/NavigationDelegate.d.ts.map +1 -1
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts +23 -0
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts.map +1 -0
- package/lib/typescript/commands/Commands.d.ts +1 -0
- package/lib/typescript/commands/Commands.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +90 -0
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/linking/DeferredLinkQueue.d.ts +26 -0
- package/lib/typescript/linking/DeferredLinkQueue.d.ts.map +1 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts +2 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.d.ts +71 -0
- package/lib/typescript/linking/LinkingHandler.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts +2 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts +21 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts +2 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.d.ts +23 -0
- package/lib/typescript/linking/RouteMatcher.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts +2 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.d.ts +16 -0
- package/lib/typescript/linking/URLParser.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.test.d.ts +2 -0
- package/lib/typescript/linking/URLParser.test.d.ts.map +1 -0
- package/lib/typescript/linking/types.d.ts +107 -0
- package/lib/typescript/linking/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +30 -0
- package/src/Navigation.ts +36 -1
- package/src/NavigationDelegate.ts +22 -0
- package/src/adapters/AndroidCustomRowForwarder.ts +83 -0
- package/src/commands/Commands.ts +15 -0
- package/src/index.ts +1 -0
- package/src/interfaces/Options.ts +92 -0
- package/src/linking/DeferredLinkQueue.test.ts +60 -0
- package/src/linking/DeferredLinkQueue.ts +55 -0
- package/src/linking/LinkingHandler.test.ts +332 -0
- package/src/linking/LinkingHandler.ts +169 -0
- package/src/linking/ModalLayoutBuilder.test.ts +105 -0
- package/src/linking/ModalLayoutBuilder.ts +60 -0
- package/src/linking/RouteMatcher.test.ts +128 -0
- package/src/linking/RouteMatcher.ts +126 -0
- package/src/linking/URLParser.test.ts +105 -0
- package/src/linking/URLParser.ts +62 -0
- package/src/linking/types.ts +115 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#import "RNNBottomTabsCustomRow.h"
|
|
2
|
+
|
|
3
|
+
@interface RNNBottomTabsCustomRowCell : UIControl
|
|
4
|
+
@property(nonatomic, strong, nullable) RNNCustomTabBarItemView *itemView;
|
|
5
|
+
@property(nonatomic, assign) NSUInteger index;
|
|
6
|
+
@end
|
|
7
|
+
|
|
8
|
+
@implementation RNNBottomTabsCustomRowCell
|
|
9
|
+
|
|
10
|
+
- (void)setItemView:(RNNCustomTabBarItemView *)itemView {
|
|
11
|
+
if (_itemView == itemView) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
[_itemView removeFromSuperview];
|
|
15
|
+
_itemView = itemView;
|
|
16
|
+
if (itemView) {
|
|
17
|
+
itemView.translatesAutoresizingMaskIntoConstraints = YES;
|
|
18
|
+
itemView.autoresizingMask =
|
|
19
|
+
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
20
|
+
itemView.frame = self.bounds;
|
|
21
|
+
[self addSubview:itemView];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (void)layoutSubviews {
|
|
26
|
+
[super layoutSubviews];
|
|
27
|
+
self.itemView.frame = self.bounds;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@end
|
|
31
|
+
|
|
32
|
+
@interface RNNBottomTabsCustomRow ()
|
|
33
|
+
@property(nonatomic, strong) NSMutableArray<RNNBottomTabsCustomRowCell *> *cells;
|
|
34
|
+
@property(nonatomic, strong) UIVisualEffectView *backgroundEffectView;
|
|
35
|
+
@property(nonatomic, strong) UIView *backgroundColorView;
|
|
36
|
+
@property(nonatomic, strong) RNNBottomTabsCustomRowOptions *options;
|
|
37
|
+
@end
|
|
38
|
+
|
|
39
|
+
@implementation RNNBottomTabsCustomRow
|
|
40
|
+
|
|
41
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
42
|
+
self = [super initWithFrame:frame];
|
|
43
|
+
if (self) {
|
|
44
|
+
_cells = [NSMutableArray new];
|
|
45
|
+
self.backgroundColor = UIColor.clearColor;
|
|
46
|
+
|
|
47
|
+
// Solid background layer (only made visible if user sets backgroundColor).
|
|
48
|
+
_backgroundColorView = [[UIView alloc] init];
|
|
49
|
+
_backgroundColorView.hidden = YES;
|
|
50
|
+
_backgroundColorView.clipsToBounds = YES;
|
|
51
|
+
if (@available(iOS 13.0, *)) {
|
|
52
|
+
_backgroundColorView.layer.cornerCurve = kCACornerCurveContinuous;
|
|
53
|
+
}
|
|
54
|
+
[self addSubview:_backgroundColorView];
|
|
55
|
+
|
|
56
|
+
// Visual effect (blur / glass) layer. Default depends on iOS version.
|
|
57
|
+
UIVisualEffect *effect = [RNNBottomTabsCustomRow defaultBackgroundEffect];
|
|
58
|
+
_backgroundEffectView = [[UIVisualEffectView alloc] initWithEffect:effect];
|
|
59
|
+
_backgroundEffectView.clipsToBounds = YES;
|
|
60
|
+
if (@available(iOS 13.0, *)) {
|
|
61
|
+
_backgroundEffectView.layer.cornerCurve = kCACornerCurveContinuous;
|
|
62
|
+
}
|
|
63
|
+
if (@available(iOS 26.0, *)) {
|
|
64
|
+
_backgroundEffectView.layer.cornerRadius = 28.0;
|
|
65
|
+
}
|
|
66
|
+
[self addSubview:_backgroundEffectView];
|
|
67
|
+
}
|
|
68
|
+
return self;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// `UIGlassEffect` is a new visual effect introduced in iOS 26. Reference it
|
|
72
|
+
// at runtime so this file still compiles against older SDKs and so we get a
|
|
73
|
+
// usable fallback on older OS versions.
|
|
74
|
+
+ (UIVisualEffect *)defaultBackgroundEffect {
|
|
75
|
+
Class glassClass = NSClassFromString(@"UIGlassEffect");
|
|
76
|
+
if (glassClass) {
|
|
77
|
+
UIVisualEffect *glass = [[glassClass alloc] init];
|
|
78
|
+
if (glass) {
|
|
79
|
+
return glass;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (@available(iOS 13.0, *)) {
|
|
83
|
+
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemChromeMaterial];
|
|
84
|
+
}
|
|
85
|
+
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
+ (UIVisualEffect *)blurBackgroundEffect {
|
|
89
|
+
if (@available(iOS 13.0, *)) {
|
|
90
|
+
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemChromeMaterial];
|
|
91
|
+
}
|
|
92
|
+
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)applyOptions:(RNNBottomTabsCustomRowOptions *)options {
|
|
96
|
+
self.options = options;
|
|
97
|
+
|
|
98
|
+
UIColor *solidColor =
|
|
99
|
+
options.backgroundColor.hasValue ? options.backgroundColor.get : nil;
|
|
100
|
+
NSString *effectName =
|
|
101
|
+
options.backgroundEffect.hasValue ? options.backgroundEffect.get : nil;
|
|
102
|
+
|
|
103
|
+
BOOL useSolidColor = solidColor != nil;
|
|
104
|
+
self.backgroundColorView.hidden = !useSolidColor;
|
|
105
|
+
if (useSolidColor) {
|
|
106
|
+
self.backgroundColorView.backgroundColor = solidColor;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Effect view stays unless explicitly disabled or overridden by solid color.
|
|
110
|
+
BOOL hideEffect = useSolidColor || [effectName isEqualToString:@"none"];
|
|
111
|
+
self.backgroundEffectView.hidden = hideEffect;
|
|
112
|
+
if (!hideEffect) {
|
|
113
|
+
if ([effectName isEqualToString:@"blur"]) {
|
|
114
|
+
self.backgroundEffectView.effect = [RNNBottomTabsCustomRow blurBackgroundEffect];
|
|
115
|
+
} else if ([effectName isEqualToString:@"glass"]) {
|
|
116
|
+
self.backgroundEffectView.effect = [RNNBottomTabsCustomRow defaultBackgroundEffect];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
[self setNeedsLayout];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
- (CGFloat)effectiveCornerRadius {
|
|
124
|
+
if (self.options.cornerRadius.hasValue) {
|
|
125
|
+
return [self.options.cornerRadius.get doubleValue];
|
|
126
|
+
}
|
|
127
|
+
if (@available(iOS 26.0, *)) {
|
|
128
|
+
return 28.0;
|
|
129
|
+
}
|
|
130
|
+
return 0.0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
- (CGFloat)effectiveHorizontalMargin {
|
|
134
|
+
if (self.options.horizontalMargin.hasValue) {
|
|
135
|
+
return [self.options.horizontalMargin.get doubleValue];
|
|
136
|
+
}
|
|
137
|
+
if (@available(iOS 26.0, *)) {
|
|
138
|
+
return 16.0;
|
|
139
|
+
}
|
|
140
|
+
return 0.0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
- (CGFloat)effectiveBottomMargin {
|
|
144
|
+
if (self.options.bottomMargin.hasValue) {
|
|
145
|
+
return [self.options.bottomMargin.get doubleValue];
|
|
146
|
+
}
|
|
147
|
+
return 0.0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
- (CGFloat)desiredRowHeightForNativeTabBarHeight:(CGFloat)nativeTabBarHeight
|
|
151
|
+
safeBottom:(CGFloat)safeBottom {
|
|
152
|
+
(void)safeBottom;
|
|
153
|
+
CGFloat contentHeight = nativeTabBarHeight;
|
|
154
|
+
if (@available(iOS 26.0, *)) {
|
|
155
|
+
contentHeight += 18.0; // default extra for iOS 26 floating bar look
|
|
156
|
+
}
|
|
157
|
+
if (self.options.height.hasValue) {
|
|
158
|
+
contentHeight = [self.options.height.get doubleValue];
|
|
159
|
+
}
|
|
160
|
+
// Safe area is applied by the controller when positioning the row frame.
|
|
161
|
+
return contentHeight + [self effectiveBottomMargin];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
- (void)setItemViews:(NSArray<RNNCustomTabBarItemView *> *)itemViews {
|
|
165
|
+
for (RNNBottomTabsCustomRowCell *cell in self.cells) {
|
|
166
|
+
[cell removeFromSuperview];
|
|
167
|
+
}
|
|
168
|
+
[self.cells removeAllObjects];
|
|
169
|
+
|
|
170
|
+
UIView *cellContainer = self.backgroundEffectView.contentView;
|
|
171
|
+
for (NSUInteger i = 0; i < itemViews.count; i++) {
|
|
172
|
+
RNNBottomTabsCustomRowCell *cell = [[RNNBottomTabsCustomRowCell alloc] init];
|
|
173
|
+
cell.index = i;
|
|
174
|
+
cell.itemView = itemViews[i];
|
|
175
|
+
[cell addTarget:self
|
|
176
|
+
action:@selector(handleCellTap:)
|
|
177
|
+
forControlEvents:UIControlEventTouchUpInside];
|
|
178
|
+
[cellContainer addSubview:cell];
|
|
179
|
+
[self.cells addObject:cell];
|
|
180
|
+
}
|
|
181
|
+
[self setNeedsLayout];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
- (void)setSelectedIndex:(NSUInteger)selectedIndex {
|
|
185
|
+
for (NSUInteger i = 0; i < self.cells.count; i++) {
|
|
186
|
+
RNNCustomTabBarItemView *itemView = self.cells[i].itemView;
|
|
187
|
+
[itemView setSelected:(i == selectedIndex)];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
- (void)handleCellTap:(RNNBottomTabsCustomRowCell *)cell {
|
|
192
|
+
if (self.onTapAtIndex) {
|
|
193
|
+
self.onTapAtIndex(cell.index);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
- (void)layoutSubviews {
|
|
198
|
+
[super layoutSubviews];
|
|
199
|
+
|
|
200
|
+
// Bottom safe area is already included in the row frame height via
|
|
201
|
+
// `desiredRowHeightForNativeTabBarHeight:safeBottom:` — do not inset it
|
|
202
|
+
// again here or the chrome shrinks by ~home-indicator height.
|
|
203
|
+
CGFloat bottomInset = [self effectiveBottomMargin];
|
|
204
|
+
CGRect content =
|
|
205
|
+
UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(0, 0, bottomInset, 0));
|
|
206
|
+
|
|
207
|
+
CGFloat horizontalMargin = [self effectiveHorizontalMargin];
|
|
208
|
+
if (horizontalMargin > 0) {
|
|
209
|
+
content = CGRectInset(content, horizontalMargin, 0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
CGFloat cornerRadius = [self effectiveCornerRadius];
|
|
213
|
+
self.backgroundEffectView.layer.cornerRadius = cornerRadius;
|
|
214
|
+
self.backgroundColorView.layer.cornerRadius = cornerRadius;
|
|
215
|
+
|
|
216
|
+
self.backgroundEffectView.frame = content;
|
|
217
|
+
self.backgroundColorView.frame = content;
|
|
218
|
+
|
|
219
|
+
if (self.cells.count == 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Cells are subviews of the effect view's contentView (so the rounded
|
|
224
|
+
// mask clips them); their frames are relative to the effect view's
|
|
225
|
+
// bounds. The solid color layer sits behind and doesn't host cells.
|
|
226
|
+
UIView *cellContainer = self.backgroundColorView.hidden
|
|
227
|
+
? self.backgroundEffectView.contentView
|
|
228
|
+
: self.backgroundColorView;
|
|
229
|
+
// Make sure cells live in the visible container.
|
|
230
|
+
if (self.cells.firstObject.superview != cellContainer) {
|
|
231
|
+
for (RNNBottomTabsCustomRowCell *cell in self.cells) {
|
|
232
|
+
[cell removeFromSuperview];
|
|
233
|
+
[cellContainer addSubview:cell];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
CGFloat totalWidth = content.size.width;
|
|
238
|
+
CGFloat totalHeight = content.size.height;
|
|
239
|
+
CGFloat width = totalWidth / (CGFloat)self.cells.count;
|
|
240
|
+
for (NSUInteger i = 0; i < self.cells.count; i++) {
|
|
241
|
+
CGFloat x = floor((CGFloat)i * width);
|
|
242
|
+
CGFloat nextX = floor((CGFloat)(i + 1) * width);
|
|
243
|
+
self.cells[i].frame = CGRectMake(x, 0, nextX - x, totalHeight);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
- (void)safeAreaInsetsDidChange {
|
|
248
|
+
[super safeAreaInsetsDidChange];
|
|
249
|
+
[self setNeedsLayout];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#import "RNNOptions.h"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visual options for the floating row that hosts custom-component bottom tab
|
|
5
|
+
* cells. Applies only when every tab declares `bottomTab.component`. All
|
|
6
|
+
* fields are optional; if omitted the row uses sensible defaults (iOS 26
|
|
7
|
+
* glass pill on iOS 26+, blur with no inset on older versions).
|
|
8
|
+
*
|
|
9
|
+
* The same option keys are exposed in JS as `bottomTabs.customRow` on both
|
|
10
|
+
* platforms. Android applies them via `RNNBottomTabsCustomRowModule`.
|
|
11
|
+
*/
|
|
12
|
+
@interface RNNBottomTabsCustomRowOptions : RNNOptions
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Override the row's content height. The native tab bar (and its safe-area
|
|
16
|
+
* inset) is preserved underneath — this only changes how tall the visible
|
|
17
|
+
* floating row appears. Defaults to the native tab bar content height (+18pt
|
|
18
|
+
* on iOS 26+ to match the larger native floating bar).
|
|
19
|
+
*/
|
|
20
|
+
@property(nonatomic, strong) Number *height;
|
|
21
|
+
|
|
22
|
+
/** Solid background color for the row. When set, overrides `backgroundEffect`. */
|
|
23
|
+
@property(nonatomic, strong) Color *backgroundColor;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Visual effect for the row background. Values: `glass` | `blur` | `none`.
|
|
27
|
+
* Default: `glass` on iOS 26+, `blur` on older versions.
|
|
28
|
+
*/
|
|
29
|
+
@property(nonatomic, strong) Text *backgroundEffect;
|
|
30
|
+
|
|
31
|
+
/** Corner radius of the row. Default: 28 on iOS 26+, 0 below. */
|
|
32
|
+
@property(nonatomic, strong) Number *cornerRadius;
|
|
33
|
+
|
|
34
|
+
/** Horizontal inset of the row from the screen edges. Default: 16 on iOS 26+, 0 below. */
|
|
35
|
+
@property(nonatomic, strong) Number *horizontalMargin;
|
|
36
|
+
|
|
37
|
+
/** Distance between the row's bottom edge and the safe-area bottom. Default: 0. */
|
|
38
|
+
@property(nonatomic, strong) Number *bottomMargin;
|
|
39
|
+
|
|
40
|
+
- (BOOL)hasValue;
|
|
41
|
+
|
|
42
|
+
@end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#import "RNNBottomTabsCustomRowOptions.h"
|
|
2
|
+
|
|
3
|
+
@implementation RNNBottomTabsCustomRowOptions
|
|
4
|
+
|
|
5
|
+
- (instancetype)initWithDict:(NSDictionary *)dict {
|
|
6
|
+
self = [super initWithDict:dict];
|
|
7
|
+
self.height = [NumberParser parse:dict key:@"height"];
|
|
8
|
+
self.backgroundColor = [ColorParser parse:dict key:@"backgroundColor"];
|
|
9
|
+
self.backgroundEffect = [TextParser parse:dict key:@"backgroundEffect"];
|
|
10
|
+
self.cornerRadius = [NumberParser parse:dict key:@"cornerRadius"];
|
|
11
|
+
self.horizontalMargin = [NumberParser parse:dict key:@"horizontalMargin"];
|
|
12
|
+
self.bottomMargin = [NumberParser parse:dict key:@"bottomMargin"];
|
|
13
|
+
return self;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (void)mergeOptions:(RNNBottomTabsCustomRowOptions *)options {
|
|
17
|
+
if (options.height.hasValue)
|
|
18
|
+
self.height = options.height;
|
|
19
|
+
if (options.backgroundColor.hasValue)
|
|
20
|
+
self.backgroundColor = options.backgroundColor;
|
|
21
|
+
if (options.backgroundEffect.hasValue)
|
|
22
|
+
self.backgroundEffect = options.backgroundEffect;
|
|
23
|
+
if (options.cornerRadius.hasValue)
|
|
24
|
+
self.cornerRadius = options.cornerRadius;
|
|
25
|
+
if (options.horizontalMargin.hasValue)
|
|
26
|
+
self.horizontalMargin = options.horizontalMargin;
|
|
27
|
+
if (options.bottomMargin.hasValue)
|
|
28
|
+
self.bottomMargin = options.bottomMargin;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
- (BOOL)hasValue {
|
|
32
|
+
return self.height.hasValue || self.backgroundColor.hasValue ||
|
|
33
|
+
self.backgroundEffect.hasValue || self.cornerRadius.hasValue ||
|
|
34
|
+
self.horizontalMargin.hasValue || self.bottomMargin.hasValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "BottomTabsAttachMode.h"
|
|
2
|
+
#import "RNNBottomTabsCustomRowOptions.h"
|
|
2
3
|
#import "RNNOptions.h"
|
|
3
4
|
#import "RNNShadowOptions.h"
|
|
4
5
|
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
@property(nonatomic, strong) Color *borderColor;
|
|
23
24
|
@property(nonatomic, strong) Number *borderWidth;
|
|
24
25
|
@property(nonatomic, strong) RNNShadowOptions *shadow;
|
|
26
|
+
@property(nonatomic, strong) RNNBottomTabsCustomRowOptions *customRow;
|
|
25
27
|
@property(nonatomic, strong) BottomTabsAttachMode *tabsAttachMode;
|
|
26
28
|
|
|
27
29
|
- (BOOL)shouldDrawBehind;
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
self.borderColor = [ColorParser parse:dict key:@"borderColor"];
|
|
27
27
|
self.borderWidth = [NumberParser parse:dict key:@"borderWidth"];
|
|
28
28
|
self.shadow = [[RNNShadowOptions alloc] initWithDict:dict[@"shadow"]];
|
|
29
|
+
self.customRow = [[RNNBottomTabsCustomRowOptions alloc] initWithDict:dict[@"customRow"]];
|
|
29
30
|
|
|
30
31
|
return self;
|
|
31
32
|
}
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
self.borderWidth = options.borderWidth;
|
|
70
71
|
|
|
71
72
|
[self.shadow mergeOptions:options.shadow];
|
|
73
|
+
[self.customRow mergeOptions:options.customRow];
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
- (BOOL)shouldDrawBehind {
|
|
@@ -9,7 +9,8 @@ typedef enum RNNComponentType {
|
|
|
9
9
|
RNNComponentTypeComponent,
|
|
10
10
|
RNNComponentTypeTopBarTitle,
|
|
11
11
|
RNNComponentTypeTopBarButton,
|
|
12
|
-
RNNComponentTypeTopBarBackground
|
|
12
|
+
RNNComponentTypeTopBarBackground,
|
|
13
|
+
RNNComponentTypeBottomTabItem
|
|
13
14
|
} RNNComponentType;
|
|
14
15
|
|
|
15
16
|
@protocol RNNComponentViewCreator
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#import "RNNReactView.h"
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hosts an `RNNReactView` that renders a user-supplied React component as a
|
|
6
|
+
* bottom tab item. The view is laid out on top of the underlying
|
|
7
|
+
* `UITabBarButton` and forwards touches to it so native selection,
|
|
8
|
+
* accessibility focus and `selectTabOnPress: false` keep working.
|
|
9
|
+
*
|
|
10
|
+
* The hosted component receives `componentId`, `tabIndex`, `selected` and
|
|
11
|
+
* `badge` props. Selected state is updated via `setSelected:`.
|
|
12
|
+
*/
|
|
13
|
+
@interface RNNCustomTabBarItemView : UIView
|
|
14
|
+
|
|
15
|
+
@property(nonatomic, readonly, strong) RNNReactView *reactView;
|
|
16
|
+
|
|
17
|
+
- (instancetype)initWithReactView:(RNNReactView *)reactView
|
|
18
|
+
tabIndex:(NSUInteger)tabIndex
|
|
19
|
+
selected:(BOOL)selected
|
|
20
|
+
badge:(NSString *)badge;
|
|
21
|
+
|
|
22
|
+
- (void)setSelected:(BOOL)selected;
|
|
23
|
+
|
|
24
|
+
- (void)setBadge:(NSString *)badge;
|
|
25
|
+
|
|
26
|
+
@end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#import "RNNCustomTabBarItemView.h"
|
|
2
|
+
|
|
3
|
+
@interface RNNCustomTabBarItemView ()
|
|
4
|
+
|
|
5
|
+
@property(nonatomic, readwrite, strong) RNNReactView *reactView;
|
|
6
|
+
@property(nonatomic, assign) NSUInteger tabIndex;
|
|
7
|
+
@property(nonatomic, assign) BOOL isSelected;
|
|
8
|
+
@property(nonatomic, copy) NSString *badge;
|
|
9
|
+
|
|
10
|
+
@end
|
|
11
|
+
|
|
12
|
+
@implementation RNNCustomTabBarItemView
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithReactView:(RNNReactView *)reactView
|
|
15
|
+
tabIndex:(NSUInteger)tabIndex
|
|
16
|
+
selected:(BOOL)selected
|
|
17
|
+
badge:(NSString *)badge {
|
|
18
|
+
self = [super initWithFrame:CGRectZero];
|
|
19
|
+
if (self) {
|
|
20
|
+
_reactView = reactView;
|
|
21
|
+
_tabIndex = tabIndex;
|
|
22
|
+
_isSelected = selected;
|
|
23
|
+
_badge = [badge copy];
|
|
24
|
+
|
|
25
|
+
self.backgroundColor = [UIColor clearColor];
|
|
26
|
+
self.userInteractionEnabled = NO;
|
|
27
|
+
|
|
28
|
+
_reactView.backgroundColor = [UIColor clearColor];
|
|
29
|
+
_reactView.userInteractionEnabled = NO;
|
|
30
|
+
[self addSubview:_reactView];
|
|
31
|
+
|
|
32
|
+
[self updateProps];
|
|
33
|
+
}
|
|
34
|
+
return self;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- (void)layoutSubviews {
|
|
38
|
+
[super layoutSubviews];
|
|
39
|
+
self.reactView.frame = self.bounds;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
- (CGSize)sizeThatFits:(CGSize)size {
|
|
43
|
+
return size;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
- (void)setSelected:(BOOL)selected {
|
|
47
|
+
if (self.isSelected == selected) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
self.isSelected = selected;
|
|
51
|
+
[self updateProps];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)setBadge:(NSString *)badge {
|
|
55
|
+
if (badge == _badge || [badge isEqualToString:_badge]) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
_badge = [badge copy];
|
|
59
|
+
[self updateProps];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
- (void)updateProps {
|
|
63
|
+
NSMutableDictionary *props = [NSMutableDictionary dictionary];
|
|
64
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
65
|
+
// RNNReactView's `properties` setter is a no-op on Fabric (it writes to
|
|
66
|
+
// the auto-synthesized ivar instead of the underlying surface). Update
|
|
67
|
+
// the React tree by writing directly to the surface so prop changes
|
|
68
|
+
// propagate to JS.
|
|
69
|
+
[props addEntriesFromDictionary:(self.reactView.surface.properties ?: @{})];
|
|
70
|
+
#else
|
|
71
|
+
[props addEntriesFromDictionary:(self.reactView.appProperties ?: @{})];
|
|
72
|
+
#endif
|
|
73
|
+
props[@"tabIndex"] = @(self.tabIndex);
|
|
74
|
+
props[@"selected"] = @(self.isSelected);
|
|
75
|
+
props[@"badge"] = self.badge ?: [NSNull null];
|
|
76
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
77
|
+
self.reactView.surface.properties = props;
|
|
78
|
+
#else
|
|
79
|
+
self.reactView.appProperties = props;
|
|
80
|
+
#endif
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@end
|
|
@@ -234,6 +234,7 @@
|
|
|
234
234
|
presenter:presenter
|
|
235
235
|
bottomTabPresenter:bottomTabPresenter
|
|
236
236
|
dotIndicatorPresenter:dotIndicatorPresenter
|
|
237
|
+
componentRegistry:_componentRegistry
|
|
237
238
|
eventEmitter:_eventEmitter
|
|
238
239
|
childViewControllers:childViewControllers
|
|
239
240
|
bottomTabsAttacher:bottomTabsAttacher];
|
|
@@ -73,6 +73,12 @@
|
|
|
73
73
|
5012242B217372B3000F5F98 /* ImageParser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50122429217372B3000F5F98 /* ImageParser.mm */; };
|
|
74
74
|
5016E8EF20209690009D4F7C /* RNNCustomTitleView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */; };
|
|
75
75
|
5016E8F020209690009D4F7C /* RNNCustomTitleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5016E8EE2020968F009D4F7C /* RNNCustomTitleView.mm */; };
|
|
76
|
+
EE4A5C0001000000ABCD0001 /* RNNCustomTabBarItemView.h in Headers */ = {isa = PBXBuildFile; fileRef = EE4A5C0001000000ABCD0003 /* RNNCustomTabBarItemView.h */; };
|
|
77
|
+
EE4A5C0001000000ABCD0002 /* RNNCustomTabBarItemView.mm in Sources */ = {isa = PBXBuildFile; fileRef = EE4A5C0001000000ABCD0004 /* RNNCustomTabBarItemView.mm */; };
|
|
78
|
+
EE4A5C0001000000ABCD0011 /* RNNBottomTabsCustomRow.h in Headers */ = {isa = PBXBuildFile; fileRef = EE4A5C0001000000ABCD0013 /* RNNBottomTabsCustomRow.h */; };
|
|
79
|
+
EE4A5C0001000000ABCD0012 /* RNNBottomTabsCustomRow.mm in Sources */ = {isa = PBXBuildFile; fileRef = EE4A5C0001000000ABCD0014 /* RNNBottomTabsCustomRow.mm */; };
|
|
80
|
+
EE4A5C0001000000ABCD0021 /* RNNBottomTabsCustomRowOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = EE4A5C0001000000ABCD0023 /* RNNBottomTabsCustomRowOptions.h */; };
|
|
81
|
+
EE4A5C0001000000ABCD0022 /* RNNBottomTabsCustomRowOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = EE4A5C0001000000ABCD0024 /* RNNBottomTabsCustomRowOptions.mm */; };
|
|
76
82
|
50175CD1207A2AA1004FE91B /* RNNComponentOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */; };
|
|
77
83
|
50175CD2207A2AA1004FE91B /* RNNComponentOptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50175CD0207A2AA1004FE91B /* RNNComponentOptions.mm */; };
|
|
78
84
|
5017D9E1239D2C6C00B74047 /* BottomTabsAttachModeFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 5017D9DF239D2C6C00B74047 /* BottomTabsAttachModeFactory.h */; };
|
|
@@ -568,6 +574,12 @@
|
|
|
568
574
|
50122429217372B3000F5F98 /* ImageParser.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImageParser.mm; sourceTree = "<group>"; };
|
|
569
575
|
5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNCustomTitleView.h; sourceTree = "<group>"; };
|
|
570
576
|
5016E8EE2020968F009D4F7C /* RNNCustomTitleView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNCustomTitleView.mm; sourceTree = "<group>"; };
|
|
577
|
+
EE4A5C0001000000ABCD0003 /* RNNCustomTabBarItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNCustomTabBarItemView.h; sourceTree = "<group>"; };
|
|
578
|
+
EE4A5C0001000000ABCD0004 /* RNNCustomTabBarItemView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNCustomTabBarItemView.mm; sourceTree = "<group>"; };
|
|
579
|
+
EE4A5C0001000000ABCD0013 /* RNNBottomTabsCustomRow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNBottomTabsCustomRow.h; sourceTree = "<group>"; };
|
|
580
|
+
EE4A5C0001000000ABCD0014 /* RNNBottomTabsCustomRow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNBottomTabsCustomRow.mm; sourceTree = "<group>"; };
|
|
581
|
+
EE4A5C0001000000ABCD0023 /* RNNBottomTabsCustomRowOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNBottomTabsCustomRowOptions.h; sourceTree = "<group>"; };
|
|
582
|
+
EE4A5C0001000000ABCD0024 /* RNNBottomTabsCustomRowOptions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNBottomTabsCustomRowOptions.mm; sourceTree = "<group>"; };
|
|
571
583
|
50175CCF207A2AA1004FE91B /* RNNComponentOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNComponentOptions.h; sourceTree = "<group>"; };
|
|
572
584
|
50175CD0207A2AA1004FE91B /* RNNComponentOptions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNComponentOptions.mm; sourceTree = "<group>"; };
|
|
573
585
|
5017D9DF239D2C6C00B74047 /* BottomTabsAttachModeFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BottomTabsAttachModeFactory.h; sourceTree = "<group>"; };
|
|
@@ -1663,6 +1675,12 @@
|
|
|
1663
1675
|
children = (
|
|
1664
1676
|
5016E8ED2020968F009D4F7C /* RNNCustomTitleView.h */,
|
|
1665
1677
|
5016E8EE2020968F009D4F7C /* RNNCustomTitleView.mm */,
|
|
1678
|
+
EE4A5C0001000000ABCD0003 /* RNNCustomTabBarItemView.h */,
|
|
1679
|
+
EE4A5C0001000000ABCD0004 /* RNNCustomTabBarItemView.mm */,
|
|
1680
|
+
EE4A5C0001000000ABCD0013 /* RNNBottomTabsCustomRow.h */,
|
|
1681
|
+
EE4A5C0001000000ABCD0014 /* RNNBottomTabsCustomRow.mm */,
|
|
1682
|
+
EE4A5C0001000000ABCD0023 /* RNNBottomTabsCustomRowOptions.h */,
|
|
1683
|
+
EE4A5C0001000000ABCD0024 /* RNNBottomTabsCustomRowOptions.mm */,
|
|
1666
1684
|
E8A5CD601F49114F00E89D0D /* RNNElement.h */,
|
|
1667
1685
|
E8A5CD611F49114F00E89D0D /* RNNElement.mm */,
|
|
1668
1686
|
E8AEDB3A1F55A1C2000F5A6A /* RNNElementView.h */,
|
|
@@ -1737,6 +1755,9 @@
|
|
|
1737
1755
|
E5F6C3A422DB4D0F0093C2CE /* UIColor+RNNUtils.h in Headers */,
|
|
1738
1756
|
5038A3C1216E1E66009280BC /* RNNFontAttributesCreator.h in Headers */,
|
|
1739
1757
|
5016E8EF20209690009D4F7C /* RNNCustomTitleView.h in Headers */,
|
|
1758
|
+
EE4A5C0001000000ABCD0001 /* RNNCustomTabBarItemView.h in Headers */,
|
|
1759
|
+
EE4A5C0001000000ABCD0011 /* RNNBottomTabsCustomRow.h in Headers */,
|
|
1760
|
+
EE4A5C0001000000ABCD0021 /* RNNBottomTabsCustomRowOptions.h in Headers */,
|
|
1740
1761
|
500623A525B7003A0086AB39 /* RNNShadowOptions.h in Headers */,
|
|
1741
1762
|
50415CBA20553B8E00BB682E /* RNNScreenTransition.h in Headers */,
|
|
1742
1763
|
5039558B2174829400B0A663 /* IntNumberParser.h in Headers */,
|
|
@@ -2042,6 +2063,9 @@
|
|
|
2042
2063
|
50CB3B6A1FDE911400AA153B /* RNNSideMenuOptions.mm in Sources */,
|
|
2043
2064
|
503A8A0623BB850A0094D1C4 /* TimeInterval.mm in Sources */,
|
|
2044
2065
|
5016E8F020209690009D4F7C /* RNNCustomTitleView.mm in Sources */,
|
|
2066
|
+
EE4A5C0001000000ABCD0002 /* RNNCustomTabBarItemView.mm in Sources */,
|
|
2067
|
+
EE4A5C0001000000ABCD0012 /* RNNBottomTabsCustomRow.mm in Sources */,
|
|
2068
|
+
EE4A5C0001000000ABCD0022 /* RNNBottomTabsCustomRowOptions.mm in Sources */,
|
|
2045
2069
|
5061B6C823D48449008B9827 /* VerticalRotationTransition.mm in Sources */,
|
|
2046
2070
|
5022EDB62405224B00852BA6 /* BottomTabPresenter.mm in Sources */,
|
|
2047
2071
|
503955982174864E00B0A663 /* NullDouble.mm in Sources */,
|
|
@@ -18,6 +18,7 @@ src/
|
|
|
18
18
|
├── components/ # React component management
|
|
19
19
|
├── events/ # Event system
|
|
20
20
|
├── interfaces/ # TypeScript type definitions
|
|
21
|
+
├── linking/ # Deep-linking framework (URL → command)
|
|
21
22
|
└── processors/ # Extensibility hooks
|
|
22
23
|
```
|
|
23
24
|
|
|
@@ -207,6 +208,35 @@ Notifies listeners of command lifecycle (start, complete).
|
|
|
207
208
|
| `RNN.PreviewCompleted` | 3D Touch preview completed |
|
|
208
209
|
| `RNN.CommandCompleted` | Navigation command finished |
|
|
209
210
|
|
|
211
|
+
## Linking Layer
|
|
212
|
+
|
|
213
|
+
Implements URL-to-screen routing on top of the standard command pipeline. The whole feature is JS-only; it consumes URLs from RN's `Linking` module and dispatches `showModal` (or a user-supplied command) when a match is found.
|
|
214
|
+
|
|
215
|
+
**Public surface** (proxied through `NavigationDelegate`):
|
|
216
|
+
- `Navigation.setLinking(config)` — configure prefixes, screen map, and customization hooks
|
|
217
|
+
- `Navigation.handleDeepLink(url)` — manually feed a URL (push notifications, branch.io, App Clips)
|
|
218
|
+
- `Navigation.setLinkingReady(ready)` — user-controlled gate for deferred deep links
|
|
219
|
+
|
|
220
|
+
**Internal modules** (`src/linking/`):
|
|
221
|
+
|
|
222
|
+
| File | Purpose |
|
|
223
|
+
|------|---------|
|
|
224
|
+
| `LinkingHandler.ts` | Orchestrator. Subscribes to `Linking.url`/`getInitialURL`, drives the queue, dispatches matches. Instantiated by `NavigationRoot`. |
|
|
225
|
+
| `URLParser.ts` | Strips prefix (longest match), removes fragment, decodes path segments + query params. |
|
|
226
|
+
| `RouteMatcher.ts` | Compiles `ScreensConfig` into a `RouteNode` tree; matches paths to screen chains with parameter extraction. |
|
|
227
|
+
| `DeferredLinkQueue.ts` | FIFO queue. Holds URLs until both gates open (root-ready + user-ready). |
|
|
228
|
+
| `ModalLayoutBuilder.ts` | Default presentation: wraps the matched chain in a `stack`, merges params into `passProps`, filters React-reserved keys (`ref`, `key`). |
|
|
229
|
+
| `types.ts` | Public types: `LinkingConfig`, `RouteMatch`, `ScreensConfig`, etc. (re-exported via `src/index.ts`). |
|
|
230
|
+
|
|
231
|
+
**Readiness gates** — a URL is dispatched only when both are true:
|
|
232
|
+
1. **Root-ready** (automatic): the first `Navigation.setRoot()` has resolved. `NavigationRoot.setRoot` calls `linkingHandler.setRootReady()` after the promise resolves.
|
|
233
|
+
2. **User-ready** (optional): `config.isReady()` returns `true`, or `setLinkingReady(true)` was called.
|
|
234
|
+
|
|
235
|
+
**Resolution priority** when a match is found:
|
|
236
|
+
1. `config.onLink(match)` — full escape hatch, RNN does nothing else
|
|
237
|
+
2. `config.getModal(match)` — custom modal layout
|
|
238
|
+
3. Default `ModalLayoutBuilder` output
|
|
239
|
+
|
|
210
240
|
## Processors Layer
|
|
211
241
|
|
|
212
242
|
### OptionProcessorsStore
|
package/lib/module/Navigation.js
CHANGED
|
@@ -21,6 +21,7 @@ import { Deprecations } from "./commands/Deprecations.js";
|
|
|
21
21
|
import { LayoutProcessor } from "./processors/LayoutProcessor.js";
|
|
22
22
|
import { LayoutProcessorsStore } from "./processors/LayoutProcessorsStore.js";
|
|
23
23
|
import { OptionsCrawler } from "./commands/OptionsCrawler.js";
|
|
24
|
+
import { LinkingHandler } from "./linking/LinkingHandler.js";
|
|
24
25
|
export class NavigationRoot {
|
|
25
26
|
TouchablePreview = TouchablePreview;
|
|
26
27
|
constructor(nativeCommandsSender, nativeEventsReceiver, appRegistryService) {
|
|
@@ -42,6 +43,7 @@ export class NavigationRoot {
|
|
|
42
43
|
this.optionsCrawler = new OptionsCrawler(this.store, this.uniqueIdProvider);
|
|
43
44
|
this.commands = new Commands(this.store, this.nativeCommandsSender, this.layoutTreeParser, this.layoutTreeCrawler, this.commandsObserver, this.uniqueIdProvider, optionsProcessor, layoutProcessor, this.optionsCrawler);
|
|
44
45
|
this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.commandsObserver, this.componentEventsObserver);
|
|
46
|
+
this.linkingHandler = new LinkingHandler(layout => this.commands.showModal(layout));
|
|
45
47
|
this.componentEventsObserver.registerOnceForAllComponentEvents();
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -84,7 +86,9 @@ export class NavigationRoot {
|
|
|
84
86
|
* Reset the app to a new layout
|
|
85
87
|
*/
|
|
86
88
|
setRoot(layout) {
|
|
87
|
-
|
|
89
|
+
const result = this.commands.setRoot(layout);
|
|
90
|
+
result.then(() => this.linkingHandler.setRootReady()).catch(() => {});
|
|
91
|
+
return result;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
/**
|
|
@@ -193,6 +197,35 @@ export class NavigationRoot {
|
|
|
193
197
|
return this.commands.getLaunchArgs();
|
|
194
198
|
}
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Configure deep link handling. Maps URL prefixes and path patterns to
|
|
202
|
+
* RNN components. By default each matched link is presented as a modal
|
|
203
|
+
* (wrapped in a stack so a topBar close button can be configured); supply
|
|
204
|
+
* `getModal` to customize the modal layout or `onLink` to bypass the
|
|
205
|
+
* default behavior entirely.
|
|
206
|
+
*/
|
|
207
|
+
setLinking(config) {
|
|
208
|
+
this.linkingHandler.configure(config);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Manually run a URL through the deep link pipeline as if it had been
|
|
213
|
+
* delivered by the OS. Useful for URLs received from push notifications
|
|
214
|
+
* or other non-`Linking` sources.
|
|
215
|
+
*/
|
|
216
|
+
handleDeepLink(url) {
|
|
217
|
+
this.linkingHandler.handleURL(url);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Signal that the app is ready to process deep links (e.g. after the
|
|
222
|
+
* user has authenticated). When set to `true`, any links that were
|
|
223
|
+
* queued while not ready are replayed in order.
|
|
224
|
+
*/
|
|
225
|
+
setLinkingReady(ready) {
|
|
226
|
+
this.linkingHandler.setLinkingReady(ready);
|
|
227
|
+
}
|
|
228
|
+
|
|
196
229
|
/**
|
|
197
230
|
* Obtain the events registry instance
|
|
198
231
|
*/
|