@react-navigation/core 8.0.0-alpha.1 → 8.0.0-alpha.11

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 (163) hide show
  1. package/lib/module/BaseNavigationContainer.js +14 -7
  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 +34 -4
  17. package/lib/module/StaticNavigation.js.map +1 -1
  18. package/lib/module/createNavigatorFactory.js +22 -1
  19. package/lib/module/createNavigatorFactory.js.map +1 -1
  20. package/lib/module/getPathFromState.js +25 -3
  21. package/lib/module/getPathFromState.js.map +1 -1
  22. package/lib/module/getStateFromPath.js +158 -73
  23. package/lib/module/getStateFromPath.js.map +1 -1
  24. package/lib/module/getStateFromRouteParams.js +24 -0
  25. package/lib/module/getStateFromRouteParams.js.map +1 -0
  26. package/lib/module/index.js +1 -1
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/theming/useTheme.js +1 -1
  29. package/lib/module/theming/useTheme.js.map +1 -1
  30. package/lib/module/types.js.map +1 -1
  31. package/lib/module/useCurrentRender.js +1 -1
  32. package/lib/module/useCurrentRender.js.map +1 -1
  33. package/lib/module/useDescriptors.js +12 -43
  34. package/lib/module/useDescriptors.js.map +1 -1
  35. package/lib/module/useFocusEvents.js +1 -1
  36. package/lib/module/useFocusEvents.js.map +1 -1
  37. package/lib/module/useFocusedListenersChildrenAdapter.js +1 -1
  38. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  39. package/lib/module/useIsFocused.js +7 -12
  40. package/lib/module/useIsFocused.js.map +1 -1
  41. package/lib/module/useNavigationBuilder.js +84 -38
  42. package/lib/module/useNavigationBuilder.js.map +1 -1
  43. package/lib/module/useNavigationCache.js +14 -56
  44. package/lib/module/useNavigationCache.js.map +1 -1
  45. package/lib/module/useNavigationHelpers.js +1 -1
  46. package/lib/module/useNavigationHelpers.js.map +1 -1
  47. package/lib/module/useNavigationIndependentTree.js +1 -1
  48. package/lib/module/useNavigationIndependentTree.js.map +1 -1
  49. package/lib/module/useNavigationState.js +4 -2
  50. package/lib/module/useNavigationState.js.map +1 -1
  51. package/lib/module/useOnAction.js +1 -1
  52. package/lib/module/useOnAction.js.map +1 -1
  53. package/lib/module/useOnGetState.js +2 -2
  54. package/lib/module/useOnGetState.js.map +1 -1
  55. package/lib/module/useOnPreventRemove.js +2 -2
  56. package/lib/module/useOnPreventRemove.js.map +1 -1
  57. package/lib/module/useOnRouteFocus.js +1 -1
  58. package/lib/module/useOnRouteFocus.js.map +1 -1
  59. package/lib/module/useOptionsGetters.js +2 -2
  60. package/lib/module/useOptionsGetters.js.map +1 -1
  61. package/lib/module/usePreventRemoveContext.js +1 -1
  62. package/lib/module/usePreventRemoveContext.js.map +1 -1
  63. package/lib/module/useRegisterNavigator.js +1 -1
  64. package/lib/module/useRegisterNavigator.js.map +1 -1
  65. package/lib/module/useScheduleUpdate.js +1 -1
  66. package/lib/module/useScheduleUpdate.js.map +1 -1
  67. package/lib/module/useStateForPath.js +1 -1
  68. package/lib/module/useStateForPath.js.map +1 -1
  69. package/lib/typescript/src/BaseNavigationContainer.d.ts +6 -3
  70. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  71. package/lib/typescript/src/ConsumedParamsContext.d.ts +8 -0
  72. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -0
  73. package/lib/typescript/src/NavigationBuilderContext.d.ts +11 -5
  74. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  75. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
  76. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
  77. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  78. package/lib/typescript/src/NavigationProvider.d.ts +4 -4
  79. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  80. package/lib/typescript/src/NavigationStateContext.d.ts +3 -3
  81. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  82. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  83. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  84. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  85. package/lib/typescript/src/StaticNavigation.d.ts +39 -24
  86. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  87. package/lib/typescript/src/createNavigatorFactory.d.ts +1 -1
  88. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  89. package/lib/typescript/src/findFocusedRoute.d.ts +3 -3
  90. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  91. package/lib/typescript/src/getActionFromState.d.ts +3 -3
  92. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  93. package/lib/typescript/src/getPathFromState.d.ts +2 -2
  94. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  95. package/lib/typescript/src/getStateFromPath.d.ts +3 -3
  96. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  97. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  98. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  99. package/lib/typescript/src/index.d.ts +2 -1
  100. package/lib/typescript/src/index.d.ts.map +1 -1
  101. package/lib/typescript/src/types.d.ts +144 -82
  102. package/lib/typescript/src/types.d.ts.map +1 -1
  103. package/lib/typescript/src/useDescriptors.d.ts +12 -25
  104. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  105. package/lib/typescript/src/useIsFocused.d.ts +3 -0
  106. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  107. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  108. package/lib/typescript/src/useNavigationBuilder.d.ts +20 -29
  109. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  110. package/lib/typescript/src/useNavigationCache.d.ts +2 -42
  111. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  112. package/lib/typescript/src/useNavigationHelpers.d.ts +18 -16
  113. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  114. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  115. package/lib/typescript/src/useOnAction.d.ts +6 -6
  116. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  117. package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
  118. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  119. package/lib/typescript/src/useRouteCache.d.ts +2 -2
  120. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  121. package/lib/typescript/src/utilities.d.ts +42 -3
  122. package/lib/typescript/src/utilities.d.ts.map +1 -1
  123. package/package.json +20 -18
  124. package/src/BaseNavigationContainer.tsx +332 -326
  125. package/src/ConsumedParamsContext.tsx +10 -0
  126. package/src/NavigationBuilderContext.tsx +14 -8
  127. package/src/NavigationFocusedRouteStateContext.tsx +4 -4
  128. package/src/NavigationIndependentTree.tsx +8 -5
  129. package/src/NavigationProvider.tsx +17 -3
  130. package/src/NavigationStateContext.tsx +5 -6
  131. package/src/PreventRemoveProvider.tsx +3 -3
  132. package/src/SceneView.tsx +58 -56
  133. package/src/StaticNavigation.tsx +121 -51
  134. package/src/createNavigatorFactory.tsx +35 -4
  135. package/src/findFocusedRoute.tsx +3 -3
  136. package/src/getActionFromState.tsx +7 -7
  137. package/src/getPathFromState.tsx +53 -9
  138. package/src/getStateFromPath.tsx +255 -97
  139. package/src/getStateFromRouteParams.tsx +60 -0
  140. package/src/index.tsx +1 -1
  141. package/src/theming/useTheme.tsx +1 -1
  142. package/src/types.tsx +330 -187
  143. package/src/useCurrentRender.tsx +1 -1
  144. package/src/useDescriptors.tsx +13 -48
  145. package/src/useFocusEvents.tsx +1 -1
  146. package/src/useFocusedListenersChildrenAdapter.tsx +1 -1
  147. package/src/useIsFocused.tsx +14 -21
  148. package/src/useNavigation.tsx +2 -2
  149. package/src/useNavigationBuilder.tsx +131 -49
  150. package/src/useNavigationCache.tsx +12 -73
  151. package/src/useNavigationHelpers.tsx +1 -1
  152. package/src/useNavigationIndependentTree.tsx +1 -1
  153. package/src/useNavigationState.tsx +6 -4
  154. package/src/useOnAction.tsx +8 -8
  155. package/src/useOnGetState.tsx +2 -2
  156. package/src/useOnPreventRemove.tsx +2 -2
  157. package/src/useOnRouteFocus.tsx +10 -12
  158. package/src/useOptionsGetters.tsx +2 -2
  159. package/src/usePreventRemoveContext.tsx +1 -1
  160. package/src/useRegisterNavigator.tsx +1 -1
  161. package/src/useScheduleUpdate.tsx +1 -3
  162. package/src/useStateForPath.tsx +1 -1
  163. package/src/utilities.tsx +94 -4
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+
3
+ type ConsumedParamsContextValue = {
4
+ isConsumed: (params: object) => boolean;
5
+ setConsumed: (params: object) => void;
6
+ };
7
+
8
+ export const ConsumedParamsContext = React.createContext<
9
+ ConsumedParamsContextValue | undefined
10
+ >(undefined);
@@ -52,20 +52,26 @@ export type ChildBeforeRemoveListener = (action: NavigationAction) => boolean;
52
52
  * Context which holds the required helpers needed to build nested navigators.
