@react-navigation/core 8.0.0-alpha.2 → 8.0.0-alpha.20
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/lib/module/BaseNavigationContainer.js +20 -9
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/ConsumedParamsContext.js +5 -0
- package/lib/module/ConsumedParamsContext.js.map +1 -0
- package/lib/module/NavigationBuilderContext.js +1 -0
- package/lib/module/NavigationBuilderContext.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +8 -4
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/NavigationProvider.js +14 -3
- package/lib/module/NavigationProvider.js.map +1 -1
- package/lib/module/NavigationStateContext.js.map +1 -1
- package/lib/module/PreventRemoveProvider.js +3 -3
- package/lib/module/PreventRemoveProvider.js.map +1 -1
- package/lib/module/SceneView.js +46 -56
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/StaticNavigation.js +166 -46
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/checkSerializable.js +11 -4
- package/lib/module/checkSerializable.js.map +1 -1
- package/lib/module/createNavigatorFactory.js +27 -5
- package/lib/module/createNavigatorFactory.js.map +1 -1
- package/lib/module/findFocusedRoute.js +7 -4
- package/lib/module/findFocusedRoute.js.map +1 -1
- package/lib/module/getActionFromState.js +40 -14
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getPathFromState.js +48 -11
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +3 -0
- package/lib/module/getPatternParts.js.map +1 -1
- package/lib/module/getStateFromPath.js +353 -166
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/getStateFromRouteParams.js +24 -0
- package/lib/module/getStateFromRouteParams.js.map +1 -0
- package/lib/module/index.js +3 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/theming/useTheme.js +1 -1
- package/lib/module/theming/useTheme.js.map +1 -1
- package/lib/module/types.js +15 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/useDescriptors.js +18 -9
- package/lib/module/useDescriptors.js.map +1 -1
- package/lib/module/useEventEmitter.js +43 -31
- package/lib/module/useEventEmitter.js.map +1 -1
- package/lib/module/useFocusEvents.js +6 -2
- package/lib/module/useFocusEvents.js.map +1 -1
- package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
- package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
- package/lib/module/useIsFocused.js +7 -12
- package/lib/module/useIsFocused.js.map +1 -1
- package/lib/module/useNavigation.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +144 -86
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +3 -3
- package/lib/module/useNavigationCache.js.map +1 -1
- package/lib/module/useNavigationHelpers.js +1 -1
- package/lib/module/useNavigationHelpers.js.map +1 -1
- package/lib/module/useNavigationIndependentTree.js +1 -1
- package/lib/module/useNavigationIndependentTree.js.map +1 -1
- package/lib/module/useNavigationState.js +6 -4
- package/lib/module/useNavigationState.js.map +1 -1
- package/lib/module/useOnAction.js +3 -3
- package/lib/module/useOnAction.js.map +1 -1
- package/lib/module/useOnGetState.js +6 -3
- package/lib/module/useOnGetState.js.map +1 -1
- package/lib/module/useOnPreventRemove.js +3 -3
- package/lib/module/useOnPreventRemove.js.map +1 -1
- package/lib/module/useOnRouteFocus.js +1 -1
- package/lib/module/useOnRouteFocus.js.map +1 -1
- package/lib/module/useOptionsGetters.js +7 -4
- package/lib/module/useOptionsGetters.js.map +1 -1
- package/lib/module/usePreventRemove.js +1 -1
- package/lib/module/usePreventRemove.js.map +1 -1
- package/lib/module/usePreventRemoveContext.js +1 -1
- package/lib/module/usePreventRemoveContext.js.map +1 -1
- package/lib/module/useRegisterNavigator.js +2 -2
- package/lib/module/useRegisterNavigator.js.map +1 -1
- package/lib/module/useRoute.js.map +1 -1
- package/lib/module/useRouteCache.js +2 -1
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/module/useScheduleUpdate.js +1 -1
- package/lib/module/useScheduleUpdate.js.map +1 -1
- package/lib/module/useStateForPath.js +1 -1
- package/lib/module/useStateForPath.js.map +1 -1
- package/lib/module/validatePathConfig.js +2 -1
- package/lib/module/validatePathConfig.js.map +1 -1
- package/lib/typescript/src/BaseNavigationContainer.d.ts +6 -3
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/ConsumedParamsContext.d.ts +8 -0
- package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -0
- package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
- package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
- package/lib/typescript/src/Group.d.ts +1 -1
- package/lib/typescript/src/Group.d.ts.map +1 -1
- package/lib/typescript/src/NavigationBuilderContext.d.ts +12 -6
- package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationContainerRefContext.d.ts +1 -1
- package/lib/typescript/src/NavigationContainerRefContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
- package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationHelpersContext.d.ts +1 -1
- package/lib/typescript/src/NavigationHelpersContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/NavigationProvider.d.ts +10 -10
- package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
- package/lib/typescript/src/NavigationStateContext.d.ts +3 -3
- package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +3 -2
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/Screen.d.ts +1 -1
- package/lib/typescript/src/Screen.d.ts.map +1 -1
- package/lib/typescript/src/StaticContainer.d.ts +1 -1
- package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
- package/lib/typescript/src/StaticNavigation.d.ts +123 -92
- package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/src/checkSerializable.d.ts +5 -3
- package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
- package/lib/typescript/src/createNavigationContainerRef.d.ts +1 -1
- package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
- package/lib/typescript/src/createNavigatorFactory.d.ts +17 -5
- package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
- package/lib/typescript/src/findFocusedRoute.d.ts +3 -3
- package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
- package/lib/typescript/src/getActionFromState.d.ts +4 -4
- package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
- package/lib/typescript/src/getPathFromState.d.ts +3 -3
- package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/src/getPatternParts.d.ts +1 -0
- package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts +5 -5
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
- package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +34 -33
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/theming/ThemeContext.d.ts +1 -1
- package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -1
- package/lib/typescript/src/theming/ThemeProvider.d.ts +2 -2
- package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/theming/useTheme.d.ts +1 -1
- package/lib/typescript/src/theming/useTheme.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +215 -131
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useChildListeners.d.ts +3 -3
- package/lib/typescript/src/useChildListeners.d.ts.map +1 -1
- package/lib/typescript/src/useComponent.d.ts +1 -1
- package/lib/typescript/src/useComponent.d.ts.map +1 -1
- package/lib/typescript/src/useDescriptors.d.ts +7 -7
- package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
- package/lib/typescript/src/useEventEmitter.d.ts +1 -1
- package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEvents.d.ts +2 -2
- package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
- package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts +2 -2
- package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts.map +1 -1
- package/lib/typescript/src/useIsFocused.d.ts +3 -0
- package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
- package/lib/typescript/src/useKeyedChildListeners.d.ts +3 -3
- package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
- package/lib/typescript/src/useNavigation.d.ts +5 -3
- package/lib/typescript/src/useNavigation.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts +24 -24
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationCache.d.ts +3 -3
- package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationContainerRef.d.ts +1 -1
- package/lib/typescript/src/useNavigationContainerRef.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationHelpers.d.ts +19 -19
- package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationState.d.ts +2 -2
- package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
- package/lib/typescript/src/useOnAction.d.ts +9 -9
- package/lib/typescript/src/useOnAction.d.ts.map +1 -1
- package/lib/typescript/src/useOnGetState.d.ts +1 -1
- package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
- package/lib/typescript/src/useOnPreventRemove.d.ts +3 -3
- package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
- package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
- package/lib/typescript/src/useOptionsGetters.d.ts +1 -1
- package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
- package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -1
- package/lib/typescript/src/useRoute.d.ts +2 -2
- package/lib/typescript/src/useRoute.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts +4 -4
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
- package/lib/typescript/src/useStateForPath.d.ts +1 -1
- package/lib/typescript/src/useStateForPath.d.ts.map +1 -1
- package/lib/typescript/src/utilities.d.ts +45 -5
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
- package/package.json +23 -20
- package/src/BaseNavigationContainer.tsx +337 -326
- package/src/ConsumedParamsContext.tsx +10 -0
- package/src/NavigationBuilderContext.tsx +14 -8
- package/src/NavigationFocusedRouteStateContext.tsx +4 -4
- package/src/NavigationIndependentTree.tsx +8 -5
- package/src/NavigationProvider.tsx +17 -3
- package/src/NavigationStateContext.tsx +5 -6
- package/src/PreventRemoveProvider.tsx +3 -3
- package/src/SceneView.tsx +58 -56
- package/src/StaticNavigation.tsx +488 -250
- package/src/checkSerializable.tsx +22 -12
- package/src/createNavigatorFactory.tsx +83 -8
- package/src/findFocusedRoute.tsx +13 -8
- package/src/getActionFromState.tsx +68 -39
- package/src/getPathFromState.tsx +91 -27
- package/src/getPatternParts.tsx +8 -0
- package/src/getStateFromPath.tsx +555 -180
- package/src/getStateFromRouteParams.tsx +60 -0
- package/src/index.tsx +47 -5
- package/src/theming/useTheme.tsx +1 -1
- package/src/types.tsx +541 -338
- package/src/useDescriptors.tsx +27 -12
- package/src/useEventEmitter.tsx +45 -36
- package/src/useFocusEvents.tsx +8 -2
- package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
- package/src/useIsFocused.tsx +14 -21
- package/src/useNavigation.tsx +8 -4
- package/src/useNavigationBuilder.tsx +220 -132
- package/src/useNavigationCache.tsx +6 -6
- package/src/useNavigationHelpers.tsx +1 -1
- package/src/useNavigationIndependentTree.tsx +1 -1
- package/src/useNavigationState.tsx +11 -7
- package/src/useOnAction.tsx +10 -10
- package/src/useOnGetState.tsx +5 -3
- package/src/useOnPreventRemove.tsx +3 -3
- package/src/useOnRouteFocus.tsx +10 -12
- package/src/useOptionsGetters.tsx +7 -4
- package/src/usePreventRemove.tsx +1 -1
- package/src/usePreventRemoveContext.tsx +1 -1
- package/src/useRegisterNavigator.tsx +2 -2
- package/src/useRoute.tsx +4 -2
- package/src/useRouteCache.tsx +2 -1
- package/src/useScheduleUpdate.tsx +1 -3
- package/src/useStateForPath.tsx +1 -1
- package/src/utilities.tsx +101 -5
- package/src/validatePathConfig.tsx +1 -0
- package/lib/module/CurrentRenderContext.js +0 -10
- package/lib/module/CurrentRenderContext.js.map +0 -1
- package/lib/module/useCurrentRender.js +0 -19
- package/lib/module/useCurrentRender.js.map +0 -1
- package/lib/typescript/src/CurrentRenderContext.d.ts +0 -9
- package/lib/typescript/src/CurrentRenderContext.d.ts.map +0 -1
- package/lib/typescript/src/useCurrentRender.d.ts +0 -14
- package/lib/typescript/src/useCurrentRender.d.ts.map +0 -1
- package/src/CurrentRenderContext.tsx +0 -9
- package/src/useCurrentRender.tsx +0 -31
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import escape from 'escape-string-regexp';
|
|
4
|
-
import
|
|
4
|
+
import queryString from 'query-string';
|
|
5
5
|
import { arrayStartsWith } from "./arrayStartsWith.js";
|
|
6
6
|
import { findFocusedRoute } from "./findFocusedRoute.js";
|
|
7
|
-
import { getPatternParts } from "./getPatternParts.js";
|
|
7
|
+
import { combinePatternParts, getPatternParts } from "./getPatternParts.js";
|
|
8
8
|
import { isArrayEqual } from "./isArrayEqual.js";
|
|
9
9
|
import { validatePathConfig } from "./validatePathConfig.js";
|
|
10
|
+
const INVALID_SCHEMA_RESULT_ERROR = 'Invalid validation result from schema. It should be an object with either "value" or "issues" property and cannot be asynchronous.';
|
|
11
|
+
const INVALID_PARSER_ERROR = 'Invalid parser. Expected a function or a Standard Schema V1 object.';
|
|
12
|
+
const PARAM_GROUP_PREFIX = 'param_';
|
|
13
|
+
const NESTED_SCREEN_PARAM_NAMES = ['screen', 'params', 'initial', 'path', 'merge', 'pop'];
|
|
14
|
+
const getExplicitParamNames = parse => {
|
|
15
|
+
const names = Object.entries(parse ?? {}).filter(([, parser]) => parser != null).map(([name]) => name);
|
|
16
|
+
return names.length ? new Set(names) : undefined;
|
|
17
|
+
};
|
|
18
|
+
const getStandardSchema = parser => {
|
|
19
|
+
if ('~standard' in parser && typeof parser['~standard'] === 'object' && parser['~standard'] !== null && 'version' in parser['~standard'] && parser['~standard'].version === 1 && 'validate' in parser['~standard'] && typeof parser['~standard'].validate === 'function') {
|
|
20
|
+
return parser['~standard'];
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
};
|
|
24
|
+
const getValidationResult = (schema, value) => {
|
|
25
|
+
const result = schema.validate(value);
|
|
26
|
+
if (result != null && typeof result === 'object' && ('value' in result || 'issues' in result && Array.isArray(result.issues))) {
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(INVALID_SCHEMA_RESULT_ERROR);
|
|
30
|
+
};
|
|
31
|
+
|
|
10
32
|
/**
|
|
11
33
|
* Utility to parse a path string to initial state object accepted by the container.
|
|
12
34
|
* This is useful for deep linking when we need to handle the incoming URL.
|
|
@@ -28,12 +50,26 @@ import { validatePathConfig } from "./validatePathConfig.js";
|
|
|
28
50
|
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
|
|
29
51
|
* @param options Extra options to fine-tune how to parse the path.
|
|
30
52
|
*/
|
|
31
|
-
export function getStateFromPath(path, options) {
|
|
53
|
+
export function getStateFromPath(path, options, previous) {
|
|
32
54
|
const {
|
|
33
55
|
initialRoutes,
|
|
34
|
-
configs
|
|
56
|
+
configs,
|
|
57
|
+
configsByScreen,
|
|
58
|
+
configsByPattern
|
|
35
59
|
} = getConfigResources(options);
|
|
36
60
|
const screens = options?.screens;
|
|
61
|
+
|
|
62
|
+
// When a matched config is shared, the same pattern can resolve to multiple branches
|
|
63
|
+
// pick the one that best matches the current navigation state
|
|
64
|
+
const resolveSharedConfig = config => {
|
|
65
|
+
if (config.shared) {
|
|
66
|
+
const candidates = configsByPattern[config.segments.join('/')];
|
|
67
|
+
if (candidates) {
|
|
68
|
+
return selectSharedConfig(config, candidates, previous, initialRoutes);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return config;
|
|
72
|
+
};
|
|
37
73
|
let remaining = path.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
|
38
74
|
.replace(/^\//, '') // Remove extra leading slash
|
|
39
75
|
.replace(/\?.*$/, ''); // Remove query params which we will handle later
|
|
@@ -54,14 +90,22 @@ export function getStateFromPath(path, options) {
|
|
|
54
90
|
// Remove the prefix from the path
|
|
55
91
|
remaining = remaining.replace(normalizedPrefix, '');
|
|
56
92
|
}
|
|
93
|
+
const decodedSegments = [];
|
|
94
|
+
for (const segment of remaining.split('/')) {
|
|
95
|
+
if (!segment) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
decodedSegments.push(decodeURIComponent(segment));
|
|
100
|
+
} catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
57
104
|
if (screens === undefined) {
|
|
58
105
|
// When no config is specified, use the path segments as route names
|
|
59
|
-
const routes =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
name
|
|
63
|
-
};
|
|
64
|
-
});
|
|
106
|
+
const routes = decodedSegments.map(name => ({
|
|
107
|
+
name
|
|
108
|
+
}));
|
|
65
109
|
if (routes.length) {
|
|
66
110
|
return createNestedStateObject(path, routes, initialRoutes);
|
|
67
111
|
}
|
|
@@ -72,31 +116,37 @@ export function getStateFromPath(path, options) {
|
|
|
72
116
|
// When handling empty path, we should only look at the root level config
|
|
73
117
|
const match = configs.find(config => config.segments.join('/') === '');
|
|
74
118
|
if (match) {
|
|
75
|
-
|
|
119
|
+
const config = resolveSharedConfig(match);
|
|
120
|
+
return createNestedStateObject(path, config.routeNames.map(name => ({
|
|
76
121
|
name
|
|
77
|
-
})), initialRoutes,
|
|
122
|
+
})), initialRoutes, config);
|
|
78
123
|
}
|
|
79
124
|
return undefined;
|
|
80
125
|
}
|
|
81
|
-
let result;
|
|
82
|
-
let current;
|
|
83
126
|
|
|
84
127
|
// We match the whole path against the regex instead of segments
|
|
85
128
|
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
|
86
|
-
const {
|
|
87
|
-
routes,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
129
|
+
for (const config of configs) {
|
|
130
|
+
const routes = matchAgainstConfig(remaining, config, configsByScreen);
|
|
131
|
+
if (routes === undefined) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
let selectedConfig = resolveSharedConfig(config);
|
|
135
|
+
let selectedRoutes = routes;
|
|
136
|
+
if (selectedConfig !== config) {
|
|
137
|
+
const rematchedRoutes = matchAgainstConfig(remaining, selectedConfig, configsByScreen);
|
|
138
|
+
if (rematchedRoutes === undefined) {
|
|
139
|
+
selectedConfig = config;
|
|
140
|
+
} else {
|
|
141
|
+
selectedRoutes = rematchedRoutes;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const state = createNestedStateObject(path, selectedRoutes, initialRoutes, selectedConfig);
|
|
145
|
+
if (state !== undefined) {
|
|
146
|
+
return state;
|
|
147
|
+
}
|
|
98
148
|
}
|
|
99
|
-
return
|
|
149
|
+
return undefined;
|
|
100
150
|
}
|
|
101
151
|
|
|
102
152
|
/**
|
|
@@ -118,11 +168,17 @@ function prepareConfigResources(options) {
|
|
|
118
168
|
const initialRoutes = getInitialRoutes(options);
|
|
119
169
|
const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
|
|
120
170
|
checkForDuplicatedConfigs(configs);
|
|
121
|
-
const
|
|
171
|
+
const configsByScreen = {};
|
|
172
|
+
const configsByPattern = {};
|
|
173
|
+
for (const c of configs) {
|
|
174
|
+
(configsByScreen[c.screen] ??= []).push(c);
|
|
175
|
+
(configsByPattern[c.segments.join('/')] ??= []).push(c);
|
|
176
|
+
}
|
|
122
177
|
return {
|
|
123
178
|
initialRoutes,
|
|
124
179
|
configs,
|
|
125
|
-
|
|
180
|
+
configsByScreen,
|
|
181
|
+
configsByPattern
|
|
126
182
|
};
|
|
127
183
|
}
|
|
128
184
|
function getInitialRoutes(options) {
|
|
@@ -137,7 +193,10 @@ function getInitialRoutes(options) {
|
|
|
137
193
|
}
|
|
138
194
|
function getSortedNormalizedConfigs(initialRoutes, screens = {}) {
|
|
139
195
|
// Create a normalized configs array which will be easier to use
|
|
140
|
-
return [].concat(...Object.keys(screens).map(key => createNormalizedConfigs(key, screens, initialRoutes, [], [], []))).
|
|
196
|
+
return [].concat(...Object.keys(screens).map(key => createNormalizedConfigs(key, screens, initialRoutes, [], [], []))).map((config, order) => ({
|
|
197
|
+
...config,
|
|
198
|
+
order
|
|
199
|
+
})).sort((a, b) => {
|
|
141
200
|
// Sort config from most specific to least specific:
|
|
142
201
|
// - more segments
|
|
143
202
|
// - static segments
|
|
@@ -160,21 +219,24 @@ function getSortedNormalizedConfigs(initialRoutes, screens = {}) {
|
|
|
160
219
|
return 1;
|
|
161
220
|
}
|
|
162
221
|
for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
|
|
222
|
+
const aSegment = a.segments[i];
|
|
223
|
+
const bSegment = b.segments[i];
|
|
224
|
+
|
|
163
225
|
// if b is longer, b gets higher priority
|
|
164
|
-
if (
|
|
226
|
+
if (aSegment == null) {
|
|
165
227
|
return 1;
|
|
166
228
|
}
|
|
167
229
|
|
|
168
230
|
// if a is longer, a gets higher priority
|
|
169
|
-
if (
|
|
231
|
+
if (bSegment == null) {
|
|
170
232
|
return -1;
|
|
171
233
|
}
|
|
172
|
-
const aWildCard =
|
|
173
|
-
const bWildCard =
|
|
174
|
-
const aParam =
|
|
175
|
-
const bParam =
|
|
176
|
-
const aRegex = aParam &&
|
|
177
|
-
const bRegex = bParam &&
|
|
234
|
+
const aWildCard = aSegment === '*';
|
|
235
|
+
const bWildCard = bSegment === '*';
|
|
236
|
+
const aParam = aSegment.startsWith(':');
|
|
237
|
+
const bParam = bSegment.startsWith(':');
|
|
238
|
+
const aRegex = aParam && aSegment.includes('(');
|
|
239
|
+
const bRegex = bParam && bSegment.includes('(');
|
|
178
240
|
|
|
179
241
|
// if both are wildcard or regex, we compare next component
|
|
180
242
|
if (aWildCard && bWildCard || aRegex && bRegex) {
|
|
@@ -225,8 +287,8 @@ function checkForDuplicatedConfigs(configs) {
|
|
|
225
287
|
// It's not a problem if the path string omitted from a inner most screen
|
|
226
288
|
// For example, it's ok if a path resolves to `A > B > C` or `A > B`
|
|
227
289
|
const intersects = a.length > b.length ? b.every((it, i) => a[i] === it) : a.every((it, i) => b[i] === it);
|
|
228
|
-
if (!intersects) {
|
|
229
|
-
throw new Error(`Found conflicting screens with the same pattern. The pattern '${pattern}' resolves to both '${a.join(' > ')}' and '${b.join(' > ')}'. Patterns must be unique and cannot resolve to more than one screen.`);
|
|
290
|
+
if (!intersects && (!acc[pattern].shared || !config.shared)) {
|
|
291
|
+
throw new Error(`Found conflicting screens with the same pattern. The pattern '${pattern}' resolves to both '${a.join(' > ')}' and '${b.join(' > ')}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`);
|
|
230
292
|
}
|
|
231
293
|
}
|
|
232
294
|
return Object.assign(acc, {
|
|
@@ -234,122 +296,131 @@ function checkForDuplicatedConfigs(configs) {
|
|
|
234
296
|
});
|
|
235
297
|
}, {});
|
|
236
298
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
299
|
+
const matchAgainstConfig = (remaining, config, configsByScreen) => {
|
|
300
|
+
if (!config.regex) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
const match = remaining.match(config.regex);
|
|
304
|
+
if (!match) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
let validationFailed = false;
|
|
308
|
+
const matchedRoutes = [];
|
|
309
|
+
for (const [index, routeName] of config.routeNames.entries()) {
|
|
310
|
+
// Check matching name, route names and pattern in case same screen is used
|
|
311
|
+
// at different levels or branches in config
|
|
312
|
+
const routeConfig = configsByScreen[routeName]?.find(c => index === c.routeNames.length - 1 && arrayStartsWith(config.routeNames, c.routeNames) && arrayStartsWith(config.segments, c.segments));
|
|
313
|
+
let params;
|
|
314
|
+
if (routeConfig && match.groups) {
|
|
315
|
+
const paramEntries = [];
|
|
316
|
+
for (const [key, value] of Object.entries(match.groups)) {
|
|
317
|
+
const index = Number(key.replace(PARAM_GROUP_PREFIX, ''));
|
|
318
|
+
const param = routeConfig.params.find(it => it.index === index);
|
|
319
|
+
if (param?.screen !== routeName || !param.name) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (value == null) {
|
|
323
|
+
paramEntries.push([param.name, undefined]);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
let decoded;
|
|
327
|
+
try {
|
|
328
|
+
decoded = decodeURIComponent(value);
|
|
329
|
+
} catch {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
const parser = routeConfig.parse?.[param.name];
|
|
333
|
+
if (!parser) {
|
|
334
|
+
paramEntries.push([param.name, decoded]);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const schema = getStandardSchema(parser);
|
|
338
|
+
if (schema) {
|
|
339
|
+
const result = getValidationResult(schema, decoded);
|
|
340
|
+
if (result.issues) {
|
|
341
|
+
validationFailed = true;
|
|
342
|
+
break;
|
|
272
343
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return [key, parsed];
|
|
276
|
-
})) : undefined;
|
|
277
|
-
if (params && Object.keys(params).length) {
|
|
278
|
-
return {
|
|
279
|
-
name: routeName,
|
|
280
|
-
params
|
|
281
|
-
};
|
|
344
|
+
paramEntries.push([param.name, result.value]);
|
|
345
|
+
continue;
|
|
282
346
|
}
|
|
283
|
-
|
|
284
|
-
name
|
|
285
|
-
|
|
347
|
+
if (typeof parser === 'function') {
|
|
348
|
+
paramEntries.push([param.name, parser(decoded)]);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
throw new Error(INVALID_PARSER_ERROR);
|
|
352
|
+
}
|
|
353
|
+
if (validationFailed) {
|
|
354
|
+
// A failed param validation invalidates the whole nested route chain for this config.
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
if (paramEntries.length) {
|
|
358
|
+
params = Object.fromEntries(paramEntries);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (params && Object.keys(params).length) {
|
|
362
|
+
matchedRoutes.push({
|
|
363
|
+
name: routeName,
|
|
364
|
+
params
|
|
365
|
+
});
|
|
366
|
+
} else {
|
|
367
|
+
matchedRoutes.push({
|
|
368
|
+
name: routeName
|
|
286
369
|
});
|
|
287
|
-
remainingPath = remainingPath.replace(match[0], '');
|
|
288
|
-
break;
|
|
289
370
|
}
|
|
290
371
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
372
|
+
if (validationFailed) {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
return matchedRoutes;
|
|
295
376
|
};
|
|
296
|
-
const createNormalizedConfigs = (screen, routeConfig, initials,
|
|
377
|
+
const createNormalizedConfigs = (screen, routeConfig, initials, parts, parentScreens, routeNames) => {
|
|
297
378
|
const configs = [];
|
|
298
379
|
routeNames.push(screen);
|
|
299
380
|
parentScreens.push(screen);
|
|
300
381
|
const config = routeConfig[screen];
|
|
301
382
|
if (typeof config === 'string') {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
});
|
|
306
|
-
configs.push(createConfigItem(screen, [...routeNames],
|
|
383
|
+
const configParts = combinePatternParts(parts, getPatternParts(config).map(part => ({
|
|
384
|
+
...part,
|
|
385
|
+
screen
|
|
386
|
+
})));
|
|
387
|
+
configs.push(createConfigItem(screen, [...routeNames], configParts));
|
|
307
388
|
} else if (typeof config === 'object') {
|
|
308
389
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
|
309
390
|
// it can have `path` property and
|
|
310
391
|
// it could have `screens` prop which has nested configs
|
|
392
|
+
const nestedScreens = 'screens' in config ? config.screens : undefined;
|
|
393
|
+
const hasNestedScreens = !!nestedScreens;
|
|
394
|
+
const currentParts = typeof config.path === 'string' ? combinePatternParts(parts, getPatternParts(config.path).map(part => ({
|
|
395
|
+
...part,
|
|
396
|
+
screen
|
|
397
|
+
})), config.exact) : parts;
|
|
311
398
|
if (typeof config.path === 'string') {
|
|
312
|
-
if (config.exact && config.path == null) {
|
|
313
|
-
throw new Error(`Screen '${screen}' doesn't specify a 'path'. A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. \`path: ''\`.`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
399
|
// We should add alias configs after the main config
|
|
317
400
|
// So unless they are more specific, main config will be matched first
|
|
318
401
|
const aliasConfigs = [];
|
|
319
402
|
if (config.alias) {
|
|
320
403
|
for (const alias of config.alias) {
|
|
321
404
|
if (typeof alias === 'string') {
|
|
322
|
-
aliasConfigs.push(createConfigItem(screen, [...routeNames],
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
405
|
+
aliasConfigs.push(createConfigItem(screen, [...routeNames], combinePatternParts(parts, getPatternParts(alias).map(part => ({
|
|
406
|
+
...part,
|
|
407
|
+
screen
|
|
408
|
+
}))), config.parse, hasNestedScreens, false));
|
|
326
409
|
} else if (typeof alias === 'object') {
|
|
327
|
-
aliasConfigs.push(createConfigItem(screen, [...routeNames], alias.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
screen,
|
|
332
|
-
path: alias.path
|
|
333
|
-
}], alias.parse));
|
|
410
|
+
aliasConfigs.push(createConfigItem(screen, [...routeNames], combinePatternParts(parts, getPatternParts(alias.path).map(part => ({
|
|
411
|
+
...part,
|
|
412
|
+
screen
|
|
413
|
+
})), alias.exact), alias.parse, hasNestedScreens, alias.shared === true));
|
|
334
414
|
}
|
|
335
415
|
}
|
|
336
416
|
}
|
|
337
|
-
|
|
338
|
-
// If it's an exact path, we don't need to keep track of the parent screens
|
|
339
|
-
// So we can clear it
|
|
340
|
-
paths.length = 0;
|
|
341
|
-
}
|
|
342
|
-
paths.push({
|
|
343
|
-
screen,
|
|
344
|
-
path: config.path
|
|
345
|
-
});
|
|
346
|
-
configs.push(createConfigItem(screen, [...routeNames], [...paths], config.parse));
|
|
417
|
+
configs.push(createConfigItem(screen, [...routeNames], currentParts, config.parse, hasNestedScreens, config.shared === true));
|
|
347
418
|
configs.push(...aliasConfigs);
|
|
348
419
|
}
|
|
349
420
|
if (typeof config !== 'string' && typeof config.path !== 'string' && config.alias?.length) {
|
|
350
421
|
throw new Error(`Screen '${screen}' doesn't specify a 'path'. A 'path' needs to be specified in order to use 'alias'.`);
|
|
351
422
|
}
|
|
352
|
-
if (
|
|
423
|
+
if (hasNestedScreens) {
|
|
353
424
|
// property `initialRouteName` without `screens` has no purpose
|
|
354
425
|
if ('initialRouteName' in config && typeof config.initialRouteName === 'string') {
|
|
355
426
|
initials.push({
|
|
@@ -357,8 +428,8 @@ const createNormalizedConfigs = (screen, routeConfig, initials, paths, parentScr
|
|
|
357
428
|
parentScreens
|
|
358
429
|
});
|
|
359
430
|
}
|
|
360
|
-
Object.keys(
|
|
361
|
-
const result = createNormalizedConfigs(nestedConfig,
|
|
431
|
+
Object.keys(nestedScreens).forEach(nestedConfig => {
|
|
432
|
+
const result = createNormalizedConfigs(nestedConfig, nestedScreens, initials, currentParts, [...parentScreens], routeNames);
|
|
362
433
|
configs.push(...result);
|
|
363
434
|
});
|
|
364
435
|
}
|
|
@@ -366,23 +437,11 @@ const createNormalizedConfigs = (screen, routeConfig, initials, paths, parentScr
|
|
|
366
437
|
routeNames.pop();
|
|
367
438
|
return configs;
|
|
368
439
|
};
|
|
369
|
-
const createConfigItem = (screen, routeNames,
|
|
370
|
-
const parts = [];
|
|
371
|
-
|
|
372
|
-
// Parse the path string into parts for easier matching
|
|
373
|
-
for (const {
|
|
374
|
-
screen,
|
|
375
|
-
path
|
|
376
|
-
} of paths) {
|
|
377
|
-
parts.push(...getPatternParts(path).map(part => ({
|
|
378
|
-
...part,
|
|
379
|
-
screen
|
|
380
|
-
})));
|
|
381
|
-
}
|
|
440
|
+
const createConfigItem = (screen, routeNames, parts, parse, hasNestedScreens = false, shared = false) => {
|
|
382
441
|
const regex = parts.length ? new RegExp(`^(${parts.map((it, i) => {
|
|
383
442
|
if (it.param) {
|
|
384
443
|
const reg = it.regex || '[^/]+';
|
|
385
|
-
return `(((
|
|
444
|
+
return `(((?<${PARAM_GROUP_PREFIX}${i}>${reg})\\/)${it.optional ? '?' : ''})`;
|
|
386
445
|
}
|
|
387
446
|
return `${it.segment === '*' ? '.*' : escape(it.segment)}\\/`;
|
|
388
447
|
}).join('')})$`) : undefined;
|
|
@@ -394,20 +453,72 @@ const createConfigItem = (screen, routeNames, paths, parse) => {
|
|
|
394
453
|
} : null).filter(it => it != null);
|
|
395
454
|
return {
|
|
396
455
|
screen,
|
|
456
|
+
order: 0,
|
|
397
457
|
regex,
|
|
398
458
|
segments,
|
|
399
459
|
params,
|
|
400
460
|
routeNames,
|
|
401
|
-
parse
|
|
461
|
+
parse,
|
|
462
|
+
explicitParamNames: getExplicitParamNames(parse),
|
|
463
|
+
hasNestedScreens,
|
|
464
|
+
shared
|
|
402
465
|
};
|
|
403
466
|
};
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
467
|
+
const selectSharedConfig = (match, candidates, previous, initialRoutes) => {
|
|
468
|
+
// Walk the previous state down to the focused leaf to get the active route path
|
|
469
|
+
const focusedRouteNames = [];
|
|
470
|
+
let current = previous;
|
|
471
|
+
while (current?.routes.length) {
|
|
472
|
+
const index = typeof current.index === 'number' ? current.index : current.routes.length - 1;
|
|
473
|
+
const route = current.routes[index];
|
|
474
|
+
if (route == null) {
|
|
475
|
+
break;
|
|
408
476
|
}
|
|
477
|
+
focusedRouteNames.push(route.name);
|
|
478
|
+
current = route.state;
|
|
409
479
|
}
|
|
410
|
-
|
|
480
|
+
|
|
481
|
+
// Rank a candidate by how far it aligns with the focused path, then by
|
|
482
|
+
// how many of its levels match a configured initial route
|
|
483
|
+
const score = routeNames => {
|
|
484
|
+
let focused = 0;
|
|
485
|
+
while (routeNames[focused] !== undefined && routeNames[focused] === focusedRouteNames[focused]) {
|
|
486
|
+
focused++;
|
|
487
|
+
}
|
|
488
|
+
let initial = 0;
|
|
489
|
+
for (const config of initialRoutes) {
|
|
490
|
+
if (arrayStartsWith(routeNames, config.parentScreens) && routeNames[config.parentScreens.length] === config.initialRouteName) {
|
|
491
|
+
initial++;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
focused,
|
|
496
|
+
initial
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
let selected = match;
|
|
500
|
+
let best = {
|
|
501
|
+
focused: -1,
|
|
502
|
+
initial: -1
|
|
503
|
+
};
|
|
504
|
+
for (const candidate of candidates) {
|
|
505
|
+
if (!candidate.shared) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const {
|
|
509
|
+
focused,
|
|
510
|
+
initial
|
|
511
|
+
} = score(candidate.routeNames);
|
|
512
|
+
const better = focused !== best.focused ? focused > best.focused : initial !== best.initial ? initial > best.initial : candidate.order < selected.order;
|
|
513
|
+
if (better) {
|
|
514
|
+
selected = candidate;
|
|
515
|
+
best = {
|
|
516
|
+
focused,
|
|
517
|
+
initial
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return selected;
|
|
411
522
|
};
|
|
412
523
|
|
|
413
524
|
// Try to find an initial route connected with the one passed
|
|
@@ -416,7 +527,9 @@ const findInitialRoute = (routeName, parentScreens, initialRoutes) => {
|
|
|
416
527
|
if (parentScreens.length === config.parentScreens.length) {
|
|
417
528
|
let sameParents = true;
|
|
418
529
|
for (let i = 0; i < parentScreens.length; i++) {
|
|
419
|
-
|
|
530
|
+
const parentScreen = parentScreens[i];
|
|
531
|
+
const configParentScreen = config.parentScreens[i];
|
|
532
|
+
if (parentScreen == null || configParentScreen == null || parentScreen.localeCompare(configParentScreen) !== 0) {
|
|
420
533
|
sameParents = false;
|
|
421
534
|
break;
|
|
422
535
|
}
|
|
@@ -470,45 +583,119 @@ const createStateObject = (initialRoute, route, isEmpty) => {
|
|
|
470
583
|
}
|
|
471
584
|
}
|
|
472
585
|
};
|
|
473
|
-
const createNestedStateObject = (path, routes, initialRoutes,
|
|
586
|
+
const createNestedStateObject = (path, routes, initialRoutes, routeConfig) => {
|
|
474
587
|
let route = routes.shift();
|
|
588
|
+
if (route == null) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
475
591
|
const parentScreens = [];
|
|
476
592
|
let initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
|
477
593
|
parentScreens.push(route.name);
|
|
478
594
|
const state = createStateObject(initialRoute, route, routes.length === 0);
|
|
479
595
|
if (routes.length > 0) {
|
|
480
596
|
let nestedState = state;
|
|
481
|
-
|
|
597
|
+
let nextRoute = routes.shift();
|
|
598
|
+
while (nextRoute != null) {
|
|
599
|
+
route = nextRoute;
|
|
482
600
|
initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
|
483
601
|
const nestedStateIndex = nestedState.index || nestedState.routes.length - 1;
|
|
484
|
-
nestedState.routes[nestedStateIndex]
|
|
602
|
+
const nestedRoute = nestedState.routes[nestedStateIndex];
|
|
603
|
+
if (nestedRoute == null) {
|
|
604
|
+
throw new Error(`Couldn't find a route at index ${nestedStateIndex}.`);
|
|
605
|
+
}
|
|
606
|
+
nestedRoute.state = createStateObject(initialRoute, route, routes.length === 0);
|
|
485
607
|
if (routes.length > 0) {
|
|
486
|
-
|
|
608
|
+
if (nestedRoute.state == null) {
|
|
609
|
+
throw new Error(`Couldn't find nested state for route '${route.name}'.`);
|
|
610
|
+
}
|
|
611
|
+
nestedState = nestedRoute.state;
|
|
487
612
|
}
|
|
488
613
|
parentScreens.push(route.name);
|
|
614
|
+
nextRoute = routes.shift();
|
|
489
615
|
}
|
|
490
616
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
617
|
+
const focusedRoute = findFocusedRoute(state);
|
|
618
|
+
if (focusedRoute == null) {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
focusedRoute.path = path.replace(/\/$/, '');
|
|
622
|
+
const pathParamNames = new Set(routeConfig?.params.filter(param => param.screen === routeConfig.screen && typeof param.name === 'string').map(param => param.name));
|
|
623
|
+
const queryParams = parseQueryParams(path, routeConfig?.parse, pathParamNames, routeConfig?.explicitParamNames, routeConfig?.hasNestedScreens, focusedRoute.params);
|
|
624
|
+
if (!queryParams.valid) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
if (queryParams.params) {
|
|
628
|
+
focusedRoute.params = {
|
|
629
|
+
...focusedRoute.params,
|
|
630
|
+
...queryParams.params
|
|
498
631
|
};
|
|
499
632
|
}
|
|
500
633
|
return state;
|
|
501
634
|
};
|
|
502
|
-
const parseQueryParams = (path, parseConfig) => {
|
|
635
|
+
const parseQueryParams = (path, parseConfig, pathParamNames = new Set(), explicitParamNames, hasNestedScreens = false, routeParams) => {
|
|
503
636
|
const query = path.split('?')[1];
|
|
504
|
-
const params = queryString.parse(query);
|
|
637
|
+
const params = query ? queryString.parse(query) : {};
|
|
638
|
+
|
|
639
|
+
// Path params should always win over same-named query params.
|
|
640
|
+
for (const name of pathParamNames) {
|
|
641
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
642
|
+
delete params[name];
|
|
643
|
+
}
|
|
505
644
|
if (parseConfig) {
|
|
506
|
-
Object.
|
|
507
|
-
if (
|
|
508
|
-
|
|
645
|
+
for (const [name, parser] of Object.entries(parseConfig)) {
|
|
646
|
+
if (!parser || pathParamNames.has(name)) {
|
|
647
|
+
continue;
|
|
509
648
|
}
|
|
510
|
-
|
|
649
|
+
const schema = getStandardSchema(parser);
|
|
650
|
+
if (!Object.hasOwn(params, name)) {
|
|
651
|
+
if (!schema) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
const result = getValidationResult(schema, undefined);
|
|
655
|
+
if (result.issues) {
|
|
656
|
+
return {
|
|
657
|
+
valid: false
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (result.value !== undefined) {
|
|
661
|
+
params[name] = result.value;
|
|
662
|
+
}
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (schema) {
|
|
666
|
+
const result = getValidationResult(schema, params[name]);
|
|
667
|
+
if (result.issues) {
|
|
668
|
+
return {
|
|
669
|
+
valid: false
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
params[name] = result.value;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const value = Array.isArray(params[name]) ? params[name][0] : params[name];
|
|
676
|
+
if (typeof value !== 'string') {
|
|
677
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
678
|
+
delete params[name];
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (typeof parser === 'function') {
|
|
682
|
+
params[name] = parser(value);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
throw new Error(INVALID_PARSER_ERROR);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (hasNestedScreens && !explicitParamNames?.has('screen') && (typeof params.screen === 'string' || routeParams != null && 'screen' in routeParams && typeof routeParams.screen === 'string')) {
|
|
689
|
+
for (const name of NESTED_SCREEN_PARAM_NAMES) {
|
|
690
|
+
if (!explicitParamNames?.has(name)) {
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
692
|
+
delete params[name];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
511
695
|
}
|
|
512
|
-
return
|
|
696
|
+
return {
|
|
697
|
+
valid: true,
|
|
698
|
+
params: Object.keys(params).length ? params : undefined
|
|
699
|
+
};
|
|
513
700
|
};
|
|
514
701
|
//# sourceMappingURL=getStateFromPath.js.map
|