@react-navigation/core 8.0.0-alpha.3 → 8.0.0-alpha.30

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 (283) hide show
  1. package/lib/module/BaseNavigationContainer.js +34 -17
  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/DataLoading.js +127 -0
  6. package/lib/module/DataLoading.js.map +1 -0
  7. package/lib/module/NavigationBuilderContext.js +1 -0
  8. package/lib/module/NavigationBuilderContext.js.map +1 -1
  9. package/lib/module/NavigationIndependentTree.js +17 -8
  10. package/lib/module/NavigationIndependentTree.js.map +1 -1
  11. package/lib/module/NavigationProvider.js +22 -11
  12. package/lib/module/NavigationProvider.js.map +1 -1
  13. package/lib/module/NavigationStateContext.js.map +1 -1
  14. package/lib/module/PreventRemoveContext.js.map +1 -1
  15. package/lib/module/PreventRemoveProvider.js +37 -27
  16. package/lib/module/PreventRemoveProvider.js.map +1 -1
  17. package/lib/module/SceneView.js +7 -40
  18. package/lib/module/SceneView.js.map +1 -1
  19. package/lib/module/StaticNavigation.js +179 -50
  20. package/lib/module/StaticNavigation.js.map +1 -1
  21. package/lib/module/StaticTreeContext.js +5 -0
  22. package/lib/module/StaticTreeContext.js.map +1 -0
  23. package/lib/module/checkSerializable.js +18 -13
  24. package/lib/module/checkSerializable.js.map +1 -1
  25. package/lib/module/createNavigationContainerRef.js +6 -8
  26. package/lib/module/createNavigationContainerRef.js.map +1 -1
  27. package/lib/module/createNavigatorFactory.js +27 -5
  28. package/lib/module/createNavigatorFactory.js.map +1 -1
  29. package/lib/module/findFocusedRoute.js +7 -4
  30. package/lib/module/findFocusedRoute.js.map +1 -1
  31. package/lib/module/getActionFromState.js +45 -20
  32. package/lib/module/getActionFromState.js.map +1 -1
  33. package/lib/module/getPathFromState.js +171 -90
  34. package/lib/module/getPathFromState.js.map +1 -1
  35. package/lib/module/getPatternParts.js +20 -4
  36. package/lib/module/getPatternParts.js.map +1 -1
  37. package/lib/module/getStateFromPath.js +485 -214
  38. package/lib/module/getStateFromPath.js.map +1 -1
  39. package/lib/module/getStateFromRouteParams.js +24 -0
  40. package/lib/module/getStateFromRouteParams.js.map +1 -0
  41. package/lib/module/index.js +5 -4
  42. package/lib/module/index.js.map +1 -1
  43. package/lib/module/theming/useTheme.js +1 -1
  44. package/lib/module/theming/useTheme.js.map +1 -1
  45. package/lib/module/types.js +35 -0
  46. package/lib/module/types.js.map +1 -1
  47. package/lib/module/useDescriptors.js +19 -11
  48. package/lib/module/useDescriptors.js.map +1 -1
  49. package/lib/module/useEventEmitter.js +48 -33
  50. package/lib/module/useEventEmitter.js.map +1 -1
  51. package/lib/module/useFocusEvents.js +12 -4
  52. package/lib/module/useFocusEvents.js.map +1 -1
  53. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  54. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  55. package/lib/module/useIsFocused.js +7 -12
  56. package/lib/module/useIsFocused.js.map +1 -1
  57. package/lib/module/useKeyedChildListeners.js +4 -2
  58. package/lib/module/useKeyedChildListeners.js.map +1 -1
  59. package/lib/module/useLazyValue.js.map +1 -1
  60. package/lib/module/useNavigation.js +2 -15
  61. package/lib/module/useNavigation.js.map +1 -1
  62. package/lib/module/useNavigationBuilder.js +201 -131
  63. package/lib/module/useNavigationBuilder.js.map +1 -1
  64. package/lib/module/useNavigationCache.js +22 -17
  65. package/lib/module/useNavigationCache.js.map +1 -1
  66. package/lib/module/useNavigationHelpers.js +28 -10
  67. package/lib/module/useNavigationHelpers.js.map +1 -1
  68. package/lib/module/useNavigationIndependentTree.js +1 -1
  69. package/lib/module/useNavigationIndependentTree.js.map +1 -1
  70. package/lib/module/useNavigationState.js +70 -35
  71. package/lib/module/useNavigationState.js.map +1 -1
  72. package/lib/module/useOnAction.js +37 -7
  73. package/lib/module/useOnAction.js.map +1 -1
  74. package/lib/module/useOnGetState.js +6 -3
  75. package/lib/module/useOnGetState.js.map +1 -1
  76. package/lib/module/useOnPreventRemove.js +39 -30
  77. package/lib/module/useOnPreventRemove.js.map +1 -1
  78. package/lib/module/useOnRouteFocus.js +1 -1
  79. package/lib/module/useOnRouteFocus.js.map +1 -1
  80. package/lib/module/useOptionsGetters.js +10 -5
  81. package/lib/module/useOptionsGetters.js.map +1 -1
  82. package/lib/module/usePreventRemove.js +10 -3
  83. package/lib/module/usePreventRemove.js.map +1 -1
  84. package/lib/module/usePreventRemoveContext.js +1 -1
  85. package/lib/module/usePreventRemoveContext.js.map +1 -1
  86. package/lib/module/useRegisterNavigator.js +2 -2
  87. package/lib/module/useRegisterNavigator.js.map +1 -1
  88. package/lib/module/useRoute.js +2 -0
  89. package/lib/module/useRoute.js.map +1 -1
  90. package/lib/module/useRouteCache.js +17 -11
  91. package/lib/module/useRouteCache.js.map +1 -1
  92. package/lib/module/useScheduleUpdate.js +9 -9
  93. package/lib/module/useScheduleUpdate.js.map +1 -1
  94. package/lib/module/useStateForPath.js +1 -1
  95. package/lib/module/useStateForPath.js.map +1 -1
  96. package/lib/module/useSyncState.js +7 -3
  97. package/lib/module/useSyncState.js.map +1 -1
  98. package/lib/module/validatePathConfig.js +50 -36
  99. package/lib/module/validatePathConfig.js.map +1 -1
  100. package/lib/typescript/src/BaseNavigationContainer.d.ts +6 -3
  101. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  102. package/lib/typescript/src/ConsumedParamsContext.d.ts +3 -0
  103. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -0
  104. package/lib/typescript/src/DataLoading.d.ts +21 -0
  105. package/lib/typescript/src/DataLoading.d.ts.map +1 -0
  106. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  107. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  108. package/lib/typescript/src/Group.d.ts +1 -1
  109. package/lib/typescript/src/Group.d.ts.map +1 -1
  110. package/lib/typescript/src/NavigationBuilderContext.d.ts +9 -8
  111. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  112. package/lib/typescript/src/NavigationContainerRefContext.d.ts +1 -1
  113. package/lib/typescript/src/NavigationContainerRefContext.d.ts.map +1 -1
  114. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
  115. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
  116. package/lib/typescript/src/NavigationHelpersContext.d.ts +1 -1
  117. package/lib/typescript/src/NavigationHelpersContext.d.ts.map +1 -1
  118. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  119. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  120. package/lib/typescript/src/NavigationProvider.d.ts +12 -13
  121. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  122. package/lib/typescript/src/NavigationStateContext.d.ts +3 -3
  123. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  124. package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
  125. package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
  126. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  127. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  128. package/lib/typescript/src/SceneView.d.ts +3 -2
  129. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  130. package/lib/typescript/src/Screen.d.ts +1 -1
  131. package/lib/typescript/src/Screen.d.ts.map +1 -1
  132. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  133. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  134. package/lib/typescript/src/StaticNavigation.d.ts +189 -109
  135. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  136. package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
  137. package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
  138. package/lib/typescript/src/checkSerializable.d.ts +8 -4
  139. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  140. package/lib/typescript/src/createNavigationContainerRef.d.ts +1 -1
  141. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  142. package/lib/typescript/src/createNavigatorFactory.d.ts +23 -5
  143. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  144. package/lib/typescript/src/findFocusedRoute.d.ts +3 -3
  145. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  146. package/lib/typescript/src/getActionFromState.d.ts +4 -4
  147. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  148. package/lib/typescript/src/getPathFromState.d.ts +3 -3
  149. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  150. package/lib/typescript/src/getPatternParts.d.ts +1 -0
  151. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  152. package/lib/typescript/src/getStateFromPath.d.ts +5 -5
  153. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  154. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  155. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  156. package/lib/typescript/src/index.d.ts +36 -33
  157. package/lib/typescript/src/index.d.ts.map +1 -1
  158. package/lib/typescript/src/theming/ThemeContext.d.ts +1 -1
  159. package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -1
  160. package/lib/typescript/src/theming/ThemeProvider.d.ts +2 -2
  161. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  162. package/lib/typescript/src/theming/useTheme.d.ts +1 -1
  163. package/lib/typescript/src/theming/useTheme.d.ts.map +1 -1
  164. package/lib/typescript/src/types.d.ts +260 -159
  165. package/lib/typescript/src/types.d.ts.map +1 -1
  166. package/lib/typescript/src/useChildListeners.d.ts +3 -3
  167. package/lib/typescript/src/useChildListeners.d.ts.map +1 -1
  168. package/lib/typescript/src/useComponent.d.ts +1 -1
  169. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  170. package/lib/typescript/src/useDescriptors.d.ts +8 -8
  171. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  172. package/lib/typescript/src/useEventEmitter.d.ts +2 -2
  173. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  174. package/lib/typescript/src/useFocusEvents.d.ts +2 -2
  175. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  176. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts +2 -2
  177. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts.map +1 -1
  178. package/lib/typescript/src/useIsFocused.d.ts +3 -0
  179. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  180. package/lib/typescript/src/useKeyedChildListeners.d.ts +3 -3
  181. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  182. package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
  183. package/lib/typescript/src/useNavigation.d.ts +4 -2
  184. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  185. package/lib/typescript/src/useNavigationBuilder.d.ts +23 -30
  186. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  187. package/lib/typescript/src/useNavigationCache.d.ts +3 -3
  188. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  189. package/lib/typescript/src/useNavigationContainerRef.d.ts +1 -1
  190. package/lib/typescript/src/useNavigationContainerRef.d.ts.map +1 -1
  191. package/lib/typescript/src/useNavigationHelpers.d.ts +19 -27
  192. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  193. package/lib/typescript/src/useNavigationState.d.ts +13 -3
  194. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  195. package/lib/typescript/src/useOnAction.d.ts +9 -9
  196. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  197. package/lib/typescript/src/useOnGetState.d.ts +1 -1
  198. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  199. package/lib/typescript/src/useOnPreventRemove.d.ts +5 -9
  200. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  201. package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
  202. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  203. package/lib/typescript/src/useOptionsGetters.d.ts +1 -1
  204. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  205. package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
  206. package/lib/typescript/src/usePreventRemoveContext.d.ts +2 -1
  207. package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
  208. package/lib/typescript/src/useRoute.d.ts +7 -3
  209. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  210. package/lib/typescript/src/useRouteCache.d.ts +5 -5
  211. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  212. package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
  213. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  214. package/lib/typescript/src/useStateForPath.d.ts +1 -1
  215. package/lib/typescript/src/useStateForPath.d.ts.map +1 -1
  216. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  217. package/lib/typescript/src/utilities.d.ts +74 -37
  218. package/lib/typescript/src/utilities.d.ts.map +1 -1
  219. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  220. package/package.json +23 -22
  221. package/src/BaseNavigationContainer.tsx +351 -333
  222. package/src/ConsumedParamsContext.tsx +5 -0
  223. package/src/DataLoading.tsx +203 -0
  224. package/src/NavigationBuilderContext.tsx +14 -9
  225. package/src/NavigationFocusedRouteStateContext.tsx +4 -4
  226. package/src/NavigationIndependentTree.tsx +18 -12
  227. package/src/NavigationProvider.tsx +26 -15
  228. package/src/NavigationStateContext.tsx +5 -6
  229. package/src/PreventRemoveContext.tsx +1 -0
  230. package/src/PreventRemoveProvider.tsx +51 -46
  231. package/src/SceneView.tsx +7 -39
  232. package/src/StaticNavigation.tsx +659 -307
  233. package/src/StaticTreeContext.tsx +7 -0
  234. package/src/checkSerializable.tsx +35 -19
  235. package/src/createNavigationContainerRef.tsx +12 -9
  236. package/src/createNavigatorFactory.tsx +92 -8
  237. package/src/findFocusedRoute.tsx +13 -8
  238. package/src/getActionFromState.tsx +86 -51
  239. package/src/getPathFromState.tsx +254 -128
  240. package/src/getPatternParts.tsx +25 -4
  241. package/src/getStateFromPath.tsx +811 -309
  242. package/src/getStateFromRouteParams.tsx +60 -0
  243. package/src/index.tsx +55 -5
  244. package/src/theming/useTheme.tsx +1 -1
  245. package/src/types.tsx +798 -373
  246. package/src/useDescriptors.tsx +28 -14
  247. package/src/useEventEmitter.tsx +52 -38
  248. package/src/useFocusEvents.tsx +15 -7
  249. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  250. package/src/useIsFocused.tsx +14 -21
  251. package/src/useKeyedChildListeners.tsx +4 -2
  252. package/src/useLazyValue.tsx +2 -2
  253. package/src/useNavigation.tsx +7 -24
  254. package/src/useNavigationBuilder.tsx +311 -186
  255. package/src/useNavigationCache.tsx +32 -25
  256. package/src/useNavigationHelpers.tsx +27 -10
  257. package/src/useNavigationIndependentTree.tsx +1 -1
  258. package/src/useNavigationState.tsx +93 -59
  259. package/src/useOnAction.tsx +61 -13
  260. package/src/useOnGetState.tsx +5 -3
  261. package/src/useOnPreventRemove.tsx +75 -42
  262. package/src/useOnRouteFocus.tsx +10 -12
  263. package/src/useOptionsGetters.tsx +11 -5
  264. package/src/usePreventRemove.tsx +12 -3
  265. package/src/usePreventRemoveContext.tsx +1 -1
  266. package/src/useRegisterNavigator.tsx +2 -2
  267. package/src/useRoute.tsx +20 -6
  268. package/src/useRouteCache.tsx +22 -16
  269. package/src/useScheduleUpdate.tsx +9 -10
  270. package/src/useStateForPath.tsx +1 -1
  271. package/src/useSyncState.tsx +8 -3
  272. package/src/utilities.tsx +144 -31
  273. package/src/validatePathConfig.tsx +86 -57
  274. package/lib/module/CurrentRenderContext.js +0 -10
  275. package/lib/module/CurrentRenderContext.js.map +0 -1
  276. package/lib/module/useCurrentRender.js +0 -19
  277. package/lib/module/useCurrentRender.js.map +0 -1
  278. package/lib/typescript/src/CurrentRenderContext.d.ts +0 -9
  279. package/lib/typescript/src/CurrentRenderContext.d.ts.map +0 -1
  280. package/lib/typescript/src/useCurrentRender.d.ts +0 -14
  281. package/lib/typescript/src/useCurrentRender.d.ts.map +0 -1
  282. package/src/CurrentRenderContext.tsx +0 -9
  283. package/src/useCurrentRender.tsx +0 -31