53
53
  */
54
54
  export const NavigationBuilderContext = React.createContext<{
55
- onAction?: (
56
- action: NavigationAction,
57
- visitedNavigators?: Set<string>
58
- ) => boolean;
59
- addListener?: AddListener;
60
- addKeyedListener?: AddKeyedListener;
61
- onRouteFocus?: (key: string) => void;
55
+ onAction?:
56
+ | ((action: NavigationAction, visitedNavigators?: Set<string>) => boolean)
57
+ | undefined;
58
+ addListener?: AddListener | undefined;
59
+ addKeyedListener?: AddKeyedListener | undefined;
60
+ onRouteFocus?: ((key: string) => void) | undefined;
62
61
  onDispatchAction: (action: NavigationAction, noop: boolean) => void;
62
+ onEmitEvent: (event: {
63
+ type: string;
64
+ defaultPrevented: boolean | undefined;
65
+ target: string | undefined;
66
+ data: unknown;
67
+ }) => void;
63
68
  onOptionsChange: (options: object) => void;
64
69
  scheduleUpdate: (callback: () => void) => void;
65
70
  flushUpdates: () => void;
66
- stackRef?: React.MutableRefObject<string | undefined>;
71
+ stackRef?: React.MutableRefObject<string | undefined> | undefined;
67
72
  }>({
68
73
  onDispatchAction: () => undefined,
74
+ onEmitEvent: () => undefined,
69
75
  onOptionsChange: () => undefined,
70
76
  scheduleUpdate: () => {
71
77
  throw new Error("Couldn't find a context for scheduling updates.");
@@ -3,11 +3,11 @@ import * as React from 'react';
3
3
  export type FocusedRouteState = {
4
4
  routes: [
5
5
  {
6
- key?: string;
6
+ key?: string | undefined;
7
7
  name: string;
8
- params?: object;
9
- path?: string;
10
- state?: FocusedRouteState;
8
+ params?: object | undefined;
9
+ path?: string | undefined;
10
+ state?: FocusedRouteState | undefined;
11
11
  },
12
12
  ];
13
13
  };
@@ -7,6 +7,7 @@ import {
7
7
  NavigationRouteContext,
8
8
  NavigationRouteNameContext,
9
9
  } from './NavigationProvider';
10
+ import { IsFocusedContext } from './useIsFocused';
10
11
 
11
12
  /**
12
13
  * Component to make the child navigation container independent of parent containers.
@@ -21,11 +22,13 @@ export function NavigationIndependentTree({
21
22
  <NamedRouteContextListContext.Provider value={undefined}>
22
23
  <NavigationRouteContext.Provider value={undefined}>
23
24
  <NavigationContext.Provider value={undefined}>
24
- <NavigationRouteNameContext.Provider value={undefined}>
25
- <NavigationIndependentTreeContext.Provider value={true}>
26
- {children}
27
- </NavigationIndependentTreeContext.Provider>
28
- </NavigationRouteNameContext.Provider>
25
+ <IsFocusedContext.Provider value={undefined}>
26
+ <NavigationRouteNameContext.Provider value={undefined}>
27
+ <NavigationIndependentTreeContext.Provider value={true}>
28
+ {children}
29
+ </NavigationIndependentTreeContext.Provider>
30
+ </NavigationRouteNameContext.Provider>
31
+ </IsFocusedContext.Provider>
29
32
  </NavigationContext.Provider>
30
33
  </NavigationRouteContext.Provider>
31
34
  </NamedRouteContextListContext.Provider>
@@ -2,6 +2,7 @@ import type { ParamListBase, Route } from '@react-navigation/routers';
2
2
  import * as React from 'react';
3
3
 
4
4
  import type { NavigationProp } from './types';
5
+ import { FocusedRouteKeyContext, IsFocusedContext } from './useIsFocused';
5
6
  import { useLazyValue } from './useLazyValue';
6
7
 
7
8
  /**
@@ -48,14 +49,27 @@ export function NavigationProvider({ route, navigation, children }: Props) {
48
49
  [NamedRouteContext, parents, route.name]
49
50
  );
50
51
 
52
+ const parentIsFocused = React.use(IsFocusedContext);
53
+ const focusedRouteKey = React.use(FocusedRouteKeyContext);
54
+
55
+ // Mark route as focused only if:
56
+ // - It doesn't have a parent navigator
57
+ // - Parent navigator is focused
58
+ const isFocused =
59
+ parentIsFocused == null || parentIsFocused
60
+ ? focusedRouteKey === route.key
61
+ : false;
62
+
51
63
  return (
52
64
  <NamedRouteContextListContext.Provider value={NamedRouteContextList}>
53
65
  <NamedRouteContext.Provider value={route}>
54
66
  <NavigationRouteContext.Provider value={route}>
55
67
  <NavigationContext.Provider value={navigation}>
56
- <NavigationRouteNameContext.Provider value={route.name}>
57
- {children}
58
- </NavigationRouteNameContext.Provider>
68
+ <IsFocusedContext.Provider value={isFocused}>
69
+ <NavigationRouteNameContext.Provider value={route.name}>
70
+ {children}
71
+ </NavigationRouteNameContext.Provider>
72
+ </IsFocusedContext.Provider>
59
73
  </NavigationContext.Provider>
60
74
  </NavigationRouteContext.Provider>
61
75
  </NamedRouteContext.Provider>
@@ -5,8 +5,8 @@ const MISSING_CONTEXT_ERROR =
5
5
  "Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
6
6
 
7
7
  export const NavigationStateContext = React.createContext<{
8
- isDefault?: true;
9
- state?: NavigationState | PartialState<NavigationState>;
8
+ isDefault?: true | undefined;
9
+ state?: NavigationState | PartialState<NavigationState> | undefined;
10
10
  getKey: () => string | undefined;
11
11
  setKey: (key: string) => void;
12
12
  getState: () => NavigationState | PartialState<NavigationState> | undefined;
@@ -14,10 +14,9 @@ export const NavigationStateContext = React.createContext<{
14
14
  state: NavigationState | PartialState<NavigationState> | undefined
15
15
  ) => void;
16
16
  getIsInitial: () => boolean;
17
- addOptionsGetter?: (
18
- key: string,
19
- getter: () => object | undefined | null
20
- ) => void;
17
+ addOptionsGetter?:
18
+ | ((key: string, getter: () => object | undefined | null) => void)
19
+ | undefined;
21
20
  }>({
22
21
  isDefault: true,
23
22
 
@@ -50,10 +50,10 @@ export function PreventRemoveProvider({ children }: Props) {
50
50
  const [preventedRoutesMap, setPreventedRoutesMap] =
51
51
  React.useState<PreventedRoutesMap>(() => new Map());
52
52
 
53
- const navigation = React.useContext(NavigationHelpersContext);
54
- const route = React.useContext(NavigationRouteContext);
53
+ const navigation = React.use(NavigationHelpersContext);
54
+ const route = React.use(NavigationRouteContext);
55
55
 
56
- const preventRemoveContextValue = React.useContext(PreventRemoveContext);
56
+ const preventRemoveContextValue = React.use(PreventRemoveContext);
57
57
  // take `setPreventRemove` from parent context - if exist it means we're in a nested context
58
58
  const setParentPrevented = preventRemoveContextValue?.setPreventRemove;
59
59
 
package/src/SceneView.tsx CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  } from '@react-navigation/routers';
7
7
  import * as React from 'react';
8
8
 
9
+ import { ConsumedParamsContext } from './ConsumedParamsContext';
9
10
  import { EnsureSingleNavigator } from './EnsureSingleNavigator';
10
11
  import { isArrayEqual } from './isArrayEqual';
11
12
  import {
@@ -70,46 +71,16 @@ export function SceneView<
70
71
  const state = getState();
71
72
 
72
73
  const routes = state.routes.map((r) => {
73
- if (r.key !== route.key) {
74
- return r;
74
+ if (r.key === route.key && r.state !== child) {
75
+ return {
76
+ ...r,
77
+ state: child,
78
+ };
75
79
  }
76
80
 
77
- const nextRoute = r.state !== child ? { ...r, state: child } : r;
78
-
79
- // Before updating the state, cleanup any nested screen and state
80
- // This will avoid the navigator trying to handle them again
81
- if (
82
- nextRoute.params &&
83
- (('state' in nextRoute.params &&
84
- typeof nextRoute.params.state === 'object' &&
85
- nextRoute.params.state !== null) ||
86
- ('screen' in nextRoute.params &&
87
- typeof nextRoute.params.screen === 'string'))
88
- ) {
89
- // @ts-expect-error: we don't have correct type for params
90
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
91
- const { state, screen, params, initial, ...rest } = nextRoute.params;
92
-
93
- if (Object.keys(rest).length) {
94
- return { ...nextRoute, params: rest };
95
- } else {
96
- const {
97
- // We destructure the params to omit them
98
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
99
- params,
100
- ...restRoute
101
- } = nextRoute;
102
-
103
- return restRoute;
104
- }
105
- }
106
-
107
- return nextRoute;
81
+ return r;
108
82
  });
109
83
 
110
- // Make sure not to update state if routes haven't changed
111
- // Otherwise this will result in params cleanup as well
112
- // We only want to cleanup params when state changes - after they are used
113
84
  if (!isArrayEqual(state.routes, routes)) {
114
85
  setState({
115
86
  ...state,
@@ -134,10 +105,39 @@ export function SceneView<
134
105
 
135
106
  const getIsInitial = React.useCallback(() => isInitialRef.current, []);
136
107
 
137
- const parentFocusedRouteState = React.useContext(
138
- NavigationFocusedRouteStateContext
108
+ const [consumedParams, setConsumedParams] = React.useState<WeakMap<
109
+ object,
110
+ true
111
+ > | null>(null);
112
+
113
+ const consumedParamsContext = React.useMemo(
114
+ () => ({
115
+ isConsumed: (params: object) => {
116
+ if (consumedParams) {
117
+ return consumedParams.has(params);
118
+ }
119
+
120
+ return false;
121
+ },
122
+ setConsumed: (params: object) => {
123
+ setConsumedParams((prev) => {
124
+ if (prev && prev.has(params)) {
125
+ return prev;
126
+ }
127
+
128
+ const map = new WeakMap<object, true>();
129
+
130
+ map.set(params, true);
131
+
132
+ return map;
133
+ });
134
+ },
135
+ }),
136
+ [consumedParams]
139
137
  );
140
138
 
139
+ const parentFocusedRouteState = React.use(NavigationFocusedRouteStateContext);
140
+
141
141
  const focusedRouteState = React.useMemo(() => {
142
142
  const state: FocusedRouteState = {
143
143
  routes: [
@@ -205,23 +205,25 @@ export function SceneView<
205
205
  : screen.component;
206
206
 
207
207
  return (
208
- <NavigationStateContext.Provider value={context}>
209
- <NavigationFocusedRouteStateContext.Provider value={focusedRouteState}>
210
- <EnsureSingleNavigator>
211
- <StaticContainer
212
- name={screen.name}
213
- render={ScreenComponent || screen.children}
214
- navigation={navigation}
215
- route={route}
216
- >
217
- {ScreenComponent !== undefined ? (
218
- <ScreenComponent navigation={navigation} route={route} />
219
- ) : screen.children !== undefined ? (
220
- screen.children({ navigation, route })
221
- ) : null}
222
- </StaticContainer>
223
- </EnsureSingleNavigator>
224
- </NavigationFocusedRouteStateContext.Provider>
225
- </NavigationStateContext.Provider>
208
+ <ConsumedParamsContext.Provider value={consumedParamsContext}>
209
+ <NavigationStateContext.Provider value={context}>
210
+ <NavigationFocusedRouteStateContext.Provider value={focusedRouteState}>
211
+ <EnsureSingleNavigator>
212
+ <StaticContainer
213
+ name={screen.name}
214
+ render={ScreenComponent || screen.children}
215
+ navigation={navigation}
216
+ route={route}
217
+ >
218
+ {ScreenComponent !== undefined ? (
219
+ <ScreenComponent navigation={navigation} route={route} />
220
+ ) : screen.children !== undefined ? (
221
+ screen.children({ navigation, route })
222
+ ) : null}
223
+ </StaticContainer>
224
+ </EnsureSingleNavigator>
225
+ </NavigationFocusedRouteStateContext.Provider>
226
+ </NavigationStateContext.Provider>
227
+ </ConsumedParamsContext.Provider>
226
228
  );
227
229
  }
@@ -21,13 +21,11 @@ import type {
21
21
  import { useRoute } from './useRoute';
22
22
  import type {
23
23
  AnyToUnknown,
24
- ExtractParamStrings,
25
- ExtractParamsType,
26
24
  FlatType,
27
25
  HasArguments,
28
- InferParse,
29
- InferPath,
26
+ InferParamsFromLinking,
30
27
  KeysOf,
28
+ StandardSchemaV1,
31
29
  UnionToIntersection,
32
30
  ValidPathPattern,
33
31
  } from './utilities';
@@ -42,27 +40,52 @@ type ParamsForScreenComponent<T> = T extends (...args: any[]) => any
42
40
  ? Params
43
41
  : undefined;
44
42
 
43
+ // If every nested route's params include `undefined`, the nested navigator
44
+ // itself can be omitted. Otherwise, require `NavigatorScreenParams`.
45
+ type ParamsForNestedNavigator<
46
+ T extends {
47
+ config: StaticConfig<NavigatorTypeBagBase>;
48
+ },
49
+ ParamList extends {} = StaticParamList<T>,
50
+ > = {
51
+ // Exclude routes with optional params so the resulting union only contains route names with required params.
52
+ // If there are no routes with required params, the resulting union will be `never`.
53
+ [RouteName in keyof ParamList]-?: undefined extends ParamList[RouteName]
54
+ ? never
55
+ : RouteName;
56
+ }[keyof ParamList] extends never
57
+ ? NavigatorScreenParams<ParamList> | undefined
58
+ : NavigatorScreenParams<ParamList>;
59
+
45
60
  type ParamsForScreen<T> =
46
61
  // Nested navigator in screen property
47
- T extends { screen: StaticNavigation<any, any, any> }
48
- ? NavigatorScreenParams<StaticParamList<T['screen']>> | undefined
62
+ T extends {
63
+ screen: { config: StaticConfig<NavigatorTypeBagBase> };
64
+ }
65
+ ? ParamsForNestedNavigator<T['screen']>
49
66
  : // Direct nested navigator
50
- T extends StaticNavigation<any, any, any>
51
- ? NavigatorScreenParams<StaticParamList<T>> | undefined
67
+ T extends { config: StaticConfig<NavigatorTypeBagBase> }
68
+ ? ParamsForNestedNavigator<T>
52
69
  : T extends {
53
70
  screen: React.ComponentType<any>;
54
71
  }
55
72
  ? ParamsForScreenComponent<T['screen']>
56
73
  : ParamsForScreenComponent<T>;
57
74
 
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;
75
+ // Only infer params from linking if it's a pattern (i.e., contains ':')
76
+ // or if parse is present for query params.
77
+ // This avoids inferring non-literals like 'string' without parse.
78
+ type ShouldInferFromLinking<Linking> = Linking extends
79
+ | ValidPathPattern
80
+ | { path: ValidPathPattern }
81
+ | { parse: Record<string, unknown> }
82
+ ? true
83
+ : false;
84
+
85
+ type MergeLinkingAndScreenParams<LinkingParams, ScreenParams> =
86
+ undefined extends ScreenParams
87
+ ? FlatType<LinkingParams>
88
+ : FlatType<LinkingParams & ScreenParams>;
66
89
 
67
90
  /**
68
91
  * Inferred params type based on both linking config and screen.
@@ -72,16 +95,13 @@ type ParamsForLinking<Linking> = Linking extends { path: string }
72
95
  */
73
96
  type ParamsForConfig<Linking, Screen> = undefined extends Linking
74
97
  ? 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>>
98
+ : ShouldInferFromLinking<Linking> extends true
99
+ ? Screen extends { config: StaticConfig<NavigatorTypeBagBase> }
100
+ ? FlatType<InferParamsFromLinking<Linking>> & ParamsForScreen<Screen>
101
+ : MergeLinkingAndScreenParams<
102
+ InferParamsFromLinking<Linking>,
103
+ ParamsForScreen<Screen>
104
+ >
85
105
  : ParamsForScreen<Screen>;
86
106
 
87
107
  type ParamListForScreens<Screens> = {
@@ -147,7 +167,7 @@ type StaticScreenConfigLinkingAlias = {
147
167
  */
148
168
  exact?: boolean;
149
169
  /**
150
- * An object mapping the param name to a function which parses the param value.
170
+ * An object mapping the param name to a parser function or a Standard Schema.
151
171
  *
152
172
  * @example
153
173
  * ```js
@@ -157,7 +177,11 @@ type StaticScreenConfigLinkingAlias = {
157
177
  * }
158
178
  * ```
159
179
  */
160
- parse?: Record<string, (value: string) => unknown>;
180
+ parse?: Record<
181
+ string,
182
+ | ((value: string) => unknown)
183
+ | StandardSchemaV1<string | string[] | null | undefined, unknown>
184
+ >;
161
185
  /**
162
186
  * An object mapping the param name to a function which converts the param value to a string.
163
187
  * By default, all params are converted to strings using `String(value)`.
@@ -184,7 +208,7 @@ export type StaticScreenConfigLinking =
184
208
 
185
209
  export type StaticScreenConfigScreen =
186
210
  | React.ComponentType<any>
187
- | StaticNavigation<any, any, any>;
211
+ | StaticNavigation<any>;
188
212
 
189
213
  export type StaticScreenConfig<
190
214
  Linking extends StaticScreenConfigLinking,
@@ -328,15 +352,18 @@ type StaticConfigScreens<
328
352
  > = {
329
353
  [RouteName in keyof ParamList]:
330
354
  | React.ComponentType<any>
331
- | StaticNavigation<any, any, any>
355
+ | StaticNavigation<any>
332
356
  | StaticScreenConfig<
333
357
  | {
334
358
  path: string;
335
- parse?: Record<string, (value: string) => any>;
359
+ parse?: Record<
360
+ string,
361
+ ((value: string) => any) | StandardSchemaV1<any, any>
362
+ >;
336
363
  }
337
364
  | string
338
365
  | undefined,
339
- StaticNavigation<any, any, any> | React.ComponentType<any>,
366
+ StaticNavigation<any> | React.ComponentType<any>,
340
367
  State,
341
368
  ScreenOptions,
342
369
  EventMap,
@@ -503,11 +530,9 @@ export type StaticParamList<
503
530
  ParamListForGroups<T['config']['groups']>
504
531
  >;
505
532
 
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>;
533
+ export type StaticNavigation<NavigatorTypeBag extends NavigatorTypeBagBase> = {
534
+ config: StaticConfig<NavigatorTypeBag>;
535
+ getComponent: () => React.ComponentType<{}>;
511
536
  };
512
537
 
513
538
  const MemoizedScreen = React.memo(
@@ -542,16 +567,13 @@ const getItemsFromScreens = (
542
567
  component = screen;
543
568
  } else if ('config' in screen) {
544
569
  isNavigator = true;
545
- component = createComponentForStaticNavigation(
546
- screen,
547
- `${name}Navigator`
548
- );
570
+ component = screen.getComponent();
549
571
  }
550
572
  } else if (isValidElementType(item)) {
551
573
  component = item;
552
574
  } else if ('config' in item) {
553
575
  isNavigator = true;
554
- component = createComponentForStaticNavigation(item, `${name}Navigator`);
576
+ component = item.getComponent();
555
577
  }
556
578
 
557
579
  if (component == null) {
@@ -589,10 +611,17 @@ const getItemsFromScreens = (
589
611
  * @param displayName Name of the component to be displayed in React DevTools.
590
612
  * @returns A component which renders the navigator.
591
613
  */
592
- export function createComponentForStaticNavigation(
593
- tree: StaticNavigation<any, any, any>,
614
+ export function createComponentForStaticConfig<
615
+ T extends {
616
+ Navigator: React.ComponentType<any>;
617
+ Group: React.ComponentType<any>;
618
+ Screen: React.ComponentType<any>;
619
+ config: StaticConfig<NavigatorTypeBagBase>;
620
+ },
621
+ >(
622
+ tree: T,
594
623
  displayName: string
595
- ): React.ComponentType<{}> {
624
+ ): React.ComponentType<Omit<React.ComponentProps<T['Navigator']>, 'children'>> {
596
625
  const { Navigator, Group, Screen, config } = tree;
597
626
  const { screens, groups, ...rest } = config;
598
627
 
@@ -637,10 +666,51 @@ export function createComponentForStaticNavigation(
637
666
  }
638
667
  }
639
668
 
640
- const NavigatorComponent = () => {
669
+ if (items.length === 0) {
670
+ throw new Error(
671
+ "Couldn't find any screens in the 'screens' or 'groups' property. Make sure to define at least one screen in the configuration."
672
+ );
673
+ }
674
+
675
+ const NavigatorComponent = ({ children: _, ...props }: typeof rest) => {
641
676
  const children = items.map((item) => item());
642
677
 
643
- return <Navigator {...rest}>{children}</Navigator>;
678
+ const screenOptions =
679
+ typeof props.screenOptions === 'function' ||
680
+ typeof rest.screenOptions === 'function'
681
+ ? (options: unknown) => ({
682
+ ...(typeof rest.screenOptions === 'function'
683
+ ? rest.screenOptions(options)
684
+ : rest.screenOptions),
685
+ ...(typeof props.screenOptions === 'function'
686
+ ? props.screenOptions(options)
687
+ : props.screenOptions),
688
+ })
689
+ : { ...rest.screenOptions, ...props.screenOptions };
690
+
691
+ const screenListeners =
692
+ typeof props.screenListeners === 'function' ||
693
+ typeof rest.screenListeners === 'function'
694
+ ? (options: unknown) => ({
695
+ ...(typeof rest.screenListeners === 'function'
696
+ ? rest.screenListeners(options)
697
+ : rest.screenListeners),
698
+ ...(typeof props.screenListeners === 'function'
699
+ ? props.screenListeners(options)
700
+ : props.screenListeners),
701
+ })
702
+ : { ...rest.screenListeners, ...props.screenListeners };
703
+
704
+ return (
705
+ <Navigator
706
+ {...rest}
707
+ {...props}
708
+ screenOptions={screenOptions}
709
+ screenListeners={screenListeners}
710
+ >
711
+ {children}
712
+ </Navigator>
713
+ );
644
714
  };
645
715
 
646
716
  NavigatorComponent.displayName = displayName;
@@ -696,19 +766,19 @@ type TreeForPathConfig = {
696
766
  * };
697
767
  * ```
698
768
  */
699
- export function createPathConfigForStaticNavigation(
769
+ export function createPathConfigForStaticNavigation<ParamList extends {}>(
700
770
  tree: TreeForPathConfig,
701
771
  options?: {
702
- initialRouteName?: string;
772
+ initialRouteName?: string | undefined;
703
773
  },
704
774
  auto?: boolean
705
- ): PathConfigMap<ParamListBase> | undefined {
775
+ ): PathConfigMap<ParamList> | undefined {
706
776
  let initialScreenHasPath: boolean = false;
707
777
  let initialScreenConfig: PathConfig<{}> | undefined;
708
778
 
709
779
  const createPathConfigForTree = (
710
780
  t: TreeForPathConfig,
711
- o: { initialRouteName?: string } | undefined,
781
+ o: { initialRouteName?: string | undefined } | undefined,
712
782
  // If a screen is a leaf node, but inside a screen with path,
713
783
  // It should not be used for initial detection
714
784
  skipInitialDetection: boolean
@@ -1,7 +1,8 @@
1
- import type * as React from 'react';
1
+ import * as React from 'react';
2
2
 
3
3
  import { Group } from './Group';
4
4
  import { Screen } from './Screen';
5
+ import { createComponentForStaticConfig } from './StaticNavigation';
5
6
 
6
7
  /**
7
8
  * Higher order component to create a `Navigator` and `Screen` pair.
@@ -11,13 +12,43 @@ import { Screen } from './Screen';
11
12
  * @returns Factory method to create a `Navigator` and `Screen` pair.
12
13
  */
13
14
  export function createNavigatorFactory(Navigator: React.ComponentType<any>) {
15
+ const displayName = Navigator.displayName ?? Navigator.name ?? 'Navigator';
16
+
14
17
  function createNavigator(config?: any): any {
15
18
  if (config != null) {
19
+ const NavigatorComponent = createComponentForStaticConfig(
20
+ {
21
+ Navigator,
22
+ Screen,
23
+ Group,
24
+ config,
25
+ },
26
+ displayName
27
+ );
28
+
16
29
  return {
17
- Navigator,
18
- Screen,
19
- Group,
20
30
  config,
31
+ with(
32
+ DecoratorComponent: React.ComponentType<{
33
+ Navigator: React.ComponentType<any>;
34
+ }>
35
+ ) {
36
+ const WithComponent = () => {
37
+ return React.createElement(DecoratorComponent, {
38
+ Navigator: NavigatorComponent,
39
+ });
40
+ };
41
+
42
+ WithComponent.displayName = `${displayName}With`;
43
+
44
+ return {
45
+ config,
46
+ getComponent: () => WithComponent,
47
+ };
48
+ },
49
+ getComponent() {
50
+ return NavigatorComponent;
51
+ },
21
52
  };
22
53
  }
23
54