@react-navigation/core 8.0.0-alpha.2 → 8.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +5 -3
  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 +8 -4
  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,17 +1,19 @@
1
- import type {
2
- NavigationState,
3
- ParamListBase,
4
- Route,
5
- } from '@react-navigation/routers';
1
+ import type { NavigationState, ParamListBase } from '@react-navigation/routers';
6
2
  import * as React from 'react';
7
3
  import { isValidElementType } from 'react-is';
8
4
 
5
+ import {
6
+ combinePatternParts,
7
+ getPatternParts,
8
+ type PatternPart,
9
+ } from './getPatternParts';
9
10
  import type {
10
11
  DefaultNavigatorOptions,
11
12
  EventMapBase,
12
13
  NavigationListBase,
13
14
  NavigatorScreenParams,
14
15
  NavigatorTypeBagBase,
16
+ NavigatorTypeBagFor,
15
17
  PathConfig,
16
18
  PathConfigMap,
17
19
  RouteGroupConfig,
@@ -21,68 +23,92 @@ import type {
21
23
  import { useRoute } from './useRoute';
22
24
  import type {
23
25
  AnyToUnknown,
24
- ExtractParamStrings,
25
- ExtractParamsType,
26
26
  FlatType,
27
27
  HasArguments,
28
- InferParse,
29
- InferPath,
28
+ InferParamsFromLinking,
30
29
  KeysOf,
30
+ StandardSchemaV1,
31
31
  UnionToIntersection,
32
32
  ValidPathPattern,
33
33
  } from './utilities';
34
34
 
35
- type ParamsForScreenComponent<T> = T extends (...args: any[]) => any
36
- ? HasArguments<T> extends true
37
- ? T extends React.ComponentType<{ route: { params: infer Params } }>
38
- ? Params
39
- : undefined
35
+ type ScreenComponentParams<T> = [T] extends [
36
+ React.ComponentType<{ route: { params: infer Params } }>,
37
+ ]
38
+ ? Params
39
+ : undefined;
40
+
41
+ type ParamsForScreenComponent<T> = [T] extends [(...args: any[]) => any]
42
+ ? // Don't infer params from a parameterless function component
43
+ // It'd infer `unknown` - but we want `undefined` if no params are accepted
44
+ HasArguments<T> extends true
45
+ ? ScreenComponentParams<T>
40
46
  : undefined
41
- : T extends React.ComponentType<{ route: { params: infer Params } }>
42
- ? Params
47
+ : ScreenComponentParams<T>;
48
+
49
+ // If every nested route's params include `undefined`, the nested navigator
50
+ // itself can be omitted. Otherwise, require `NavigatorScreenParams`.
51
+ type ParamsForNestedNavigator<
52
+ T extends { config: any },
53
+ ParamList extends {} = StaticParamList<T>,
54
+ Params = NavigatorScreenParams<ParamList>,
55
+ > = {
56
+ // Map each route to `never` when params are optional, or name when params are required
57
+ // If every route has optional params, the resulting union is `never`
58
+ [RouteName in keyof ParamList]-?: undefined extends ParamList[RouteName]
59
+ ? never
60
+ : RouteName;
61
+ }[keyof ParamList] extends never
62
+ ? Params | undefined
63
+ : Params;
64
+
65
+ // If the screens were constructed from a `Record<K, V>`,
66
+ // the screen value (`T`) may be a union.
67
+ // A naked `T extends ...` conditional would distribute over every screen in the union,
68
+ // creating a large params union per route.
69
+ // Wrapping `T` in a tuple checks the union as a whole instead of distributing,
70
+ // https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
71
+ type ParamsForScreen<T> = [T] extends [{ config: any }]
72
+ ? ParamsForNestedNavigator<T>
73
+ : [T] extends [React.ComponentType<any>]
74
+ ? ParamsForScreenComponent<T>
43
75
  : undefined;
44
76
 
45
- type ParamsForScreen<T> =
46
- // Nested navigator in screen property
47
- T extends { screen: StaticNavigation<any, any, any> }
48
- ? NavigatorScreenParams<StaticParamList<T['screen']>> | undefined
49
- : // Direct nested navigator
50
- T extends StaticNavigation<any, any, any>
51
- ? NavigatorScreenParams<StaticParamList<T>> | undefined
52
- : T extends {
53
- screen: React.ComponentType<any>;
54
- }
55
- ? ParamsForScreenComponent<T['screen']>
56
- : ParamsForScreenComponent<T>;
57
-
58
- type ParamsForLinking<Linking> = Linking extends { path: string }
59
- ? ExtractParamsType<
60
- ExtractParamStrings<InferPath<Linking>>,
61
- InferParse<Linking>
62
- >
63
- : Linking extends string
64
- ? ExtractParamsType<ExtractParamStrings<Linking>, undefined>
65
- : undefined;
77
+ // Unlike `ParamsForScreen`, we want to distribute for `Record<string, Component | StaticNavigation>`
78
+ // This creates union like:
79
+ // `C extends C ? ParamsForScreen<C> : never) | (S extends S ? ParamsForScreen<S> : never)`
80
+ // `= ParamsForScreen<C> | ParamsForScreen<S>`
81
+ type ParamsForScreenEntry<T> = T extends T ? ParamsForScreen<T> : never;
82
+
83
+ // Only infer params from linking if it's a pattern (i.e., contains ':')
84
+ // or if parse is present for query params.
85
+ // This avoids inferring non-literals like 'string' without parse.
86
+ type ShouldInferFromLinking<Linking> = Linking extends
87
+ | ValidPathPattern
88
+ | { path: ValidPathPattern }
89
+ | { parse: Record<string, unknown> }
90
+ ? true
91
+ : false;
66
92
 
67
93
  /**
68
- * Inferred params type based on both linking config and screen.
69
- * - When linking is undefined: infers params from screen
70
- * - When screen is a nested navigator: merges path params with navigator screen params
71
- * - Otherwise: merges path params with screen component params
94
+ * Params type derived from both linking and screen.
95
+ *
96
+ * - Linking has no extractable params (no ':' in path, no `parse`): returns
97
+ * screen params alone.
98
+ * - Linking has params + screen is a nested navigator: intersects path params
99
+ * with the navigator's screen params.
100
+ * - Linking has params + screen is a component without route params: returns
101
+ * path params alone (avoids `LP & undefined = never`).
102
+ * - Linking has params + screen has route params: intersects them.
72
103
  */
73
- type ParamsForConfig<Linking, Screen> = undefined extends Linking
74
- ? ParamsForScreen<Screen>
75
- : // Only infer params from linking if it's a pattern (i.e., contains ':')
76
- // This avoids inferring non-literals like 'string'
77
- Linking extends ValidPathPattern | { path: ValidPathPattern }
78
- ? Screen extends StaticNavigation<any, any, any>
79
- ? FlatType<ParamsForLinking<Linking>> & ParamsForScreen<Screen>
80
- : // Don't combine if `undefined`, otherwise it'll result in `never`
81
- undefined extends ParamsForScreen<Screen>
82
- ? // Only flatten when not a navigator to keep it legible
83
- FlatType<ParamsForLinking<Linking>>
84
- : FlatType<ParamsForLinking<Linking> & ParamsForScreen<Screen>>
85
- : ParamsForScreen<Screen>;
104
+ type ParamsForConfig<Linking, Screen, Params = ParamsForScreen<Screen>> =
105
+ ShouldInferFromLinking<Linking> extends true
106
+ ? [Screen] extends [{ config: any }]
107
+ ? FlatType<InferParamsFromLinking<Linking>> & Params
108
+ : undefined extends Params
109
+ ? FlatType<InferParamsFromLinking<Linking>>
110
+ : FlatType<InferParamsFromLinking<Linking> & Params>
111
+ : Params;
86
112
 
87
113
  type ParamListForScreens<Screens> = {
88
114
  [Key in KeysOf<Screens>]: Screens[Key] extends StaticScreenConfig<
@@ -91,10 +117,11 @@ type ParamListForScreens<Screens> = {
91
117
  any,
92
118
  any,
93
119
  any,
120
+ any,
94
121
  any
95
122
  >
96
123
  ? ParamsForConfig<Linking, Screen>
97
- : ParamsForScreen<Screens[Key]>;
124
+ : ParamsForScreenEntry<Screens[Key]>;
98
125
  };
99
126
 
100
127
  type ParamListForGroups<
@@ -113,25 +140,25 @@ type ParamListForGroups<
113
140
  ? ParamListForScreens<UnionToIntersection<Screens>>
114
141
  : {};
115
142
 
116
- type RouteType<Params> = Readonly<
117
- FlatType<
118
- Omit<Route<string, AnyToUnknown<Params>>, 'params'> &
119
- Readonly<
120
- undefined extends Params
121
- ? {
122
- /**
123
- * Params for this route
124
- */
125
- params?: AnyToUnknown<Params>;
126
- }
127
- : {
128
- /**
129
- * Params for this route
130
- */
131
- params: AnyToUnknown<Params>;
132
- }
133
- >
134
- >
143
+ type RouteType<Params, P = AnyToUnknown<Params>> = Readonly<
144
+ {
145
+ key: string;
146
+ name: string;
147
+ path?: string | undefined;
148
+ history?: { type: 'params'; params: object }[] | undefined;
149
+ } & (undefined extends Params
150
+ ? {
151
+ /**
152
+ * Params for this route
153
+ */
154
+ params?: P;
155
+ }
156
+ : {
157
+ /**
158
+ * Params for this route
159
+ */
160
+ params: P;
161
+ })
135
162
  >;
136
163
 
137
164
  type StaticScreenConfigLinkingAlias = {
@@ -147,7 +174,7 @@ type StaticScreenConfigLinkingAlias = {
147
174
  */
148
175
  exact?: boolean;
149
176
  /**
150
- * An object mapping the param name to a function which parses the param value.
177
+ * An object mapping the param name to a parser function or a Standard Schema.
151
178
  *
152
179
  * @example
153
180
  * ```js
@@ -157,7 +184,11 @@ type StaticScreenConfigLinkingAlias = {
157
184
  * }
158
185
  * ```
159
186
  */
160
- parse?: Record<string, (value: string) => unknown>;
187
+ parse?: Record<
188
+ string,
189
+ | ((value: string) => unknown)
190
+ | StandardSchemaV1<string | string[] | null | undefined, unknown>
191
+ >;
161
192
  /**
162
193
  * An object mapping the param name to a function which converts the param value to a string.
163
194
  * By default, all params are converted to strings using `String(value)`.
@@ -170,6 +201,21 @@ type StaticScreenConfigLinkingAlias = {
170
201
  * ```
171
202
  */
172
203
  stringify?: Record<string, (value: unknown) => string>;
204
+ /**
205
+ * Whether this path can resolve to multiple routes.
206
+ * Both routes should specify `shared: true` for this to work.
207
+ *
208
+ * When automatic path generation is enabled,
209
+ * screens using the same path pattern and the same screen component
210
+ * or navigator will be automatically marked as shared.
211
+ * Explicitly specifying the option overrides the automatic detection.
212
+ *
213
+ * This is useful when same screen used in multiple navigators,
214
+ * e.g. tabs with stacks containing profile screen in each stack.
215
+ *
216
+ * The path defined first will be the canonical path.
217
+ */
218
+ shared?: boolean;
173
219
  };
174
220
 
175
221
  export type StaticScreenConfigLinking =
@@ -179,12 +225,22 @@ export type StaticScreenConfigLinking =
179
225
  * Additional path alias that will be matched to the same screen.
180
226
  */
181
227
  alias?: (string | StaticScreenConfigLinkingAlias)[];
228
+ /**
229
+ * Name of the initial route to use for the nested navigator when the path matches.
230
+ */
231
+ initialRouteName?: string | undefined;
232
+ /**
233
+ * Path configuration for child screens in the nested navigator.
234
+ * Only supported when the screen renders a navigator with dynamic API.
235
+ */
236
+ screens?: PathConfigMap<ParamListBase> | undefined;
182
237
  })
238
+ | null
183
239
  | undefined;
184
240
 
185
241
  export type StaticScreenConfigScreen =
186
242
  | React.ComponentType<any>
187
- | StaticNavigation<any, any, any>;
243
+ | StaticNavigation<any>;
188
244
 
189
245
  export type StaticScreenConfig<
190
246
  Linking extends StaticScreenConfigLinking,
@@ -319,24 +375,48 @@ export type StaticScreenConfig<
319
375
  navigationKey?: string;
320
376
  };
321
377
 
378
+ export type StaticScreenFactory<in out Bag extends NavigatorTypeBagBase> = <
379
+ const Linking extends StaticScreenConfigLinking,
380
+ const Screen extends StaticScreenConfigScreen,
381
+ >(
382
+ config: StaticScreenConfig<
383
+ Linking,
384
+ Screen,
385
+ Bag['State'],
386
+ Bag['ScreenOptions'],
387
+ Bag['EventMap'],
388
+ Bag['NavigationList'][keyof Bag['ParamList']]
389
+ >
390
+ ) => StaticScreenConfig<
391
+ Linking,
392
+ Screen,
393
+ Bag['State'],
394
+ Bag['ScreenOptions'],
395
+ Bag['EventMap'],
396
+ Bag['NavigationList'][keyof Bag['ParamList']]
397
+ >;
398
+
399
+ /**
400
+ * Helper to create a typed `createXScreen` for static configuration.
401
+ */
402
+ export function createScreenFactory<
403
+ TypeBag extends NavigatorTypeBagBase,
404
+ >(): StaticScreenFactory<NavigatorTypeBagFor<TypeBag, ParamListBase>> {
405
+ return ((config: unknown) => config) as never;
406
+ }
407
+
322
408
  type StaticConfigScreens<
323
- ParamList extends ParamListBase,
324
- State extends NavigationState,
325
- ScreenOptions extends {},
326
- EventMap extends EventMapBase,
327
- NavigationList extends NavigationListBase<ParamList>,
409
+ in out ParamList extends ParamListBase,
410
+ in out State extends NavigationState,
411
+ in out ScreenOptions extends {},
412
+ in out EventMap extends EventMapBase,
413
+ in out NavigationList extends NavigationListBase<ParamList>,
328
414
  > = {
329
415
  [RouteName in keyof ParamList]:
330
- | React.ComponentType<any>
331
- | StaticNavigation<any, any, any>
416
+ | StaticScreenConfigScreen
332
417
  | StaticScreenConfig<
333
- | {
334
- path: string;
335
- parse?: Record<string, (value: string) => any>;
336
- }
337
- | string
338
- | undefined,
339
- StaticNavigation<any, any, any> | React.ComponentType<any>,
418
+ StaticScreenConfigLinking,
419
+ StaticScreenConfigScreen,
340
420
  State,
341
421
  ScreenOptions,
342
422
  EventMap,
@@ -360,27 +440,6 @@ type StaticConfigGroup<
360
440
  * This can be useful for conditional rendering of group of screens.
361
441
  */
362
442
  if?: () => boolean;
363
- /**
364
- * Linking config for the screens in the group.
365
- * This can be a string to specify the path, or an object following properties:
366
- * - `path`
367
- * - `stringify`
368
- * - `parse`
369
- *
370
- * The path specified will be prepended to the paths of the screens in the group.
371
- * The `parse` and `stringify` properties will be merged.
372
- *
373
- * @example
374
- * ```js
375
- * linking: {
376
- * path: 'users/:id',
377
- * parse: {
378
- * id: (id) => parseInt(id, 10),
379
- * }
380
- * },
381
- * ```
382
- */
383
- linking?: LinkingForGroup;
384
443
  /**
385
444
  * Static navigation config or Component to render for the screen.
386
445
  */
@@ -410,16 +469,26 @@ type StaticConfigInternal<
410
469
  EventMap extends EventMapBase,
411
470
  NavigationList extends NavigationListBase<ParamList>,
412
471
  Navigator extends React.ComponentType<any>,
472
+ Screens = StaticConfigScreens<
473
+ ParamList,
474
+ State,
475
+ ScreenOptions,
476
+ EventMap,
477
+ NavigationList
478
+ >,
479
+ Groups = {
480
+ [key: string]: StaticConfigGroup<
481
+ ParamList,
482
+ State,
483
+ ScreenOptions,
484
+ EventMap,
485
+ NavigationList
486
+ >;
487
+ },
413
488
  > = Omit<
414
489
  Omit<
415
490
  React.ComponentProps<Navigator>,
416
- keyof DefaultNavigatorOptions<
417
- ParamListBase,
418
- NavigationState,
419
- {},
420
- EventMapBase,
421
- NavigationList[keyof ParamList]
422
- >
491
+ keyof DefaultNavigatorOptions<any, any, any, any, any>
423
492
  > &
424
493
  DefaultNavigatorOptions<
425
494
  ParamList,
@@ -435,49 +504,21 @@ type StaticConfigInternal<
435
504
  /**
436
505
  * Screens to render in the navigator and their configuration.
437
506
  */
438
- screens: StaticConfigScreens<
439
- ParamList,
440
- State,
441
- ScreenOptions,
442
- EventMap,
443
- NavigationList
444
- >;
507
+ screens: Screens;
445
508
  /**
446
509
  * Groups of screens to render in the navigator and their configuration.
447
510
  */
448
- groups?: {
449
- [key: string]: StaticConfigGroup<
450
- ParamList,
451
- State,
452
- ScreenOptions,
453
- EventMap,
454
- NavigationList
455
- >;
456
- };
511
+ groups?: Groups;
457
512
  }
458
513
  | {
459
514
  /**
460
515
  * Screens to render in the navigator and their configuration.
461
516
  */
462
- screens?: StaticConfigScreens<
463
- ParamList,
464
- State,
465
- ScreenOptions,
466
- EventMap,
467
- NavigationList
468
- >;
517
+ screens?: Screens;
469
518
  /**
470
519
  * Groups of screens to render in the navigator and their configuration.
471
520
  */
472
- groups: {
473
- [key: string]: StaticConfigGroup<
474
- ParamList,
475
- State,
476
- ScreenOptions,
477
- EventMap,
478
- NavigationList
479
- >;
480
- };
521
+ groups: Groups;
481
522
  }
482
523
  );
