@sigmela/router 0.1.3 → 0.2.1
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 +1523 -501
- package/lib/module/RouterContext.js +1 -1
- package/lib/module/ScreenStack/ScreenStack.web.js +388 -117
- package/lib/module/ScreenStack/ScreenStackContext.js +21 -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 +109 -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 +116 -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 +31 -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 +29 -32
- package/package.json +6 -5
- package/lib/module/web/TransitionStack.js +0 -227
- package/lib/typescript/src/web/TransitionStack.d.ts +0 -21
package/lib/module/Navigation.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
// import { ScreenStackItem as RNNScreenStackItem } from 'react-native-screens';
|
|
4
|
-
|
|
5
|
-
import { RenderTabBar } from './TabBar/RenderTabBar';
|
|
6
3
|
import { ScreenStackItem } from "./ScreenStackItem/index.js";
|
|
7
4
|
import { RouterContext } from "./RouterContext.js";
|
|
8
5
|
import { ScreenStack } from "./ScreenStack/index.js";
|
|
@@ -20,19 +17,16 @@ export const Navigation = /*#__PURE__*/memo(({
|
|
|
20
17
|
appearance
|
|
21
18
|
}) => {
|
|
22
19
|
const [root, setRoot] = useState(() => ({
|
|
23
|
-
hasTabBar: router.hasTabBar(),
|
|
24
20
|
rootId: router.getRootStackId()
|
|
25
21
|
}));
|
|
26
22
|
useEffect(() => {
|
|
27
23
|
return router.subscribeRoot(() => {
|
|
28
24
|
setRoot({
|
|
29
|
-
hasTabBar: router.hasTabBar(),
|
|
30
25
|
rootId: router.getRootStackId()
|
|
31
26
|
});
|
|
32
27
|
});
|
|
33
28
|
}, [router]);
|
|
34
29
|
const {
|
|
35
|
-
hasTabBar,
|
|
36
30
|
rootId
|
|
37
31
|
} = root;
|
|
38
32
|
const rootTransition = router.getRootTransition();
|
|
@@ -43,10 +37,7 @@ export const Navigation = /*#__PURE__*/memo(({
|
|
|
43
37
|
value: router,
|
|
44
38
|
children: /*#__PURE__*/_jsxs(ScreenStack, {
|
|
45
39
|
style: styles.flex,
|
|
46
|
-
children: [
|
|
47
|
-
tabBar: router.tabBar,
|
|
48
|
-
appearance: appearance
|
|
49
|
-
}), rootItems.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
|
|
40
|
+
children: [rootItems.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
|
|
50
41
|
stackId: rootId,
|
|
51
42
|
item: item,
|
|
52
43
|
stackAnimation: rootTransition,
|
|
@@ -1,43 +1,82 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { nanoid } from 'nanoid/non-secure';
|
|
4
|
-
import { match } from 'path-to-regexp';
|
|
4
|
+
import { match as pathMatchFactory } from 'path-to-regexp';
|
|
5
|
+
import qs from 'query-string';
|
|
5
6
|
export class NavigationStack {
|
|
6
7
|
routes = [];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor(idOrOptions, maybeOptions) {
|
|
8
|
+
children = [];
|
|
9
|
+
debugEnabled = false;
|
|
10
|
+
constructor(idOrOptions, maybeOptions, maybeDebug) {
|
|
11
11
|
if (typeof idOrOptions === 'string') {
|
|
12
12
|
this.stackId = idOrOptions ?? `stack-${nanoid()}`;
|
|
13
13
|
this.defaultOptions = maybeOptions;
|
|
14
|
+
this.debugEnabled = maybeDebug ?? false;
|
|
14
15
|
} else {
|
|
15
16
|
this.stackId = `stack-${nanoid()}`;
|
|
16
17
|
this.defaultOptions = idOrOptions;
|
|
18
|
+
this.debugEnabled = false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
log(message, data) {
|
|
22
|
+
if (this.debugEnabled) {
|
|
23
|
+
if (data !== undefined) {
|
|
24
|
+
console.log(`[NavigationStack] ${message}`, data);
|
|
25
|
+
} else {
|
|
26
|
+
console.log(`[NavigationStack] ${message}`);
|
|
27
|
+
}
|
|
17
28
|
}
|
|
18
29
|
}
|
|
19
30
|
getId() {
|
|
20
31
|
return this.stackId;
|
|
21
32
|
}
|
|
22
|
-
addScreen(
|
|
33
|
+
addScreen(pathPattern, mixedComponent, options) {
|
|
23
34
|
const {
|
|
24
35
|
component,
|
|
25
|
-
controller
|
|
36
|
+
controller,
|
|
37
|
+
childNode
|
|
26
38
|
} = this.extractComponent(mixedComponent);
|
|
39
|
+
const parsed = qs.parseUrl(pathPattern);
|
|
40
|
+
const pathnamePattern = parsed.url || '/';
|
|
41
|
+
const rawQuery = parsed.query || {};
|
|
42
|
+
const isWildcardPath = pathnamePattern === '*';
|
|
43
|
+
const queryPattern = this.buildQueryPattern(rawQuery);
|
|
44
|
+
const baseSpecificity = this.computeBaseSpecificity(pathnamePattern, isWildcardPath, queryPattern);
|
|
27
45
|
const routeId = `${this.stackId}-route-${this.routes.length}`;
|
|
28
|
-
const
|
|
29
|
-
|
|
46
|
+
const pathMatcher = !isWildcardPath ? pathMatchFactory(pathnamePattern) : null;
|
|
47
|
+
const matchPath = p => {
|
|
48
|
+
if (isWildcardPath) {
|
|
49
|
+
return {
|
|
50
|
+
params: {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const result = pathMatcher ? pathMatcher(p) : null;
|
|
54
|
+
return result ? {
|
|
55
|
+
params: result.params ?? {}
|
|
56
|
+
} : false;
|
|
57
|
+
};
|
|
58
|
+
const builtRoute = {
|
|
30
59
|
routeId,
|
|
31
|
-
path,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
60
|
+
path: pathPattern,
|
|
61
|
+
pathnamePattern,
|
|
62
|
+
isWildcardPath,
|
|
63
|
+
queryPattern,
|
|
64
|
+
baseSpecificity,
|
|
65
|
+
matchPath,
|
|
38
66
|
component,
|
|
39
67
|
controller,
|
|
40
|
-
options
|
|
68
|
+
options,
|
|
69
|
+
childNode
|
|
70
|
+
};
|
|
71
|
+
this.routes.push(builtRoute);
|
|
72
|
+
this.log('addRoute', {
|
|
73
|
+
stackId: this.stackId,
|
|
74
|
+
routeId,
|
|
75
|
+
path: pathPattern,
|
|
76
|
+
pathnamePattern,
|
|
77
|
+
isWildcardPath,
|
|
78
|
+
queryPattern,
|
|
79
|
+
baseSpecificity
|
|
41
80
|
});
|
|
42
81
|
return this;
|
|
43
82
|
}
|
|
@@ -53,6 +92,28 @@ export class NavigationStack {
|
|
|
53
92
|
stackPresentation: 'sheet'
|
|
54
93
|
});
|
|
55
94
|
}
|
|
95
|
+
addStack(prefixOrStack, maybeStack) {
|
|
96
|
+
const hasExplicitPrefix = typeof prefixOrStack === 'string';
|
|
97
|
+
const prefixRaw = hasExplicitPrefix ? prefixOrStack : '';
|
|
98
|
+
const stack = hasExplicitPrefix ? maybeStack : prefixOrStack;
|
|
99
|
+
if (!stack) {
|
|
100
|
+
throw new Error('NavigationStack.addStack: child stack is required');
|
|
101
|
+
}
|
|
102
|
+
const prefix = this.normalizePrefix(prefixRaw ?? '');
|
|
103
|
+
this.children.push({
|
|
104
|
+
prefix,
|
|
105
|
+
node: stack
|
|
106
|
+
});
|
|
107
|
+
this.log('addStack', {
|
|
108
|
+
stackId: this.stackId,
|
|
109
|
+
childId: stack.getId(),
|
|
110
|
+
prefix
|
|
111
|
+
});
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
getChildren() {
|
|
115
|
+
return this.children.slice();
|
|
116
|
+
}
|
|
56
117
|
getRoutes() {
|
|
57
118
|
return this.routes.slice();
|
|
58
119
|
}
|
|
@@ -62,17 +123,105 @@ export class NavigationStack {
|
|
|
62
123
|
getDefaultOptions() {
|
|
63
124
|
return this.defaultOptions;
|
|
64
125
|
}
|
|
126
|
+
getNodeRoutes() {
|
|
127
|
+
return this.getRoutes();
|
|
128
|
+
}
|
|
129
|
+
getNodeChildren() {
|
|
130
|
+
return this.children.slice();
|
|
131
|
+
}
|
|
132
|
+
getRenderer() {
|
|
133
|
+
return () => null;
|
|
134
|
+
}
|
|
135
|
+
seed() {
|
|
136
|
+
const first = this.getFirstRoute();
|
|
137
|
+
if (!first) return null;
|
|
138
|
+
return {
|
|
139
|
+
routeId: first.routeId,
|
|
140
|
+
params: {},
|
|
141
|
+
path: first.path,
|
|
142
|
+
stackId: this.stackId
|
|
143
|
+
};
|
|
144
|
+
}
|
|
65
145
|
extractComponent(component) {
|
|
146
|
+
if (this.isNavigationNode(component)) {
|
|
147
|
+
return {
|
|
148
|
+
controller: undefined,
|
|
149
|
+
component: component.getRenderer(),
|
|
150
|
+
childNode: component
|
|
151
|
+
};
|
|
152
|
+
}
|
|
66
153
|
const componentWithController = component;
|
|
67
154
|
if (componentWithController?.component) {
|
|
68
155
|
return {
|
|
69
156
|
controller: componentWithController.controller,
|
|
70
|
-
component: componentWithController.component
|
|
157
|
+
component: componentWithController.component,
|
|
158
|
+
childNode: componentWithController.childNode
|
|
71
159
|
};
|
|
72
160
|
}
|
|
73
161
|
return {
|
|
74
162
|
component: component,
|
|
75
|
-
controller: undefined
|
|
163
|
+
controller: undefined,
|
|
164
|
+
childNode: undefined
|
|
76
165
|
};
|
|
77
166
|
}
|
|
167
|
+
isNavigationNode(obj) {
|
|
168
|
+
return obj && typeof obj === 'object' && typeof obj.getNodeRoutes === 'function' && typeof obj.getNodeChildren === 'function' && typeof obj.getRenderer === 'function';
|
|
169
|
+
}
|
|
170
|
+
buildQueryPattern(rawQuery) {
|
|
171
|
+
const patternEntries = [];
|
|
172
|
+
for (const [key, value] of Object.entries(rawQuery)) {
|
|
173
|
+
if (typeof value !== 'string') {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (value.startsWith(':') && value.length > 1) {
|
|
177
|
+
patternEntries.push([key, {
|
|
178
|
+
type: 'param',
|
|
179
|
+
name: value.slice(1)
|
|
180
|
+
}]);
|
|
181
|
+
} else {
|
|
182
|
+
patternEntries.push([key, {
|
|
183
|
+
type: 'const',
|
|
184
|
+
value
|
|
185
|
+
}]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!patternEntries.length) return null;
|
|
189
|
+
return Object.fromEntries(patternEntries);
|
|
190
|
+
}
|
|
191
|
+
computeBaseSpecificity(pathnamePattern, isWildcardPath, queryPattern) {
|
|
192
|
+
let pathScore = 0;
|
|
193
|
+
if (!isWildcardPath) {
|
|
194
|
+
const segments = pathnamePattern.split('/').filter(Boolean);
|
|
195
|
+
for (const seg of segments) {
|
|
196
|
+
if (seg.startsWith(':')) {
|
|
197
|
+
pathScore += 1;
|
|
198
|
+
} else {
|
|
199
|
+
pathScore += 2;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
pathScore = 0;
|
|
204
|
+
}
|
|
205
|
+
let queryScore = 0;
|
|
206
|
+
if (queryPattern) {
|
|
207
|
+
for (const token of Object.values(queryPattern)) {
|
|
208
|
+
if (token.type === 'const') {
|
|
209
|
+
queryScore += 2;
|
|
210
|
+
} else {
|
|
211
|
+
queryScore += 1;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return pathScore + queryScore;
|
|
216
|
+
}
|
|
217
|
+
normalizePrefix(input) {
|
|
218
|
+
if (!input || input === '/') {
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
let normalized = input.startsWith('/') ? input : `/${input}`;
|
|
222
|
+
if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
223
|
+
normalized = normalized.slice(0, -1);
|
|
224
|
+
}
|
|
225
|
+
return normalized;
|
|
226
|
+
}
|
|
78
227
|
}
|