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
package/src/Navigation.ts
CHANGED
|
@@ -28,6 +28,8 @@ import { LayoutProcessorsStore } from './processors/LayoutProcessorsStore';
|
|
|
28
28
|
import { CommandName } from './interfaces/CommandName';
|
|
29
29
|
import { OptionsCrawler } from './commands/OptionsCrawler';
|
|
30
30
|
import { OptionsProcessor as OptionProcessor } from './interfaces/Processors';
|
|
31
|
+
import { LinkingHandler } from './linking/LinkingHandler';
|
|
32
|
+
import { LinkingConfig } from './linking/types';
|
|
31
33
|
|
|
32
34
|
export class NavigationRoot {
|
|
33
35
|
public readonly TouchablePreview = TouchablePreview;
|
|
@@ -45,6 +47,7 @@ export class NavigationRoot {
|
|
|
45
47
|
private readonly componentEventsObserver: ComponentEventsObserver;
|
|
46
48
|
private readonly componentWrapper: ComponentWrapper;
|
|
47
49
|
private readonly optionsCrawler: OptionsCrawler;
|
|
50
|
+
private readonly linkingHandler: LinkingHandler;
|
|
48
51
|
|
|
49
52
|
constructor(
|
|
50
53
|
private readonly nativeCommandsSender: NativeCommandsSender,
|
|
@@ -96,6 +99,7 @@ export class NavigationRoot {
|
|
|
96
99
|
this.commandsObserver,
|
|
97
100
|
this.componentEventsObserver
|
|
98
101
|
);
|
|
102
|
+
this.linkingHandler = new LinkingHandler((layout) => this.commands.showModal(layout));
|
|
99
103
|
|
|
100
104
|
this.componentEventsObserver.registerOnceForAllComponentEvents();
|
|
101
105
|
}
|
|
@@ -168,7 +172,9 @@ export class NavigationRoot {
|
|
|
168
172
|
* Reset the app to a new layout
|
|
169
173
|
*/
|
|
170
174
|
public setRoot(layout: LayoutRoot): Promise<string> {
|
|
171
|
-
|
|
175
|
+
const result = this.commands.setRoot(layout);
|
|
176
|
+
result.then(() => this.linkingHandler.setRootReady()).catch(() => {});
|
|
177
|
+
return result;
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
/**
|
|
@@ -280,6 +286,35 @@ export class NavigationRoot {
|
|
|
280
286
|
return this.commands.getLaunchArgs();
|
|
281
287
|
}
|
|
282
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Configure deep link handling. Maps URL prefixes and path patterns to
|
|
291
|
+
* RNN components. By default each matched link is presented as a modal
|
|
292
|
+
* (wrapped in a stack so a topBar close button can be configured); supply
|
|
293
|
+
* `getModal` to customize the modal layout or `onLink` to bypass the
|
|
294
|
+
* default behavior entirely.
|
|
295
|
+
*/
|
|
296
|
+
public setLinking(config: LinkingConfig): void {
|
|
297
|
+
this.linkingHandler.configure(config);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Manually run a URL through the deep link pipeline as if it had been
|
|
302
|
+
* delivered by the OS. Useful for URLs received from push notifications
|
|
303
|
+
* or other non-`Linking` sources.
|
|
304
|
+
*/
|
|
305
|
+
public handleDeepLink(url: string): void {
|
|
306
|
+
this.linkingHandler.handleURL(url);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Signal that the app is ready to process deep links (e.g. after the
|
|
311
|
+
* user has authenticated). When set to `true`, any links that were
|
|
312
|
+
* queued while not ready are replayed in order.
|
|
313
|
+
*/
|
|
314
|
+
public setLinkingReady(ready: boolean): void {
|
|
315
|
+
this.linkingHandler.setLinkingReady(ready);
|
|
316
|
+
}
|
|
317
|
+
|
|
283
318
|
/**
|
|
284
319
|
* Obtain the events registry instance
|
|
285
320
|
*/
|
|
@@ -10,6 +10,7 @@ import { NavigationRoot } from './Navigation';
|
|
|
10
10
|
import { NativeCommandsSender } from './adapters/NativeCommandsSender';
|
|
11
11
|
import { NativeEventsReceiver } from './adapters/NativeEventsReceiver';
|
|
12
12
|
import { AppRegistryService } from './adapters/AppRegistryService';
|
|
13
|
+
import { LinkingConfig } from './linking/types';
|
|
13
14
|
|
|
14
15
|
export class NavigationDelegate {
|
|
15
16
|
private concreteNavigation: NavigationRoot;
|
|
@@ -204,6 +205,27 @@ export class NavigationDelegate {
|
|
|
204
205
|
return this.concreteNavigation.getLaunchArgs();
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Configure deep link handling.
|
|
210
|
+
*/
|
|
211
|
+
public setLinking(config: LinkingConfig): void {
|
|
212
|
+
this.concreteNavigation.setLinking(config);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Manually feed a URL into the deep link pipeline.
|
|
217
|
+
*/
|
|
218
|
+
public handleDeepLink(url: string): void {
|
|
219
|
+
this.concreteNavigation.handleDeepLink(url);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Signal readiness to process deep links. Flushes any queued links.
|
|
224
|
+
*/
|
|
225
|
+
public setLinkingReady(ready: boolean): void {
|
|
226
|
+
this.concreteNavigation.setLinkingReady(ready);
|
|
227
|
+
}
|
|
228
|
+
|
|
207
229
|
/**
|
|
208
230
|
* Obtain the events registry instance
|
|
209
231
|
*/
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { Options } from '../interfaces/Options';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Walks a Layout / Options tree looking for `bottomTabs.customRow`
|
|
7
|
+
* configuration and forwards it to the Android-only native module
|
|
8
|
+
* `RNNBottomTabsCustomRowModule`.
|
|
9
|
+
*
|
|
10
|
+
* On iOS this is a no-op — the existing native options parser already picks
|
|
11
|
+
* up `bottomTabs.customRow` and applies it to the iOS custom row.
|
|
12
|
+
*
|
|
13
|
+
* The original layout/options object is *never* modified — both pipelines
|
|
14
|
+
* (iOS native parser, Android native parser which currently ignores the
|
|
15
|
+
* field) keep seeing the same data.
|
|
16
|
+
*/
|
|
17
|
+
export class AndroidCustomRowForwarder {
|
|
18
|
+
forwardFromLayout(layout: any) {
|
|
19
|
+
if (!this.shouldForward()) return;
|
|
20
|
+
const config = this.findCustomRowInLayout(layout);
|
|
21
|
+
if (config) this.send(config);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
forwardFromLayouts(layouts: any[]) {
|
|
25
|
+
if (!this.shouldForward()) return;
|
|
26
|
+
for (const layout of layouts) {
|
|
27
|
+
const config = this.findCustomRowInLayout(layout);
|
|
28
|
+
if (config) {
|
|
29
|
+
this.send(config);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
forwardFromOptions(options: Options | undefined) {
|
|
36
|
+
if (!this.shouldForward() || !options) return;
|
|
37
|
+
const config = this.extractFromOptions(options);
|
|
38
|
+
if (config) this.send(config);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private shouldForward(): boolean {
|
|
42
|
+
return Platform.OS === 'android';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private findCustomRowInLayout(layout: any): object | null {
|
|
46
|
+
if (!layout || typeof layout !== 'object') return null;
|
|
47
|
+
// Walk both the raw layout shape (`layout.options`) and the
|
|
48
|
+
// post-`layoutTreeParser.parse` shape (`layout.data.options`).
|
|
49
|
+
const direct =
|
|
50
|
+
this.extractFromOptions(layout.options) ??
|
|
51
|
+
this.extractFromOptions(layout.data?.options);
|
|
52
|
+
if (direct) return direct;
|
|
53
|
+
const children = layout.children;
|
|
54
|
+
if (Array.isArray(children)) {
|
|
55
|
+
for (const child of children) {
|
|
56
|
+
const found = this.findCustomRowInLayout(child);
|
|
57
|
+
if (found) return found;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private extractFromOptions(options: Options | undefined): object | null {
|
|
64
|
+
if (!options || typeof options !== 'object') return null;
|
|
65
|
+
const bottomTabs: any = (options as any).bottomTabs;
|
|
66
|
+
if (!bottomTabs || typeof bottomTabs !== 'object') return null;
|
|
67
|
+
const customRow = bottomTabs.customRow;
|
|
68
|
+
if (!customRow || typeof customRow !== 'object') return null;
|
|
69
|
+
return customRow;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private send(config: object): void {
|
|
73
|
+
const nativeModule = (NativeModules as any).RNNBottomTabsCustomRowModule;
|
|
74
|
+
if (!nativeModule || typeof nativeModule.configure !== 'function') {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
nativeModule.configure(config);
|
|
79
|
+
} catch {
|
|
80
|
+
// Native module not ready yet; attacher will pick up config on next rescan.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/commands/Commands.ts
CHANGED
|
@@ -13,8 +13,11 @@ import { Store } from '../components/Store';
|
|
|
13
13
|
import { LayoutProcessor } from '../processors/LayoutProcessor';
|
|
14
14
|
import { CommandName } from '../interfaces/CommandName';
|
|
15
15
|
import { OptionsCrawler } from './OptionsCrawler';
|
|
16
|
+
import { AndroidCustomRowForwarder } from '../adapters/AndroidCustomRowForwarder';
|
|
16
17
|
|
|
17
18
|
export class Commands {
|
|
19
|
+
private readonly androidCustomRowForwarder = new AndroidCustomRowForwarder();
|
|
20
|
+
|
|
18
21
|
constructor(
|
|
19
22
|
private readonly store: Store,
|
|
20
23
|
private readonly nativeCommandsSender: NativeCommandsSender,
|
|
@@ -59,6 +62,8 @@ export class Commands {
|
|
|
59
62
|
this.layoutTreeCrawler.crawl(overlayLayout, CommandName.SetRoot);
|
|
60
63
|
});
|
|
61
64
|
|
|
65
|
+
this.androidCustomRowForwarder.forwardFromLayouts([root, ...modals, ...overlays]);
|
|
66
|
+
|
|
62
67
|
const result = this.nativeCommandsSender.setRoot(commandId, { root, modals, overlays });
|
|
63
68
|
return result;
|
|
64
69
|
}
|
|
@@ -67,6 +72,8 @@ export class Commands {
|
|
|
67
72
|
const input = cloneDeep(options);
|
|
68
73
|
this.optionsProcessor.processDefaultOptions(input, CommandName.SetDefaultOptions);
|
|
69
74
|
|
|
75
|
+
this.androidCustomRowForwarder.forwardFromOptions(input);
|
|
76
|
+
|
|
70
77
|
this.nativeCommandsSender.setDefaultOptions(input);
|
|
71
78
|
this.commandsObserver.notify(CommandName.SetDefaultOptions, { options });
|
|
72
79
|
}
|
|
@@ -82,6 +89,8 @@ export class Commands {
|
|
|
82
89
|
`Navigation.mergeOptions was invoked on component with id: ${componentId} before it is mounted, this can cause UI issues and should be avoided.\n Use static options instead.`
|
|
83
90
|
);
|
|
84
91
|
|
|
92
|
+
this.androidCustomRowForwarder.forwardFromOptions(input);
|
|
93
|
+
|
|
85
94
|
this.nativeCommandsSender.mergeOptions(componentId, input);
|
|
86
95
|
this.commandsObserver.notify(CommandName.MergeOptions, { componentId, options });
|
|
87
96
|
}
|
|
@@ -101,6 +110,8 @@ export class Commands {
|
|
|
101
110
|
this.commandsObserver.notify(CommandName.ShowModal, { commandId, layout: layoutNode });
|
|
102
111
|
this.layoutTreeCrawler.crawl(layoutNode, CommandName.ShowModal);
|
|
103
112
|
|
|
113
|
+
this.androidCustomRowForwarder.forwardFromLayout(layoutNode);
|
|
114
|
+
|
|
104
115
|
const result = this.nativeCommandsSender.showModal(commandId, layoutNode);
|
|
105
116
|
return result;
|
|
106
117
|
}
|
|
@@ -135,6 +146,8 @@ export class Commands {
|
|
|
135
146
|
this.commandsObserver.notify(CommandName.Push, { commandId, componentId, layout });
|
|
136
147
|
this.layoutTreeCrawler.crawl(layout, CommandName.Push);
|
|
137
148
|
|
|
149
|
+
this.androidCustomRowForwarder.forwardFromLayout(layout);
|
|
150
|
+
|
|
138
151
|
const result = this.nativeCommandsSender.push(commandId, componentId, layout);
|
|
139
152
|
return result;
|
|
140
153
|
}
|
|
@@ -181,6 +194,8 @@ export class Commands {
|
|
|
181
194
|
this.layoutTreeCrawler.crawl(layoutNode, CommandName.SetStackRoot);
|
|
182
195
|
});
|
|
183
196
|
|
|
197
|
+
this.androidCustomRowForwarder.forwardFromLayouts(input);
|
|
198
|
+
|
|
184
199
|
const result = this.nativeCommandsSender.setStackRoot(commandId, componentId, input);
|
|
185
200
|
return result;
|
|
186
201
|
}
|
package/src/index.ts
CHANGED
|
@@ -999,6 +999,54 @@ export interface OptionsBottomTabs {
|
|
|
999
999
|
* Control the shadow of the Bottom tabs bar
|
|
1000
1000
|
*/
|
|
1001
1001
|
shadow?: ShadowOptions;
|
|
1002
|
+
/**
|
|
1003
|
+
* Visual options for the floating row that hosts custom React-component
|
|
1004
|
+
* bottom tab cells. Only takes effect when every `bottomTab.component`
|
|
1005
|
+
* is set on that `bottomTabs` layout.
|
|
1006
|
+
*
|
|
1007
|
+
* Same JS shape on iOS and Android. iOS applies options via the native
|
|
1008
|
+
* parser; Android receives them through `RNNBottomTabsCustomRowModule`.
|
|
1009
|
+
*/
|
|
1010
|
+
customRow?: BottomTabsCustomRowOptions;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export interface BottomTabsCustomRowOptions {
|
|
1014
|
+
/**
|
|
1015
|
+
* Content height of the row in points (iOS) / dp (Android). Excludes
|
|
1016
|
+
* safe-area inset. Total row height = `height` + safe bottom + `bottomMargin`.
|
|
1017
|
+
*
|
|
1018
|
+
* Default: native tab content height; on iOS 26+ an extra 18pt when omitted.
|
|
1019
|
+
*/
|
|
1020
|
+
height?: number;
|
|
1021
|
+
/**
|
|
1022
|
+
* Solid background color for the row. When set, overrides
|
|
1023
|
+
* `backgroundEffect`.
|
|
1024
|
+
*/
|
|
1025
|
+
backgroundColor?: Color;
|
|
1026
|
+
/**
|
|
1027
|
+
* Visual effect for the row background.
|
|
1028
|
+
* - `glass`: iOS 26+ `UIGlassEffect`.
|
|
1029
|
+
* - `blur`: `UIBlurEffect` with `systemChromeMaterial`.
|
|
1030
|
+
* - `none`: fully transparent.
|
|
1031
|
+
*
|
|
1032
|
+
* Default: `glass` on iOS 26+, `blur` on older versions.
|
|
1033
|
+
*/
|
|
1034
|
+
backgroundEffect?: 'glass' | 'blur' | 'none';
|
|
1035
|
+
/**
|
|
1036
|
+
* Corner radius applied to the row's background.
|
|
1037
|
+
* Default: 28 on iOS 26+, 0 below.
|
|
1038
|
+
*/
|
|
1039
|
+
cornerRadius?: number;
|
|
1040
|
+
/**
|
|
1041
|
+
* Horizontal inset from the screen edges.
|
|
1042
|
+
* Default: 16 on iOS 26+, 0 below.
|
|
1043
|
+
*/
|
|
1044
|
+
horizontalMargin?: number;
|
|
1045
|
+
/**
|
|
1046
|
+
* Distance between the row's bottom edge and the safe-area bottom.
|
|
1047
|
+
* Default: 0.
|
|
1048
|
+
*/
|
|
1049
|
+
bottomMargin?: number;
|
|
1002
1050
|
}
|
|
1003
1051
|
|
|
1004
1052
|
export interface ShadowOptions {
|
|
@@ -1035,6 +1083,45 @@ export type ImageResource = ImageSourcePropType | string | ImageSystemSource;
|
|
|
1035
1083
|
export interface OptionsBottomTab {
|
|
1036
1084
|
dotIndicator?: DotIndicatorOptions;
|
|
1037
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* Render a React component as the tab item content (icon + text area).
|
|
1088
|
+
*
|
|
1089
|
+
* When set, the native icon, text and font props (`text`, `icon`,
|
|
1090
|
+
* `selectedIcon`, `sfSymbol`, `sfSelectedSymbol`, `iconColor`,
|
|
1091
|
+
* `selectedIconColor`, `iconWidth`, `iconHeight`, `iconInsets`,
|
|
1092
|
+
* `fontFamily`, `fontWeight`, `fontSize`, `selectedFontSize`,
|
|
1093
|
+
* `textColor`, `selectedTextColor`) are ignored for that tab.
|
|
1094
|
+
*
|
|
1095
|
+
* For the custom rendering to take effect every tab in the
|
|
1096
|
+
* `bottomTabs` layout must declare a `component`. If only some
|
|
1097
|
+
* tabs declare one a warning is logged and native rendering is
|
|
1098
|
+
* used for all tabs.
|
|
1099
|
+
*
|
|
1100
|
+
* The component receives the following initial props and
|
|
1101
|
+
* subsequent prop updates:
|
|
1102
|
+
* - `componentId`: stable id for this tab item instance
|
|
1103
|
+
* - `tabIndex`: position of the tab (0-based)
|
|
1104
|
+
* - `selected`: whether this tab is currently selected
|
|
1105
|
+
* - `badge`: current badge text (mirrors `bottomTab.badge`)
|
|
1106
|
+
*
|
|
1107
|
+
* Native still owns selection, hide/show, drawBehind, animations
|
|
1108
|
+
* and the dot indicator. To switch tabs from inside the component
|
|
1109
|
+
* use `Navigation.mergeOptions(parentId, { bottomTabs: { currentTabIndex } })`.
|
|
1110
|
+
*/
|
|
1111
|
+
component?: {
|
|
1112
|
+
/**
|
|
1113
|
+
* The registered name of the component (passed to
|
|
1114
|
+
* `Navigation.registerComponent`).
|
|
1115
|
+
*/
|
|
1116
|
+
name: string;
|
|
1117
|
+
/**
|
|
1118
|
+
* Props passed once when the component is created. Updates from
|
|
1119
|
+
* native (`selected`, `badge`) are delivered as separate prop
|
|
1120
|
+
* updates.
|
|
1121
|
+
*/
|
|
1122
|
+
passProps?: object;
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1038
1125
|
/**
|
|
1039
1126
|
* Set the text to display below the icon
|
|
1040
1127
|
*/
|
|
@@ -1574,6 +1661,11 @@ export interface AnimationOptions {
|
|
|
1574
1661
|
export interface NavigationBarOptions {
|
|
1575
1662
|
backgroundColor?: Color;
|
|
1576
1663
|
visible?: boolean;
|
|
1664
|
+
/**
|
|
1665
|
+
* Draw screen content behind the system navigation bar while keeping it visible.
|
|
1666
|
+
* On Android 15+ edge-to-edge, use with `backgroundColor: 'transparent'`.
|
|
1667
|
+
*/
|
|
1668
|
+
drawBehind?: boolean;
|
|
1577
1669
|
}
|
|
1578
1670
|
|
|
1579
1671
|
/**
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { DeferredLinkQueue } from './DeferredLinkQueue';
|
|
2
|
+
|
|
3
|
+
describe('DeferredLinkQueue', () => {
|
|
4
|
+
let uut: DeferredLinkQueue;
|
|
5
|
+
let flushed: string[];
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
uut = new DeferredLinkQueue();
|
|
9
|
+
flushed = [];
|
|
10
|
+
uut.setFlushCallback((url) => flushed.push(url));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('starts not ready', () => {
|
|
14
|
+
expect(uut.isReady()).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('enqueues URLs while not ready', () => {
|
|
18
|
+
uut.enqueue('myapp://a');
|
|
19
|
+
uut.enqueue('myapp://b');
|
|
20
|
+
expect(uut.pending()).toEqual(['myapp://a', 'myapp://b']);
|
|
21
|
+
expect(flushed).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('flushes queued URLs in order when becoming ready', () => {
|
|
25
|
+
uut.enqueue('myapp://a');
|
|
26
|
+
uut.enqueue('myapp://b');
|
|
27
|
+
uut.setReady(true);
|
|
28
|
+
expect(flushed).toEqual(['myapp://a', 'myapp://b']);
|
|
29
|
+
expect(uut.pending()).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not re-flush when already ready', () => {
|
|
33
|
+
uut.setReady(true);
|
|
34
|
+
uut.enqueue('myapp://a');
|
|
35
|
+
uut.setReady(true);
|
|
36
|
+
expect(flushed).toEqual([]);
|
|
37
|
+
expect(uut.pending()).toEqual(['myapp://a']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('flushes again after toggling not-ready → ready', () => {
|
|
41
|
+
uut.setReady(true);
|
|
42
|
+
uut.setReady(false);
|
|
43
|
+
uut.enqueue('myapp://x');
|
|
44
|
+
uut.setReady(true);
|
|
45
|
+
expect(flushed).toEqual(['myapp://x']);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('clear() drops pending URLs', () => {
|
|
49
|
+
uut.enqueue('myapp://a');
|
|
50
|
+
uut.clear();
|
|
51
|
+
uut.setReady(true);
|
|
52
|
+
expect(flushed).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('flush() is a no-op when no callback is registered', () => {
|
|
56
|
+
const fresh = new DeferredLinkQueue();
|
|
57
|
+
fresh.enqueue('myapp://a');
|
|
58
|
+
expect(() => fresh.flush()).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds URLs while the linking pipeline is not ready and replays them in
|
|
3
|
+
* order once it becomes ready.
|
|
4
|
+
*
|
|
5
|
+
* Readiness is set by the owning `LinkingHandler` and reflects the combined
|
|
6
|
+
* state of:
|
|
7
|
+
* - whether the first `setRoot` has resolved (so a modal can be presented),
|
|
8
|
+
* - whether the user-supplied `isReady` predicate (if any) returns `true`.
|
|
9
|
+
*/
|
|
10
|
+
export class DeferredLinkQueue {
|
|
11
|
+
private queue: string[] = [];
|
|
12
|
+
private ready = false;
|
|
13
|
+
private flushCallback: ((url: string) => void) | null = null;
|
|
14
|
+
|
|
15
|
+
public setFlushCallback(callback: (url: string) => void): void {
|
|
16
|
+
this.flushCallback = callback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public setReady(ready: boolean): void {
|
|
20
|
+
const becameReady = !this.ready && ready;
|
|
21
|
+
this.ready = ready;
|
|
22
|
+
if (becameReady) {
|
|
23
|
+
this.flush();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public isReady(): boolean {
|
|
28
|
+
return this.ready;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Enqueue a URL for later processing. Should only be called when the
|
|
33
|
+
* queue is not ready; the handler decides when to enqueue vs process.
|
|
34
|
+
*/
|
|
35
|
+
public enqueue(url: string): void {
|
|
36
|
+
this.queue.push(url);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public flush(): void {
|
|
40
|
+
if (!this.flushCallback) return;
|
|
41
|
+
const pending = [...this.queue];
|
|
42
|
+
this.queue = [];
|
|
43
|
+
for (const url of pending) {
|
|
44
|
+
this.flushCallback(url);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public clear(): void {
|
|
49
|
+
this.queue = [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public pending(): string[] {
|
|
53
|
+
return [...this.queue];
|
|
54
|
+
}
|
|
55
|
+
}
|