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

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 (285) hide show
  1. package/lib/module/BaseNavigationContainer.js +38 -19
  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 +20 -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 +3 -0
  14. package/lib/module/NavigationStateContext.js.map +1 -1
  15. package/lib/module/PreventRemoveContext.js.map +1 -1
  16. package/lib/module/PreventRemoveProvider.js +37 -27
  17. package/lib/module/PreventRemoveProvider.js.map +1 -1
  18. package/lib/module/SceneView.js +10 -41
  19. package/lib/module/SceneView.js.map +1 -1
  20. package/lib/module/StaticNavigation.js +179 -50
  21. package/lib/module/StaticNavigation.js.map +1 -1
  22. package/lib/module/StaticTreeContext.js +5 -0
  23. package/lib/module/StaticTreeContext.js.map +1 -0
  24. package/lib/module/checkSerializable.js +18 -13
  25. package/lib/module/checkSerializable.js.map +1 -1
  26. package/lib/module/createNavigationContainerRef.js +6 -8
  27. package/lib/module/createNavigationContainerRef.js.map +1 -1
  28. package/lib/module/createNavigatorFactory.js +27 -5
  29. package/lib/module/createNavigatorFactory.js.map +1 -1
  30. package/lib/module/findFocusedRoute.js +7 -4
  31. package/lib/module/findFocusedRoute.js.map +1 -1
  32. package/lib/module/getActionFromState.js +45 -20
  33. package/lib/module/getActionFromState.js.map +1 -1
  34. package/lib/module/getPathFromState.js +171 -90
  35. package/lib/module/getPathFromState.js.map +1 -1
  36. package/lib/module/getPatternParts.js +20 -4
  37. package/lib/module/getPatternParts.js.map +1 -1
  38. package/lib/module/getStateFromPath.js +487 -217
  39. package/lib/module/getStateFromPath.js.map +1 -1
  40. package/lib/module/getStateFromRouteParams.js +23 -0
  41. package/lib/module/getStateFromRouteParams.js.map +1 -0
  42. package/lib/module/index.js +5 -4
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/theming/useTheme.js +1 -1
  45. package/lib/module/theming/useTheme.js.map +1 -1
  46. package/lib/module/types.js +35 -0
  47. package/lib/module/types.js.map +1 -1
  48. package/lib/module/useDescriptors.js +21 -11
  49. package/lib/module/useDescriptors.js.map +1 -1
  50. package/lib/module/useEventEmitter.js +48 -33
  51. package/lib/module/useEventEmitter.js.map +1 -1
  52. package/lib/module/useFocusEvents.js +12 -4
  53. package/lib/module/useFocusEvents.js.map +1 -1
  54. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  55. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  56. package/lib/module/useIsFocused.js +7 -12
  57. package/lib/module/useIsFocused.js.map +1 -1
  58. package/lib/module/useKeyedChildListeners.js +4 -2
  59. package/lib/module/useKeyedChildListeners.js.map +1 -1
  60. package/lib/module/useLazyValue.js.map +1 -1
  61. package/lib/module/useNavigation.js +2 -15
  62. package/lib/module/useNavigation.js.map +1 -1
  63. package/lib/module/useNavigationBuilder.js +204 -131
  64. package/lib/module/useNavigationBuilder.js.map +1 -1
  65. package/lib/module/useNavigationCache.js +22 -17
  66. package/lib/module/useNavigationCache.js.map +1 -1
  67. package/lib/module/useNavigationHelpers.js +28 -10
  68. package/lib/module/useNavigationHelpers.js.map +1 -1
  69. package/lib/module/useNavigationIndependentTree.js +1 -1
  70. package/lib/module/useNavigationIndependentTree.js.map +1 -1
  71. package/lib/module/useNavigationState.js +125 -45
  72. package/lib/module/useNavigationState.js.map +1 -1
  73. package/lib/module/useOnAction.js +37 -7
  74. package/lib/module/useOnAction.js.map +1 -1
  75. package/lib/module/useOnGetState.js +6 -3
  76. package/lib/module/useOnGetState.js.map +1 -1
  77. package/lib/module/useOnPreventRemove.js +39 -30
  78. package/lib/module/useOnPreventRemove.js.map +1 -1
  79. package/lib/module/useOnRouteFocus.js +1 -1
  80. package/lib/module/useOnRouteFocus.js.map +1 -1
  81. package/lib/module/useOptionsGetters.js +10 -5
  82. package/lib/module/useOptionsGetters.js.map +1 -1
  83. package/lib/module/usePreventRemove.js +10 -3
  84. package/lib/module/usePreventRemove.js.map +1 -1
  85. package/lib/module/usePreventRemoveContext.js +1 -1
  86. package/lib/module/usePreventRemoveContext.js.map +1 -1
  87. package/lib/module/useRegisterNavigator.js +2 -2
  88. package/lib/module/useRegisterNavigator.js.map +1 -1
  89. package/lib/module/useRoute.js +2 -0
  90. package/lib/module/useRoute.js.map +1 -1
  91. package/lib/module/useRouteCache.js +17 -11
  92. package/lib/module/useRouteCache.js.map +1 -1
  93. package/lib/module/useScheduleUpdate.js +9 -9
  94. package/lib/module/useScheduleUpdate.js.map +1 -1
  95. package/lib/module/useStateForPath.js +1 -1
  96. package/lib/module/useStateForPath.js.map +1 -1
  97. package/lib/module/useSyncState.js +32 -9
  98. package/lib/module/useSyncState.js.map +1 -1
  99. package/lib/module/validatePathConfig.js +50 -36
  100. package/lib/module/validatePathConfig.js.map +1 -1
  101. package/lib/typescript/src/BaseNavigationContainer.d.ts +6 -3
  102. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  103. package/lib/typescript/src/ConsumedParamsContext.d.ts +3 -0
  104. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -0
  105. package/lib/typescript/src/DataLoading.d.ts +21 -0
  106. package/lib/typescript/src/DataLoading.d.ts.map +1 -0
  107. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  108. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  109. package/lib/typescript/src/Group.d.ts +1 -1
  110. package/lib/typescript/src/Group.d.ts.map +1 -1
  111. package/lib/typescript/src/NavigationBuilderContext.d.ts +9 -8
  112. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  113. package/lib/typescript/src/NavigationContainerRefContext.d.ts +1 -1
  114. package/lib/typescript/src/NavigationContainerRefContext.d.ts.map +1 -1
  115. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
  116. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
  117. package/lib/typescript/src/NavigationHelpersContext.d.ts +1 -1
  118. package/lib/typescript/src/NavigationHelpersContext.d.ts.map +1 -1
  119. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  120. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  121. package/lib/typescript/src/NavigationProvider.d.ts +12 -13
  122. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  123. package/lib/typescript/src/NavigationStateContext.d.ts +4 -3
  124. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  125. package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
  126. package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
  127. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  128. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  129. package/lib/typescript/src/SceneView.d.ts +4 -2
  130. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  131. package/lib/typescript/src/Screen.d.ts +1 -1
  132. package/lib/typescript/src/Screen.d.ts.map +1 -1
  133. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  134. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  135. package/lib/typescript/src/StaticNavigation.d.ts +189 -109
  136. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  137. package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
  138. package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
  139. package/lib/typescript/src/checkSerializable.d.ts +8 -4
  140. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  141. package/lib/typescript/src/createNavigationContainerRef.d.ts +1 -1
  142. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  143. package/lib/typescript/src/createNavigatorFactory.d.ts +23 -5
  144. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  145. package/lib/typescript/src/findFocusedRoute.d.ts +3 -8
  146. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  147. package/lib/typescript/src/getActionFromState.d.ts +4 -4
  148. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  149. package/lib/typescript/src/getPathFromState.d.ts +3 -3
  150. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  151. package/lib/typescript/src/getPatternParts.d.ts +1 -0
  152. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  153. package/lib/typescript/src/getStateFromPath.d.ts +4 -7
  154. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  155. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  156. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  157. package/lib/typescript/src/index.d.ts +36 -33
  158. package/lib/typescript/src/index.d.ts.map +1 -1
  159. package/lib/typescript/src/theming/ThemeContext.d.ts +1 -1
  160. package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -1
  161. package/lib/typescript/src/theming/ThemeProvider.d.ts +2 -2
  162. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  163. package/lib/typescript/src/theming/useTheme.d.ts +1 -1
  164. package/lib/typescript/src/theming/useTheme.d.ts.map +1 -1
  165. package/lib/typescript/src/types.d.ts +260 -159
  166. package/lib/typescript/src/types.d.ts.map +1 -1
  167. package/lib/typescript/src/useChildListeners.d.ts +3 -3
  168. package/lib/typescript/src/useChildListeners.d.ts.map +1 -1
  169. package/lib/typescript/src/useComponent.d.ts +1 -1
  170. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  171. package/lib/typescript/src/useDescriptors.d.ts +10 -9
  172. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  173. package/lib/typescript/src/useEventEmitter.d.ts +2 -2
  174. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  175. package/lib/typescript/src/useFocusEvents.d.ts +2 -2
  176. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  177. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts +2 -2
  178. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts.map +1 -1
  179. package/lib/typescript/src/useIsFocused.d.ts +3 -0
  180. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  181. package/lib/typescript/src/useKeyedChildListeners.d.ts +3 -3
  182. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  183. package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
  184. package/lib/typescript/src/useNavigation.d.ts +4 -2
  185. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  186. package/lib/typescript/src/useNavigationBuilder.d.ts +23 -30
  187. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  188. package/lib/typescript/src/useNavigationCache.d.ts +3 -3
  189. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  190. package/lib/typescript/src/useNavigationContainerRef.d.ts +1 -1
  191. package/lib/typescript/src/useNavigationContainerRef.d.ts.map +1 -1
  192. package/lib/typescript/src/useNavigationHelpers.d.ts +19 -27
  193. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  194. package/lib/typescript/src/useNavigationState.d.ts +17 -3
  195. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  196. package/lib/typescript/src/useOnAction.d.ts +9 -9
  197. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  198. package/lib/typescript/src/useOnGetState.d.ts +1 -1
  199. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  200. package/lib/typescript/src/useOnPreventRemove.d.ts +5 -9
  201. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  202. package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
  203. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  204. package/lib/typescript/src/useOptionsGetters.d.ts +1 -1
  205. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  206. package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
  207. package/lib/typescript/src/usePreventRemoveContext.d.ts +2 -1
  208. package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
  209. package/lib/typescript/src/useRoute.d.ts +7 -3
  210. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  211. package/lib/typescript/src/useRouteCache.d.ts +5 -5
  212. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  213. package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
  214. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  215. package/lib/typescript/src/useStateForPath.d.ts +1 -1
  216. package/lib/typescript/src/useStateForPath.d.ts.map +1 -1
  217. package/lib/typescript/src/useSyncState.d.ts +1 -0
  218. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  219. package/lib/typescript/src/utilities.d.ts +74 -37
  220. package/lib/typescript/src/utilities.d.ts.map +1 -1
  221. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  222. package/package.json +23 -22
  223. package/src/BaseNavigationContainer.tsx +362 -334
  224. package/src/ConsumedParamsContext.tsx +5 -0
  225. package/src/DataLoading.tsx +203 -0
  226. package/src/NavigationBuilderContext.tsx +14 -9
  227. package/src/NavigationFocusedRouteStateContext.tsx +4 -4
  228. package/src/NavigationIndependentTree.tsx +23 -12
  229. package/src/NavigationProvider.tsx +26 -15
  230. package/src/NavigationStateContext.tsx +9 -6
  231. package/src/PreventRemoveContext.tsx +1 -0
  232. package/src/PreventRemoveProvider.tsx +51 -46
  233. package/src/SceneView.tsx +11 -39
  234. package/src/StaticNavigation.tsx +659 -307
  235. package/src/StaticTreeContext.tsx +7 -0
  236. package/src/checkSerializable.tsx +35 -19
  237. package/src/createNavigationContainerRef.tsx +12 -9
  238. package/src/createNavigatorFactory.tsx +92 -8
  239. package/src/findFocusedRoute.tsx +16 -16
  240. package/src/getActionFromState.tsx +87 -51
  241. package/src/getPathFromState.tsx +254 -128
  242. package/src/getPatternParts.tsx +25 -4
  243. package/src/getStateFromPath.tsx +821 -331
  244. package/src/getStateFromRouteParams.tsx +59 -0
  245. package/src/index.tsx +55 -5
  246. package/src/theming/useTheme.tsx +1 -1
  247. package/src/types.tsx +798 -373
  248. package/src/useDescriptors.tsx +31 -14
  249. package/src/useEventEmitter.tsx +52 -38
  250. package/src/useFocusEvents.tsx +15 -7
  251. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  252. package/src/useIsFocused.tsx +14 -21
  253. package/src/useKeyedChildListeners.tsx +4 -2
  254. package/src/useLazyValue.tsx +2 -2
  255. package/src/useNavigation.tsx +7 -24
  256. package/src/useNavigationBuilder.tsx +314 -186
  257. package/src/useNavigationCache.tsx +32 -25
  258. package/src/useNavigationHelpers.tsx +27 -10
  259. package/src/useNavigationIndependentTree.tsx +1 -1
  260. package/src/useNavigationState.tsx +168 -69
  261. package/src/useOnAction.tsx +61 -13
  262. package/src/useOnGetState.tsx +5 -3
  263. package/src/useOnPreventRemove.tsx +75 -42
  264. package/src/useOnRouteFocus.tsx +10 -12
  265. package/src/useOptionsGetters.tsx +11 -5
  266. package/src/usePreventRemove.tsx +12 -3
  267. package/src/usePreventRemoveContext.tsx +1 -1
  268. package/src/useRegisterNavigator.tsx +2 -2
  269. package/src/useRoute.tsx +20 -6
  270. package/src/useRouteCache.tsx +22 -16
  271. package/src/useScheduleUpdate.tsx +9 -10
  272. package/src/useStateForPath.tsx +1 -1
  273. package/src/useSyncState.tsx +35 -9
  274. package/src/utilities.tsx +144 -31
  275. package/src/validatePathConfig.tsx +86 -57
  276. package/lib/module/CurrentRenderContext.js +0 -10
  277. package/lib/module/CurrentRenderContext.js.map +0 -1
  278. package/lib/module/useCurrentRender.js +0 -19
  279. package/lib/module/useCurrentRender.js.map +0 -1
  280. package/lib/typescript/src/CurrentRenderContext.d.ts +0 -9
  281. package/lib/typescript/src/CurrentRenderContext.d.ts.map +0 -1
  282. package/lib/typescript/src/useCurrentRender.d.ts +0 -14
  283. package/lib/typescript/src/useCurrentRender.d.ts.map +0 -1
  284. package/src/CurrentRenderContext.tsx +0 -9
  285. package/src/useCurrentRender.tsx +0 -31
