@sigmela/router 0.0.11
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/LICENSE +20 -0
- package/README.md +346 -0
- package/lib/module/Navigation.js +74 -0
- package/lib/module/NavigationStack.js +72 -0
- package/lib/module/Router.js +571 -0
- package/lib/module/RouterContext.js +33 -0
- package/lib/module/ScreenStackItem.js +61 -0
- package/lib/module/StackRenderer.js +29 -0
- package/lib/module/TabBar/RenderTabBar.js +122 -0
- package/lib/module/TabBar/TabBar.js +74 -0
- package/lib/module/TabBar/TabBarContext.js +4 -0
- package/lib/module/TabBar/useTabBar.js +11 -0
- package/lib/module/createController.js +5 -0
- package/lib/module/index.js +14 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +3 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Navigation.d.ts +8 -0
- package/lib/typescript/src/NavigationStack.d.ts +30 -0
- package/lib/typescript/src/Router.d.ts +70 -0
- package/lib/typescript/src/RouterContext.d.ts +19 -0
- package/lib/typescript/src/ScreenStackItem.d.ts +12 -0
- package/lib/typescript/src/StackRenderer.d.ts +6 -0
- package/lib/typescript/src/TabBar/RenderTabBar.d.ts +8 -0
- package/lib/typescript/src/TabBar/TabBar.d.ts +43 -0
- package/lib/typescript/src/TabBar/TabBarContext.d.ts +3 -0
- package/lib/typescript/src/TabBar/useTabBar.d.ts +2 -0
- package/lib/typescript/src/createController.d.ts +14 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/types.d.ts +244 -0
- package/package.json +166 -0
- package/src/Navigation.tsx +102 -0
- package/src/NavigationStack.ts +106 -0
- package/src/Router.ts +684 -0
- package/src/RouterContext.tsx +58 -0
- package/src/ScreenStackItem.tsx +64 -0
- package/src/StackRenderer.tsx +41 -0
- package/src/TabBar/RenderTabBar.tsx +154 -0
- package/src/TabBar/TabBar.ts +106 -0
- package/src/TabBar/TabBarContext.ts +4 -0
- package/src/TabBar/useTabBar.ts +10 -0
- package/src/createController.ts +27 -0
- package/src/index.ts +24 -0
- package/src/types.ts +272 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { ColorValue, StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
import type { BottomTabsScreenAppearance, BottomTabsScreenProps, ScreenProps as RNSScreenProps, ScreenStackHeaderConfigProps, TabBarItemLabelVisibilityMode, TabBarMinimizeBehavior } from 'react-native-screens';
|
|
3
|
+
export type TabItem = Omit<BottomTabsScreenProps, 'isFocused' | 'children'>;
|
|
4
|
+
export type NavigationState<Route extends TabItem> = {
|
|
5
|
+
index: number;
|
|
6
|
+
routes: Route[];
|
|
7
|
+
};
|
|
8
|
+
export type Scope = 'global' | 'tab' | 'root';
|
|
9
|
+
export type ScreenOptions = Partial<RNSScreenProps> & {
|
|
10
|
+
header?: ScreenStackHeaderConfigProps;
|
|
11
|
+
};
|
|
12
|
+
export type HistoryItem = {
|
|
13
|
+
key: string;
|
|
14
|
+
scope: Scope;
|
|
15
|
+
routeId: string;
|
|
16
|
+
component: React.ComponentType<any>;
|
|
17
|
+
options?: ScreenOptions;
|
|
18
|
+
params?: Record<string, unknown>;
|
|
19
|
+
query?: Record<string, unknown>;
|
|
20
|
+
passProps?: any;
|
|
21
|
+
tabIndex?: number;
|
|
22
|
+
stackId?: string;
|
|
23
|
+
pattern?: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
};
|
|
26
|
+
export type VisibleRoute = {
|
|
27
|
+
routeId: string;
|
|
28
|
+
stackId?: string;
|
|
29
|
+
tabIndex?: number;
|
|
30
|
+
scope: Scope;
|
|
31
|
+
path?: string;
|
|
32
|
+
params?: Record<string, unknown>;
|
|
33
|
+
query?: Record<string, unknown>;
|
|
34
|
+
} | null;
|
|
35
|
+
export type CompiledRoute = {
|
|
36
|
+
routeId: string;
|
|
37
|
+
scope: Scope;
|
|
38
|
+
path: string;
|
|
39
|
+
match: (path: string) => false | {
|
|
40
|
+
params: Record<string, any>;
|
|
41
|
+
};
|
|
42
|
+
component: React.ComponentType<any>;
|
|
43
|
+
controller?: import('./createController').Controller<any, any>;
|
|
44
|
+
options?: ScreenOptions;
|
|
45
|
+
tabIndex?: number;
|
|
46
|
+
stackId?: string;
|
|
47
|
+
};
|
|
48
|
+
export type TabBarConfig = {
|
|
49
|
+
/**
|
|
50
|
+
* @summary Specifies the minimize behavior for the tab bar.
|
|
51
|
+
*
|
|
52
|
+
* Available starting from iOS 26.
|
|
53
|
+
*
|
|
54
|
+
* The following values are currently supported:
|
|
55
|
+
*
|
|
56
|
+
* - `automatic` - resolves to the system default minimize behavior
|
|
57
|
+
* - `never` - the tab bar does not minimize
|
|
58
|
+
* - `onScrollDown` - the tab bar minimizes when scrolling down and
|
|
59
|
+
* expands when scrolling back up
|
|
60
|
+
* - `onScrollUp` - the tab bar minimizes when scrolling up and expands
|
|
61
|
+
* when scrolling back down
|
|
62
|
+
*
|
|
63
|
+
* The supported values correspond to the official UIKit documentation:
|
|
64
|
+
* @see {@link https://developer.apple.com/documentation/uikit/uitabbarcontroller/minimizebehavior|UITabBarController.MinimizeBehavior}
|
|
65
|
+
*
|
|
66
|
+
* @default Defaults to `automatic`.
|
|
67
|
+
*
|
|
68
|
+
* @platform ios
|
|
69
|
+
* @supported iOS 26 or higher
|
|
70
|
+
*/
|
|
71
|
+
tabBarMinimizeBehavior?: TabBarMinimizeBehavior;
|
|
72
|
+
};
|
|
73
|
+
export interface NavigationAppearance {
|
|
74
|
+
tabBar?: {
|
|
75
|
+
/**
|
|
76
|
+
* @summary Specifies the standard tab bar appearance.
|
|
77
|
+
*
|
|
78
|
+
* Allows to customize the appearance depending on the tab bar item layout (stacked,
|
|
79
|
+
* inline, compact inline) and state (normal, selected, focused, disabled).
|
|
80
|
+
*
|
|
81
|
+
* @platform ios
|
|
82
|
+
*/
|
|
83
|
+
standardAppearance?: BottomTabsScreenAppearance;
|
|
84
|
+
/**
|
|
85
|
+
* @summary Specifies the tab bar appearace when edge of scrollable content aligns
|
|
86
|
+
* with the edge of the tab bar.
|
|
87
|
+
*
|
|
88
|
+
* Allows to customize the appearance depending on the tab bar item layout (stacked,
|
|
89
|
+
* inline, compact inline) and state (normal, selected, focused, disabled).
|
|
90
|
+
*
|
|
91
|
+
* If this property is `undefined`, UIKit uses `standardAppearance`, modified to
|
|
92
|
+
* have a transparent background.
|
|
93
|
+
*
|
|
94
|
+
* @platform ios
|
|
95
|
+
*/
|
|
96
|
+
scrollEdgeAppearance?: BottomTabsScreenAppearance;
|
|
97
|
+
/**
|
|
98
|
+
* @summary Specifies the background color for the entire tab bar.
|
|
99
|
+
*
|
|
100
|
+
* @platform android
|
|
101
|
+
*/
|
|
102
|
+
backgroundColor?: ColorValue;
|
|
103
|
+
tabBarItemStyle?: {
|
|
104
|
+
/**
|
|
105
|
+
* @summary Specifies the font family used for the title of each tab bar item.
|
|
106
|
+
*
|
|
107
|
+
* @platform android
|
|
108
|
+
*/
|
|
109
|
+
titleFontFamily?: TextStyle['fontFamily'];
|
|
110
|
+
/**
|
|
111
|
+
* @summary Specifies the font size used for the title of each tab bar item.
|
|
112
|
+
*
|
|
113
|
+
* The size is represented in scale-independent pixels (sp).
|
|
114
|
+
*
|
|
115
|
+
* @platform android
|
|
116
|
+
*/
|
|
117
|
+
titleFontSize?: TextStyle['fontSize'];
|
|
118
|
+
/**
|
|
119
|
+
* @summary Specifies the font size used for the title of each tab bar item in active state.
|
|
120
|
+
*
|
|
121
|
+
* The size is represented in scale-independent pixels (sp).
|
|
122
|
+
*
|
|
123
|
+
* @platform android
|
|
124
|
+
*/
|
|
125
|
+
titleFontSizeActive?: TextStyle['fontSize'];
|
|
126
|
+
/**
|
|
127
|
+
* @summary Specifies the font weight used for the title of each tab bar item.
|
|
128
|
+
*
|
|
129
|
+
* @platform android
|
|
130
|
+
*/
|
|
131
|
+
titleFontWeight?: TextStyle['fontWeight'];
|
|
132
|
+
/**
|
|
133
|
+
* @summary Specifies the font style used for the title of each tab bar item.
|
|
134
|
+
*
|
|
135
|
+
* @platform android
|
|
136
|
+
*/
|
|
137
|
+
titleFontStyle?: TextStyle['fontStyle'];
|
|
138
|
+
/**
|
|
139
|
+
* @summary Specifies the font color used for the title of each tab bar item.
|
|
140
|
+
*
|
|
141
|
+
* @platform android
|
|
142
|
+
*/
|
|
143
|
+
titleFontColor?: TextStyle['color'];
|
|
144
|
+
/**
|
|
145
|
+
* @summary Specifies the font color used for the title of each tab bar item in active state.
|
|
146
|
+
*
|
|
147
|
+
* If not provided, `tabBarItemTitleFontColor` is used.
|
|
148
|
+
*
|
|
149
|
+
* @platform android
|
|
150
|
+
*/
|
|
151
|
+
titleFontColorActive?: TextStyle['color'];
|
|
152
|
+
/**
|
|
153
|
+
* @summary Specifies the icon color for each tab bar item.
|
|
154
|
+
*
|
|
155
|
+
* @platform android
|
|
156
|
+
*/
|
|
157
|
+
iconColor?: ColorValue;
|
|
158
|
+
/**
|
|
159
|
+
* @summary Specifies the icon color for each tab bar item in active state.
|
|
160
|
+
*
|
|
161
|
+
* If not provided, `tabBarItemIconColor` is used.
|
|
162
|
+
*
|
|
163
|
+
* @platform android
|
|
164
|
+
*/
|
|
165
|
+
iconColorActive?: ColorValue;
|
|
166
|
+
/**
|
|
167
|
+
* @summary Specifies the background color of the active indicator.
|
|
168
|
+
*
|
|
169
|
+
* @platform android
|
|
170
|
+
*/
|
|
171
|
+
activeIndicatorColor?: ColorValue;
|
|
172
|
+
/**
|
|
173
|
+
* @summary Specifies if the active indicator should be used.
|
|
174
|
+
*
|
|
175
|
+
* @default true
|
|
176
|
+
*
|
|
177
|
+
* @platform android
|
|
178
|
+
*/
|
|
179
|
+
activeIndicatorEnabled?: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* @summary Specifies the color of each tab bar item's ripple effect.
|
|
182
|
+
*
|
|
183
|
+
* @platform android
|
|
184
|
+
*/
|
|
185
|
+
rippleColor?: ColorValue;
|
|
186
|
+
/**
|
|
187
|
+
* @summary Specifies the label visibility mode.
|
|
188
|
+
*
|
|
189
|
+
* The label visibility mode defines when the labels of each item bar should be displayed.
|
|
190
|
+
*
|
|
191
|
+
* The following values are available:
|
|
192
|
+
* - `auto` - the label behaves as in “labeled” mode when there are 3 items or less, or as in “selected” mode when there are 4 items or more
|
|
193
|
+
* - `selected` - the label is only shown on the selected navigation item
|
|
194
|
+
* - `labeled` - the label is shown on all navigation items
|
|
195
|
+
* - `unlabeled` - the label is hidden for all navigation items
|
|
196
|
+
*
|
|
197
|
+
* The supported values correspond to the official Material Components documentation:
|
|
198
|
+
* @see {@link https://github.com/material-components/material-components-android/blob/master/docs/components/BottomNavigation.md#making-navigation-bar-accessible|Material Components documentation}
|
|
199
|
+
*
|
|
200
|
+
* @default auto
|
|
201
|
+
* @platform android
|
|
202
|
+
*/
|
|
203
|
+
labelVisibilityMode?: TabBarItemLabelVisibilityMode;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* @summary Specifies the color used for selected tab's text and icon color.
|
|
207
|
+
*
|
|
208
|
+
* Starting from iOS 26, it also impacts glow of Liquid Glass tab
|
|
209
|
+
* selection view.
|
|
210
|
+
*
|
|
211
|
+
* `tabBarItemTitleFontColor` and `tabBarItemIconColor` defined on
|
|
212
|
+
* BottomTabsScreen component override this color.
|
|
213
|
+
*
|
|
214
|
+
* @platform ios
|
|
215
|
+
*/
|
|
216
|
+
tintColor?: ColorValue;
|
|
217
|
+
/**
|
|
218
|
+
* @summary Experimental prop for changing container control.
|
|
219
|
+
*
|
|
220
|
+
* If set to true, tab screen changes need to be handled by JS using
|
|
221
|
+
* onNativeFocusChange callback (controlled/programatically-driven).
|
|
222
|
+
*
|
|
223
|
+
* If set to false, tab screen change will not be prevented by the
|
|
224
|
+
* native side (managed/natively-driven).
|
|
225
|
+
*
|
|
226
|
+
* On iOS, some features are not fully implemented for managed tabs
|
|
227
|
+
* (e.g. overrideScrollViewContentInsetAdjustmentBehavior).
|
|
228
|
+
*
|
|
229
|
+
* On Android, only controlled tabs are currently supported.
|
|
230
|
+
*
|
|
231
|
+
* @default Defaults to `false`.
|
|
232
|
+
*
|
|
233
|
+
* @platform android, ios
|
|
234
|
+
*/
|
|
235
|
+
experimentalControlNavigationStateInJS?: boolean;
|
|
236
|
+
};
|
|
237
|
+
screenStyle?: StyleProp<ViewStyle>;
|
|
238
|
+
/**
|
|
239
|
+
* Global header appearance applied to all screens with visible headers.
|
|
240
|
+
* Per-screen header options override these.
|
|
241
|
+
*/
|
|
242
|
+
header?: ScreenStackHeaderConfigProps;
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigmela/router",
|
|
3
|
+
"version": "0.0.11",
|
|
4
|
+
"description": "React Native Router",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.ts",
|
|
10
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
11
|
+
"default": "./lib/module/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"*.podspec",
|
|
22
|
+
"react-native.config.js",
|
|
23
|
+
"!ios/build",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!android/gradle",
|
|
26
|
+
"!android/gradlew",
|
|
27
|
+
"!android/gradlew.bat",
|
|
28
|
+
"!android/local.properties",
|
|
29
|
+
"!**/__tests__",
|
|
30
|
+
"!**/__fixtures__",
|
|
31
|
+
"!**/__mocks__",
|
|
32
|
+
"!**/.*"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"example": "yarn workspace @sigmela/router-example",
|
|
36
|
+
"test": "jest",
|
|
37
|
+
"typecheck": "tsc",
|
|
38
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
39
|
+
"clean": "del-cli lib",
|
|
40
|
+
"prepare": "bob build && del-cli \"lib/**/*.map\"",
|
|
41
|
+
"release": "release-it --only-version"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react-native",
|
|
45
|
+
"ios",
|
|
46
|
+
"android"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/sigmela/router.git"
|
|
51
|
+
},
|
|
52
|
+
"author": "https://github.com/bogoslavskiy <help@sigmela.com> (https://github.com/bogoslavskiy)",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/sigmela/router/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/sigmela/router#readme",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public",
|
|
60
|
+
"registry": "https://registry.npmjs.org/"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
64
|
+
"@eslint/compat": "^1.3.2",
|
|
65
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
66
|
+
"@eslint/js": "^9.35.0",
|
|
67
|
+
"@evilmartians/lefthook": "^1.12.3",
|
|
68
|
+
"@react-native/babel-preset": "0.81.1",
|
|
69
|
+
"@react-native/eslint-config": "^0.81.1",
|
|
70
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
71
|
+
"@types/jest": "^29.5.14",
|
|
72
|
+
"@types/react": "^19.1.12",
|
|
73
|
+
"commitlint": "^19.8.1",
|
|
74
|
+
"del-cli": "^6.0.0",
|
|
75
|
+
"eslint": "^9.35.0",
|
|
76
|
+
"eslint-config-prettier": "^10.1.8",
|
|
77
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
78
|
+
"jest": "^29.7.0",
|
|
79
|
+
"prettier": "^3.6.2",
|
|
80
|
+
"react": "19.1.0",
|
|
81
|
+
"react-native": "0.81.4",
|
|
82
|
+
"react-native-builder-bob": "^0.40.13",
|
|
83
|
+
"react-native-screens": "^4.16.0",
|
|
84
|
+
"release-it": "^19.0.4",
|
|
85
|
+
"typescript": "^5.9.2"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"react": "*",
|
|
89
|
+
"react-native": "*",
|
|
90
|
+
"react-native-screens": ">=4.16.0"
|
|
91
|
+
},
|
|
92
|
+
"workspaces": [
|
|
93
|
+
"example"
|
|
94
|
+
],
|
|
95
|
+
"packageManager": "yarn@3.6.1",
|
|
96
|
+
"jest": {
|
|
97
|
+
"preset": "react-native",
|
|
98
|
+
"modulePathIgnorePatterns": [
|
|
99
|
+
"<rootDir>/example/node_modules",
|
|
100
|
+
"<rootDir>/lib/"
|
|
101
|
+
],
|
|
102
|
+
"transformIgnorePatterns": [
|
|
103
|
+
"node_modules/(?!((jest-)?react-native|@react-native|react-native-screens|nanoid|query-string|decode-uri-component|split-on-first|filter-obj)/)"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"commitlint": {
|
|
107
|
+
"extends": [
|
|
108
|
+
"@commitlint/config-conventional"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"release-it": {
|
|
112
|
+
"git": {
|
|
113
|
+
"commitMessage": "chore: release ${version}",
|
|
114
|
+
"tagName": "v${version}"
|
|
115
|
+
},
|
|
116
|
+
"npm": {
|
|
117
|
+
"publish": true
|
|
118
|
+
},
|
|
119
|
+
"github": {
|
|
120
|
+
"release": true
|
|
121
|
+
},
|
|
122
|
+
"plugins": {
|
|
123
|
+
"@release-it/conventional-changelog": {
|
|
124
|
+
"preset": {
|
|
125
|
+
"name": "angular"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"prettier": {
|
|
131
|
+
"quoteProps": "consistent",
|
|
132
|
+
"singleQuote": true,
|
|
133
|
+
"tabWidth": 2,
|
|
134
|
+
"trailingComma": "es5",
|
|
135
|
+
"useTabs": false
|
|
136
|
+
},
|
|
137
|
+
"react-native-builder-bob": {
|
|
138
|
+
"source": "src",
|
|
139
|
+
"output": "lib",
|
|
140
|
+
"targets": [
|
|
141
|
+
[
|
|
142
|
+
"module",
|
|
143
|
+
{
|
|
144
|
+
"esm": true,
|
|
145
|
+
"sourceMaps": false
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
"typescript",
|
|
150
|
+
{
|
|
151
|
+
"project": "tsconfig.build.json"
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
"create-react-native-library": {
|
|
157
|
+
"languages": "js",
|
|
158
|
+
"type": "library",
|
|
159
|
+
"version": "0.54.3"
|
|
160
|
+
},
|
|
161
|
+
"dependencies": {
|
|
162
|
+
"nanoid": "^5.1.6",
|
|
163
|
+
"path-to-regexp": "^8.3.0",
|
|
164
|
+
"query-string": "^9.3.1"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ScreenStack,
|
|
3
|
+
ScreenStackItem as RNNScreenStackItem,
|
|
4
|
+
} from 'react-native-screens';
|
|
5
|
+
import {
|
|
6
|
+
memo,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useState,
|
|
10
|
+
useSyncExternalStore,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { RenderTabBar } from './TabBar/RenderTabBar';
|
|
13
|
+
import { ScreenStackItem } from './ScreenStackItem';
|
|
14
|
+
import { RouterContext } from './RouterContext';
|
|
15
|
+
import { StyleSheet } from 'react-native';
|
|
16
|
+
import { Router } from './Router';
|
|
17
|
+
import type { NavigationAppearance } from './types';
|
|
18
|
+
|
|
19
|
+
export interface NavigationProps {
|
|
20
|
+
router: Router;
|
|
21
|
+
appearance?: NavigationAppearance;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const EMPTY_HISTORY: any[] = [];
|
|
25
|
+
|
|
26
|
+
function useStackHistory(router: Router, stackId?: string) {
|
|
27
|
+
const subscribe = useCallback(
|
|
28
|
+
(cb: () => void) =>
|
|
29
|
+
stackId ? router.subscribeStack(stackId, cb) : () => {},
|
|
30
|
+
[router, stackId]
|
|
31
|
+
);
|
|
32
|
+
const get = useCallback(
|
|
33
|
+
() => (stackId ? router.getStackHistory(stackId) : EMPTY_HISTORY),
|
|
34
|
+
[router, stackId]
|
|
35
|
+
);
|
|
36
|
+
return useSyncExternalStore(subscribe, get, get);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Navigation = memo<NavigationProps>(({ router, appearance }) => {
|
|
40
|
+
const [root, setRoot] = useState(() => ({
|
|
41
|
+
hasTabBar: router.hasTabBar(),
|
|
42
|
+
rootId: router.getRootStackId(),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return router.subscribeRoot(() => {
|
|
47
|
+
setRoot({
|
|
48
|
+
hasTabBar: router.hasTabBar(),
|
|
49
|
+
rootId: router.getRootStackId(),
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}, [router]);
|
|
53
|
+
|
|
54
|
+
const { hasTabBar, rootId } = root;
|
|
55
|
+
const rootTransition = router.getRootTransition();
|
|
56
|
+
const globalId = router.getGlobalStackId();
|
|
57
|
+
const rootItems = useStackHistory(router, rootId);
|
|
58
|
+
const globalItems = useStackHistory(router, globalId);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<RouterContext.Provider value={router}>
|
|
62
|
+
<ScreenStack style={styles.flex}>
|
|
63
|
+
{hasTabBar && (
|
|
64
|
+
<RNNScreenStackItem
|
|
65
|
+
screenId="root-tabbar"
|
|
66
|
+
headerConfig={{ hidden: true }}
|
|
67
|
+
style={styles.flex}
|
|
68
|
+
stackAnimation={rootTransition}
|
|
69
|
+
>
|
|
70
|
+
<RenderTabBar
|
|
71
|
+
tabBar={router.tabBar!}
|
|
72
|
+
appearance={appearance?.tabBar}
|
|
73
|
+
/>
|
|
74
|
+
</RNNScreenStackItem>
|
|
75
|
+
)}
|
|
76
|
+
{rootItems.map((item) => (
|
|
77
|
+
<ScreenStackItem
|
|
78
|
+
key={item.key}
|
|
79
|
+
stackId={rootId}
|
|
80
|
+
item={item}
|
|
81
|
+
stackAnimation={rootTransition}
|
|
82
|
+
screenStyle={appearance?.screenStyle}
|
|
83
|
+
headerAppearance={appearance?.header}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
86
|
+
{globalItems.map((item) => (
|
|
87
|
+
<ScreenStackItem
|
|
88
|
+
key={item.key}
|
|
89
|
+
stackId={globalId}
|
|
90
|
+
item={item}
|
|
91
|
+
screenStyle={appearance?.screenStyle}
|
|
92
|
+
headerAppearance={appearance?.header}
|
|
93
|
+
/>
|
|
94
|
+
))}
|
|
95
|
+
</ScreenStack>
|
|
96
|
+
</RouterContext.Provider>
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const styles = StyleSheet.create({
|
|
101
|
+
flex: { flex: 1 },
|
|
102
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ScreenOptions } from './types';
|
|
2
|
+
import { nanoid } from 'nanoid/non-secure';
|
|
3
|
+
import { match } from 'path-to-regexp';
|
|
4
|
+
import {
|
|
5
|
+
type ComponentWithController,
|
|
6
|
+
type MixedComponent,
|
|
7
|
+
} from './createController';
|
|
8
|
+
|
|
9
|
+
type BuiltRoute = {
|
|
10
|
+
routeId: string;
|
|
11
|
+
path: string;
|
|
12
|
+
match: (path: string) => false | { params: Record<string, any> };
|
|
13
|
+
component: React.ComponentType<any>;
|
|
14
|
+
controller?: ComponentWithController['controller'];
|
|
15
|
+
options?: ScreenOptions; // per-screen options only (no stack defaults merged)
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class NavigationStack {
|
|
19
|
+
private readonly stackId: string;
|
|
20
|
+
private readonly routes: BuiltRoute[] = [];
|
|
21
|
+
private readonly defaultOptions: ScreenOptions | undefined;
|
|
22
|
+
|
|
23
|
+
// Overloads
|
|
24
|
+
constructor();
|
|
25
|
+
constructor(id: string);
|
|
26
|
+
constructor(defaultOptions: ScreenOptions);
|
|
27
|
+
constructor(id: string, defaultOptions: ScreenOptions);
|
|
28
|
+
constructor(
|
|
29
|
+
idOrOptions?: string | ScreenOptions,
|
|
30
|
+
maybeOptions?: ScreenOptions
|
|
31
|
+
) {
|
|
32
|
+
if (typeof idOrOptions === 'string') {
|
|
33
|
+
this.stackId = idOrOptions ?? `stack-${nanoid()}`;
|
|
34
|
+
this.defaultOptions = maybeOptions;
|
|
35
|
+
} else {
|
|
36
|
+
this.stackId = `stack-${nanoid()}`;
|
|
37
|
+
this.defaultOptions = idOrOptions;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public getId(): string {
|
|
42
|
+
return this.stackId;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public addScreen(
|
|
46
|
+
path: string,
|
|
47
|
+
mixedComponent: MixedComponent,
|
|
48
|
+
options?: ScreenOptions
|
|
49
|
+
): NavigationStack {
|
|
50
|
+
const { component, controller } = this.extractComponent(mixedComponent);
|
|
51
|
+
const routeId = `${this.stackId}-route-${this.routes.length}`;
|
|
52
|
+
const matcher = match(path);
|
|
53
|
+
|
|
54
|
+
this.routes.push({
|
|
55
|
+
routeId,
|
|
56
|
+
path,
|
|
57
|
+
match: (p: string) => {
|
|
58
|
+
const result = matcher(p);
|
|
59
|
+
return result ? { params: (result as any).params ?? {} } : false;
|
|
60
|
+
},
|
|
61
|
+
component,
|
|
62
|
+
controller,
|
|
63
|
+
options,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public addModal(
|
|
70
|
+
path: string,
|
|
71
|
+
mixedComponent: MixedComponent,
|
|
72
|
+
options?: ScreenOptions
|
|
73
|
+
): NavigationStack {
|
|
74
|
+
return this.addScreen(path, mixedComponent, {
|
|
75
|
+
...options,
|
|
76
|
+
stackPresentation: 'modal',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public getRoutes(): BuiltRoute[] {
|
|
81
|
+
return this.routes.slice();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public getFirstRoute(): BuiltRoute | undefined {
|
|
85
|
+
return this.routes[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public getDefaultOptions(): ScreenOptions | undefined {
|
|
89
|
+
return this.defaultOptions;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private extractComponent(component: MixedComponent) {
|
|
93
|
+
const componentWithController = component as ComponentWithController;
|
|
94
|
+
if (componentWithController?.component) {
|
|
95
|
+
return {
|
|
96
|
+
controller: componentWithController.controller,
|
|
97
|
+
component: componentWithController.component,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
component: component as React.ComponentType<any>,
|
|
103
|
+
controller: undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|