@sigmela/router 0.1.2 → 0.2.0
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 +177 -833
- package/lib/module/Navigation.js +1 -10
- package/lib/module/NavigationStack.js +168 -19
- package/lib/module/Router.js +1508 -501
- package/lib/module/RouterContext.js +1 -1
- package/lib/module/ScreenStack/ScreenStack.web.js +343 -117
- package/lib/module/ScreenStack/ScreenStackContext.js +15 -0
- package/lib/module/ScreenStack/animationHelpers.js +72 -0
- package/lib/module/ScreenStackItem/ScreenStackItem.js +2 -1
- package/lib/module/ScreenStackItem/ScreenStackItem.web.js +76 -16
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +2 -1
- package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.web.js +1 -1
- package/lib/module/SplitView/RenderSplitView.native.js +85 -0
- package/lib/module/SplitView/RenderSplitView.web.js +79 -0
- package/lib/module/SplitView/SplitView.js +89 -0
- package/lib/module/SplitView/SplitViewContext.js +4 -0
- package/lib/module/SplitView/index.js +5 -0
- package/lib/module/SplitView/useSplitView.js +11 -0
- package/lib/module/StackRenderer.js +4 -2
- package/lib/module/TabBar/RenderTabBar.native.js +118 -33
- package/lib/module/TabBar/RenderTabBar.web.js +52 -47
- package/lib/module/TabBar/TabBar.js +117 -3
- package/lib/module/TabBar/index.js +4 -1
- package/lib/module/TabBar/useTabBarHeight.js +22 -0
- package/lib/module/index.js +3 -4
- package/lib/module/navigationNode.js +3 -0
- package/lib/module/styles.css +693 -28
- package/lib/typescript/src/NavigationStack.d.ts +25 -13
- package/lib/typescript/src/Router.d.ts +147 -34
- package/lib/typescript/src/RouterContext.d.ts +1 -1
- package/lib/typescript/src/ScreenStack/ScreenStack.web.d.ts +0 -2
- package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +22 -0
- package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +6 -0
- package/lib/typescript/src/ScreenStackItem/ScreenStackItem.types.d.ts +5 -1
- package/lib/typescript/src/ScreenStackItem/ScreenStackItem.web.d.ts +1 -1
- package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +8 -0
- package/lib/typescript/src/SplitView/RenderSplitView.web.d.ts +8 -0
- package/lib/typescript/src/SplitView/SplitView.d.ts +31 -0
- package/lib/typescript/src/SplitView/SplitViewContext.d.ts +3 -0
- package/lib/typescript/src/SplitView/index.d.ts +5 -0
- package/lib/typescript/src/SplitView/useSplitView.d.ts +2 -0
- package/lib/typescript/src/StackRenderer.d.ts +2 -1
- package/lib/typescript/src/TabBar/TabBar.d.ts +27 -3
- package/lib/typescript/src/TabBar/index.d.ts +3 -0
- package/lib/typescript/src/TabBar/useTabBarHeight.d.ts +18 -0
- package/lib/typescript/src/createController.d.ts +1 -0
- package/lib/typescript/src/index.d.ts +4 -3
- package/lib/typescript/src/navigationNode.d.ts +41 -0
- package/lib/typescript/src/types.d.ts +21 -32
- package/package.json +6 -5
- package/lib/module/web/TransitionStack.js +0 -227
- package/lib/typescript/src/web/TransitionStack.d.ts +0 -21
|
@@ -4,7 +4,7 @@ import { StackRenderer } from "../StackRenderer.js";
|
|
|
4
4
|
import { TabBarContext } from "./TabBarContext.js";
|
|
5
5
|
import { useRouter } from "../RouterContext.js";
|
|
6
6
|
import { TabIcon } from './TabIcon';
|
|
7
|
-
import { useCallback, useSyncExternalStore, memo,
|
|
7
|
+
import { useCallback, useSyncExternalStore, memo, useMemo } from 'react';
|
|
8
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
9
|
const isImageSource = value => {
|
|
10
10
|
if (value == null) return false;
|
|
@@ -18,9 +18,22 @@ const isImageSource = value => {
|
|
|
18
18
|
return false;
|
|
19
19
|
};
|
|
20
20
|
const toColorString = c => typeof c === 'string' ? c : undefined;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const TabStackRenderer = /*#__PURE__*/memo(({
|
|
22
|
+
stack,
|
|
23
|
+
appearance
|
|
24
|
+
}) => {
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
const stackId = stack.getId();
|
|
27
|
+
const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
|
|
28
|
+
const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
|
|
29
|
+
const history = useSyncExternalStore(subscribe, get, get);
|
|
30
|
+
return /*#__PURE__*/_jsx(StackRenderer, {
|
|
31
|
+
appearance: appearance,
|
|
32
|
+
stack: stack,
|
|
33
|
+
history: history
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
TabStackRenderer.displayName = 'TabStackRenderer';
|
|
24
37
|
export const RenderTabBar = /*#__PURE__*/memo(({
|
|
25
38
|
tabBar,
|
|
26
39
|
appearance
|
|
@@ -33,9 +46,6 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
33
46
|
index,
|
|
34
47
|
config
|
|
35
48
|
} = snapshot;
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
router.ensureTabSeed(index);
|
|
38
|
-
}, [index, router]);
|
|
39
49
|
const focusedTab = tabs[index];
|
|
40
50
|
const stack = focusedTab ? tabBar.stacks[focusedTab.tabKey] : undefined;
|
|
41
51
|
const Screen = focusedTab ? tabBar.screens[focusedTab.tabKey] : undefined;
|
|
@@ -44,22 +54,21 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
44
54
|
if (!targetTab) return;
|
|
45
55
|
const targetStack = tabBar.stacks[targetTab.tabKey];
|
|
46
56
|
if (targetStack) {
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
router.replace(toPath, true);
|
|
57
|
+
// Keep TabBar UI in sync immediately.
|
|
58
|
+
if (nextIndex !== index) {
|
|
59
|
+
tabBar.onIndexChange(nextIndex);
|
|
60
|
+
}
|
|
61
|
+
const firstRoutePath = targetStack.getFirstRoute()?.path;
|
|
62
|
+
if (firstRoutePath) {
|
|
63
|
+
// Web behavior: reset all preserved stacks when switching tabs.
|
|
64
|
+
// This keeps browser URL and Router state always consistent.
|
|
65
|
+
router.reset(firstRoutePath);
|
|
57
66
|
return;
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
if (nextIndex !== index) {
|
|
70
|
+
tabBar.onIndexChange(nextIndex);
|
|
71
|
+
}
|
|
63
72
|
}, [router, tabBar, tabs, index]);
|
|
64
73
|
const tabBarStyle = useMemo(() => {
|
|
65
74
|
const tabBarBg = toColorString(appearance?.tabBar?.backgroundColor);
|
|
@@ -73,27 +82,23 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
73
82
|
fontWeight: appearance?.tabBar?.title?.fontWeight,
|
|
74
83
|
fontStyle: appearance?.tabBar?.title?.fontStyle
|
|
75
84
|
}), [appearance?.tabBar?.title?.fontFamily, appearance?.tabBar?.title?.fontSize, appearance?.tabBar?.title?.fontWeight, appearance?.tabBar?.title?.fontStyle]);
|
|
76
|
-
|
|
77
|
-
// If a custom component is provided, render it instead of the default web tab bar
|
|
78
85
|
const CustomTabBar = config.component;
|
|
79
|
-
return /*#__PURE__*/_jsx(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
className: "tab-bar",
|
|
96
|
-
style: tabBarStyle,
|
|
86
|
+
return /*#__PURE__*/_jsx(TabBarContext.Provider, {
|
|
87
|
+
value: tabBar,
|
|
88
|
+
children: /*#__PURE__*/_jsxs("div", {
|
|
89
|
+
className: "tab-stacks-container",
|
|
90
|
+
children: [stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
|
|
91
|
+
appearance: appearance,
|
|
92
|
+
stack: stack
|
|
93
|
+
}) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null, CustomTabBar ? /*#__PURE__*/_jsx(CustomTabBar, {
|
|
94
|
+
onTabPress: onTabClick,
|
|
95
|
+
activeIndex: index,
|
|
96
|
+
tabs: tabs
|
|
97
|
+
}) : /*#__PURE__*/_jsx("div", {
|
|
98
|
+
className: "tab-bar",
|
|
99
|
+
style: tabBarStyle,
|
|
100
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
101
|
+
className: "tab-bar-inner",
|
|
97
102
|
children: tabs.map((tab, i) => {
|
|
98
103
|
const isActive = i === index;
|
|
99
104
|
const iconTint = toColorString(isActive ? appearance?.tabBar?.iconColorActive : appearance?.tabBar?.iconColor);
|
|
@@ -113,18 +118,18 @@ export const RenderTabBar = /*#__PURE__*/memo(({
|
|
|
113
118
|
source: tab.icon,
|
|
114
119
|
tintColor: iconTint
|
|
115
120
|
}) : null
|
|
116
|
-
}),
|
|
117
|
-
className: "tab-item-label-badge",
|
|
118
|
-
children: tab.badgeValue
|
|
119
|
-
}) : null, /*#__PURE__*/_jsx("div", {
|
|
121
|
+
}), /*#__PURE__*/_jsx("div", {
|
|
120
122
|
className: "tab-item-label",
|
|
121
123
|
style: labelStyle,
|
|
122
124
|
children: tab.title
|
|
123
|
-
})
|
|
125
|
+
}), tab.badgeValue ? /*#__PURE__*/_jsx("span", {
|
|
126
|
+
className: "tab-item-label-badge",
|
|
127
|
+
children: tab.badgeValue
|
|
128
|
+
}) : null]
|
|
124
129
|
}, tab.tabKey);
|
|
125
130
|
})
|
|
126
|
-
})
|
|
127
|
-
})
|
|
131
|
+
})
|
|
132
|
+
})]
|
|
128
133
|
})
|
|
129
134
|
});
|
|
130
135
|
});
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { RenderTabBar } from './RenderTabBar';
|
|
5
|
+
|
|
6
|
+
// Legacy icon format for backward compatibility
|
|
4
7
|
|
|
5
8
|
export class TabBar {
|
|
6
9
|
screens = {};
|
|
7
10
|
stacks = {};
|
|
8
11
|
listeners = new Set();
|
|
9
12
|
constructor(options = {}) {
|
|
13
|
+
this.tabBarId = `tabbar-${Math.random().toString(36).slice(2)}`;
|
|
10
14
|
this.state = {
|
|
11
15
|
tabs: [],
|
|
12
16
|
index: options.initialIndex ?? 0,
|
|
@@ -15,6 +19,9 @@ export class TabBar {
|
|
|
15
19
|
}
|
|
16
20
|
};
|
|
17
21
|
}
|
|
22
|
+
getId() {
|
|
23
|
+
return this.tabBarId;
|
|
24
|
+
}
|
|
18
25
|
addTab(tab) {
|
|
19
26
|
const {
|
|
20
27
|
key,
|
|
@@ -22,9 +29,12 @@ export class TabBar {
|
|
|
22
29
|
} = tab;
|
|
23
30
|
const nextIndex = this.state.tabs.length;
|
|
24
31
|
const tabKey = key ?? `tab-${nextIndex}`;
|
|
25
|
-
this.state.tabs
|
|
32
|
+
const nextTabs = [...this.state.tabs, {
|
|
26
33
|
tabKey,
|
|
27
34
|
...rest
|
|
35
|
+
}];
|
|
36
|
+
this.setState({
|
|
37
|
+
tabs: nextTabs
|
|
28
38
|
});
|
|
29
39
|
if (tab.stack) {
|
|
30
40
|
this.stacks[tabKey] = tab.stack;
|
|
@@ -65,7 +75,18 @@ export class TabBar {
|
|
|
65
75
|
this.notifyListeners();
|
|
66
76
|
}
|
|
67
77
|
notifyListeners() {
|
|
68
|
-
|
|
78
|
+
// Do not allow one listener to break all others.
|
|
79
|
+
for (const listener of Array.from(this.listeners)) {
|
|
80
|
+
try {
|
|
81
|
+
listener();
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// TabBar has no debug flag; keep behavior quiet in production.
|
|
84
|
+
if (__DEV__) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.error('[TabBar] listener error', e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
69
90
|
}
|
|
70
91
|
subscribe(listener) {
|
|
71
92
|
this.listeners.add(listener);
|
|
@@ -73,4 +94,97 @@ export class TabBar {
|
|
|
73
94
|
this.listeners.delete(listener);
|
|
74
95
|
};
|
|
75
96
|
}
|
|
97
|
+
getTabs() {
|
|
98
|
+
return this.state.tabs.slice();
|
|
99
|
+
}
|
|
100
|
+
getInitialIndex() {
|
|
101
|
+
return this.state.index ?? 0;
|
|
102
|
+
}
|
|
103
|
+
getActiveChildId() {
|
|
104
|
+
const activeTab = this.state.tabs[this.state.index];
|
|
105
|
+
if (!activeTab) return undefined;
|
|
106
|
+
const stack = this.stacks[activeTab.tabKey];
|
|
107
|
+
return stack?.getId();
|
|
108
|
+
}
|
|
109
|
+
switchToRoute(routeId) {
|
|
110
|
+
const idx = this.findTabIndexByRoute(routeId);
|
|
111
|
+
if (idx === -1) return;
|
|
112
|
+
if (idx === this.state.index) return;
|
|
113
|
+
this.setState({
|
|
114
|
+
index: idx
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
hasRoute(routeId) {
|
|
118
|
+
return this.findTabIndexByRoute(routeId) !== -1;
|
|
119
|
+
}
|
|
120
|
+
setActiveChildByRoute(routeId) {
|
|
121
|
+
this.switchToRoute(routeId);
|
|
122
|
+
}
|
|
123
|
+
getNodeRoutes() {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
getNodeChildren() {
|
|
127
|
+
const children = [];
|
|
128
|
+
for (let idx = 0; idx < this.state.tabs.length; idx++) {
|
|
129
|
+
const tab = this.state.tabs[idx];
|
|
130
|
+
const stack = tab ? this.stacks[tab.tabKey] : undefined;
|
|
131
|
+
if (stack) {
|
|
132
|
+
children.push({
|
|
133
|
+
prefix: '',
|
|
134
|
+
node: stack,
|
|
135
|
+
onMatch: () => this.onIndexChange(idx)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return children;
|
|
140
|
+
}
|
|
141
|
+
getRenderer() {
|
|
142
|
+
// eslint-disable-next-line consistent-this
|
|
143
|
+
const tabBarInstance = this;
|
|
144
|
+
return function TabBarScreen(props) {
|
|
145
|
+
return /*#__PURE__*/React.createElement(RenderTabBar, {
|
|
146
|
+
tabBar: tabBarInstance,
|
|
147
|
+
appearance: props?.appearance
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
seed() {
|
|
152
|
+
const activeTab = this.state.tabs[this.state.index];
|
|
153
|
+
if (!activeTab) return null;
|
|
154
|
+
const stack = this.stacks[activeTab.tabKey];
|
|
155
|
+
if (!stack) return null;
|
|
156
|
+
const firstRoute = stack.getFirstRoute();
|
|
157
|
+
if (!firstRoute) return null;
|
|
158
|
+
return {
|
|
159
|
+
routeId: firstRoute.routeId,
|
|
160
|
+
path: firstRoute.path,
|
|
161
|
+
stackId: stack.getId()
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
findTabIndexByRoute(routeId) {
|
|
165
|
+
for (let i = 0; i < this.state.tabs.length; i++) {
|
|
166
|
+
const tab = this.state.tabs[i];
|
|
167
|
+
const stack = tab && this.stacks[tab.tabKey];
|
|
168
|
+
if (!stack) continue;
|
|
169
|
+
const hasRoute = this.nodeHasRoute(stack, routeId);
|
|
170
|
+
if (hasRoute) {
|
|
171
|
+
return i;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return -1;
|
|
175
|
+
}
|
|
176
|
+
nodeHasRoute(node, routeId) {
|
|
177
|
+
const routes = node.getNodeRoutes();
|
|
178
|
+
for (const r of routes) {
|
|
179
|
+
if (r.routeId === routeId) return true;
|
|
180
|
+
if (r.childNode) {
|
|
181
|
+
if (this.nodeHasRoute(r.childNode, routeId)) return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const children = node.getNodeChildren();
|
|
185
|
+
for (const child of children) {
|
|
186
|
+
if (this.nodeHasRoute(child.node, routeId)) return true;
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
76
190
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
export { RenderTabBar } from './RenderTabBar';
|
|
3
|
+
export { RenderTabBar } from './RenderTabBar';
|
|
4
|
+
export { TabBar } from "./TabBar.js";
|
|
5
|
+
export { useTabBar } from "./useTabBar.js";
|
|
6
|
+
export { useTabBarHeight, TAB_BAR_HEIGHT } from "./useTabBarHeight.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The height of the TabBar in pixels
|
|
5
|
+
*/
|
|
6
|
+
export const TAB_BAR_HEIGHT = 57;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook that returns the height of the TabBar
|
|
10
|
+
* @returns {number} The height of the TabBar in pixels
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const TabBarExample = () => {
|
|
15
|
+
* const tabBarHeight = useTabBarHeight();
|
|
16
|
+
* return <div style={{ paddingBottom: tabBarHeight }}>Content</div>;
|
|
17
|
+
* };
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const useTabBarHeight = () => {
|
|
21
|
+
return TAB_BAR_HEIGHT;
|
|
22
|
+
};
|
package/lib/module/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
export { useTabBar } from "./TabBar/useTabBar.js";
|
|
4
|
+
export { useTabBarHeight, TAB_BAR_HEIGHT } from "./TabBar/useTabBarHeight.js";
|
|
4
5
|
export { TabBar } from "./TabBar/TabBar.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
|
|
6
|
+
export { SplitView } from "./SplitView/SplitView.js";
|
|
7
|
+
export { useSplitView } from "./SplitView/useSplitView.js";
|
|
9
8
|
export { Router } from "./Router.js";
|
|
10
9
|
export { Navigation } from "./Navigation.js";
|
|
11
10
|
export { createController } from "./createController.js";
|