@@ -6,12 +6,19 @@ import type {
6
6
  import * as React from 'react';
7
7
  import { isValidElementType } from 'react-is';
8
8
 
9
+ import {
10
+ combinePatternParts,
11
+ getPatternParts,
12
+ type PatternPart,
13
+ } from './getPatternParts';
14
+ import { StaticTreeContext } from './StaticTreeContext';
9
15
  import type {
10
- DefaultNavigatorOptions,
11
16
  EventMapBase,
12
17
  NavigationListBase,
18
+ NavigatorProps,
13
19
  NavigatorScreenParams,
14
20
  NavigatorTypeBagBase,
21
+ NavigatorTypeBagFor,
15
22
  PathConfig,
16
23
  PathConfigMap,
17
24
  RouteGroupConfig,
@@ -21,68 +28,107 @@ import type {
21
28
  import { useRoute } from './useRoute';
22
29
  import type {
23
30
  AnyToUnknown,
24
- ExtractParamStrings,
25
- ExtractParamsType,
26
31
  FlatType,
27
32
  HasArguments,
28
- InferParse,
29
- InferPath,
33
+ InferParamsFromLinking,
30
34
  KeysOf,
35
+ NoExcessCallbackReturn,
36
+ NoExcessObject,
37
+ NoExcessValue,
38
+ NotUndefinedObject,
39
+ StandardSchemaV1,
31
40
  UnionToIntersection,
32
41
  ValidPathPattern,
33
42
  } from './utilities';
34
43
 
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
44
+ type ScreenComponentParams<T> = [T] extends [
45
+ React.ComponentType<{ route: { params: infer Params } }>,
46
+ ]
47
+ ? Params
48
+ : undefined;
49
+
50
+ type ParamsForScreenComponent<T> = [T] extends [(...args: any[]) => any]
51
+ ? // Don't infer params from a parameterless function component
52
+ // It'd infer `unknown` - but we want `undefined` if no params are accepted
53
+ HasArguments<T> extends true
54
+ ? ScreenComponentParams<T>
40
55
  : undefined
41
- : T extends React.ComponentType<{ route: { params: infer Params } }>
42
- ? Params
56
+ : ScreenComponentParams<T>;
57
+
58
+ // If the initial route's params include `undefined`, the nested navigator itself can be optional.
59
+ // If no initial route is specified, check if every nested route's params include `undefined`.
60
+ // Otherwise the nested navigator needs to be required.
61
+ type ParamsForNestedNavigator<
62
+ T extends { config: any },
63
+ ParamList extends {} = StaticParamList<T>,
64
+ Params = NavigatorScreenParams<ParamList>,
65
+ RouteNames extends keyof ParamList = T['config'] extends {
66
+ initialRouteName: infer RouteName extends keyof ParamList;
67
+ }
68
+ ? RouteName
69
+ : keyof ParamList,
70
+ > = {
71
+ [RouteName in RouteNames]-?: undefined extends ParamList[RouteName]
72
+ ? never
73
+ : RouteName;
74
+ }[RouteNames] extends never
75
+ ? Params | undefined
76
+ : Params;
77
+
78
+ // If the screens were constructed from a `Record<K, V>`,
79
+ // the screen value (`T`) may be a union.
80
+ // A naked `T extends ...` conditional would distribute over every screen in the union,
81
+ // creating a large params union per route.
82
+ // Wrapping `T` in a tuple checks the union as a whole instead of distributing,
83
+ // https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
84
+ type ParamsForScreen<T> = [T] extends [{ config: any }]
85
+ ? ParamsForNestedNavigator<T>
86
+ : [T] extends [React.ComponentType<any>]
87
+ ? ParamsForScreenComponent<T>
43
88
  : undefined;
44
89
 
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;
90
+ // Unlike `ParamsForScreen`, we want to distribute for `Record<string, Component | StaticNavigation>`
91
+ // This creates union like:
92
+ // `C extends C ? ParamsForScreen<C> : never) | (S extends S ? ParamsForScreen<S> : never)`
93
+ // `= ParamsForScreen<C> | ParamsForScreen<S>`
94
+ type ParamsForScreenEntry<T> = T extends T ? ParamsForScreen<T> : never;
95
+
96
+ // Only infer params from linking if it's a pattern (i.e., contains ':')
97
+ // or if parse is present, which can also define query params.
98
+ // This avoids inferring non-literals like 'string' without parse.
99
+ type ShouldInferFromLinking<Linking> = Linking extends {
100
+ parse: Record<string, unknown>;
101
+ }
102
+ ? true
103
+ : Linking extends ValidPathPattern | { path: ValidPathPattern }
104
+ ? true
105
+ : false;
66
106
 
67
107
  /**
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
108
+ * Params type derived from both linking and screen.
109
+ *
110
+ * - Linking has no extractable params (no ':' in path, no `parse`): returns
111
+ * screen params alone.
112
+ * - Linking has params + screen is a nested navigator: intersects path params
113
+ * with the navigator's screen params.
114
+ * - Linking has params + screen is a component without route params: returns
115
+ * path params alone (avoids `LP & undefined = never`).
116
+ * - Linking has params + screen has route params: intersects them.
72
117
  */
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>;
118
+ type ParamsForConfig<Linking, Screen, Params = ParamsForScreen<Screen>> =
119
+ ShouldInferFromLinking<Linking> extends true
120
+ ? [Screen] extends [{ config: any }]
121
+ ? undefined extends Params
122
+ ? FlatType<InferParamsFromLinking<Linking>> & Params
123
+ : // Optional linking params make the whole route params type optional when intersected.
124
+ // e.g. `({...} | undefined) & NavigatorScreenParams<{...}>` adds `| undefined` to the whole type.
125
+ // But if nested navigator had required params, it should still be required.
126
+ // So we remove `| undefined` from linking params before intersecting with nested navigator params.
127
+ FlatType<NotUndefinedObject<InferParamsFromLinking<Linking>>> & Params
128
+ : undefined extends Params
129
+ ? FlatType<InferParamsFromLinking<Linking>>
130
+ : FlatType<InferParamsFromLinking<Linking> & Params>
131
+ : Params;
86
132
 
87
133
  type ParamListForScreens<Screens> = {
88
134
  [Key in KeysOf<Screens>]: Screens[Key] extends StaticScreenConfig<
@@ -91,21 +137,16 @@ type ParamListForScreens<Screens> = {
91
137
  any,
92
138
  any,
93
139
  any,
140
+ any,
141
+ any,
142
+ any,
94
143
  any
95
144
  >
96
145
  ? ParamsForConfig<Linking, Screen>
97
- : ParamsForScreen<Screens[Key]>;
146
+ : ParamsForScreenEntry<Screens[Key]>;
98
147
  };
99
148
 
100
- type ParamListForGroups<
101
- Groups extends
102
- | Readonly<{
103
- [key: string]: {
104
- screens: {};
105
- };
106
- }>
107
- | undefined,
108
- > = Groups extends {
149
+ type ParamListForGroups<Groups> = Groups extends {
109
150
  [key: string]: {
110
151
  screens: infer Screens;
111
152
  };
@@ -113,26 +154,22 @@ type ParamListForGroups<
113
154
  ? ParamListForScreens<UnionToIntersection<Screens>>
114
155
  : {};
115
156
 
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
- >
135
- >;
157
+ type StaticRouteBase = Readonly<Omit<Route<string>, 'params'>>;
158
+
159
+ type StaticRoute<Params, P = AnyToUnknown<Params>> = StaticRouteBase &
160
+ (undefined extends Params
161
+ ? {
162
+ /**
163
+ * Params for this route
164
+ */
165
+ readonly params?: P extends object ? Readonly<P> : P;
166
+ }
167
+ : {
168
+ /**
169
+ * Params for this route
170
+ */
171
+ readonly params: Readonly<P>;
172
+ });
136
173
 
137
174
  type StaticScreenConfigLinkingAlias = {
138
175
  /**
@@ -147,7 +184,7 @@ type StaticScreenConfigLinkingAlias = {
147
184
  */
148
185
  exact?: boolean;
149
186
  /**
150
- * An object mapping the param name to a function which parses the param value.
187
+ * An object mapping the param name to a parser function or a Standard Schema.
151
188
  *
152
189
  * @example
153
190
  * ```js
@@ -157,10 +194,14 @@ type StaticScreenConfigLinkingAlias = {
157
194
  * }
158
195
  * ```
159
196
  */
160
- parse?: Record<string, (value: string) => unknown>;
197
+ parse?: Record<
198
+ string,
199
+ | ((value: string) => unknown)
200
+ | StandardSchemaV1<string | string[] | null | undefined, unknown>
201
+ >;
161
202
  /**
162
203
  * An object mapping the param name to a function which converts the param value to a string.
163
- * By default, all params are converted to strings using `String(value)`.
204
+ * By default, arrays and null are preserved for query params, and other values are converted using `String(value)`.
164
205
  *
165
206
  * @example
166
207
  * ```js
@@ -170,31 +211,60 @@ type StaticScreenConfigLinkingAlias = {
170
211
  * ```
171
212
  */
172
213
  stringify?: Record<string, (value: unknown) => string>;
214
+ /**
215
+ * Whether this path can resolve to multiple routes.
216
+ * Both routes should specify `shared: true` for this to work.
217
+ *
218
+ * When automatic path generation is enabled,
219
+ * screens using the same path pattern and the same screen component
220
+ * or navigator will be automatically marked as shared.
221
+ * Explicitly specifying the option overrides the automatic detection.
222
+ *
223
+ * This is useful when same screen used in multiple navigators,
224
+ * e.g. tabs with stacks containing profile screen in each stack.
225
+ *
226
+ * The path defined first will be the canonical path.
227
+ */
228
+ shared?: boolean;
229
+ };
230
+
231
+ type StaticScreenConfigLinkingExtras = {
232
+ /**
233
+ * Additional path alias that will be matched to the same screen.
234
+ */
235
+ alias?: (string | StaticScreenConfigLinkingAlias)[];
236
+ /**
237
+ * Name of the initial route to use for the nested navigator when the path matches.
238
+ */
239
+ initialRouteName?: string | undefined;
240
+ /**
241
+ * Path configuration for child screens in the nested navigator.
242
+ * Only supported when the screen renders a navigator with dynamic API.
243
+ */
244
+ screens?: PathConfigMap<ParamListBase> | undefined;
173
245
  };
174
246
 
175
247
  export type StaticScreenConfigLinking =
176
248
  | string
177
- | (StaticScreenConfigLinkingAlias & {
178
- /**
179
- * Additional path alias that will be matched to the same screen.
180
- */
181
- alias?: (string | StaticScreenConfigLinkingAlias)[];
182
- })
249
+ | (StaticScreenConfigLinkingAlias & StaticScreenConfigLinkingExtras)
250
+ | null
183
251
  | undefined;
184
252
 
185
253
  export type StaticScreenConfigScreen =
186
254
  | React.ComponentType<any>
187
- | StaticNavigation<any, any, any>;
188
-
189
- export type StaticScreenConfig<
190
- Linking extends StaticScreenConfigLinking,
191
- Screen,
192
- State extends NavigationState,
193
- ScreenOptions extends {},
194
- EventMap extends EventMapBase,
195
- Navigation,
196
- Params = ParamsForConfig<Linking, Screen>,
197
- > = {
255
+ | StaticNavigation<any>;
256
+
257
+ export interface StaticScreenConfig<
258
+ out Linking extends StaticScreenConfigLinking,
259
+ out Screen,
260
+ in out State extends NavigationState,
261
+ in out ScreenOptions extends {},
262
+ in out EventMap extends EventMapBase,
263
+ in out Navigation,
264
+ in out Params = ParamsForConfig<Linking, Screen>,
265
+ in out ProvidedOptions extends {} = {},
266
+ in out ProvidedListeners extends {} = {},
267
+ > {
198
268
  /**
199
269
  * Static navigation config or Component to render for the screen.
200
270
  */
@@ -228,10 +298,10 @@ export type StaticScreenConfig<
228
298
  options?:
229
299
  | ScreenOptions
230
300
  | ((props: {
231
- route: RouteType<Params>;
301
+ route: StaticRoute<Params>;
232
302
  navigation: Navigation;
233
303
  theme: Theme;
234
- }) => ScreenOptions);
304
+ }) => NoExcessCallbackReturn<ProvidedOptions, ScreenOptions>);
235
305
 
236
306
  /**
237
307
  * Event listeners for this screen.
@@ -248,9 +318,12 @@ export type StaticScreenConfig<
248
318
  listeners?:
249
319
  | ScreenListeners<State, EventMap>
250
320
  | ((props: {
251
- route: RouteType<Params>;
321
+ route: StaticRoute<Params>;
252
322
  navigation: Navigation;
253
- }) => ScreenListeners<State, EventMap>);
323
+ }) => NoExcessCallbackReturn<
324
+ ProvidedListeners,
325
+ ScreenListeners<State, EventMap>
326
+ >);
254
327
 
255
328
  /**
256
329
  * Layout for this screen.
@@ -268,7 +341,7 @@ export type StaticScreenConfig<
268
341
  *
269
342
  */
270
343
  layout?: (props: {
271
- route: RouteType<Params>;
344
+ route: StaticRoute<Params>;
272
345
  options: ScreenOptions;
273
346
  navigation: Navigation;
274
347
  theme: Theme;
@@ -295,7 +368,9 @@ export type StaticScreenConfig<
295
368
  * getId: ({ params }) => params?.userId,
296
369
  * ```
297
370
  */
298
- getId?: (props: { params: AnyToUnknown<Params> }) => string | undefined;
371
+ getId?: (props: {
372
+ params: AnyToUnknown<Params extends object ? Readonly<Params> : Params>;
373
+ }) => string | undefined;
299
374
 
300
375
  /**
301
376
  * Linking config for the screen.
@@ -317,31 +392,75 @@ export type StaticScreenConfig<
317
392
  * Optional key for this screen.
318
393
  */
319
394
  navigationKey?: string;
320
- };
395
+
396
+ /**
397
+ * Loader function to start loading data when the screen is navigated to.
398
+ *
399
+ * @example
400
+ * ```js
401
+ * UNSTABLE_loader: ({ params }) => loadProfile(params.id),
402
+ * ```
403
+ */
404
+ UNSTABLE_loader?: (options: {
405
+ name: string;
406
+ params: AnyToUnknown<Params>;
407
+ }) => Promise<void>;
408
+ }
409
+
410
+ export type StaticScreenFactory<in out Bag extends NavigatorTypeBagBase> = <
411
+ const Linking extends StaticScreenConfigLinking,
412
+ const Screen extends StaticScreenConfigScreen,
413
+ ProvidedOptions extends {} = {},
414
+ ProvidedListeners extends {} = {},
415
+ >(
416
+ config: StaticScreenConfig<
417
+ Linking,
418
+ Screen,
419
+ Bag['State'],
420
+ Bag['ScreenOptions'],
421
+ Bag['EventMap'],
422
+ Bag['NavigationList'][keyof Bag['ParamList']],
423
+ ParamsForConfig<Linking, Screen>,
424
+ ProvidedOptions,
425
+ ProvidedListeners
426
+ >
427
+ ) => StaticScreenConfig<
428
+ Linking,
429
+ Screen,
430
+ Bag['State'],
431
+ Bag['ScreenOptions'],
432
+ Bag['EventMap'],
433
+ Bag['NavigationList'][keyof Bag['ParamList']]
434
+ >;
435
+
436
+ /**
437
+ * Helper to create a typed `createXScreen` for static configuration.
438
+ */
439
+ export function createScreenFactory<
440
+ TypeBag extends NavigatorTypeBagBase,
441
+ >(): StaticScreenFactory<NavigatorTypeBagFor<TypeBag, ParamListBase>> {
442
+ return ((config: unknown) => config) as never;
443
+ }
321
444
 
322
445
  type StaticConfigScreens<
323
- ParamList extends ParamListBase,
324
- State extends NavigationState,
325
- ScreenOptions extends {},
326
- EventMap extends EventMapBase,
327
- NavigationList extends NavigationListBase<ParamList>,
446
+ in out ParamList extends ParamListBase,
447
+ in out State extends NavigationState,
448
+ in out ScreenOptions extends {},
449
+ in out EventMap extends EventMapBase,
450
+ in out NavigationList extends NavigationListBase<ParamList>,
328
451
  > = {
329
452
  [RouteName in keyof ParamList]:
330
- | React.ComponentType<any>
331
- | StaticNavigation<any, any, any>
453
+ | StaticScreenConfigScreen
332
454
  | 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>,
455
+ StaticScreenConfigLinking,
456
+ StaticScreenConfigScreen,
340
457
  State,
341
458
  ScreenOptions,
342
459
  EventMap,
343
460
  NavigationList[RouteName],
344
- any
461
+ any,
462
+ {},
463
+ {}
345
464
  >;
346
465
  };
347
466
 
@@ -360,27 +479,6 @@ type StaticConfigGroup<
360
479
  * This can be useful for conditional rendering of group of screens.
361
480
  */
362
481
  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
482
  /**
385
483
  * Static navigation config or Component to render for the screen.
386
484
  */
@@ -393,101 +491,160 @@ type StaticConfigGroup<
393
491
  >;
394
492
  };
395
493
 
396
- export type StaticConfig<Bag extends NavigatorTypeBagBase> =
397
- StaticConfigInternal<
494
+ type StaticConfigScreensForBag<Bag extends NavigatorTypeBagBase> =
495
+ StaticConfigScreens<
398
496
  Bag['ParamList'],
399
497
  Bag['State'],
400
498
  Bag['ScreenOptions'],
401
499
  Bag['EventMap'],
402
- Bag['NavigationList'],
403
- Bag['Navigator']
500
+ Bag['NavigationList']
404
501
  >;
405
502
 
406
- type StaticConfigInternal<
407
- ParamList extends ParamListBase,
408
- State extends NavigationState,
409
- ScreenOptions extends {},
410
- EventMap extends EventMapBase,
411
- NavigationList extends NavigationListBase<ParamList>,
412
- Navigator extends React.ComponentType<any>,
413
- > = Omit<
414
- Omit<
415
- React.ComponentProps<Navigator>,
416
- keyof DefaultNavigatorOptions<
417
- ParamListBase,
418
- NavigationState,
419
- {},
420
- EventMapBase,
421
- NavigationList[keyof ParamList]
422
- >
423
- > &
424
- DefaultNavigatorOptions<
425
- ParamList,
426
- State,
427
- ScreenOptions,
428
- EventMap,
429
- NavigationList[keyof ParamList]
430
- >,
503
+ type StaticConfigGroupsForBag<Bag extends NavigatorTypeBagBase> = {
504
+ [key: string]: StaticConfigGroup<
505
+ Bag['ParamList'],
506
+ Bag['State'],
507
+ Bag['ScreenOptions'],
508
+ Bag['EventMap'],
509
+ Bag['NavigationList']
510
+ >;
511
+ };
512
+
513
+ type StaticConfigCommon<Bag extends NavigatorTypeBagBase> = Omit<
514
+ NavigatorProps<
515
+ Bag['ParamList'],
516
+ Bag['State'],
517
+ Bag['ScreenOptions'],
518
+ Bag['EventMap'],
519
+ Bag['NavigationList'][keyof Bag['ParamList']],
520
+ Bag['Navigator']
521
+ >,
431
522
  'screens' | 'children'
432
- > &
523
+ >;
524
+
525
+ export type StaticConfigBase<
526
+ Bag extends NavigatorTypeBagBase,
527
+ Screens = StaticConfigScreensForBag<Bag>,
528
+ Groups = StaticConfigGroupsForBag<Bag>,
529
+ > = StaticConfigCommon<Bag> & {
530
+ /**
531
+ * Screens to render in the navigator and their configuration.
532
+ */
533
+ screens?: Screens;
534
+ /**
535
+ * Groups of screens to render in the navigator and their configuration.
536
+ */
537
+ groups?: Groups;
538
+ };
539
+
540
+ export type StaticConfig<
541
+ Bag extends NavigatorTypeBagBase,
542
+ Screens = StaticConfigScreensForBag<Bag>,
543
+ Groups = StaticConfigGroupsForBag<Bag>,
544
+ > = StaticConfigCommon<Bag> &
433
545
  (
434
546
  | {
435
547
  /**
436
548
  * Screens to render in the navigator and their configuration.
437
549
  */
438
- screens: StaticConfigScreens<
439
- ParamList,
440
- State,
441
- ScreenOptions,
442
- EventMap,
443
- NavigationList
444
- >;
550
+ screens: Screens;
445
551
  /**
446
552
  * Groups of screens to render in the navigator and their configuration.
447
553
  */
448
- groups?: {
449
- [key: string]: StaticConfigGroup<
450
- ParamList,
451
- State,
452
- ScreenOptions,
453
- EventMap,
454
- NavigationList
455
- >;
456
- };
554
+ groups?: Groups;
457
555
  }
458
556
  | {
459
557
  /**
460
558
  * Screens to render in the navigator and their configuration.
461
559
  */
462
- screens?: StaticConfigScreens<
463
- ParamList,
464
- State,
465
- ScreenOptions,
466
- EventMap,
467
- NavigationList
468
- >;
560
+ screens?: Screens;
469
561
  /**
470
562
  * Groups of screens to render in the navigator and their configuration.
471
563
  */
472
- groups: {
473
- [key: string]: StaticConfigGroup<
474
- ParamList,
475
- State,
476
- ScreenOptions,
477
- EventMap,
478
- NavigationList
479
- >;
480
- };
564
+ groups: Groups;
481
565
  }
482
566
  );
483
567
 
568
+ type NoExcessScreen<
569
+ Screen,
570
+ ScreenOptions extends {},
571
+ Listeners extends {},
572
+ > = (Screen extends { screen: unknown; options: unknown }
573
+ ? { options: NoExcessValue<Screen['options'], ScreenOptions> }
574
+ : unknown) &
575
+ (Screen extends { screen: unknown; listeners: unknown }
576
+ ? { listeners: NoExcessValue<Screen['listeners'], Listeners> }
577
+ : unknown);
578
+
579
+ type NoExcessScreens<
580
+ Screens,
581
+ ScreenOptions extends {},
582
+ Listeners extends {},
583
+ > = {
584
+ [RouteName in keyof Screens]: NoExcessScreen<
585
+ Screens[RouteName],
586
+ ScreenOptions,
587
+ Listeners
588
+ >;
589
+ };
590
+
591
+ type NoExcessGroups<
592
+ Groups,
593
+ Bag extends NavigatorTypeBagBase,
594
+ ScreenOptions extends {},
595
+ Listeners extends {},
596
+ > = {
597
+ [GroupName in keyof Groups]: NoExcessObject<
598
+ Groups[GroupName],
599
+ StaticConfigGroup<
600
+ ParamListBase,
601
+ NavigationState,
602
+ {},
603
+ EventMapBase,
604
+ NavigationListBase<ParamListBase>
605
+ >
606
+ > &
607
+ NoExcessStaticConfig<Groups[GroupName], Bag, ScreenOptions, Listeners>;
608
+ };
609
+
610
+ // Each property is probed with `Config extends { key: infer X }` and the results are intersected.
611
+ // These conditionals stay deferred while the config is inferred, so they don't interfere with the inference.
612
+ // Indexed access (`Config['screens']`) or a mapped type over the whole config breaks the param list inference.
613
+ export type NoExcessStaticConfig<
614
+ Config,
615
+ Bag extends NavigatorTypeBagBase,
616
+ ScreenOptions extends {} = Bag['ScreenOptions'],
617
+ Listeners extends {} = ScreenListeners<Bag['State'], Bag['EventMap']>,
618
+ > = (Config extends { screenOptions: infer ProvidedOptions }
619
+ ? {
620
+ screenOptions: NoExcessValue<ProvidedOptions, ScreenOptions>;
621
+ }
622
+ : unknown) &
623
+ (Config extends { screenListeners: infer ProvidedListeners }
624
+ ? {
625
+ screenListeners: NoExcessValue<ProvidedListeners, Listeners>;
626
+ }
627
+ : unknown) &
628
+ (Config extends { screens: infer Screens }
629
+ ? {
630
+ screens: NoExcessScreens<Screens, ScreenOptions, Listeners>;
631
+ }
632
+ : unknown) &
633
+ (Config extends { groups: infer Groups }
634
+ ? {
635
+ groups: NoExcessGroups<Groups, Bag, ScreenOptions, Listeners>;
636
+ }
637
+ : unknown);
638
+
484
639
  /**
485
640
  * Props for a screen component which is rendered by a static navigator.
486
641
  * Takes the route params as a generic argument.
487
642
  */
488
- export type StaticScreenProps<T extends Record<string, unknown> | undefined> = {
643
+ export type StaticScreenProps<
644
+ in out T extends Record<string, unknown> | undefined,
645
+ > = {
489
646
  route: {
490
- params: T;
647
+ readonly params: T;
491
648
  };
492
649
  };
493
650
 
@@ -503,11 +660,11 @@ export type StaticParamList<
503
660
  ParamListForGroups<T['config']['groups']>
504
661
  >;
505
662
 
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>;
663
+ export type StaticNavigation<
664
+ in out NavigatorTypeBag extends NavigatorTypeBagBase,
665
+ > = {
666
+ config: StaticConfigBase<NavigatorTypeBag>;
667
+ getComponent: () => React.ComponentType<{}>;
511
668
  };
512
669
 
513
670
  const MemoizedScreen = React.memo(
@@ -542,16 +699,13 @@ const getItemsFromScreens = (
542
699
  component = screen;
543
700
  } else if ('config' in screen) {
544
701
  isNavigator = true;
545
- component = createComponentForStaticNavigation(
546
- screen,
547
- `${name}Navigator`
548
- );
702
+ component = screen.getComponent();
549
703
  }
550
704
  } else if (isValidElementType(item)) {
551
705
  component = item;
552
706
  } else if ('config' in item) {
553
707
  isNavigator = true;
554
- component = createComponentForStaticNavigation(item, `${name}Navigator`);
708
+ component = item.getComponent();
555
709
  }
556
710
 
557
711
  if (component == null) {
@@ -589,10 +743,17 @@ const getItemsFromScreens = (
589
743
  * @param displayName Name of the component to be displayed in React DevTools.
590
744
  * @returns A component which renders the navigator.
591
745
  */
592
- export function createComponentForStaticNavigation(
593
- tree: StaticNavigation<any, any, any>,
746
+ export function createComponentForStaticConfig<
747
+ T extends {
748
+ Navigator: React.ComponentType<any>;
749
+ Group: React.ComponentType<any>;
750
+ Screen: React.ComponentType<any>;
751
+ config: StaticConfig<NavigatorTypeBagBase>;
752
+ },
753
+ >(
754
+ tree: T,
594
755
  displayName: string
595
- ): React.ComponentType<{}> {
756
+ ): React.ComponentType<Omit<React.ComponentProps<T['Navigator']>, 'children'>> {
596
757
  const { Navigator, Group, Screen, config } = tree;
597
758
  const { screens, groups, ...rest } = config;
598
759
 
@@ -637,10 +798,53 @@ export function createComponentForStaticNavigation(
637
798
  }
638
799
  }
639
800
 
640
- const NavigatorComponent = () => {
801
+ if (items.length === 0) {
802
+ throw new Error(
803
+ "Couldn't find any screens in the 'screens' or 'groups' property. Make sure to define at least one screen in the configuration."
804
+ );
805
+ }
806
+
807
+ const NavigatorComponent = ({ children: _, ...props }: typeof rest) => {
641
808
  const children = items.map((item) => item());
642
809
 
643
- return <Navigator {...rest}>{children}</Navigator>;
810
+ const screenOptions =
811
+ typeof props.screenOptions === 'function' ||
812
+ typeof rest.screenOptions === 'function'
813
+ ? (options: unknown) => ({
814
+ ...(typeof rest.screenOptions === 'function'
815
+ ? rest.screenOptions(options)
816
+ : rest.screenOptions),
817
+ ...(typeof props.screenOptions === 'function'
818
+ ? props.screenOptions(options)
819
+ : props.screenOptions),
820
+ })
821
+ : { ...rest.screenOptions, ...props.screenOptions };
822
+
823
+ const screenListeners =
824
+ typeof props.screenListeners === 'function' ||
825
+ typeof rest.screenListeners === 'function'
826
+ ? (options: unknown) => ({
827
+ ...(typeof rest.screenListeners === 'function'
828
+ ? rest.screenListeners(options)
829
+ : rest.screenListeners),
830
+ ...(typeof props.screenListeners === 'function'
831
+ ? props.screenListeners(options)
832
+ : props.screenListeners),
833
+ })
834
+ : { ...rest.screenListeners, ...props.screenListeners };
835
+
836
+ return (
837
+ <StaticTreeContext.Provider value={tree}>
838
+ <Navigator
839
+ {...rest}
840
+ {...props}
841
+ screenOptions={screenOptions}
842
+ screenListeners={screenListeners}
843
+ >
844
+ {children}
845
+ </Navigator>
846
+ </StaticTreeContext.Provider>
847
+ );
644
848
  };
645
849
 
646
850
  NavigatorComponent.displayName = displayName;
@@ -648,33 +852,111 @@ export function createComponentForStaticNavigation(
648
852
  return NavigatorComponent;
649
853
  }
650
854
 
651
- type LinkingForGroup =
652
- | Pick<PathConfig<any>, 'path' | 'stringify' | 'parse'>
653
- | string;
855
+ export type TreeForPathConfig = {
856
+ config: ConfigForPathConfig;
857
+ };
654
858
 
655
- 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
- };
859
+ type ConfigForPathConfig = {
860
+ initialRouteName?: string;
861
+ screens?: Record<string, ScreenForPathConfig>;
862
+ groups?: Record<string, { screens: Record<string, ScreenForPathConfig> }>;
863
+ };
864
+
865
+ type LinkingForPathConfig =
866
+ | string
867
+ | (Partial<StaticScreenConfigLinkingAlias> & StaticScreenConfigLinkingExtras)
868
+ | null
869
+ | undefined;
870
+
871
+ type ScreenValueForPathConfig = React.ComponentType<{}> | TreeForPathConfig;
872
+
873
+ type ScreenForPathConfig =
874
+ | ScreenValueForPathConfig
875
+ | {
876
+ screen: ScreenValueForPathConfig;
877
+ initialParams?: object;
878
+ linking?: LinkingForPathConfig;
879
+ UNSTABLE_loader?: unknown;
676
880
  };
881
+
882
+ type PathConfigMapForStaticNavigation = Record<
883
+ string,
884
+ string | PathConfigForStaticNavigation | undefined
885
+ >;
886
+
887
+ type PathConfigForStaticNavigation = Omit<
888
+ PathConfig<ParamListBase>,
889
+ 'screens'
890
+ > & {
891
+ initialRouteName?: string | undefined;
892
+ screens?: PathConfigMapForStaticNavigation | undefined;
893
+ };
894
+
895
+ // Mark screens that resolve to the same pattern and same component or navigator
896
+ const markSharedPathConfigs = (
897
+ screens: PathConfigMapForStaticNavigation,
898
+ sources: WeakMap<PathConfigForStaticNavigation, ScreenValueForPathConfig>,
899
+ blocked: WeakSet<PathConfigForStaticNavigation>
900
+ ) => {
901
+ const candidates = new Map<string, PathConfigForStaticNavigation[]>();
902
+
903
+ const collect = (
904
+ screens: PathConfigMapForStaticNavigation,
905
+ parentParts: PatternPart[],
906
+ disabled: boolean
907
+ ) => {
908
+ for (const config of Object.values(screens)) {
909
+ if (config == null || typeof config === 'string') {
910
+ continue;
911
+ }
912
+
913
+ const parts =
914
+ typeof config.path === 'string'
915
+ ? combinePatternParts(
916
+ parentParts,
917
+ getPatternParts(config.path),
918
+ config.exact
919
+ )
920
+ : parentParts;
921
+
922
+ if (sources.has(config) && typeof config.path === 'string') {
923
+ if (disabled) {
924
+ blocked.add(config);
925
+ }
926
+
927
+ const pattern = parts.map((part) => part.segment).join('/');
928
+ const entries = candidates.get(pattern) ?? [];
929
+
930
+ entries.push(config);
931
+ candidates.set(pattern, entries);
932
+ }
933
+
934
+ if (config.screens) {
935
+ collect(config.screens, parts, disabled || config.shared === false);
936
+ }
937
+ }
677
938
  };
939
+
940
+ collect(screens, [], false);
941
+
942
+ for (const entries of candidates.values()) {
943
+ for (const entry of entries) {
944
+ const source = sources.get(entry);
945
+
946
+ if (
947
+ entry.shared === undefined &&
948
+ !blocked.has(entry) &&
949
+ entries.some(
950
+ (candidate) =>
951
+ candidate !== entry &&
952
+ !blocked.has(candidate) &&
953
+ sources.get(candidate) === source
954
+ )
955
+ ) {
956
+ entry.shared = true;
957
+ }
958
+ }
959
+ }
678
960
  };
679
961
 
680
962
  /**
@@ -696,32 +978,49 @@ type TreeForPathConfig = {
696
978
  * };
697
979
  * ```
698
980
  */
699
- export function createPathConfigForStaticNavigation(
981
+ export function createPathConfigForStaticNavigation<ParamList extends {}>(
700
982
  tree: TreeForPathConfig,
701
983
  options?: {
702
- initialRouteName?: string;
984
+ initialRouteName?: string | undefined;
703
985
  },
704
986
  auto?: boolean
705
- ): PathConfigMap<ParamListBase> | undefined {
987
+ ): PathConfigMap<ParamList> | undefined {
706
988
  let initialScreenHasPath: boolean = false;
707
989
  let initialScreenConfig: PathConfig<{}> | undefined;
990
+ let hasEmptyPath = false;
991
+
992
+ const blocked = new WeakSet<PathConfigForStaticNavigation>();
993
+ const sources = new WeakMap<
994
+ PathConfigForStaticNavigation,
995
+ ScreenValueForPathConfig
996
+ >();
708
997
 
709
998
  const createPathConfigForTree = (
710
999
  t: TreeForPathConfig,
711
- o: { initialRouteName?: string } | undefined,
1000
+ o: { initialRouteName?: string | undefined } | undefined,
712
1001
  // If a screen is a leaf node, but inside a screen with path,
713
1002
  // It should not be used for initial detection
714
- skipInitialDetection: boolean
1003
+ skipInitialDetection: boolean,
1004
+ allowAutoEmptyPath: boolean
715
1005
  ) => {
1006
+ const initialRouteName = o?.initialRouteName ?? t.config.initialRouteName;
1007
+
1008
+ if (initialRouteName != null) {
1009
+ const routeNames = new Set(Object.keys(t.config.screens ?? {}));
1010
+
1011
+ for (const group of Object.values(t.config.groups ?? {})) {
1012
+ Object.keys(group.screens).forEach((name) => routeNames.add(name));
1013
+ }
1014
+
1015
+ if (!routeNames.has(initialRouteName)) {
1016
+ throw new Error(
1017
+ `Couldn't find a screen named '${initialRouteName}' to use as 'initialRouteName'.`
1018
+ );
1019
+ }
1020
+ }
1021
+
716
1022
  const createPathConfigForScreens = (
717
- screens: StaticConfigScreens<
718
- ParamListBase,
719
- NavigationState,
720
- {},
721
- EventMapBase,
722
- Record<string, unknown>
723
- >,
724
- groupLinking: LinkingForGroup | undefined,
1023
+ screens: Record<string, ScreenForPathConfig>,
725
1024
  initialRouteName: string | undefined
726
1025
  ) => {
727
1026
  return Object.fromEntries(
@@ -740,95 +1039,144 @@ export function createPathConfigForStaticNavigation(
740
1039
  return 0;
741
1040
  })
742
1041
  .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) {
1042
+ const screenConfig: PathConfigForStaticNavigation = {};
1043
+
1044
+ sources.set(
1045
+ screenConfig,
1046
+ typeof item === 'object' && 'screen' in item ? item.screen : item
1047
+ );
1048
+
1049
+ const normalizePath = (path: string) =>
1050
+ path.replace(/^\/+|\/+$/g, '');
1051
+
1052
+ if ('linking' in item && item.linking !== undefined) {
756
1053
  if (typeof item.linking === 'string') {
757
1054
  screenConfig.path = item.linking;
758
- } else {
759
- Object.assign(screenConfig, item.linking);
1055
+ } else if (
1056
+ item.linking != null &&
1057
+ typeof item.linking === 'object'
1058
+ ) {
1059
+ Object.assign(screenConfig, { ...item.linking });
760
1060
  }
761
1061
  }
762
1062
 
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
- }
1063
+ if (screenConfig.exact && screenConfig.path == null) {
1064
+ throw new Error(
1065
+ "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: ''`."
1066
+ );
777
1067
  }
778
1068
 
779
1069
  if (typeof screenConfig.path === 'string') {
780
1070
  screenConfig.path = normalizePath(screenConfig.path);
781
1071
  }
782
1072
 
1073
+ if (screenConfig.alias != null) {
1074
+ screenConfig.alias = screenConfig.alias.map((alias) => {
1075
+ if (typeof alias === 'string') {
1076
+ return normalizePath(alias);
1077
+ }
1078
+
1079
+ return {
1080
+ ...alias,
1081
+ path: normalizePath(alias.path),
1082
+ };
1083
+ });
1084
+ }
1085
+
783
1086
  let screens;
784
1087
 
1088
+ const hasExplicitScreens =
1089
+ 'screens' in screenConfig && screenConfig.screens != null;
1090
+ const hasDisabledLinking =
1091
+ 'linking' in item && item.linking === null;
1092
+ const childOptions =
1093
+ 'initialRouteName' in screenConfig &&
1094
+ typeof screenConfig.initialRouteName === 'string'
1095
+ ? { initialRouteName: screenConfig.initialRouteName }
1096
+ : undefined;
1097
+
1098
+ if (
1099
+ hasExplicitScreens &&
1100
+ childOptions != null &&
1101
+ !Object.keys(screenConfig.screens ?? {}).includes(
1102
+ childOptions.initialRouteName
1103
+ )
1104
+ ) {
1105
+ throw new Error(
1106
+ `Couldn't find a screen named '${childOptions.initialRouteName}' to use as 'initialRouteName'.`
1107
+ );
1108
+ }
1109
+
785
1110
  const skipInitialDetectionInChild =
786
1111
  skipInitialDetection ||
787
1112
  (screenConfig.path != null && screenConfig.path !== '');
1113
+ const allowAutoEmptyPathInChild =
1114
+ allowAutoEmptyPath &&
1115
+ (initialRouteName == null || key === initialRouteName);
788
1116
 
789
- if ('config' in item) {
1117
+ if (
1118
+ !hasExplicitScreens &&
1119
+ !hasDisabledLinking &&
1120
+ 'config' in item
1121
+ ) {
790
1122
  screens = createPathConfigForTree(
791
1123
  item,
792
- undefined,
793
- skipInitialDetectionInChild
1124
+ childOptions,
1125
+ skipInitialDetectionInChild,
1126
+ allowAutoEmptyPathInChild
794
1127
  );
795
1128
  } else if (
1129
+ !hasExplicitScreens &&
1130
+ !hasDisabledLinking &&
796
1131
  'screen' in item &&
797
1132
  'config' in item.screen &&
798
1133
  (item.screen.config.screens || item.screen.config.groups)
799
1134
  ) {
800
1135
  screens = createPathConfigForTree(
801
1136
  item.screen,
802
- undefined,
803
- skipInitialDetectionInChild
1137
+ childOptions,
1138
+ skipInitialDetectionInChild,
1139
+ allowAutoEmptyPathInChild
804
1140
  );
805
1141
  }
806
1142
 
807
1143
  if (screens) {
808
- // @ts-expect-error - we can't type this properly
809
1144
  screenConfig.screens = screens;
810
1145
  }
811
1146
 
812
1147
  if (
813
1148
  auto &&
814
1149
  !('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)
1150
+ // Skip generating path for screens that specify linking config as `null` explicitly
1151
+ !hasDisabledLinking
817
1152
  ) {
818
1153
  if (screenConfig.path != null) {
819
1154
  if (!skipInitialDetection) {
820
- if (key === initialRouteName && screenConfig.path != null) {
821
- initialScreenHasPath = true;
822
- } else if (screenConfig.path === '') {
1155
+ if (screenConfig.path === '') {
823
1156
  // We encounter a leaf screen with empty path,
824
1157
  // Clear the initial screen config as it's not needed anymore
825
1158
  initialScreenConfig = undefined;
1159
+ hasEmptyPath = true;
1160
+
1161
+ if (allowAutoEmptyPath && key === initialRouteName) {
1162
+ initialScreenHasPath = true;
1163
+ }
1164
+ } else if (
1165
+ allowAutoEmptyPath &&
1166
+ key === initialRouteName &&
1167
+ // Keep an already discovered fallback unless the initial route
1168
+ // explicitly uses an empty path.
1169
+ initialScreenConfig == null
1170
+ ) {
1171
+ initialScreenHasPath = true;
826
1172
  }
827
1173
  }
828
1174
  } else {
829
1175
  if (
830
- !groupPath &&
831
1176
  !skipInitialDetection &&
1177
+ allowAutoEmptyPath &&
1178
+ (initialRouteName == null || key === initialRouteName) &&
1179
+ !hasEmptyPath &&
832
1180
  initialScreenConfig == null
833
1181
  ) {
834
1182
  initialScreenConfig = screenConfig;
@@ -836,8 +1184,8 @@ export function createPathConfigForStaticNavigation(
836
1184
 
837
1185
  screenConfig.path = normalizePath(
838
1186
  key
839
- .replace(/([A-Z]+)/g, '-$1')
840
- .replace(/^-/, '')
1187
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
1188
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
841
1189
  .toLowerCase()
842
1190
  );
843
1191
  }
@@ -849,7 +1197,7 @@ export function createPathConfigForStaticNavigation(
849
1197
  );
850
1198
  };
851
1199
 
852
- const screens = {};
1200
+ const screens: PathConfigMapForStaticNavigation = {};
853
1201
 
854
1202
  // Loop through the config to find screens and groups
855
1203
  // So we add the screens and groups in the same order as they are defined
@@ -859,7 +1207,6 @@ export function createPathConfigForStaticNavigation(
859
1207
  screens,
860
1208
  createPathConfigForScreens(
861
1209
  t.config.screens,
862
- undefined,
863
1210
  o?.initialRouteName ?? t.config.initialRouteName
864
1211
  )
865
1212
  );
@@ -871,7 +1218,6 @@ export function createPathConfigForStaticNavigation(
871
1218
  screens,
872
1219
  createPathConfigForScreens(
873
1220
  group.screens,
874
- group.linking,
875
1221
  o?.initialRouteName ?? t.config.initialRouteName
876
1222
  )
877
1223
  );
@@ -886,11 +1232,17 @@ export function createPathConfigForStaticNavigation(
886
1232
  return screens;
887
1233
  };
888
1234
 
889
- const screens = createPathConfigForTree(tree, options, false);
1235
+ const screens = createPathConfigForTree(tree, options, false, true);
890
1236
 
891
1237
  if (auto && initialScreenConfig && !initialScreenHasPath) {
892
1238
  initialScreenConfig.path = '';
893
1239
  }
894
1240
 
895
- return screens;
1241
+ if (!screens) {
1242
+ return undefined;
1243
+ }
1244
+
1245
+ markSharedPathConfigs(screens, sources, blocked);
1246
+
1247
+ return screens as PathConfigMap<ParamList>;
896
1248
  }