@sigmela/router 0.2.2 → 0.2.4
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 +65 -9
- package/lib/module/Navigation.js +13 -10
- package/lib/module/NavigationStack.js +13 -1
- package/lib/module/Router.js +123 -6
- package/lib/module/ScreenStack/ScreenStack.web.js +15 -9
- package/lib/module/ScreenStack/animationHelpers.js +12 -2
- package/lib/module/ScreenStackItem/ScreenStackItem.js +4 -1
- package/lib/module/ScreenStackItem/ScreenStackItem.web.js +19 -5
- package/lib/module/SplitView/RenderSplitView.native.js +57 -64
- package/lib/module/TabBar/RenderTabBar.native.js +95 -5
- package/lib/module/TabBar/RenderTabBar.web.js +39 -0
- package/lib/module/TabBar/TabBar.js +21 -9
- package/lib/module/styles.css +95 -18
- package/lib/module/types.js +11 -1
- package/lib/typescript/src/Navigation.d.ts +8 -0
- package/lib/typescript/src/NavigationStack.d.ts +3 -2
- package/lib/typescript/src/Router.d.ts +12 -1
- package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +2 -2
- package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +7 -0
- package/lib/typescript/src/TabBar/TabBar.d.ts +23 -0
- package/lib/typescript/src/types.d.ts +14 -1
- package/package.json +1 -1
|
@@ -1,85 +1,78 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ScreenStackItem } from "../ScreenStackItem/index.js";
|
|
4
|
+
import { ScreenStack } from "../ScreenStack/index.js";
|
|
4
5
|
import { SplitViewContext } from "./SplitViewContext.js";
|
|
5
6
|
import { useRouter } from "../RouterContext.js";
|
|
6
|
-
import { memo, useCallback, useSyncExternalStore } from 'react';
|
|
7
|
-
import { StyleSheet
|
|
8
|
-
import { jsx as _jsx
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
|
|
17
|
-
const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
|
|
18
|
-
const history = useSyncExternalStore(subscribe, get, get);
|
|
19
|
-
let historyToRender = history;
|
|
20
|
-
if (fallbackToFirstRoute && historyToRender.length === 0) {
|
|
21
|
-
const first = stack.getFirstRoute();
|
|
22
|
-
if (first) {
|
|
23
|
-
const activePath = router.getActiveRoute()?.path;
|
|
24
|
-
historyToRender = [{
|
|
25
|
-
key: `splitview-seed-${stackId}`,
|
|
26
|
-
routeId: first.routeId,
|
|
27
|
-
component: first.component,
|
|
28
|
-
options: first.options,
|
|
29
|
-
stackId,
|
|
30
|
-
pattern: first.path,
|
|
31
|
-
path: activePath ?? first.path
|
|
32
|
-
}];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return /*#__PURE__*/_jsx(StackRenderer, {
|
|
36
|
-
appearance: appearance,
|
|
37
|
-
stack: stack,
|
|
38
|
-
history: historyToRender
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
StackSliceRenderer.displayName = 'SplitViewStackSliceRendererNative';
|
|
7
|
+
import { memo, useCallback, useSyncExternalStore, useMemo } from 'react';
|
|
8
|
+
import { StyleSheet } from 'react-native';
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
/**
|
|
11
|
+
* On native (iPhone), SplitView renders primary and secondary screens
|
|
12
|
+
* in a SINGLE ScreenStack to get native push/pop animations.
|
|
13
|
+
*
|
|
14
|
+
* The combined history is: [...primaryHistory, ...secondaryHistory]
|
|
15
|
+
* This way, navigating from primary to secondary is a native push.
|
|
16
|
+
*/
|
|
42
17
|
export const RenderSplitView = /*#__PURE__*/memo(({
|
|
43
18
|
splitView,
|
|
44
19
|
appearance
|
|
45
20
|
}) => {
|
|
46
21
|
const router = useRouter();
|
|
22
|
+
|
|
23
|
+
// Subscribe to primary stack
|
|
24
|
+
const primaryId = splitView.primary.getId();
|
|
25
|
+
const subscribePrimary = useCallback(cb => router.subscribeStack(primaryId, cb), [router, primaryId]);
|
|
26
|
+
const getPrimary = useCallback(() => router.getStackHistory(primaryId), [router, primaryId]);
|
|
27
|
+
const primaryHistory = useSyncExternalStore(subscribePrimary, getPrimary, getPrimary);
|
|
28
|
+
|
|
29
|
+
// Subscribe to secondary stack
|
|
47
30
|
const secondaryId = splitView.secondary.getId();
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const secondaryHistory = useSyncExternalStore(
|
|
51
|
-
|
|
31
|
+
const subscribeSecondary = useCallback(cb => router.subscribeStack(secondaryId, cb), [router, secondaryId]);
|
|
32
|
+
const getSecondary = useCallback(() => router.getStackHistory(secondaryId), [router, secondaryId]);
|
|
33
|
+
const secondaryHistory = useSyncExternalStore(subscribeSecondary, getSecondary, getSecondary);
|
|
34
|
+
|
|
35
|
+
// Fallback: if primary is empty, seed with first route
|
|
36
|
+
const primaryHistoryToRender = useMemo(() => {
|
|
37
|
+
if (primaryHistory.length > 0) {
|
|
38
|
+
return primaryHistory;
|
|
39
|
+
}
|
|
40
|
+
const first = splitView.primary.getFirstRoute();
|
|
41
|
+
if (!first) return [];
|
|
42
|
+
const activePath = router.getActiveRoute()?.path;
|
|
43
|
+
return [{
|
|
44
|
+
key: `splitview-seed-${primaryId}`,
|
|
45
|
+
routeId: first.routeId,
|
|
46
|
+
component: first.component,
|
|
47
|
+
options: first.options,
|
|
48
|
+
stackId: primaryId,
|
|
49
|
+
pattern: first.path,
|
|
50
|
+
path: activePath ?? first.path
|
|
51
|
+
}];
|
|
52
|
+
}, [primaryHistory, splitView.primary, primaryId, router]);
|
|
53
|
+
|
|
54
|
+
// Combine histories: primary screens first, then secondary screens on top
|
|
55
|
+
// This gives native push animation when navigating from primary to secondary
|
|
56
|
+
const combinedHistory = useMemo(() => {
|
|
57
|
+
return [...primaryHistoryToRender, ...secondaryHistory];
|
|
58
|
+
}, [primaryHistoryToRender, secondaryHistory]);
|
|
59
|
+
|
|
60
|
+
// Use primary stack ID for the combined ScreenStack
|
|
61
|
+
// (secondary items will animate as if pushed onto this stack)
|
|
52
62
|
return /*#__PURE__*/_jsx(SplitViewContext.Provider, {
|
|
53
63
|
value: splitView,
|
|
54
|
-
children: /*#__PURE__*/
|
|
64
|
+
children: /*#__PURE__*/_jsx(ScreenStack, {
|
|
55
65
|
style: styles.container,
|
|
56
|
-
children:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
stack: splitView.primary,
|
|
62
|
-
fallbackToFirstRoute: true
|
|
63
|
-
})
|
|
64
|
-
}), hasSecondary ? /*#__PURE__*/_jsx(View, {
|
|
65
|
-
style: styles.secondary,
|
|
66
|
-
children: /*#__PURE__*/_jsx(StackSliceRenderer, {
|
|
67
|
-
appearance: appearance,
|
|
68
|
-
stack: splitView.secondary
|
|
69
|
-
})
|
|
70
|
-
}) : null]
|
|
66
|
+
children: combinedHistory.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
|
|
67
|
+
appearance: appearance,
|
|
68
|
+
stackId: item.stackId,
|
|
69
|
+
item: item
|
|
70
|
+
}, `splitview-${item.key}`))
|
|
71
71
|
})
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
const styles = StyleSheet.create({
|
|
75
75
|
container: {
|
|
76
76
|
flex: 1
|
|
77
|
-
},
|
|
78
|
-
primary: {
|
|
79
|
-
flex: 1
|
|
80
|
-
},
|
|
81
|
-
secondary: {
|
|
82
|
-
...StyleSheet.absoluteFillObject,
|
|
83
|
-
zIndex: 2
|
|
84
77
|
}
|
|
85
78
|
});
|
|
@@ -5,7 +5,7 @@ import { TabBarContext } from "./TabBarContext.js";
|
|
|
5
5
|
import { useRouter } from "../RouterContext.js";
|
|
6
6
|
import { BottomTabsScreen, BottomTabs, ScreenStackItem } from 'react-native-screens';
|
|
7
7
|
import { Platform, StyleSheet, View } from 'react-native';
|
|
8
|
-
import { useCallback, useSyncExternalStore, memo, useEffect, useState } from 'react';
|
|
8
|
+
import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
const isImageSource = value => {
|
|
11
11
|
if (value == null) return false;
|
|
@@ -146,10 +146,21 @@ const TabStackRenderer = /*#__PURE__*/memo(({
|
|
|
146
146
|
});
|
|
147
147
|
});
|
|
148
148
|
TabStackRenderer.displayName = 'TabStackRenderer';
|
|
149
|
+
const TabNodeRenderer = /*#__PURE__*/memo(({
|
|
150
|
+
node,
|
|
151
|
+
appearance
|
|
152
|
+
}) => {
|
|
153
|
+
const Renderer = useMemo(() => node.getRenderer(), [node]);
|
|
154
|
+
return /*#__PURE__*/_jsx(Renderer, {
|
|
155
|
+
appearance: appearance
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
TabNodeRenderer.displayName = 'TabNodeRenderer';
|
|
149
159
|
export const RenderTabBar = /*#__PURE__*/memo(({
|
|
150
160
|
tabBar,
|
|
151
161
|
appearance = {}
|
|
152
162
|
}) => {
|
|
163
|
+
const router = useRouter();
|
|
153
164
|
const subscribe = useCallback(cb => tabBar.subscribe(cb), [tabBar]);
|
|
154
165
|
const snapshot = useSyncExternalStore(subscribe, tabBar.getState, tabBar.getState);
|
|
155
166
|
const {
|
|
@@ -172,11 +183,82 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
172
183
|
const onNativeFocusChange = useCallback(event => {
|
|
173
184
|
const tabKey = event.nativeEvent.tabKey;
|
|
174
185
|
const tabIndex = tabs.findIndex(route => route.tabKey === tabKey);
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
if (tabIndex === -1) return;
|
|
187
|
+
const targetTab = tabs[tabIndex];
|
|
188
|
+
if (!targetTab) return;
|
|
189
|
+
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
190
|
+
const targetNode = tabBar.nodes[targetTab.tabKey];
|
|
191
|
+
|
|
192
|
+
// Update TabBar UI state
|
|
193
|
+
if (tabIndex !== index) {
|
|
194
|
+
tabBar.onIndexChange(tabIndex);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Navigate to the target stack's first route if needed
|
|
198
|
+
if (targetStack) {
|
|
199
|
+
const stackId = targetStack.getId();
|
|
200
|
+
const stackHistory = router.getStackHistory(stackId);
|
|
201
|
+
// Only navigate if stack is empty (first visit)
|
|
202
|
+
if (stackHistory.length === 0) {
|
|
203
|
+
const firstRoute = targetStack.getFirstRoute();
|
|
204
|
+
if (firstRoute?.path) {
|
|
205
|
+
router.navigate(firstRoute.path);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else if (targetNode) {
|
|
209
|
+
// For nodes like SplitView, check if we need to seed it
|
|
210
|
+
const nodeId = targetNode.getId?.();
|
|
211
|
+
if (nodeId) {
|
|
212
|
+
const nodeHistory = router.getStackHistory(nodeId);
|
|
213
|
+
if (nodeHistory.length === 0) {
|
|
214
|
+
const seed = targetNode.seed?.();
|
|
215
|
+
if (seed?.path) {
|
|
216
|
+
const prefix = targetTab.tabPrefix ?? '';
|
|
217
|
+
const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
|
|
218
|
+
router.navigate(fullPath);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, [tabs, tabBar, index, router]);
|
|
177
224
|
const onTabPress = useCallback(nextIndex => {
|
|
178
|
-
|
|
179
|
-
|
|
225
|
+
const targetTab = tabs[nextIndex];
|
|
226
|
+
if (!targetTab) return;
|
|
227
|
+
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
228
|
+
const targetNode = tabBar.nodes[targetTab.tabKey];
|
|
229
|
+
|
|
230
|
+
// Update TabBar UI state
|
|
231
|
+
if (nextIndex !== index) {
|
|
232
|
+
tabBar.onIndexChange(nextIndex);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Navigate to the target stack's first route if needed
|
|
236
|
+
if (targetStack) {
|
|
237
|
+
const stackId = targetStack.getId();
|
|
238
|
+
const stackHistory = router.getStackHistory(stackId);
|
|
239
|
+
// Only navigate if stack is empty (first visit)
|
|
240
|
+
if (stackHistory.length === 0) {
|
|
241
|
+
const firstRoute = targetStack.getFirstRoute();
|
|
242
|
+
if (firstRoute?.path) {
|
|
243
|
+
router.navigate(firstRoute.path);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} else if (targetNode) {
|
|
247
|
+
// For nodes like SplitView, check if we need to seed it
|
|
248
|
+
const nodeId = targetNode.getId?.();
|
|
249
|
+
if (nodeId) {
|
|
250
|
+
const nodeHistory = router.getStackHistory(nodeId);
|
|
251
|
+
if (nodeHistory.length === 0) {
|
|
252
|
+
const seed = targetNode.seed?.();
|
|
253
|
+
if (seed?.path) {
|
|
254
|
+
const prefix = targetTab.tabPrefix ?? '';
|
|
255
|
+
const fullPath = prefix && !seed.path.startsWith(prefix) ? `${prefix}${seed.path.startsWith('/') ? '' : '/'}${seed.path}` : seed.path;
|
|
256
|
+
router.navigate(fullPath);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}, [tabs, tabBar, index, router]);
|
|
180
262
|
const containerProps = {
|
|
181
263
|
tabBarBackgroundColor: backgroundColor,
|
|
182
264
|
tabBarItemTitleFontFamily: title?.fontFamily,
|
|
@@ -245,12 +327,16 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
245
327
|
children: tabs.filter(t => visited[t.tabKey]).map(tab => {
|
|
246
328
|
const isActive = tab.tabKey === tabs[index]?.tabKey;
|
|
247
329
|
const stackForTab = tabBar.stacks[tab.tabKey];
|
|
330
|
+
const nodeForTab = tabBar.nodes[tab.tabKey];
|
|
248
331
|
const ScreenForTab = tabBar.screens[tab.tabKey];
|
|
249
332
|
return /*#__PURE__*/_jsx(View, {
|
|
250
333
|
style: [styles.flex, !isActive && styles.hidden],
|
|
251
334
|
children: stackForTab ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
252
335
|
appearance: appearance,
|
|
253
336
|
stack: stackForTab
|
|
337
|
+
}) : nodeForTab ? /*#__PURE__*/_jsx(TabNodeRenderer, {
|
|
338
|
+
appearance: appearance,
|
|
339
|
+
node: nodeForTab
|
|
254
340
|
}) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
|
|
255
341
|
}, `tab-content-${tab.tabKey}`);
|
|
256
342
|
})
|
|
@@ -277,6 +363,7 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
277
363
|
children: tabs.map(tab => {
|
|
278
364
|
const isFocused = tab.tabKey === tabs[index]?.tabKey;
|
|
279
365
|
const stack = tabBar.stacks[tab.tabKey];
|
|
366
|
+
const node = tabBar.nodes[tab.tabKey];
|
|
280
367
|
const Screen = tabBar.screens[tab.tabKey];
|
|
281
368
|
const icon = getTabIcon(tab);
|
|
282
369
|
return /*#__PURE__*/_jsx(BottomTabsScreen, {
|
|
@@ -292,6 +379,9 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
292
379
|
children: stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
293
380
|
appearance: appearance,
|
|
294
381
|
stack: stack
|
|
382
|
+
}) : node ? /*#__PURE__*/_jsx(TabNodeRenderer, {
|
|
383
|
+
appearance: appearance,
|
|
384
|
+
node: node
|
|
295
385
|
}) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
|
|
296
386
|
}, tab.tabKey);
|
|
297
387
|
})
|
|
@@ -34,6 +34,29 @@ const TabStackRenderer = /*#__PURE__*/memo(({
|
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
TabStackRenderer.displayName = 'TabStackRenderer';
|
|
37
|
+
const TabNodeRenderer = /*#__PURE__*/memo(({
|
|
38
|
+
node,
|
|
39
|
+
appearance
|
|
40
|
+
}) => {
|
|
41
|
+
const Renderer = useMemo(() => node.getRenderer(), [node]);
|
|
42
|
+
return /*#__PURE__*/_jsx(Renderer, {
|
|
43
|
+
appearance: appearance
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
TabNodeRenderer.displayName = 'TabNodeRenderer';
|
|
47
|
+
const joinPrefixAndPath = (prefix, path) => {
|
|
48
|
+
const base = (prefix ?? '').trim();
|
|
49
|
+
const child = (path || '/').trim();
|
|
50
|
+
if (!base) return child || '/';
|
|
51
|
+
const baseNorm = base === '/' ? '' : base.endsWith('/') ? base.slice(0, -1) : base;
|
|
52
|
+
if (!child || child === '/') {
|
|
53
|
+
return baseNorm || '/';
|
|
54
|
+
}
|
|
55
|
+
if (child.startsWith('/')) {
|
|
56
|
+
return `${baseNorm}${child}`;
|
|
57
|
+
}
|
|
58
|
+
return `${baseNorm}/${child}`;
|
|
59
|
+
};
|
|
37
60
|
export const RenderTabBar = /*#__PURE__*/memo(({
|
|
38
61
|
tabBar,
|
|
39
62
|
appearance
|
|
@@ -48,11 +71,13 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
48
71
|
} = snapshot;
|
|
49
72
|
const focusedTab = tabs[index];
|
|
50
73
|
const stack = focusedTab ? tabBar.stacks[focusedTab.tabKey] : undefined;
|
|
74
|
+
const node = focusedTab ? tabBar.nodes[focusedTab.tabKey] : undefined;
|
|
51
75
|
const Screen = focusedTab ? tabBar.screens[focusedTab.tabKey] : undefined;
|
|
52
76
|
const onTabClick = useCallback(nextIndex => {
|
|
53
77
|
const targetTab = tabs[nextIndex];
|
|
54
78
|
if (!targetTab) return;
|
|
55
79
|
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
80
|
+
const targetNode = tabBar.nodes[targetTab.tabKey];
|
|
56
81
|
if (targetStack) {
|
|
57
82
|
// Keep TabBar UI in sync immediately.
|
|
58
83
|
if (nextIndex !== index) {
|
|
@@ -65,6 +90,17 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
65
90
|
router.reset(firstRoutePath);
|
|
66
91
|
return;
|
|
67
92
|
}
|
|
93
|
+
} else if (targetNode) {
|
|
94
|
+
// Keep TabBar UI in sync immediately.
|
|
95
|
+
if (nextIndex !== index) {
|
|
96
|
+
tabBar.onIndexChange(nextIndex);
|
|
97
|
+
}
|
|
98
|
+
const seedPath = targetNode.seed?.()?.path;
|
|
99
|
+
const fallbackFirstPath = targetNode.getNodeRoutes()?.[0]?.path;
|
|
100
|
+
const path = seedPath ?? fallbackFirstPath ?? '/';
|
|
101
|
+
const fullPath = joinPrefixAndPath(targetTab.tabPrefix, path);
|
|
102
|
+
router.reset(fullPath);
|
|
103
|
+
return;
|
|
68
104
|
}
|
|
69
105
|
if (nextIndex !== index) {
|
|
70
106
|
tabBar.onIndexChange(nextIndex);
|
|
@@ -97,6 +133,9 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
97
133
|
children: [stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
98
134
|
appearance: appearance,
|
|
99
135
|
stack: stack
|
|
136
|
+
}) : node ? /*#__PURE__*/_jsx(TabNodeRenderer, {
|
|
137
|
+
appearance: appearance,
|
|
138
|
+
node: node
|
|
100
139
|
}) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null, CustomTabBar ? /*#__PURE__*/_jsx(CustomTabBar, {
|
|
101
140
|
onTabPress: onTabClick,
|
|
102
141
|
activeIndex: index,
|
|
@@ -8,6 +8,7 @@ import { RenderTabBar } from './RenderTabBar';
|
|
|
8
8
|
export class TabBar {
|
|
9
9
|
screens = {};
|
|
10
10
|
stacks = {};
|
|
11
|
+
nodes = {};
|
|
11
12
|
listeners = new Set();
|
|
12
13
|
constructor(options = {}) {
|
|
13
14
|
this.tabBarId = `tabbar-${Math.random().toString(36).slice(2)}`;
|
|
@@ -23,6 +24,10 @@ export class TabBar {
|
|
|
23
24
|
return this.tabBarId;
|
|
24
25
|
}
|
|
25
26
|
addTab(tab) {
|
|
27
|
+
const sourcesCount = (tab.stack ? 1 : 0) + (tab.node ? 1 : 0) + (tab.screen ? 1 : 0);
|
|
28
|
+
if (sourcesCount !== 1) {
|
|
29
|
+
throw new Error(`TabBar.addTab: exactly one of { stack, node, screen } must be provided (got ${sourcesCount})`);
|
|
30
|
+
}
|
|
26
31
|
const {
|
|
27
32
|
key,
|
|
28
33
|
...rest
|
|
@@ -31,6 +36,7 @@ export class TabBar {
|
|
|
31
36
|
const tabKey = key ?? `tab-${nextIndex}`;
|
|
32
37
|
const nextTabs = [...this.state.tabs, {
|
|
33
38
|
tabKey,
|
|
39
|
+
tabPrefix: tab.prefix,
|
|
34
40
|
...rest
|
|
35
41
|
}];
|
|
36
42
|
this.setState({
|
|
@@ -38,6 +44,8 @@ export class TabBar {
|
|
|
38
44
|
});
|
|
39
45
|
if (tab.stack) {
|
|
40
46
|
this.stacks[tabKey] = tab.stack;
|
|
47
|
+
} else if (tab.node) {
|
|
48
|
+
this.nodes[tabKey] = tab.node;
|
|
41
49
|
} else if (tab.screen) {
|
|
42
50
|
this.screens[tabKey] = tab.screen;
|
|
43
51
|
}
|
|
@@ -102,8 +110,8 @@ export class TabBar {
|
|
|
102
110
|
getActiveChildId() {
|
|
103
111
|
const activeTab = this.state.tabs[this.state.index];
|
|
104
112
|
if (!activeTab) return undefined;
|
|
105
|
-
const
|
|
106
|
-
return
|
|
113
|
+
const node = this.nodes[activeTab.tabKey] ?? this.stacks[activeTab.tabKey];
|
|
114
|
+
return node?.getId();
|
|
107
115
|
}
|
|
108
116
|
switchToRoute(routeId) {
|
|
109
117
|
const idx = this.findTabIndexByRoute(routeId);
|
|
@@ -126,11 +134,11 @@ export class TabBar {
|
|
|
126
134
|
const children = [];
|
|
127
135
|
for (let idx = 0; idx < this.state.tabs.length; idx++) {
|
|
128
136
|
const tab = this.state.tabs[idx];
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
137
|
+
const node = tab ? this.nodes[tab.tabKey] ?? this.stacks[tab.tabKey] : undefined;
|
|
138
|
+
if (node) {
|
|
131
139
|
children.push({
|
|
132
|
-
prefix: '',
|
|
133
|
-
node
|
|
140
|
+
prefix: tab?.tabPrefix ?? '',
|
|
141
|
+
node,
|
|
134
142
|
onMatch: () => this.onIndexChange(idx)
|
|
135
143
|
});
|
|
136
144
|
}
|
|
@@ -150,6 +158,10 @@ export class TabBar {
|
|
|
150
158
|
seed() {
|
|
151
159
|
const activeTab = this.state.tabs[this.state.index];
|
|
152
160
|
if (!activeTab) return null;
|
|
161
|
+
const node = this.nodes[activeTab.tabKey];
|
|
162
|
+
if (node) {
|
|
163
|
+
return node.seed?.() ?? null;
|
|
164
|
+
}
|
|
153
165
|
const stack = this.stacks[activeTab.tabKey];
|
|
154
166
|
if (!stack) return null;
|
|
155
167
|
const firstRoute = stack.getFirstRoute();
|
|
@@ -163,9 +175,9 @@ export class TabBar {
|
|
|
163
175
|
findTabIndexByRoute(routeId) {
|
|
164
176
|
for (let i = 0; i < this.state.tabs.length; i++) {
|
|
165
177
|
const tab = this.state.tabs[i];
|
|
166
|
-
const
|
|
167
|
-
if (!
|
|
168
|
-
const hasRoute = this.nodeHasRoute(
|
|
178
|
+
const node = tab ? this.nodes[tab.tabKey] ?? this.stacks[tab.tabKey] : undefined;
|
|
179
|
+
if (!node) continue;
|
|
180
|
+
const hasRoute = this.nodeHasRoute(node, routeId);
|
|
169
181
|
if (hasRoute) {
|
|
170
182
|
return i;
|
|
171
183
|
}
|