@react-navigation/core 8.0.0-alpha.2 → 8.0.0-alpha.21
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 +4 -2
- 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 +5 -3
- 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
package/src/getStateFromPath.tsx
CHANGED
|
@@ -4,30 +4,46 @@ import type {
|
|
|
4
4
|
PartialState,
|
|
5
5
|
} from '@react-navigation/routers';
|
|
6
6
|
import escape from 'escape-string-regexp';
|
|
7
|
-
import
|
|
7
|
+
import queryString from 'query-string';
|
|
8
8
|
|
|
9
9
|
import { arrayStartsWith } from './arrayStartsWith';
|
|
10
10
|
import { findFocusedRoute } from './findFocusedRoute';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
combinePatternParts,
|
|
13
|
+
getPatternParts,
|
|
14
|
+
type PatternPart,
|
|
15
|
+
} from './getPatternParts';
|
|
12
16
|
import { isArrayEqual } from './isArrayEqual';
|
|
13
17
|
import type { PathConfig, PathConfigMap } from './types';
|
|
18
|
+
import type {
|
|
19
|
+
StandardSchemaV1,
|
|
20
|
+
StandardSchemaValidationResult,
|
|
21
|
+
} from './utilities';
|
|
14
22
|
import { validatePathConfig } from './validatePathConfig';
|
|
15
23
|
|
|
16
24
|
type Options<ParamList extends {}> = {
|
|
17
|
-
path?: string;
|
|
18
|
-
initialRouteName?: string;
|
|
25
|
+
path?: string | undefined;
|
|
26
|
+
initialRouteName?: string | undefined;
|
|
19
27
|
screens: PathConfigMap<ParamList>;
|
|
20
28
|
};
|
|
21
29
|
|
|
22
|
-
type
|
|
30
|
+
type ParseConfigValue =
|
|
31
|
+
| ((value: string) => unknown)
|
|
32
|
+
| StandardSchemaV1<unknown, unknown>;
|
|
33
|
+
|
|
34
|
+
type ParseConfig = Record<string, ParseConfigValue | undefined>;
|
|
23
35
|
|
|
24
36
|
type RouteConfig = {
|
|
25
37
|
screen: string;
|
|
26
|
-
|
|
38
|
+
order: number;
|
|
39
|
+
regex?: RegExp | undefined;
|
|
27
40
|
segments: string[];
|
|
28
|
-
params: { screen: string; name?: string; index: number }[];
|
|
41
|
+
params: { screen: string; name?: string | undefined; index: number }[];
|
|
29
42
|
routeNames: string[];
|
|
30
|
-
parse?: ParseConfig;
|
|
43
|
+
parse?: ParseConfig | undefined;
|
|
44
|
+
explicitParamNames?: Set<string> | undefined;
|
|
45
|
+
hasNestedScreens: boolean;
|
|
46
|
+
shared: boolean;
|
|
31
47
|
};
|
|
32
48
|
|
|
33
49
|
type InitialRouteConfig = {
|
|
@@ -36,18 +52,80 @@ type InitialRouteConfig = {
|
|
|
36
52
|
};
|
|
37
53
|
|
|
38
54
|
type ResultState = PartialState<NavigationState> & {
|
|
39
|
-
state?: ResultState;
|
|
55
|
+
state?: ResultState | undefined;
|
|
40
56
|
};
|
|
41
57
|
|
|
42
58
|
type ParsedRoute = {
|
|
43
59
|
name: string;
|
|
44
|
-
path?: string;
|
|
60
|
+
path?: string | undefined;
|
|
45
61
|
params?: Record<string, unknown> | undefined;
|
|
46
62
|
};
|
|
47
63
|
|
|
64
|
+
type RoutePatternPart = PatternPart & { screen: string };
|
|
65
|
+
|
|
48
66
|
type ConfigResources = {
|
|
49
67
|
initialRoutes: InitialRouteConfig[];
|
|
50
68
|
configs: RouteConfig[];
|
|
69
|
+
configsByScreen: Record<string, RouteConfig[]>;
|
|
70
|
+
configsByPattern: Record<string, RouteConfig[]>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const INVALID_SCHEMA_RESULT_ERROR =
|
|
74
|
+
'Invalid validation result from schema. It should be an object with either "value" or "issues" property and cannot be asynchronous.';
|
|
75
|
+
|
|
76
|
+
const INVALID_PARSER_ERROR =
|
|
77
|
+
'Invalid parser. Expected a function or a Standard Schema V1 object.';
|
|
78
|
+
|
|
79
|
+
const PARAM_GROUP_PREFIX = 'param_';
|
|
80
|
+
|
|
81
|
+
const NESTED_SCREEN_PARAM_NAMES = [
|
|
82
|
+
'screen',
|
|
83
|
+
'params',
|
|
84
|
+
'initial',
|
|
85
|
+
'path',
|
|
86
|
+
'merge',
|
|
87
|
+
'pop',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const getExplicitParamNames = (parse?: ParseConfig) => {
|
|
91
|
+
const names = Object.entries(parse ?? {})
|
|
92
|
+
.filter(([, parser]) => parser != null)
|
|
93
|
+
.map(([name]) => name);
|
|
94
|
+
|
|
95
|
+
return names.length ? new Set(names) : undefined;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getStandardSchema = (parser: ParseConfigValue) => {
|
|
99
|
+
if (
|
|
100
|
+
'~standard' in parser &&
|
|
101
|
+
typeof parser['~standard'] === 'object' &&
|
|
102
|
+
parser['~standard'] !== null &&
|
|
103
|
+
'version' in parser['~standard'] &&
|
|
104
|
+
parser['~standard'].version === 1 &&
|
|
105
|
+
'validate' in parser['~standard'] &&
|
|
106
|
+
typeof parser['~standard'].validate === 'function'
|
|
107
|
+
) {
|
|
108
|
+
return parser['~standard'];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const getValidationResult = (
|
|
115
|
+
schema: StandardSchemaV1<unknown, unknown>['~standard'],
|
|
116
|
+
value: unknown
|
|
117
|
+
): StandardSchemaValidationResult<unknown> => {
|
|
118
|
+
const result = schema.validate(value);
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
result != null &&
|
|
122
|
+
typeof result === 'object' &&
|
|
123
|
+
('value' in result || ('issues' in result && Array.isArray(result.issues)))
|
|
124
|
+
) {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw new Error(INVALID_SCHEMA_RESULT_ERROR);
|
|
51
129
|
};
|
|
52
130
|
|
|
53
131
|
/**
|
|
@@ -73,12 +151,28 @@ type ConfigResources = {
|
|
|
73
151
|
*/
|
|
74
152
|
export function getStateFromPath<ParamList extends {}>(
|
|
75
153
|
path: string,
|
|
76
|
-
options?: Options<ParamList
|
|
154
|
+
options?: Options<ParamList>,
|
|
155
|
+
previous?: NavigationState
|
|
77
156
|
): ResultState | undefined {
|
|
78
|
-
const { initialRoutes, configs } =
|
|
157
|
+
const { initialRoutes, configs, configsByScreen, configsByPattern } =
|
|
158
|
+
getConfigResources(options);
|
|
79
159
|
|
|
80
160
|
const screens = options?.screens;
|
|
81
161
|
|
|
162
|
+
// When a matched config is shared, the same pattern can resolve to multiple branches
|
|
163
|
+
// pick the one that best matches the current navigation state
|
|
164
|
+
const resolveSharedConfig = (config: RouteConfig) => {
|
|
165
|
+
if (config.shared) {
|
|
166
|
+
const candidates = configsByPattern[config.segments.join('/')];
|
|
167
|
+
|
|
168
|
+
if (candidates) {
|
|
169
|
+
return selectSharedConfig(config, candidates, previous, initialRoutes);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config;
|
|
174
|
+
};
|
|
175
|
+
|
|
82
176
|
let remaining = path
|
|
83
177
|
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
|
84
178
|
.replace(/^\//, '') // Remove extra leading slash
|
|
@@ -102,15 +196,23 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
102
196
|
remaining = remaining.replace(normalizedPrefix, '');
|
|
103
197
|
}
|
|
104
198
|
|
|
199
|
+
const decodedSegments: string[] = [];
|
|
200
|
+
|
|
201
|
+
for (const segment of remaining.split('/')) {
|
|
202
|
+
if (!segment) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
decodedSegments.push(decodeURIComponent(segment));
|
|
208
|
+
} catch {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
105
213
|
if (screens === undefined) {
|
|
106
214
|
// When no config is specified, use the path segments as route names
|
|
107
|
-
const routes =
|
|
108
|
-
.split('/')
|
|
109
|
-
.filter(Boolean)
|
|
110
|
-
.map((segment) => {
|
|
111
|
-
const name = decodeURIComponent(segment);
|
|
112
|
-
return { name };
|
|
113
|
-
});
|
|
215
|
+
const routes = decodedSegments.map((name) => ({ name }));
|
|
114
216
|
|
|
115
217
|
if (routes.length) {
|
|
116
218
|
return createNestedStateObject(path, routes, initialRoutes);
|
|
@@ -125,36 +227,58 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
125
227
|
const match = configs.find((config) => config.segments.join('/') === '');
|
|
126
228
|
|
|
127
229
|
if (match) {
|
|
230
|
+
const config = resolveSharedConfig(match);
|
|
231
|
+
|
|
128
232
|
return createNestedStateObject(
|
|
129
233
|
path,
|
|
130
|
-
|
|
234
|
+
config.routeNames.map((name) => ({ name })),
|
|
131
235
|
initialRoutes,
|
|
132
|
-
|
|
236
|
+
config
|
|
133
237
|
);
|
|
134
238
|
}
|
|
135
239
|
|
|
136
240
|
return undefined;
|
|
137
241
|
}
|
|
138
242
|
|
|
139
|
-
let result: PartialState<NavigationState> | undefined;
|
|
140
|
-
let current: PartialState<NavigationState> | undefined;
|
|
141
|
-
|
|
142
243
|
// We match the whole path against the regex instead of segments
|
|
143
244
|
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
|
144
|
-
const
|
|
245
|
+
for (const config of configs) {
|
|
246
|
+
const routes = matchAgainstConfig(remaining, config, configsByScreen);
|
|
145
247
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
remaining = remainingPath;
|
|
150
|
-
result = current;
|
|
151
|
-
}
|
|
248
|
+
if (routes === undefined) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
152
251
|
|
|
153
|
-
|
|
154
|
-
|
|
252
|
+
let selectedConfig = resolveSharedConfig(config);
|
|
253
|
+
let selectedRoutes = routes;
|
|
254
|
+
|
|
255
|
+
if (selectedConfig !== config) {
|
|
256
|
+
const rematchedRoutes = matchAgainstConfig(
|
|
257
|
+
remaining,
|
|
258
|
+
selectedConfig,
|
|
259
|
+
configsByScreen
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (rematchedRoutes === undefined) {
|
|
263
|
+
selectedConfig = config;
|
|
264
|
+
} else {
|
|
265
|
+
selectedRoutes = rematchedRoutes;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const state = createNestedStateObject(
|
|
270
|
+
path,
|
|
271
|
+
selectedRoutes,
|
|
272
|
+
initialRoutes,
|
|
273
|
+
selectedConfig
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (state !== undefined) {
|
|
277
|
+
return state;
|
|
278
|
+
}
|
|
155
279
|
}
|
|
156
280
|
|
|
157
|
-
return
|
|
281
|
+
return undefined;
|
|
158
282
|
}
|
|
159
283
|
|
|
160
284
|
/**
|
|
@@ -188,12 +312,19 @@ function prepareConfigResources(options?: Options<{}>) {
|
|
|
188
312
|
|
|
189
313
|
checkForDuplicatedConfigs(configs);
|
|
190
314
|
|
|
191
|
-
const
|
|
315
|
+
const configsByScreen: Record<string, RouteConfig[]> = {};
|
|
316
|
+
const configsByPattern: Record<string, RouteConfig[]> = {};
|
|
317
|
+
|
|
318
|
+
for (const c of configs) {
|
|
319
|
+
(configsByScreen[c.screen] ??= []).push(c);
|
|
320
|
+
(configsByPattern[c.segments.join('/')] ??= []).push(c);
|
|
321
|
+
}
|
|
192
322
|
|
|
193
323
|
return {
|
|
194
324
|
initialRoutes,
|
|
195
325
|
configs,
|
|
196
|
-
|
|
326
|
+
configsByScreen,
|
|
327
|
+
configsByPattern,
|
|
197
328
|
};
|
|
198
329
|
}
|
|
199
330
|
|
|
@@ -221,6 +352,7 @@ function getSortedNormalizedConfigs(
|
|
|
221
352
|
createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
|
|
222
353
|
)
|
|
223
354
|
)
|
|
355
|
+
.map((config, order) => ({ ...config, order }))
|
|
224
356
|
.sort((a, b) => {
|
|
225
357
|
// Sort config from most specific to least specific:
|
|
226
358
|
// - more segments
|
|
@@ -246,22 +378,25 @@ function getSortedNormalizedConfigs(
|
|
|
246
378
|
}
|
|
247
379
|
|
|
248
380
|
for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
|
|
381
|
+
const aSegment = a.segments[i];
|
|
382
|
+
const bSegment = b.segments[i];
|
|
383
|
+
|
|
249
384
|
// if b is longer, b gets higher priority
|
|
250
|
-
if (
|
|
385
|
+
if (aSegment == null) {
|
|
251
386
|
return 1;
|
|
252
387
|
}
|
|
253
388
|
|
|
254
389
|
// if a is longer, a gets higher priority
|
|
255
|
-
if (
|
|
390
|
+
if (bSegment == null) {
|
|
256
391
|
return -1;
|
|
257
392
|
}
|
|
258
393
|
|
|
259
|
-
const aWildCard =
|
|
260
|
-
const bWildCard =
|
|
261
|
-
const aParam =
|
|
262
|
-
const bParam =
|
|
263
|
-
const aRegex = aParam &&
|
|
264
|
-
const bRegex = bParam &&
|
|
394
|
+
const aWildCard = aSegment === '*';
|
|
395
|
+
const bWildCard = bSegment === '*';
|
|
396
|
+
const aParam = aSegment.startsWith(':');
|
|
397
|
+
const bParam = bSegment.startsWith(':');
|
|
398
|
+
const aRegex = aParam && aSegment.includes('(');
|
|
399
|
+
const bRegex = bParam && bSegment.includes('(');
|
|
265
400
|
|
|
266
401
|
// if both are wildcard or regex, we compare next component
|
|
267
402
|
if ((aWildCard && bWildCard) || (aRegex && bRegex)) {
|
|
@@ -319,13 +454,13 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
|
|
|
319
454
|
? b.every((it, i) => a[i] === it)
|
|
320
455
|
: a.every((it, i) => b[i] === it);
|
|
321
456
|
|
|
322
|
-
if (!intersects) {
|
|
457
|
+
if (!intersects && (!acc[pattern].shared || !config.shared)) {
|
|
323
458
|
throw new Error(
|
|
324
459
|
`Found conflicting screens with the same pattern. The pattern '${
|
|
325
460
|
pattern
|
|
326
461
|
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
|
327
462
|
' > '
|
|
328
|
-
)}'. Patterns must be unique and cannot resolve to more than one screen.`
|
|
463
|
+
)}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
|
|
329
464
|
);
|
|
330
465
|
}
|
|
331
466
|
}
|
|
@@ -336,90 +471,118 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
|
|
|
336
471
|
}, {});
|
|
337
472
|
}
|
|
338
473
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
474
|
+
const matchAgainstConfig = (
|
|
475
|
+
remaining: string,
|
|
476
|
+
config: RouteConfig,
|
|
477
|
+
configsByScreen: Record<string, RouteConfig[]>
|
|
478
|
+
) => {
|
|
479
|
+
if (!config.regex) {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
346
482
|
|
|
347
|
-
const
|
|
348
|
-
let routes: ParsedRoute[] | undefined;
|
|
349
|
-
let remainingPath = remaining;
|
|
483
|
+
const match = remaining.match(config.regex);
|
|
350
484
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
485
|
+
if (!match) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
356
488
|
|
|
357
|
-
|
|
489
|
+
let validationFailed = false;
|
|
490
|
+
const matchedRoutes: ParsedRoute[] = [];
|
|
358
491
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
492
|
+
for (const [index, routeName] of config.routeNames.entries()) {
|
|
493
|
+
// Check matching name, route names and pattern in case same screen is used
|
|
494
|
+
// at different levels or branches in config
|
|
495
|
+
const routeConfig = configsByScreen[routeName]?.find(
|
|
496
|
+
(c) =>
|
|
497
|
+
index === c.routeNames.length - 1 &&
|
|
498
|
+
arrayStartsWith(config.routeNames, c.routeNames) &&
|
|
499
|
+
arrayStartsWith(config.segments, c.segments)
|
|
500
|
+
);
|
|
369
501
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return null;
|
|
385
|
-
})
|
|
386
|
-
.filter((it) => it != null)
|
|
387
|
-
.map(([key, value]) => {
|
|
388
|
-
if (value == null) {
|
|
389
|
-
return [key, undefined];
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const decoded = decodeURIComponent(value);
|
|
393
|
-
const parsed = routeConfig.parse?.[key]
|
|
394
|
-
? routeConfig.parse[key](decoded)
|
|
395
|
-
: decoded;
|
|
396
|
-
|
|
397
|
-
return [key, parsed];
|
|
398
|
-
})
|
|
399
|
-
)
|
|
400
|
-
: undefined;
|
|
502
|
+
let params: Record<string, unknown> | undefined;
|
|
503
|
+
|
|
504
|
+
if (routeConfig && match.groups) {
|
|
505
|
+
const paramEntries: [string, unknown][] = [];
|
|
506
|
+
|
|
507
|
+
for (const [key, value] of Object.entries(match.groups)) {
|
|
508
|
+
const index = Number(key.replace(PARAM_GROUP_PREFIX, ''));
|
|
509
|
+
const param = routeConfig.params.find((it) => it.index === index);
|
|
510
|
+
|
|
511
|
+
if (param?.screen !== routeName || !param.name) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
401
514
|
|
|
402
|
-
if (
|
|
403
|
-
|
|
515
|
+
if (value == null) {
|
|
516
|
+
paramEntries.push([param.name, undefined]);
|
|
517
|
+
continue;
|
|
404
518
|
}
|
|
405
519
|
|
|
406
|
-
|
|
407
|
-
});
|
|
520
|
+
let decoded: string;
|
|
408
521
|
|
|
409
|
-
|
|
522
|
+
try {
|
|
523
|
+
decoded = decodeURIComponent(value);
|
|
524
|
+
} catch {
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
410
527
|
|
|
411
|
-
|
|
528
|
+
const parser = routeConfig.parse?.[param.name];
|
|
529
|
+
|
|
530
|
+
if (!parser) {
|
|
531
|
+
paramEntries.push([param.name, decoded]);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const schema = getStandardSchema(parser);
|
|
536
|
+
|
|
537
|
+
if (schema) {
|
|
538
|
+
const result = getValidationResult(schema, decoded);
|
|
539
|
+
|
|
540
|
+
if (result.issues) {
|
|
541
|
+
validationFailed = true;
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
paramEntries.push([param.name, result.value]);
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (typeof parser === 'function') {
|
|
550
|
+
paramEntries.push([param.name, parser(decoded)]);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
throw new Error(INVALID_PARSER_ERROR);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (validationFailed) {
|
|
558
|
+
// A failed param validation invalidates the whole nested route chain for this config.
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (paramEntries.length) {
|
|
563
|
+
params = Object.fromEntries(paramEntries);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (params && Object.keys(params).length) {
|
|
568
|
+
matchedRoutes.push({ name: routeName, params });
|
|
569
|
+
} else {
|
|
570
|
+
matchedRoutes.push({ name: routeName });
|
|
412
571
|
}
|
|
413
572
|
}
|
|
414
573
|
|
|
415
|
-
|
|
574
|
+
if (validationFailed) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return matchedRoutes;
|
|
416
579
|
};
|
|
417
580
|
|
|
418
581
|
const createNormalizedConfigs = (
|
|
419
582
|
screen: string,
|
|
420
583
|
routeConfig: Record<string, string | PathConfig<{}>>,
|
|
421
584
|
initials: InitialRouteConfig[],
|
|
422
|
-
|
|
585
|
+
parts: RoutePatternPart[],
|
|
423
586
|
parentScreens: string[],
|
|
424
587
|
routeNames: string[]
|
|
425
588
|
): RouteConfig[] => {
|
|
@@ -432,19 +595,28 @@ const createNormalizedConfigs = (
|
|
|
432
595
|
const config = routeConfig[screen];
|
|
433
596
|
|
|
434
597
|
if (typeof config === 'string') {
|
|
435
|
-
|
|
436
|
-
|
|
598
|
+
const configParts = combinePatternParts(
|
|
599
|
+
parts,
|
|
600
|
+
getPatternParts(config).map((part) => ({ ...part, screen }))
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
configs.push(createConfigItem(screen, [...routeNames], configParts));
|
|
437
604
|
} else if (typeof config === 'object') {
|
|
438
605
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
|
439
606
|
// it can have `path` property and
|
|
440
607
|
// it could have `screens` prop which has nested configs
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
608
|
+
const nestedScreens = 'screens' in config ? config.screens : undefined;
|
|
609
|
+
const hasNestedScreens = !!nestedScreens;
|
|
610
|
+
const currentParts =
|
|
611
|
+
typeof config.path === 'string'
|
|
612
|
+
? combinePatternParts(
|
|
613
|
+
parts,
|
|
614
|
+
getPatternParts(config.path).map((part) => ({ ...part, screen })),
|
|
615
|
+
config.exact
|
|
616
|
+
)
|
|
617
|
+
: parts;
|
|
447
618
|
|
|
619
|
+
if (typeof config.path === 'string') {
|
|
448
620
|
// We should add alias configs after the main config
|
|
449
621
|
// So unless they are more specific, main config will be matched first
|
|
450
622
|
const aliasConfigs = [];
|
|
@@ -456,8 +628,13 @@ const createNormalizedConfigs = (
|
|
|
456
628
|
createConfigItem(
|
|
457
629
|
screen,
|
|
458
630
|
[...routeNames],
|
|
459
|
-
|
|
460
|
-
|
|
631
|
+
combinePatternParts(
|
|
632
|
+
parts,
|
|
633
|
+
getPatternParts(alias).map((part) => ({ ...part, screen }))
|
|
634
|
+
),
|
|
635
|
+
config.parse,
|
|
636
|
+
hasNestedScreens,
|
|
637
|
+
false
|
|
461
638
|
)
|
|
462
639
|
);
|
|
463
640
|
} else if (typeof alias === 'object') {
|
|
@@ -465,25 +642,32 @@ const createNormalizedConfigs = (
|
|
|
465
642
|
createConfigItem(
|
|
466
643
|
screen,
|
|
467
644
|
[...routeNames],
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
645
|
+
combinePatternParts(
|
|
646
|
+
parts,
|
|
647
|
+
getPatternParts(alias.path).map((part) => ({
|
|
648
|
+
...part,
|
|
649
|
+
screen,
|
|
650
|
+
})),
|
|
651
|
+
alias.exact
|
|
652
|
+
),
|
|
653
|
+
alias.parse,
|
|
654
|
+
hasNestedScreens,
|
|
655
|
+
alias.shared === true
|
|
472
656
|
)
|
|
473
657
|
);
|
|
474
658
|
}
|
|
475
659
|
}
|
|
476
660
|
}
|
|
477
661
|
|
|
478
|
-
if (config.exact) {
|
|
479
|
-
// If it's an exact path, we don't need to keep track of the parent screens
|
|
480
|
-
// So we can clear it
|
|
481
|
-
paths.length = 0;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
paths.push({ screen, path: config.path });
|
|
485
662
|
configs.push(
|
|
486
|
-
createConfigItem(
|
|
663
|
+
createConfigItem(
|
|
664
|
+
screen,
|
|
665
|
+
[...routeNames],
|
|
666
|
+
currentParts,
|
|
667
|
+
config.parse,
|
|
668
|
+
hasNestedScreens,
|
|
669
|
+
config.shared === true
|
|
670
|
+
)
|
|
487
671
|
);
|
|
488
672
|
|
|
489
673
|
configs.push(...aliasConfigs);
|
|
@@ -499,7 +683,7 @@ const createNormalizedConfigs = (
|
|
|
499
683
|
);
|
|
500
684
|
}
|
|
501
685
|
|
|
502
|
-
if (
|
|
686
|
+
if (hasNestedScreens) {
|
|
503
687
|
// property `initialRouteName` without `screens` has no purpose
|
|
504
688
|
if (
|
|
505
689
|
'initialRouteName' in config &&
|
|
@@ -511,12 +695,12 @@ const createNormalizedConfigs = (
|
|
|
511
695
|
});
|
|
512
696
|
}
|
|
513
697
|
|
|
514
|
-
Object.keys(
|
|
698
|
+
Object.keys(nestedScreens).forEach((nestedConfig) => {
|
|
515
699
|
const result = createNormalizedConfigs(
|
|
516
700
|
nestedConfig,
|
|
517
|
-
|
|
701
|
+
nestedScreens as Record<string, string | PathConfig<{}>>,
|
|
518
702
|
initials,
|
|
519
|
-
|
|
703
|
+
currentParts,
|
|
520
704
|
[...parentScreens],
|
|
521
705
|
routeNames
|
|
522
706
|
);
|
|
@@ -534,16 +718,11 @@ const createNormalizedConfigs = (
|
|
|
534
718
|
const createConfigItem = (
|
|
535
719
|
screen: string,
|
|
536
720
|
routeNames: string[],
|
|
537
|
-
|
|
538
|
-
parse?: ParseConfig
|
|
721
|
+
parts: RoutePatternPart[],
|
|
722
|
+
parse?: ParseConfig,
|
|
723
|
+
hasNestedScreens = false,
|
|
724
|
+
shared = false
|
|
539
725
|
): RouteConfig => {
|
|
540
|
-
const parts: (PatternPart & { screen: string })[] = [];
|
|
541
|
-
|
|
542
|
-
// Parse the path string into parts for easier matching
|
|
543
|
-
for (const { screen, path } of paths) {
|
|
544
|
-
parts.push(...getPatternParts(path).map((part) => ({ ...part, screen })));
|
|
545
|
-
}
|
|
546
|
-
|
|
547
726
|
const regex = parts.length
|
|
548
727
|
? new RegExp(
|
|
549
728
|
`^(${parts
|
|
@@ -551,7 +730,7 @@ const createConfigItem = (
|
|
|
551
730
|
if (it.param) {
|
|
552
731
|
const reg = it.regex || '[^/]+';
|
|
553
732
|
|
|
554
|
-
return `(((
|
|
733
|
+
return `(((?<${PARAM_GROUP_PREFIX}${i}>${reg})\\/)${it.optional ? '?' : ''})`;
|
|
555
734
|
}
|
|
556
735
|
|
|
557
736
|
return `${it.segment === '*' ? '.*' : escape(it.segment)}\\/`;
|
|
@@ -575,25 +754,95 @@ const createConfigItem = (
|
|
|
575
754
|
|
|
576
755
|
return {
|
|
577
756
|
screen,
|
|
757
|
+
order: 0,
|
|
578
758
|
regex,
|
|
579
759
|
segments,
|
|
580
760
|
params,
|
|
581
761
|
routeNames,
|
|
582
762
|
parse,
|
|
763
|
+
explicitParamNames: getExplicitParamNames(parse),
|
|
764
|
+
hasNestedScreens,
|
|
765
|
+
shared,
|
|
583
766
|
};
|
|
584
767
|
};
|
|
585
768
|
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
769
|
+
const selectSharedConfig = (
|
|
770
|
+
match: RouteConfig,
|
|
771
|
+
candidates: RouteConfig[],
|
|
772
|
+
previous: NavigationState | PartialState<NavigationState> | undefined,
|
|
773
|
+
initialRoutes: InitialRouteConfig[]
|
|
774
|
+
) => {
|
|
775
|
+
// Walk the previous state down to the focused leaf to get the active route path
|
|
776
|
+
const focusedRouteNames: string[] = [];
|
|
777
|
+
|
|
778
|
+
let current = previous;
|
|
779
|
+
|
|
780
|
+
while (current?.routes.length) {
|
|
781
|
+
const index =
|
|
782
|
+
typeof current.index === 'number'
|
|
783
|
+
? current.index
|
|
784
|
+
: current.routes.length - 1;
|
|
785
|
+
|
|
786
|
+
const route = current.routes[index];
|
|
787
|
+
|
|
788
|
+
if (route == null) {
|
|
789
|
+
break;
|
|
593
790
|
}
|
|
791
|
+
|
|
792
|
+
focusedRouteNames.push(route.name);
|
|
793
|
+
current = route.state;
|
|
594
794
|
}
|
|
595
795
|
|
|
596
|
-
|
|
796
|
+
// Rank a candidate by how far it aligns with the focused path, then by
|
|
797
|
+
// how many of its levels match a configured initial route
|
|
798
|
+
const score = (routeNames: string[]) => {
|
|
799
|
+
let focused = 0;
|
|
800
|
+
|
|
801
|
+
while (
|
|
802
|
+
routeNames[focused] !== undefined &&
|
|
803
|
+
routeNames[focused] === focusedRouteNames[focused]
|
|
804
|
+
) {
|
|
805
|
+
focused++;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
let initial = 0;
|
|
809
|
+
|
|
810
|
+
for (const config of initialRoutes) {
|
|
811
|
+
if (
|
|
812
|
+
arrayStartsWith(routeNames, config.parentScreens) &&
|
|
813
|
+
routeNames[config.parentScreens.length] === config.initialRouteName
|
|
814
|
+
) {
|
|
815
|
+
initial++;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return { focused, initial };
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
let selected = match;
|
|
823
|
+
let best = { focused: -1, initial: -1 };
|
|
824
|
+
|
|
825
|
+
for (const candidate of candidates) {
|
|
826
|
+
if (!candidate.shared) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const { focused, initial } = score(candidate.routeNames);
|
|
831
|
+
|
|
832
|
+
const better =
|
|
833
|
+
focused !== best.focused
|
|
834
|
+
? focused > best.focused
|
|
835
|
+
: initial !== best.initial
|
|
836
|
+
? initial > best.initial
|
|
837
|
+
: candidate.order < selected.order;
|
|
838
|
+
|
|
839
|
+
if (better) {
|
|
840
|
+
selected = candidate;
|
|
841
|
+
best = { focused, initial };
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return selected;
|
|
597
846
|
};
|
|
598
847
|
|
|
599
848
|
// Try to find an initial route connected with the one passed
|
|
@@ -606,7 +855,14 @@ const findInitialRoute = (
|
|
|
606
855
|
if (parentScreens.length === config.parentScreens.length) {
|
|
607
856
|
let sameParents = true;
|
|
608
857
|
for (let i = 0; i < parentScreens.length; i++) {
|
|
609
|
-
|
|
858
|
+
const parentScreen = parentScreens[i];
|
|
859
|
+
const configParentScreen = config.parentScreens[i];
|
|
860
|
+
|
|
861
|
+
if (
|
|
862
|
+
parentScreen == null ||
|
|
863
|
+
configParentScreen == null ||
|
|
864
|
+
parentScreen.localeCompare(configParentScreen) !== 0
|
|
865
|
+
) {
|
|
610
866
|
sameParents = false;
|
|
611
867
|
break;
|
|
612
868
|
}
|
|
@@ -657,9 +913,14 @@ const createNestedStateObject = (
|
|
|
657
913
|
path: string,
|
|
658
914
|
routes: ParsedRoute[],
|
|
659
915
|
initialRoutes: InitialRouteConfig[],
|
|
660
|
-
|
|
661
|
-
) => {
|
|
662
|
-
let route = routes.shift()
|
|
916
|
+
routeConfig?: RouteConfig
|
|
917
|
+
): InitialState | undefined => {
|
|
918
|
+
let route = routes.shift();
|
|
919
|
+
|
|
920
|
+
if (route == null) {
|
|
921
|
+
return undefined;
|
|
922
|
+
}
|
|
923
|
+
|
|
663
924
|
const parentScreens: string[] = [];
|
|
664
925
|
|
|
665
926
|
let initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
|
@@ -675,57 +936,171 @@ const createNestedStateObject = (
|
|
|
675
936
|
if (routes.length > 0) {
|
|
676
937
|
let nestedState = state;
|
|
677
938
|
|
|
678
|
-
|
|
939
|
+
let nextRoute = routes.shift();
|
|
940
|
+
|
|
941
|
+
while (nextRoute != null) {
|
|
942
|
+
route = nextRoute;
|
|
679
943
|
initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
|
680
944
|
|
|
681
945
|
const nestedStateIndex =
|
|
682
946
|
nestedState.index || nestedState.routes.length - 1;
|
|
947
|
+
const nestedRoute = nestedState.routes[nestedStateIndex];
|
|
683
948
|
|
|
684
|
-
|
|
949
|
+
if (nestedRoute == null) {
|
|
950
|
+
throw new Error(`Couldn't find a route at index ${nestedStateIndex}.`);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
nestedRoute.state = createStateObject(
|
|
685
954
|
initialRoute,
|
|
686
955
|
route,
|
|
687
956
|
routes.length === 0
|
|
688
957
|
);
|
|
689
958
|
|
|
690
959
|
if (routes.length > 0) {
|
|
691
|
-
|
|
692
|
-
|
|
960
|
+
if (nestedRoute.state == null) {
|
|
961
|
+
throw new Error(
|
|
962
|
+
`Couldn't find nested state for route '${route.name}'.`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
nestedState = nestedRoute.state;
|
|
693
967
|
}
|
|
694
968
|
|
|
695
969
|
parentScreens.push(route.name);
|
|
970
|
+
nextRoute = routes.shift();
|
|
696
971
|
}
|
|
697
972
|
}
|
|
698
973
|
|
|
699
|
-
|
|
700
|
-
|
|
974
|
+
const focusedRoute = findFocusedRoute(state);
|
|
975
|
+
|
|
976
|
+
if (focusedRoute == null) {
|
|
977
|
+
return undefined;
|
|
978
|
+
}
|
|
701
979
|
|
|
702
|
-
|
|
980
|
+
focusedRoute.path = path.replace(/\/$/, '');
|
|
981
|
+
|
|
982
|
+
const pathParamNames = new Set(
|
|
983
|
+
routeConfig?.params
|
|
984
|
+
.filter(
|
|
985
|
+
(param): param is { screen: string; name: string; index: number } =>
|
|
986
|
+
param.screen === routeConfig.screen && typeof param.name === 'string'
|
|
987
|
+
)
|
|
988
|
+
.map((param) => param.name)
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
const queryParams = parseQueryParams(
|
|
703
992
|
path,
|
|
704
|
-
|
|
993
|
+
routeConfig?.parse,
|
|
994
|
+
pathParamNames,
|
|
995
|
+
routeConfig?.explicitParamNames,
|
|
996
|
+
routeConfig?.hasNestedScreens,
|
|
997
|
+
focusedRoute.params
|
|
705
998
|
);
|
|
706
999
|
|
|
707
|
-
if (
|
|
708
|
-
|
|
1000
|
+
if (!queryParams.valid) {
|
|
1001
|
+
return undefined;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (queryParams.params) {
|
|
1005
|
+
focusedRoute.params = { ...focusedRoute.params, ...queryParams.params };
|
|
709
1006
|
}
|
|
710
1007
|
|
|
711
1008
|
return state;
|
|
712
1009
|
};
|
|
713
1010
|
|
|
714
|
-
const parseQueryParams = (
|
|
1011
|
+
const parseQueryParams = (
|
|
1012
|
+
path: string,
|
|
1013
|
+
parseConfig?: ParseConfig,
|
|
1014
|
+
pathParamNames: Set<string> = new Set(),
|
|
1015
|
+
explicitParamNames?: Set<string>,
|
|
1016
|
+
hasNestedScreens = false,
|
|
1017
|
+
routeParams?: object
|
|
1018
|
+
):
|
|
1019
|
+
| { valid: true; params?: Record<string, unknown> | undefined }
|
|
1020
|
+
| { valid: false } => {
|
|
715
1021
|
const query = path.split('?')[1];
|
|
716
|
-
const params: Record<string, unknown> = queryString.parse(query);
|
|
1022
|
+
const params: Record<string, unknown> = query ? queryString.parse(query) : {};
|
|
1023
|
+
|
|
1024
|
+
// Path params should always win over same-named query params.
|
|
1025
|
+
for (const name of pathParamNames) {
|
|
1026
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1027
|
+
delete params[name];
|
|
1028
|
+
}
|
|
717
1029
|
|
|
718
1030
|
if (parseConfig) {
|
|
719
|
-
Object.
|
|
720
|
-
if (
|
|
721
|
-
|
|
722
|
-
parseConfig[name] &&
|
|
723
|
-
typeof params[name] === 'string'
|
|
724
|
-
) {
|
|
725
|
-
params[name] = parseConfig[name](params[name]);
|
|
1031
|
+
for (const [name, parser] of Object.entries(parseConfig)) {
|
|
1032
|
+
if (!parser || pathParamNames.has(name)) {
|
|
1033
|
+
continue;
|
|
726
1034
|
}
|
|
727
|
-
|
|
1035
|
+
|
|
1036
|
+
const schema = getStandardSchema(parser);
|
|
1037
|
+
|
|
1038
|
+
if (!Object.hasOwn(params, name)) {
|
|
1039
|
+
if (!schema) {
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const result = getValidationResult(schema, undefined);
|
|
1044
|
+
|
|
1045
|
+
if (result.issues) {
|
|
1046
|
+
return { valid: false };
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (result.value !== undefined) {
|
|
1050
|
+
params[name] = result.value;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
if (schema) {
|
|
1057
|
+
const result = getValidationResult(schema, params[name]);
|
|
1058
|
+
|
|
1059
|
+
if (result.issues) {
|
|
1060
|
+
return { valid: false };
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
params[name] = result.value;
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const value = Array.isArray(params[name])
|
|
1068
|
+
? params[name][0]
|
|
1069
|
+
: params[name];
|
|
1070
|
+
|
|
1071
|
+
if (typeof value !== 'string') {
|
|
1072
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1073
|
+
delete params[name];
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (typeof parser === 'function') {
|
|
1078
|
+
params[name] = parser(value);
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
throw new Error(INVALID_PARSER_ERROR);
|
|
1083
|
+
}
|
|
728
1084
|
}
|
|
729
1085
|
|
|
730
|
-
|
|
1086
|
+
if (
|
|
1087
|
+
hasNestedScreens &&
|
|
1088
|
+
!explicitParamNames?.has('screen') &&
|
|
1089
|
+
(typeof params.screen === 'string' ||
|
|
1090
|
+
(routeParams != null &&
|
|
1091
|
+
'screen' in routeParams &&
|
|
1092
|
+
typeof routeParams.screen === 'string'))
|
|
1093
|
+
) {
|
|
1094
|
+
for (const name of NESTED_SCREEN_PARAM_NAMES) {
|
|
1095
|
+
if (!explicitParamNames?.has(name)) {
|
|
1096
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1097
|
+
delete params[name];
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
valid: true,
|
|
1104
|
+
params: Object.keys(params).length ? params : undefined,
|
|
1105
|
+
};
|
|
731
1106
|
};
|