@react-navigation/core 8.0.0-alpha.4 → 8.0.0-alpha.40

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