@@ -1,17 +1,14 @@
1
- import type {
2
- NavigationState,
3
- PartialState,
4
- Route,
5
- } from '@react-navigation/routers';
6
- import * as queryString from 'query-string';
1
+ import type { NavigationState, PartialState } from '@react-navigation/routers';
2
+ import queryString from 'query-string';
7
3
 
8
4
  import { getPatternParts, type PatternPart } from './getPatternParts';
5
+ import { getStateFromRouteParams } from './getStateFromRouteParams';
9
6
  import type { PathConfig, PathConfigMap } from './types';
10
7
  import { validatePathConfig } from './validatePathConfig';
11
8
 
12
9
  type Options<ParamList extends {}> = {
13
- path?: string;
14
- initialRouteName?: string;
10
+ path?: string | undefined;
11
+ initialRouteName?: string | undefined;
15
12
  screens: PathConfigMap<ParamList>;
16
13
  };
17
14
 
@@ -19,40 +16,87 @@ type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
19
16
 
20
17
  type StringifyConfig = Record<string, ((value: unknown) => string) | undefined>;
21
18
 
19
+ type SerializedParamValue = string | string[] | null;
20
+
22
21
  type ConfigItem = {
23
- parts?: PatternPart[];
24
- stringify?: StringifyConfig;
25
- screens?: Record<string, ConfigItem>;
22
+ parts?: PatternPart[] | undefined;
23
+ ownParts: PatternPart[];
24
+ stringify?: StringifyConfig | undefined;
25
+ screens?: Record<string, ConfigItem> | undefined;
26
+ };
27
+
28
+ const serializeParamValue = (value: unknown): SerializedParamValue =>
29
+ value === null
30
+ ? null
31
+ : Array.isArray(value)
32
+ ? value.map(String)
33
+ : String(value);
34
+
35
+ const encodePathParam = (value: string) => {
36
+ let result = '';
37
+
38
+ for (const char of value) {
39
+ const charCode = char.charCodeAt(0);
40
+ const safe =
41
+ (charCode >= 65 && charCode <= 90) ||
42
+ (charCode >= 97 && charCode <= 122) ||
43
+ (charCode >= 48 && charCode <= 57) ||
44
+ "-._~!$&'()*+,;=:@".includes(char);
45
+
46
+ result += safe ? char : encodeURIComponent(char);
47
+ }
48
+
49
+ return result;
26
50
  };
