@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.
Files changed (251) hide show
  1. package/lib/module/BaseNavigationContainer.js +20 -9
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/ConsumedParamsContext.js +5 -0
  4. package/lib/module/ConsumedParamsContext.js.map +1 -0
  5. package/lib/module/NavigationBuilderContext.js +1 -0
  6. package/lib/module/NavigationBuilderContext.js.map +1 -1
  7. package/lib/module/NavigationIndependentTree.js +8 -4
  8. package/lib/module/NavigationIndependentTree.js.map +1 -1
  9. package/lib/module/NavigationProvider.js +14 -3
  10. package/lib/module/NavigationProvider.js.map +1 -1
  11. package/lib/module/NavigationStateContext.js.map +1 -1
  12. package/lib/module/PreventRemoveProvider.js +3 -3
  13. package/lib/module/PreventRemoveProvider.js.map +1 -1
  14. package/lib/module/SceneView.js +46 -56
  15. package/lib/module/SceneView.js.map +1 -1
  16. package/lib/module/StaticNavigation.js +166 -46
  17. package/lib/module/StaticNavigation.js.map +1 -1
  18. package/lib/module/checkSerializable.js +11 -4
  19. package/lib/module/checkSerializable.js.map +1 -1
  20. package/lib/module/createNavigatorFactory.js +27 -5
  21. package/lib/module/createNavigatorFactory.js.map +1 -1
  22. package/lib/module/findFocusedRoute.js +7 -4
  23. package/lib/module/findFocusedRoute.js.map +1 -1
  24. package/lib/module/getActionFromState.js +40 -14
  25. package/lib/module/getActionFromState.js.map +1 -1
  26. package/lib/module/getPathFromState.js +48 -11
  27. package/lib/module/getPathFromState.js.map +1 -1
  28. package/lib/module/getPatternParts.js +3 -0
  29. package/lib/module/getPatternParts.js.map +1 -1
  30. package/lib/module/getStateFromPath.js +353 -166
  31. package/lib/module/getStateFromPath.js.map +1 -1
  32. package/lib/module/getStateFromRouteParams.js +24 -0
  33. package/lib/module/getStateFromRouteParams.js.map +1 -0
  34. package/lib/module/index.js +3 -4
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/theming/useTheme.js +1 -1
  37. package/lib/module/theming/useTheme.js.map +1 -1
  38. package/lib/module/types.js +15 -0
  39. package/lib/module/types.js.map +1 -1
  40. package/lib/module/useDescriptors.js +18 -9
  41. package/lib/module/useDescriptors.js.map +1 -1
  42. package/lib/module/useEventEmitter.js +43 -31
  43. package/lib/module/useEventEmitter.js.map +1 -1
  44. package/lib/module/useFocusEvents.js +6 -2
  45. package/lib/module/useFocusEvents.js.map +1 -1
  46. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  47. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  48. package/lib/module/useIsFocused.js +7 -12
  49. package/lib/module/useIsFocused.js.map +1 -1
  50. package/lib/module/useNavigation.js.map +1 -1
  51. package/lib/module/useNavigationBuilder.js +144 -86
  52. package/lib/module/useNavigationBuilder.js.map +1 -1
  53. package/lib/module/useNavigationCache.js +3 -3
  54. package/lib/module/useNavigationCache.js.map +1 -1
  55. package/lib/module/useNavigationHelpers.js +1 -1
  56. package/lib/module/useNavigationHelpers.js.map +1 -1
  57. package/lib/module/useNavigationIndependentTree.js +1 -1
  58. package/lib/module/useNavigationIndependentTree.js.map +1 -1
  59. package/lib/module/useNavigationState.js +6 -4
  60. package/lib/module/useNavigationState.js.map +1 -1
  61. package/lib/module/useOnAction.js +3 -3
  62. package/lib/module/useOnAction.js.map +1 -1
  63. package/lib/module/useOnGetState.js +6 -3
  64. package/lib/module/useOnGetState.js.map +1 -1
  65. package/lib/module/useOnPreventRemove.js +3 -3
  66. package/lib/module/useOnPreventRemove.js.map +1 -1
  67. package/lib/module/useOnRouteFocus.js +1 -1
  68. package/lib/module/useOnRouteFocus.js.map +1 -1
  69. package/lib/module/useOptionsGetters.js +7 -4
  70. package/lib/module/useOptionsGetters.js.map +1 -1
  71. package/lib/module/usePreventRemove.js +1 -1
  72. package/lib/module/usePreventRemove.js.map +1 -1
  73. package/lib/module/usePreventRemoveContext.js +1 -1
  74. package/lib/module/usePreventRemoveContext.js.map +1 -1
  75. package/lib/module/useRegisterNavigator.js +2 -2
  76. package/lib/module/useRegisterNavigator.js.map +1 -1
  77. package/lib/module/useRoute.js.map +1 -1
  78. package/lib/module/useRouteCache.js +2 -1
  79. package/lib/module/useRouteCache.js.map +1 -1
  80. package/lib/module/useScheduleUpdate.js +1 -1
  81. package/lib/module/useScheduleUpdate.js.map +1 -1
  82. package/lib/module/useStateForPath.js +1 -1
  83. package/lib/module/useStateForPath.js.map +1 -1
  84. package/lib/module/validatePathConfig.js +2 -1
  85. package/lib/module/validatePathConfig.js.map +1 -1
  86. package/lib/typescript/src/BaseNavigationContainer.d.ts +6 -3
  87. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  88. package/lib/typescript/src/ConsumedParamsContext.d.ts +8 -0
  89. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -0
  90. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  91. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  92. package/lib/typescript/src/Group.d.ts +1 -1
  93. package/lib/typescript/src/Group.d.ts.map +1 -1
  94. package/lib/typescript/src/NavigationBuilderContext.d.ts +12 -6
  95. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  96. package/lib/typescript/src/NavigationContainerRefContext.d.ts +1 -1
  97. package/lib/typescript/src/NavigationContainerRefContext.d.ts.map +1 -1
  98. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
  99. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
  100. package/lib/typescript/src/NavigationHelpersContext.d.ts +1 -1
  101. package/lib/typescript/src/NavigationHelpersContext.d.ts.map +1 -1
  102. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  103. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  104. package/lib/typescript/src/NavigationProvider.d.ts +10 -10
  105. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  106. package/lib/typescript/src/NavigationStateContext.d.ts +3 -3
  107. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  108. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  109. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  110. package/lib/typescript/src/SceneView.d.ts +3 -2
  111. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  112. package/lib/typescript/src/Screen.d.ts +1 -1
  113. package/lib/typescript/src/Screen.d.ts.map +1 -1
  114. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  115. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  116. package/lib/typescript/src/StaticNavigation.d.ts +123 -92
  117. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  118. package/lib/typescript/src/checkSerializable.d.ts +5 -3
  119. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  120. package/lib/typescript/src/createNavigationContainerRef.d.ts +1 -1
  121. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  122. package/lib/typescript/src/createNavigatorFactory.d.ts +17 -5
  123. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  124. package/lib/typescript/src/findFocusedRoute.d.ts +3 -3
  125. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  126. package/lib/typescript/src/getActionFromState.d.ts +4 -4
  127. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  128. package/lib/typescript/src/getPathFromState.d.ts +3 -3
  129. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  130. package/lib/typescript/src/getPatternParts.d.ts +1 -0
  131. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  132. package/lib/typescript/src/getStateFromPath.d.ts +5 -5
  133. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  134. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  135. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  136. package/lib/typescript/src/index.d.ts +34 -33
  137. package/lib/typescript/src/index.d.ts.map +1 -1
  138. package/lib/typescript/src/theming/ThemeContext.d.ts +1 -1
  139. package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -1
  140. package/lib/typescript/src/theming/ThemeProvider.d.ts +2 -2
  141. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  142. package/lib/typescript/src/theming/useTheme.d.ts +1 -1
  143. package/lib/typescript/src/theming/useTheme.d.ts.map +1 -1
  144. package/lib/typescript/src/types.d.ts +215 -131
  145. package/lib/typescript/src/types.d.ts.map +1 -1
  146. package/lib/typescript/src/useChildListeners.d.ts +3 -3
  147. package/lib/typescript/src/useChildListeners.d.ts.map +1 -1
  148. package/lib/typescript/src/useComponent.d.ts +1 -1
  149. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  150. package/lib/typescript/src/useDescriptors.d.ts +7 -7
  151. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  152. package/lib/typescript/src/useEventEmitter.d.ts +1 -1
  153. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  154. package/lib/typescript/src/useFocusEvents.d.ts +2 -2
  155. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  156. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts +2 -2
  157. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts.map +1 -1
  158. package/lib/typescript/src/useIsFocused.d.ts +3 -0
  159. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  160. package/lib/typescript/src/useKeyedChildListeners.d.ts +3 -3
  161. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  162. package/lib/typescript/src/useNavigation.d.ts +4 -2
  163. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  164. package/lib/typescript/src/useNavigationBuilder.d.ts +24 -24
  165. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  166. package/lib/typescript/src/useNavigationCache.d.ts +3 -3
  167. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  168. package/lib/typescript/src/useNavigationContainerRef.d.ts +1 -1
  169. package/lib/typescript/src/useNavigationContainerRef.d.ts.map +1 -1
  170. package/lib/typescript/src/useNavigationHelpers.d.ts +19 -19
  171. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  172. package/lib/typescript/src/useNavigationState.d.ts +2 -2
  173. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  174. package/lib/typescript/src/useOnAction.d.ts +9 -9
  175. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  176. package/lib/typescript/src/useOnGetState.d.ts +1 -1
  177. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  178. package/lib/typescript/src/useOnPreventRemove.d.ts +3 -3
  179. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  180. package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
  181. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  182. package/lib/typescript/src/useOptionsGetters.d.ts +1 -1
  183. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  184. package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -1
  185. package/lib/typescript/src/useRoute.d.ts +2 -2
  186. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  187. package/lib/typescript/src/useRouteCache.d.ts +4 -4
  188. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  189. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  190. package/lib/typescript/src/useStateForPath.d.ts +1 -1
  191. package/lib/typescript/src/useStateForPath.d.ts.map +1 -1
  192. package/lib/typescript/src/utilities.d.ts +45 -5
  193. package/lib/typescript/src/utilities.d.ts.map +1 -1
  194. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  195. package/package.json +23 -20
  196. package/src/BaseNavigationContainer.tsx +337 -326
  197. package/src/ConsumedParamsContext.tsx +10 -0
  198. package/src/NavigationBuilderContext.tsx +14 -8
  199. package/src/NavigationFocusedRouteStateContext.tsx +4 -4
  200. package/src/NavigationIndependentTree.tsx +8 -5
  201. package/src/NavigationProvider.tsx +17 -3
  202. package/src/NavigationStateContext.tsx +5 -6
  203. package/src/PreventRemoveProvider.tsx +3 -3
  204. package/src/SceneView.tsx +58 -56
  205. package/src/StaticNavigation.tsx +488 -250
  206. package/src/checkSerializable.tsx +22 -12
  207. package/src/createNavigatorFactory.tsx +83 -8
  208. package/src/findFocusedRoute.tsx +13 -8
  209. package/src/getActionFromState.tsx +68 -39
  210. package/src/getPathFromState.tsx +91 -27
  211. package/src/getPatternParts.tsx +8 -0
  212. package/src/getStateFromPath.tsx +555 -180
  213. package/src/getStateFromRouteParams.tsx +60 -0
  214. package/src/index.tsx +47 -5
  215. package/src/theming/useTheme.tsx +1 -1
  216. package/src/types.tsx +541 -338
  217. package/src/useDescriptors.tsx +27 -12
  218. package/src/useEventEmitter.tsx +45 -36
  219. package/src/useFocusEvents.tsx +8 -2
  220. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  221. package/src/useIsFocused.tsx +14 -21
  222. package/src/useNavigation.tsx +5 -3
  223. package/src/useNavigationBuilder.tsx +220 -132
  224. package/src/useNavigationCache.tsx +6 -6
  225. package/src/useNavigationHelpers.tsx +1 -1
  226. package/src/useNavigationIndependentTree.tsx +1 -1
  227. package/src/useNavigationState.tsx +11 -7
  228. package/src/useOnAction.tsx +10 -10
  229. package/src/useOnGetState.tsx +5 -3
  230. package/src/useOnPreventRemove.tsx +3 -3
  231. package/src/useOnRouteFocus.tsx +10 -12
  232. package/src/useOptionsGetters.tsx +7 -4
  233. package/src/usePreventRemove.tsx +1 -1
  234. package/src/usePreventRemoveContext.tsx +1 -1
  235. package/src/useRegisterNavigator.tsx +2 -2
  236. package/src/useRoute.tsx +4 -2
  237. package/src/useRouteCache.tsx +2 -1
  238. package/src/useScheduleUpdate.tsx +1 -3
  239. package/src/useStateForPath.tsx +1 -1
  240. package/src/utilities.tsx +101 -5
  241. package/src/validatePathConfig.tsx +1 -0
  242. package/lib/module/CurrentRenderContext.js +0 -10
  243. package/lib/module/CurrentRenderContext.js.map +0 -1
  244. package/lib/module/useCurrentRender.js +0 -19
  245. package/lib/module/useCurrentRender.js.map +0 -1
  246. package/lib/typescript/src/CurrentRenderContext.d.ts +0 -9
  247. package/lib/typescript/src/CurrentRenderContext.d.ts.map +0 -1
  248. package/lib/typescript/src/useCurrentRender.d.ts +0 -14
  249. package/lib/typescript/src/useCurrentRender.d.ts.map +0 -1
  250. package/src/CurrentRenderContext.tsx +0 -9
  251. 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 * as queryString from 'query-string';
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 = remaining.split('/').filter(Boolean).map(segment => {
60
- const name = decodeURIComponent(segment);
61
- return {
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
- return createNestedStateObject(path, match.routeNames.map(name => ({
119
+ const config = resolveSharedConfig(match);
120
+ return createNestedStateObject(path, config.routeNames.map(name => ({
76
121
  name
77
- })), initialRoutes, configs);
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
- remainingPath
89
- } = matchAgainstConfigs(remaining, configs);
90
- if (routes !== undefined) {
91
- // This will always be empty if full path matched
92
- current = createNestedStateObject(path, routes, initialRoutes, configs);
93
- remaining = remainingPath;
94
- result = current;
95
- }
96
- if (current == null || result == null) {
97
- return undefined;
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 result;
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 configWithRegexes = getConfigsWithRegexes(configs);
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
- configWithRegexes
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, [], [], []))).sort((a, b) => {
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 (a.segments[i] == null) {
226
+ if (aSegment == null) {
165
227
  return 1;
166
228
  }
167
229
 
168
230
  // if a is longer, a gets higher priority
169
- if (b.segments[i] == null) {
231
+ if (bSegment == null) {
170
232
  return -1;
171
233
  }
172
- const aWildCard = a.segments[i] === '*';
173
- const bWildCard = b.segments[i] === '*';
174
- const aParam = a.segments[i].startsWith(':');
175
- const bParam = b.segments[i].startsWith(':');
176
- const aRegex = aParam && a.segments[i].includes('(');
177
- const bRegex = bParam && b.segments[i].includes('(');
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
- function getConfigsWithRegexes(configs) {
238
- return configs.map(c => ({
239
- ...c,
240
- // Add `$` to the regex to make sure it matches till end of the path and not just beginning
241
- regex: c.regex ? new RegExp(c.regex.source + '$') : undefined
242
- }));
243
- }
244
- const matchAgainstConfigs = (remaining, configs) => {
245
- let routes;
246
- let remainingPath = remaining;
247
-
248
- // Go through all configs, and see if the next path segment matches our regex
249
- for (const config of configs) {
250
- if (!config.regex) {
251
- continue;
252
- }
253
- const match = remainingPath.match(config.regex);
254
-
255
- // If our regex matches, we need to extract params from the path
256
- if (match) {
257
- routes = config.routeNames.map(routeName => {
258
- const routeConfig = configs.find(c => {
259
- // Check matching name AND pattern in case same screen is used at different levels in config
260
- return c.screen === routeName && arrayStartsWith(config.segments, c.segments);
261
- });
262
- const params = routeConfig && match.groups ? Object.fromEntries(Object.entries(match.groups).map(([key, value]) => {
263
- const index = Number(key.replace('param_', ''));
264
- const param = routeConfig.params.find(it => it.index === index);
265
- if (param?.screen === routeName && param?.name) {
266
- return [param.name, value];
267
- }
268
- return null;
269
- }).filter(it => it != null).map(([key, value]) => {
270
- if (value == null) {
271
- return [key, undefined];
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
- const decoded = decodeURIComponent(value);
274
- const parsed = routeConfig.parse?.[key] ? routeConfig.parse[key](decoded) : decoded;
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
- return {
284
- name: routeName
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
- return {
292
- routes,
293
- remainingPath
294
- };
372
+ if (validationFailed) {
373
+ return undefined;
374
+ }
375
+ return matchedRoutes;
295
376
  };
296
- const createNormalizedConfigs = (screen, routeConfig, initials, paths, parentScreens, routeNames) => {
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
- paths.push({
303
- screen,
304
- path: config
305
- });
306
- configs.push(createConfigItem(screen, [...routeNames], [...paths]));
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], [...paths, {
323
- screen,
324
- path: alias
325
- }], config.parse));
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.exact ? [{
328
- screen,
329
- path: alias.path
330
- }] : [...paths, {
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
- if (config.exact) {
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 ('screens' in config && config.screens) {
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(config.screens).forEach(nestedConfig => {
361
- const result = createNormalizedConfigs(nestedConfig, config.screens, initials, [...paths], [...parentScreens], routeNames);
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, paths, parse) => {
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 `(((?<param_${i}>${reg})\\/)${it.optional ? '?' : ''})`;
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 findParseConfigForRoute = (routeName, flatConfig) => {
405
- for (const config of flatConfig) {
406
- if (routeName === config.routeNames[config.routeNames.length - 1]) {
407
- return config.parse;
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
- return undefined;
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
- if (parentScreens[i].localeCompare(config.parentScreens[i]) !== 0) {
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, flatConfig) => {
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
- while (route = routes.shift()) {
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].state = createStateObject(initialRoute, route, routes.length === 0);
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
- nestedState = nestedState.routes[nestedStateIndex].state;
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
- route = findFocusedRoute(state);
492
- route.path = path.replace(/\/$/, '');
493
- const params = parseQueryParams(path, flatConfig ? findParseConfigForRoute(route.name, flatConfig) : undefined);
494
- if (params) {
495
- route.params = {
496
- ...route.params,
497
- ...params
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.keys(params).forEach(name => {
507
- if (Object.hasOwnProperty.call(parseConfig, name) && parseConfig[name] && typeof params[name] === 'string') {
508
- params[name] = parseConfig[name](params[name]);
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 Object.keys(params).length ? params : undefined;
696
+ return {
697
+ valid: true,
698
+ params: Object.keys(params).length ? params : undefined
699
+ };
513
700
  };
514
701
  //# sourceMappingURL=getStateFromPath.js.map