483
524
 
@@ -485,7 +526,9 @@ type StaticConfigInternal<
485
526
  * Props for a screen component which is rendered by a static navigator.
486
527
  * Takes the route params as a generic argument.
487
528
  */
488
- export type StaticScreenProps<T extends Record<string, unknown> | undefined> = {
529
+ export type StaticScreenProps<
530
+ in out T extends Record<string, unknown> | undefined,
531
+ > = {
489
532
  route: {
490
533
  params: T;
491
534
  };
@@ -503,11 +546,11 @@ export type StaticParamList<
503
546
  ParamListForGroups<T['config']['groups']>
504
547
  >;
505
548
 
506
- export type StaticNavigation<NavigatorProps, GroupProps, ScreenProps> = {
507
- Navigator: React.ComponentType<NavigatorProps>;
508
- Group: React.ComponentType<GroupProps>;
509
- Screen: React.ComponentType<ScreenProps>;
510
- config: StaticConfig<NavigatorTypeBagBase>;
549
+ export type StaticNavigation<
550
+ in out NavigatorTypeBag extends NavigatorTypeBagBase,
551
+ > = {
552
+ config: StaticConfig<NavigatorTypeBag>;
553
+ getComponent: () => React.ComponentType<{}>;
511
554
  };
512
555
 
513
556
  const MemoizedScreen = React.memo(
@@ -542,16 +585,13 @@ const getItemsFromScreens = (
542
585
  component = screen;
543
586
  } else if ('config' in screen) {
544
587
  isNavigator = true;
545
- component = createComponentForStaticNavigation(
546
- screen,
547
- `${name}Navigator`
548
- );
588
+ component = screen.getComponent();
549
589
  }
550
590
  } else if (isValidElementType(item)) {
551
591
  component = item;
552
592
  } else if ('config' in item) {
553
593
  isNavigator = true;
554
- component = createComponentForStaticNavigation(item, `${name}Navigator`);
594
+ component = item.getComponent();
555
595
  }
556
596
 
557
597
  if (component == null) {
@@ -589,10 +629,17 @@ const getItemsFromScreens = (
589
629
  * @param displayName Name of the component to be displayed in React DevTools.
590
630
  * @returns A component which renders the navigator.
591
631
  */
592
- export function createComponentForStaticNavigation(
593
- tree: StaticNavigation<any, any, any>,
632
+ export function createComponentForStaticConfig<
633
+ T extends {
634
+ Navigator: React.ComponentType<any>;
635
+ Group: React.ComponentType<any>;
636
+ Screen: React.ComponentType<any>;
637
+ config: StaticConfig<NavigatorTypeBagBase>;
638
+ },
639
+ >(
640
+ tree: T,
594
641
  displayName: string
595
- ): React.ComponentType<{}> {
642
+ ): React.ComponentType<Omit<React.ComponentProps<T['Navigator']>, 'children'>> {
596
643
  const { Navigator, Group, Screen, config } = tree;
597
644
  const { screens, groups, ...rest } = config;
598
645
 
@@ -637,10 +684,51 @@ export function createComponentForStaticNavigation(
637
684
  }
638
685
  }
639
686
 
640
- const NavigatorComponent = () => {
687
+ if (items.length === 0) {
688
+ throw new Error(
689
+ "Couldn't find any screens in the 'screens' or 'groups' property. Make sure to define at least one screen in the configuration."
690
+ );
691
+ }
692
+
693
+ const NavigatorComponent = ({ children: _, ...props }: typeof rest) => {
641
694
  const children = items.map((item) => item());
642
695
 
643
- return <Navigator {...rest}>{children}</Navigator>;
696
+ const screenOptions =
697
+ typeof props.screenOptions === 'function' ||
698
+ typeof rest.screenOptions === 'function'
699
+ ? (options: unknown) => ({
700
+ ...(typeof rest.screenOptions === 'function'
701
+ ? rest.screenOptions(options)
702
+ : rest.screenOptions),
703
+ ...(typeof props.screenOptions === 'function'
704
+ ? props.screenOptions(options)
705
+ : props.screenOptions),
706
+ })
707
+ : { ...rest.screenOptions, ...props.screenOptions };
708
+
709
+ const screenListeners =
710
+ typeof props.screenListeners === 'function' ||
711
+ typeof rest.screenListeners === 'function'
712
+ ? (options: unknown) => ({
713
+ ...(typeof rest.screenListeners === 'function'
714
+ ? rest.screenListeners(options)
715
+ : rest.screenListeners),
716
+ ...(typeof props.screenListeners === 'function'
717
+ ? props.screenListeners(options)
718
+ : props.screenListeners),
719
+ })
720
+ : { ...rest.screenListeners, ...props.screenListeners };
721
+
722
+ return (
723
+ <Navigator
724
+ {...rest}
725
+ {...props}
726
+ screenOptions={screenOptions}
727
+ screenListeners={screenListeners}
728
+ >
729
+ {children}
730
+ </Navigator>
731
+ );
644
732
  };
645
733
 
646
734
  NavigatorComponent.displayName = displayName;
@@ -648,33 +736,113 @@ export function createComponentForStaticNavigation(
648
736
  return NavigatorComponent;
649
737
  }
650
738
 
651
- type LinkingForGroup =
652
- | Pick<PathConfig<any>, 'path' | 'stringify' | 'parse'>
653
- | string;
654
-
655
739
  type TreeForPathConfig = {
656
- config: {
657
- initialRouteName?: string;
658
- screens?: StaticConfigScreens<
659
- ParamListBase,
660
- NavigationState,
661
- {},
662
- EventMapBase,
663
- Record<string, unknown>
664
- >;
665
- groups?: {
666
- [key: string]: {
667
- linking?: LinkingForGroup;
668
- screens: StaticConfigScreens<
669
- ParamListBase,
670
- NavigationState,
671
- {},
672
- EventMapBase,
673
- Record<string, unknown>
674
- >;
675
- };
740
+ config: ConfigForPathConfig;
741
+ };
742
+
743
+ type ConfigForPathConfig = {
744
+ initialRouteName?: string;
745
+ screens?: Record<string, ScreenForPathConfig>;
746
+ groups?: Record<string, { screens: Record<string, ScreenForPathConfig> }>;
747
+ };
748
+
749
+ type LinkingForPathConfig =
750
+ | string
751
+ | (Partial<StaticScreenConfigLinkingAlias> & {
752
+ alias?: (string | StaticScreenConfigLinkingAlias)[];
753
+ initialRouteName?: string | undefined;
754
+ screens?: PathConfigMap<ParamListBase> | undefined;
755
+ })
756
+ | null
757
+ | undefined;
758
+
759
+ type ScreenValueForPathConfig = React.ComponentType<{}> | TreeForPathConfig;
760
+
761
+ type ScreenForPathConfig =
762
+ | ScreenValueForPathConfig
763
+ | {
764
+ screen: ScreenValueForPathConfig;
765
+ linking?: LinkingForPathConfig;
676
766
  };
767
+
768
+ type PathConfigMapForStaticNavigation = Record<
769
+ string,
770
+ string | PathConfigForStaticNavigation | undefined
771
+ >;
772
+
773
+ type PathConfigForStaticNavigation = Omit<
774
+ PathConfig<ParamListBase>,
775
+ 'screens'
776
+ > & {
777
+ initialRouteName?: string | undefined;
778
+ screens?: PathConfigMapForStaticNavigation | undefined;
779
+ };
780
+
781
+ // Mark screens that resolve to the same pattern and same component or navigator
782
+ const markSharedPathConfigs = (
783
+ screens: PathConfigMapForStaticNavigation,
784
+ sources: WeakMap<PathConfigForStaticNavigation, ScreenValueForPathConfig>,
785
+ blocked: WeakSet<PathConfigForStaticNavigation>
786
+ ) => {
787
+ const candidates = new Map<string, PathConfigForStaticNavigation[]>();
788
+
789
+ const collect = (
790
+ screens: PathConfigMapForStaticNavigation,
791
+ parentParts: PatternPart[],
792
+ disabled: boolean
793
+ ) => {
794
+ for (const config of Object.values(screens)) {
795
+ if (config == null || typeof config === 'string') {
796
+ continue;
797
+ }
798
+
799
+ const parts =
800
+ typeof config.path === 'string'
801
+ ? combinePatternParts(
802
+ parentParts,
803
+ getPatternParts(config.path),
804
+ config.exact
805
+ )
806
+ : parentParts;
807
+
808
+ if (sources.has(config) && typeof config.path === 'string') {
809
+ if (disabled) {
810
+ blocked.add(config);
811
+ }
812
+
813
+ const pattern = parts.map((part) => part.segment).join('/');
814
+ const entries = candidates.get(pattern) ?? [];
815
+
816
+ entries.push(config);
817
+ candidates.set(pattern, entries);
818
+ }
819
+
820
+ if (config.screens) {
821
+ collect(config.screens, parts, disabled || config.shared === false);
822
+ }
823
+ }
677
824
  };
825
+
826
+ collect(screens, [], false);
827
+
828
+ for (const entries of candidates.values()) {
829
+ for (const entry of entries) {
830
+ const source = sources.get(entry);
831
+
832
+ if (
833
+ entry.shared === undefined &&
834
+ !blocked.has(entry) &&
835
+ entries.some(
836
+ (candidate) =>
837
+ candidate !== entry &&
838
+ !blocked.has(candidate) &&
839
+ sources.get(candidate) === source
840
+ )
841
+ ) {
842
+ entry.shared = true;
843
+ }
844
+ }
845
+ }
678
846
  };
679
847
 
680
848
  /**
@@ -696,32 +864,49 @@ type TreeForPathConfig = {
696
864
  * };
697
865
  * ```
698
866
  */
699
- export function createPathConfigForStaticNavigation(
867
+ export function createPathConfigForStaticNavigation<ParamList extends {}>(
700
868
  tree: TreeForPathConfig,
701
869
  options?: {
702
- initialRouteName?: string;
870
+ initialRouteName?: string | undefined;
703
871
  },
704
872
  auto?: boolean
705
- ): PathConfigMap<ParamListBase> | undefined {
873
+ ): PathConfigMap<ParamList> | undefined {
706
874
  let initialScreenHasPath: boolean = false;
707
875
  let initialScreenConfig: PathConfig<{}> | undefined;
876
+ let hasEmptyPath = false;
877
+
878
+ const blocked = new WeakSet<PathConfigForStaticNavigation>();
879
+ const sources = new WeakMap<
880
+ PathConfigForStaticNavigation,
881
+ ScreenValueForPathConfig
882
+ >();
708
883
 
709
884
  const createPathConfigForTree = (
710
885
  t: TreeForPathConfig,
711
- o: { initialRouteName?: string } | undefined,
886
+ o: { initialRouteName?: string | undefined } | undefined,
712
887
  // If a screen is a leaf node, but inside a screen with path,
713
888
  // It should not be used for initial detection
714
- skipInitialDetection: boolean
889
+ skipInitialDetection: boolean,
890
+ allowAutoEmptyPath: boolean
715
891
  ) => {
892
+ const initialRouteName = o?.initialRouteName ?? t.config.initialRouteName;
893
+
894
+ if (initialRouteName != null) {
895
+ const routeNames = new Set(Object.keys(t.config.screens ?? {}));
896
+
897
+ for (const group of Object.values(t.config.groups ?? {})) {
898
+ Object.keys(group.screens).forEach((name) => routeNames.add(name));
899
+ }
900
+
901
+ if (!routeNames.has(initialRouteName)) {
902
+ throw new Error(
903
+ `Couldn't find a screen named '${initialRouteName}' to use as 'initialRouteName'.`
904
+ );
905
+ }
906
+ }
907
+
716
908
  const createPathConfigForScreens = (
717
- screens: StaticConfigScreens<
718
- ParamListBase,
719
- NavigationState,
720
- {},
721
- EventMapBase,
722
- Record<string, unknown>
723
- >,
724
- groupLinking: LinkingForGroup | undefined,
909
+ screens: Record<string, ScreenForPathConfig>,
725
910
  initialRouteName: string | undefined
726
911
  ) => {
727
912
  return Object.fromEntries(
@@ -740,95 +925,144 @@ export function createPathConfigForStaticNavigation(
740
925
  return 0;
741
926
  })
742
927
  .map(([key, item]) => {
743
- const screenConfig: PathConfig<{}> = {};
744
- const groupPath =
745
- typeof groupLinking === 'string'
746
- ? groupLinking
747
- : groupLinking?.path;
748
-
749
- const normalizePath = (path: string) => {
750
- return `${groupPath ?? ''}/${path}`
751
- .replace(/^\//, '') // Remove extra leading slash
752
- .replace(/\/$/, ''); // Remove extra trailing slash
753
- };
754
-
755
- if ('linking' in item) {
928
+ const screenConfig: PathConfigForStaticNavigation = {};
929
+
930
+ sources.set(
931
+ screenConfig,
932
+ typeof item === 'object' && 'screen' in item ? item.screen : item
933
+ );
934
+
935
+ const normalizePath = (path: string) =>
936
+ path.replace(/^\/+|\/+$/g, '');
937
+
938
+ if ('linking' in item && item.linking !== undefined) {
756
939
  if (typeof item.linking === 'string') {
757
940
  screenConfig.path = item.linking;
758
- } else {
759
- Object.assign(screenConfig, item.linking);
941
+ } else if (
942
+ item.linking != null &&
943
+ typeof item.linking === 'object'
944
+ ) {
945
+ Object.assign(screenConfig, { ...item.linking });
760
946
  }
761
947
  }
762
948
 
763
- if (typeof groupLinking !== 'string') {
764
- if (groupLinking?.parse != null) {
765
- screenConfig.parse = {
766
- ...groupLinking.parse,
767
- ...screenConfig.parse,
768
- };
769
- }
770
-
771
- if (groupLinking?.stringify != null) {
772
- screenConfig.stringify = {
773
- ...groupLinking.stringify,
774
- ...screenConfig.stringify,
775
- };
776
- }
949
+ if (screenConfig.exact && screenConfig.path == null) {
950
+ throw new Error(
951
+ "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: ''`."
952
+ );
777
953
  }
778
954
 
779
955
  if (typeof screenConfig.path === 'string') {
780
956
  screenConfig.path = normalizePath(screenConfig.path);
781
957
  }
782
958
 
959
+ if (screenConfig.alias != null) {
960
+ screenConfig.alias = screenConfig.alias.map((alias) => {
961
+ if (typeof alias === 'string') {
962
+ return normalizePath(alias);
963
+ }
964
+
965
+ return {
966
+ ...alias,
967
+ path: normalizePath(alias.path),
968
+ };
969
+ });
970
+ }
971
+
783
972
  let screens;
784
973
 
974
+ const hasExplicitScreens =
975
+ 'screens' in screenConfig && screenConfig.screens != null;
976
+ const hasDisabledLinking =
977
+ 'linking' in item && item.linking === null;
978
+ const childOptions =
979
+ 'initialRouteName' in screenConfig &&
980
+ typeof screenConfig.initialRouteName === 'string'
981
+ ? { initialRouteName: screenConfig.initialRouteName }
982
+ : undefined;
983
+
984
+ if (
985
+ hasExplicitScreens &&
986
+ childOptions != null &&
987
+ !Object.keys(screenConfig.screens ?? {}).includes(
988
+ childOptions.initialRouteName
989
+ )
990
+ ) {
991
+ throw new Error(
992
+ `Couldn't find a screen named '${childOptions.initialRouteName}' to use as 'initialRouteName'.`
993
+ );
994
+ }
995
+
785
996
  const skipInitialDetectionInChild =
786
997
  skipInitialDetection ||
787
998
  (screenConfig.path != null && screenConfig.path !== '');
999
+ const allowAutoEmptyPathInChild =
1000
+ allowAutoEmptyPath &&
1001
+ (initialRouteName == null || key === initialRouteName);
788
1002
 
789
- if ('config' in item) {
1003
+ if (
1004
+ !hasExplicitScreens &&
1005
+ !hasDisabledLinking &&
1006
+ 'config' in item
1007
+ ) {
790
1008
  screens = createPathConfigForTree(
791
1009
  item,
792
- undefined,
793
- skipInitialDetectionInChild
1010
+ childOptions,
1011
+ skipInitialDetectionInChild,
1012
+ allowAutoEmptyPathInChild
794
1013
  );
795
1014
  } else if (
1015
+ !hasExplicitScreens &&
1016
+ !hasDisabledLinking &&
796
1017
  'screen' in item &&
797
1018
  'config' in item.screen &&
798
1019
  (item.screen.config.screens || item.screen.config.groups)
799
1020
  ) {
800
1021
  screens = createPathConfigForTree(
801
1022
  item.screen,
802
- undefined,
803
- skipInitialDetectionInChild
1023
+ childOptions,
1024
+ skipInitialDetectionInChild,
1025
+ allowAutoEmptyPathInChild
804
1026
  );
805
1027
  }
806
1028
 
807
1029
  if (screens) {
808
- // @ts-expect-error - we can't type this properly
809
1030
  screenConfig.screens = screens;
810
1031
  }
811
1032
 
812
1033
  if (
813
1034
  auto &&
814
1035
  !('screens' in screenConfig && screenConfig.screens) &&
815
- // Skip generating path for screens that specify linking config as `undefined` or `null` explicitly
816
- !('linking' in item && item.linking == null)
1036
+ // Skip generating path for screens that specify linking config as `null` explicitly
1037
+ !hasDisabledLinking
817
1038
  ) {
818
1039
  if (screenConfig.path != null) {
819
1040
  if (!skipInitialDetection) {
820
- if (key === initialRouteName && screenConfig.path != null) {
821
- initialScreenHasPath = true;
822
- } else if (screenConfig.path === '') {
1041
+ if (screenConfig.path === '') {
823
1042
  // We encounter a leaf screen with empty path,
824
1043
  // Clear the initial screen config as it's not needed anymore
825
1044
  initialScreenConfig = undefined;
1045
+ hasEmptyPath = true;
1046
+
1047
+ if (allowAutoEmptyPath && key === initialRouteName) {
1048
+ initialScreenHasPath = true;
1049
+ }
1050
+ } else if (
1051
+ allowAutoEmptyPath &&
1052
+ key === initialRouteName &&
1053
+ // Keep an already discovered fallback unless the initial route
1054
+ // explicitly uses an empty path.
1055
+ initialScreenConfig == null
1056
+ ) {
1057
+ initialScreenHasPath = true;
826
1058
  }
827
1059
  }
828
1060
  } else {
829
1061
  if (
830
- !groupPath &&
831
1062
  !skipInitialDetection &&
1063
+ allowAutoEmptyPath &&
1064
+ (initialRouteName == null || key === initialRouteName) &&
1065
+ !hasEmptyPath &&
832
1066
  initialScreenConfig == null
833
1067
  ) {
834
1068
  initialScreenConfig = screenConfig;
@@ -849,7 +1083,7 @@ export function createPathConfigForStaticNavigation(
849
1083
  );
850
1084
  };
851
1085
 
852
- const screens = {};
1086
+ const screens: PathConfigMapForStaticNavigation = {};
853
1087
 
854
1088
  // Loop through the config to find screens and groups
855
1089
  // So we add the screens and groups in the same order as they are defined
@@ -859,7 +1093,6 @@ export function createPathConfigForStaticNavigation(
859
1093
  screens,
860
1094
  createPathConfigForScreens(
861
1095
  t.config.screens,
862
- undefined,
863
1096
  o?.initialRouteName ?? t.config.initialRouteName
864
1097
  )
865
1098
  );
@@ -871,7 +1104,6 @@ export function createPathConfigForStaticNavigation(
871
1104
  screens,
872
1105
  createPathConfigForScreens(
873
1106
  group.screens,
874
- group.linking,
875
1107
  o?.initialRouteName ?? t.config.initialRouteName
876
1108
  )
877
1109
  );
@@ -886,11 +1118,17 @@ export function createPathConfigForStaticNavigation(
886
1118
  return screens;
887
1119
  };
888
1120
 
889
- const screens = createPathConfigForTree(tree, options, false);
1121
+ const screens = createPathConfigForTree(tree, options, false, true);
890
1122
 
891
1123
  if (auto && initialScreenConfig && !initialScreenHasPath) {
892
1124
  initialScreenConfig.path = '';
893
1125
  }
894
1126
 
895
- return screens;
1127
+ if (!screens) {
1128
+ return undefined;
1129
+ }
1130
+
1131
+ markSharedPathConfigs(screens, sources, blocked);
1132
+
1133
+ return screens as PathConfigMap<ParamList>;
896
1134
  }