27
51
 
28
- const getActiveRoute = (state: State): { name: string; params?: object } => {
52
+ const getActiveRoute = (
53
+ state: State,
54
+ configs: Record<string, ConfigItem> | undefined,
55
+ getRouteState: (
56
+ route: State['routes'][number],
57
+ config: ConfigItem | undefined
58
+ ) => State | undefined
59
+ ): { name: string; params?: object | undefined } => {
29
60
  const route =
30
61
  typeof state.index === 'number'
31
62
  ? state.routes[state.index]
32
63
  : state.routes[state.routes.length - 1];
33
64
 
34
- if (route.state) {
35
- return getActiveRoute(route.state);
65
+ if (route == null) {
66
+ throw new Error(`Couldn't find the active route.`);
67
+ }
68
+
69
+ const config = configs?.[route.name];
70
+ const routeState = getRouteState(route, config);
71
+
72
+ if (routeState) {
73
+ return getActiveRoute(routeState, config?.screens, getRouteState);
36
74
  }
37
75
 
38
76
  return route;
39
77
  };
40
78
 
41
79
  const cachedNormalizedConfigs = new WeakMap<
42
- PathConfigMap<{}>,
80
+ Options<{}>,
43
81
  Record<string, ConfigItem>
44
82
  >();
45
83
 
46
84
  const getNormalizedConfigs = (options?: Options<{}>) => {
47
- if (!options?.screens) return {};
85
+ if (!options) {
86
+ return {};
87
+ }
48
88
 
49
- const cached = cachedNormalizedConfigs.get(options?.screens);
89
+ const cached = cachedNormalizedConfigs.get(options);
50
90
 
51
- if (cached) return cached;
91
+ if (cached) {
92
+ return cached;
93
+ }
94
+
95
+ validatePathConfig(options);
52
96
 
53
97
  const normalizedConfigs = createNormalizedConfigs(options.screens);
54
98
 
55
- cachedNormalizedConfigs.set(options.screens, normalizedConfigs);
99
+ cachedNormalizedConfigs.set(options, normalizedConfigs);
56
100
 
57
101
  return normalizedConfigs;
58
102
  };
@@ -96,105 +140,177 @@ export function getPathFromState<ParamList extends {}>(
96
140
  );
97
141
  }
98
142
 
99
- if (options) {
100
- validatePathConfig(options);
101
- }
102
-
103
143
  const configs = getNormalizedConfigs(options);
144
+ const cachedRouteStates = new Map<object, State | undefined>();
145
+
146
+ const getRouteState = (
147
+ route: State['routes'][number],
148
+ config: ConfigItem | undefined
149
+ ) => {
150
+ if (route.state) {
151
+ return route.state;
152
+ }
153
+
154
+ const hasScreenParams =
155
+ route.params &&
156
+ 'screen' in route.params &&
157
+ typeof route.params.screen === 'string' &&
158
+ config?.screens?.[route.params.screen];
159
+
160
+ const hasStateParams =
161
+ route.params && 'state' in route.params && config?.screens;
162
+
163
+ if (
164
+ !route.params ||
165
+ !config?.screens ||
166
+ (!hasScreenParams && !hasStateParams)
167
+ ) {
168
+ return undefined;
169
+ }
170
+
171
+ if (!cachedRouteStates.has(route)) {
172
+ cachedRouteStates.set(route, getStateFromRouteParams(route.params));
173
+ }
174
+
175
+ return cachedRouteStates.get(route);
176
+ };
177
+
178
+ const focusedRoute = getActiveRoute(state, configs, getRouteState);
104
179
 
105
180
  let path = '/';
106
181
  let current: State | undefined = state;
107
182
 
108
- const allParams: Record<string, string> = {};
109
-
110
183
  while (current) {
111
- let index = typeof current.index === 'number' ? current.index : 0;
112
- let route = current.routes[index] as Route<string> & {
113
- state?: State;
114
- };
184
+ let index: number = typeof current.index === 'number' ? current.index : 0;
185
+
186
+ const initialRoute: State['routes'][number] | undefined =
187
+ current.routes[index];
188
+
189
+ if (initialRoute == null) {
190
+ throw new Error(`Couldn't find a route at index ${index}.`);
191
+ }
192
+
193
+ let route: State['routes'][number] = initialRoute;
115
194
 
116
195
  let parts: PatternPart[] | undefined;
117
196
 
118
- let focusedParams: Record<string, string> | undefined;
119
- let currentOptions = configs;
197
+ // Values for each pattern part object, so the same param name
198
+ // at different nesting levels gets the value from its own level
199
+ const partValues = new Map<PatternPart, SerializedParamValue | undefined>();
120
200
 
121
- const focusedRoute = getActiveRoute(state);
201
+ let focusedParams: Record<string, SerializedParamValue> | undefined;
202
+ let currentOptions = configs;
122
203
 
123
204
  // Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
124
205
  const nestedRouteNames = [];
125
206
 
126
207
  let hasNext = true;
127
208
 
128
- while (route.name in currentOptions && hasNext) {
129
- parts = currentOptions[route.name].parts;
209
+ while (hasNext) {
210
+ const config = currentOptions[route.name];
211
+
212
+ if (config == null) {
213
+ break;
214
+ }
215
+
216
+ parts = config.parts;
217
+
218
+ const ownParts = config.ownParts;
130
219
 
131
220
  nestedRouteNames.push(route.name);
132
221
 
133
222
  if (route.params) {
134
- const options = currentOptions[route.name];
135
-
136
- const currentParams = Object.fromEntries(
137
- Object.entries(route.params)
138
- .map(([key, value]): [string, string] | null => {
139
- if (value === undefined) {
140
- if (options) {
141
- const optional = options.parts?.find(
142
- (part) => part.param === key
143
- )?.optional;
144
-
145
- if (optional) {
146
- return null;
147
- }
148
- } else {
149
- return null;
150
- }
223
+ const options = config;
224
+ const params = route.params as Record<string, unknown>;
225
+ const currentParams: Record<string, SerializedParamValue> = {};
226
+
227
+ for (const key in params) {
228
+ const value = params[key];
229
+
230
+ if (value === undefined) {
231
+ let optional = false;
232
+
233
+ for (const part of ownParts) {
234
+ if (part.param === key) {
235
+ optional = part.optional === true;
236
+ break;
151
237
  }
238
+ }
239
+
240
+ if (optional) {
241
+ continue;
242
+ }
243
+ }
244
+
245
+ const stringify = options.stringify?.[key];
246
+
247
+ currentParams[key] = stringify
248
+ ? stringify(value)
249
+ : serializeParamValue(value);
250
+ }
152
251
 
153
- const stringify = options?.stringify?.[key] ?? String;
252
+ const claimedParams = new Set<string>();
154
253
 
155
- return [key, stringify(value)];
156
- })
157
- .filter((entry) => entry != null)
158
- );
254
+ for (const part of ownParts) {
255
+ if (part.param && part.param in currentParams) {
256
+ const value = currentParams[part.param];
159
257
 
160
- if (parts?.length) {
161
- Object.assign(allParams, currentParams);
258
+ if (value !== undefined) {
259
+ partValues.set(part, value);
260
+ claimedParams.add(part.param);
261
+ }
262
+ }
162
263
  }
163
264
 
164
265
  if (focusedRoute === route) {
165
266
  // If this is the focused route, keep the params for later use
166
267
  // We save it here since it's been stringified already
167
- focusedParams = { ...currentParams };
168
-
169
- parts
170
- // eslint-disable-next-line no-loop-func
171
- ?.forEach(({ param }) => {
172
- if (param) {
173
- // Remove the params present in the pattern since we'll only use the rest for query string
174
- if (focusedParams) {
175
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
176
- delete focusedParams[param];
177
- }
178
- }
179
- });
268
+ // Params claimed by the pattern shouldn't be repeated in the query string
269
+ focusedParams = {};
270
+
271
+ for (const key in currentParams) {
272
+ const value = currentParams[key];
273
+
274
+ if (
275
+ !claimedParams.has(key) &&
276
+ value !== undefined &&
277
+ value !== 'undefined'
278
+ ) {
279
+ focusedParams[key] = value;
280
+ }
281
+ }
282
+ }
283
+ }
284
+
285
+ for (const part of ownParts) {
286
+ if (part.param && !partValues.has(part)) {
287
+ partValues.set(part, undefined);
180
288
  }
181
289
  }
182
290
 
183
291
  // If there is no `screens` property or no nested state, we return pattern
184
- if (!currentOptions[route.name].screens || route.state === undefined) {
292
+ const routeState = getRouteState(route, config);
293
+
294
+ if (!config.screens || routeState == null) {
185
295
  hasNext = false;
186
296
  } else {
187
297
  index =
188
- typeof route.state.index === 'number'
189
- ? route.state.index
190
- : route.state.routes.length - 1;
298
+ typeof routeState.index === 'number'
299
+ ? routeState.index
300
+ : routeState.routes.length - 1;
301
+
302
+ const nextRoute: State['routes'][number] | undefined =
303
+ routeState.routes[index];
191
304
 
192
- const nextRoute = route.state.routes[index];
193
- const nestedConfig = currentOptions[route.name].screens;
305
+ if (nextRoute == null) {
306
+ throw new Error(`Couldn't find a route at index ${index}.`);
307
+ }
308
+
309
+ const nestedConfig = config.screens;
194
310
 
195
311
  // if there is config for next route name, we go deeper
196
- if (nestedConfig && nextRoute.name in nestedConfig) {
197
- route = nextRoute as Route<string> & { state?: State };
312
+ if (nextRoute.name in nestedConfig) {
313
+ route = nextRoute;
198
314
  currentOptions = nestedConfig;
199
315
  } else {
200
316
  // If not, there is no sense in going deeper in config
@@ -203,62 +319,68 @@ export function getPathFromState<ParamList extends {}>(
203
319
  }
204
320
  }
205
321
 
206
- if (currentOptions[route.name] !== undefined) {
207
- path += parts
208
- ?.map(({ segment, param, optional }) => {
322
+ const routeState = getRouteState(route, currentOptions[route.name]);
323
+
324
+ if (currentOptions[route.name] != null) {
325
+ if (parts) {
326
+ let index = 0;
327
+
328
+ for (const part of parts) {
329
+ const { segment, param, optional } = part;
330
+
331
+ if (index > 0) {
332
+ path += '/';
333
+ }
334
+
335
+ index++;
336
+
209
337
  // We don't know what to show for wildcard patterns
210
338
  // Showing the route name seems ok, though whatever we show here will be incorrect
211
339
  // Since the page doesn't actually exist
212
340
  if (segment === '*') {
213
- return route.name;
341
+ path += route.name;
342
+ continue;
214
343
  }
215
344
 
216
345
  // If the path has a pattern for a param, put the param in the path
217
346
  if (param) {
218
- const value = allParams[param];
347
+ const value = partValues.get(part);
219
348
 
220
349
  if (value === undefined && optional) {
221
350
  // Optional params without value assigned in route.params should be ignored
222
- return '';
351
+ continue;
223
352
  }
224
353
 
225
354
  // Valid characters according to
226
355
  // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 (see pchar definition)
227
- return Array.from(String(value))
228
- .map((char) =>
229
- /[^A-Za-z0-9\-._~!$&'()*+,;=:@]/g.test(char)
230
- ? encodeURIComponent(char)
231
- : char
232
- )
233
- .join('');
356
+ path += encodePathParam(String(value));
357
+ continue;
234
358
  }
235
359
 
236
- return encodeURIComponent(segment);
237
- })
238
- .join('/');
360
+ path += encodeURIComponent(segment);
361
+ }
362
+ }
239
363
  } else {
240
364
  path += encodeURIComponent(route.name);
241
365
  }
242
366
 
243
367
  if (!focusedParams && focusedRoute.params) {
244
- focusedParams = Object.fromEntries(
245
- Object.entries(focusedRoute.params).map(([key, value]) => [
246
- key,
247
- String(value),
248
- ])
249
- );
250
- }
368
+ focusedParams = {};
251
369
 
252
- if (route.state) {
253
- path += '/';
254
- } else if (focusedParams) {
255
- for (const param in focusedParams) {
256
- if (focusedParams[param] === 'undefined') {
257
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
258
- delete focusedParams[param];
370
+ const params = focusedRoute.params as Record<string, unknown>;
371
+
372
+ for (const key in params) {
373
+ const value = serializeParamValue(params[key]);
374
+
375
+ if (value !== 'undefined') {
376
+ focusedParams[key] = value;
259
377
  }
260
378
  }
379
+ }
261
380
 
381
+ if (routeState) {
382
+ path += '/';
383
+ } else if (focusedParams) {
262
384
  const query = queryString.stringify(focusedParams, { sort: false });
263
385
 
264
386
  if (query) {
@@ -266,7 +388,7 @@ export function getPathFromState<ParamList extends {}>(
266
388
  }
267
389
  }
268
390
 
269
- current = route.state;
391
+ current = routeState;
270
392
  }
271
393
 
272
394
  // Include the root path if specified
@@ -292,14 +414,13 @@ const createConfigItem = (
292
414
  parentParts?: PatternPart[]
293
415
  ): ConfigItem => {
294
416
  if (typeof config === 'string') {
295
- // If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
296
- const parts = getPatternParts(config);
417
+ const ownParts = getPatternParts(config);
297
418
 
298
419
  if (parentParts) {
299
- return { parts: [...parentParts, ...parts] };
420
+ return { parts: [...parentParts, ...ownParts], ownParts };
300
421
  }
301
422
 
302
- return { parts };
423
+ return { parts: ownParts, ownParts };
303
424
  }
304
425
 
305
426
  if (config.exact && config.path === undefined) {
@@ -310,14 +431,12 @@ const createConfigItem = (
310
431
 
311
432
  // If an object is specified as the value (e.g. Foo: { ... }),
312
433
  // It can have `path` property and `screens` prop which has nested configs
434
+ const ownParts = config.path ? getPatternParts(config.path) : [];
313
435
  const parts =
314
436
  config.exact !== true
315
- ? [
316
- ...(parentParts || []),
317
- ...(config.path ? getPatternParts(config.path) : []),
318
- ]
319
- : config.path
320
- ? getPatternParts(config.path)
437
+ ? [...(parentParts || []), ...ownParts]
438
+ : ownParts.length
439
+ ? ownParts
321
440
  : undefined;
322
441
 
323
442
  const screens =
@@ -327,6 +446,7 @@ const createConfigItem = (
327
446
 
328
447
  return {
329
448
  parts,
449
+ ownParts,
330
450
  stringify: config.stringify,
331
451
  screens,
332
452
  };
@@ -335,11 +455,17 @@ const createConfigItem = (
335
455
  const createNormalizedConfigs = (
336
456
  options: PathConfigMap<object>,
337
457
  parts?: PatternPart[]
338
- ): Record<string, ConfigItem> =>
339
- Object.fromEntries(
340
- Object.entries(options).map(([name, c]) => {
341
- const result = createConfigItem(c, parts);
342
-
343
- return [name, result];
344
- })
345
- );
458
+ ): Record<string, ConfigItem> => {
459
+ const configs: Record<string, ConfigItem> = {};
460
+ const screens = options as Record<string, PathConfig<{}> | string>;
461
+
462
+ for (const name in screens) {
463
+ const config = screens[name];
464
+
465
+ if (config !== undefined) {
466
+ configs[name] = createConfigItem(config, parts);
467
+ }
468
+ }
469
+
470
+ return configs;
471
+ };
@@ -15,6 +15,8 @@ export function getPatternParts(path: string): PatternPart[] {
15
15
 
16
16
  let isRegex = false;
17
17
  let isParam = false;
18
+ let isEscaped = false;
19
+ let isInCharClass = false;
18
20
  let regexInnerParens = 0;
19
21
 
20
22
  // One extra iteration to add the last character
@@ -34,7 +36,7 @@ export function getPatternParts(path: string): PatternPart[] {
34
36
  `Encountered ':' in the middle of a segment in path: ${path}`
35
37
  );
36
38
  }
37
- } else if (char === '(') {
39
+ } else if (char === '(' && !isEscaped && !isInCharClass) {
38
40
  if (isParam) {
39
41
  if (isRegex) {
40
42
  // The '(' is part of the regex if we're already inside one
@@ -47,13 +49,13 @@ export function getPatternParts(path: string): PatternPart[] {
47
49
  `Encountered '(' without preceding ':' in path: ${path}`
48
50
  );
49
51
  }
50
- } else if (char === ')') {
52
+ } else if (char === ')' && !isEscaped && !isInCharClass) {
51
53
  if (isParam && isRegex) {
52
54
  if (regexInnerParens) {
53
55
  // The ')' is part of the regex if we're already inside one
54
56
  regexInnerParens--;
55
- current.regex += char;
56
57
  } else {
58
+ current.regex += char;
57
59
  isRegex = false;
58
60
  isParam = false;
59
61
  }
@@ -62,7 +64,7 @@ export function getPatternParts(path: string): PatternPart[] {
62
64
  `Encountered ')' without preceding '(' in path: ${path}`
63
65
  );
64
66
  }
65
- } else if (char === '?') {
67
+ } else if (char === '?' && !isRegex) {
66
68
  if (current.param) {
67
69
  isParam = false;
68
70
 
@@ -102,6 +104,17 @@ export function getPatternParts(path: string): PatternPart[] {
102
104
  if (isRegex) {
103
105
  current.regex = current.regex || '';
104
106
  current.regex += char;
107
+
108
+ // Track escapes and character classes so parens inside them are not special
109
+ if (isEscaped) {
110
+ isEscaped = false;
111
+ } else if (char === '\\') {
112
+ isEscaped = true;
113
+ } else if (char === '[') {
114
+ isInCharClass = true;
115
+ } else if (char === ']') {
116
+ isInCharClass = false;
117
+ }
105
118
  }
106
119
 
107
120
  if (isParam && !isRegex) {
@@ -124,3 +137,11 @@ export function getPatternParts(path: string): PatternPart[] {
124
137
 
125
138
  return parts;
126
139
  }
140
+
141
+ export function combinePatternParts<T extends PatternPart>(
142
+ parentParts: T[],
143
+ parts: T[],
144
+ exact = false
145
+ ): T[] {
146
+ return exact ? parts : [...parentParts, ...parts];
147
+ }