@react-navigation/core 8.0.0-alpha.23 → 8.0.0-alpha.25

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 (153) hide show
  1. package/lib/module/BaseNavigationContainer.js +5 -2
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/DataLoading.js +102 -0
  4. package/lib/module/DataLoading.js.map +1 -0
  5. package/lib/module/NavigationBuilderContext.js.map +1 -1
  6. package/lib/module/NavigationIndependentTree.js +7 -6
  7. package/lib/module/NavigationIndependentTree.js.map +1 -1
  8. package/lib/module/NavigationProvider.js +12 -12
  9. package/lib/module/NavigationProvider.js.map +1 -1
  10. package/lib/module/PreventRemoveContext.js.map +1 -1
  11. package/lib/module/PreventRemoveProvider.js +34 -24
  12. package/lib/module/PreventRemoveProvider.js.map +1 -1
  13. package/lib/module/StaticNavigation.js +15 -7
  14. package/lib/module/StaticNavigation.js.map +1 -1
  15. package/lib/module/StaticTreeContext.js +5 -0
  16. package/lib/module/StaticTreeContext.js.map +1 -0
  17. package/lib/module/checkSerializable.js +7 -9
  18. package/lib/module/checkSerializable.js.map +1 -1
  19. package/lib/module/createNavigationContainerRef.js +6 -8
  20. package/lib/module/createNavigationContainerRef.js.map +1 -1
  21. package/lib/module/createNavigatorFactory.js.map +1 -1
  22. package/lib/module/getActionFromState.js +11 -11
  23. package/lib/module/getActionFromState.js.map +1 -1
  24. package/lib/module/getPathFromState.js +125 -96
  25. package/lib/module/getPathFromState.js.map +1 -1
  26. package/lib/module/getPatternParts.js +2 -2
  27. package/lib/module/getPatternParts.js.map +1 -1
  28. package/lib/module/getStateFromPath.js +136 -78
  29. package/lib/module/getStateFromPath.js.map +1 -1
  30. package/lib/module/index.js +1 -0
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/types.js +13 -0
  33. package/lib/module/types.js.map +1 -1
  34. package/lib/module/useEventEmitter.js +5 -2
  35. package/lib/module/useEventEmitter.js.map +1 -1
  36. package/lib/module/useKeyedChildListeners.js +4 -2
  37. package/lib/module/useKeyedChildListeners.js.map +1 -1
  38. package/lib/module/useNavigation.js +2 -15
  39. package/lib/module/useNavigation.js.map +1 -1
  40. package/lib/module/useNavigationBuilder.js +5 -21
  41. package/lib/module/useNavigationBuilder.js.map +1 -1
  42. package/lib/module/useNavigationCache.js +19 -14
  43. package/lib/module/useNavigationCache.js.map +1 -1
  44. package/lib/module/useNavigationHelpers.js +27 -9
  45. package/lib/module/useNavigationHelpers.js.map +1 -1
  46. package/lib/module/useNavigationState.js +69 -36
  47. package/lib/module/useNavigationState.js.map +1 -1
  48. package/lib/module/useOnAction.js +25 -3
  49. package/lib/module/useOnAction.js.map +1 -1
  50. package/lib/module/useOnPreventRemove.js +35 -24
  51. package/lib/module/useOnPreventRemove.js.map +1 -1
  52. package/lib/module/useOptionsGetters.js +3 -1
  53. package/lib/module/useOptionsGetters.js.map +1 -1
  54. package/lib/module/usePreventRemove.js +9 -2
  55. package/lib/module/usePreventRemove.js.map +1 -1
  56. package/lib/module/useRoute.js.map +1 -1
  57. package/lib/module/useRouteCache.js +14 -7
  58. package/lib/module/useRouteCache.js.map +1 -1
  59. package/lib/module/useScheduleUpdate.js +8 -8
  60. package/lib/module/useScheduleUpdate.js.map +1 -1
  61. package/lib/module/useSyncState.js +4 -1
  62. package/lib/module/useSyncState.js.map +1 -1
  63. package/lib/module/validatePathConfig.js +50 -37
  64. package/lib/module/validatePathConfig.js.map +1 -1
  65. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  66. package/lib/typescript/src/DataLoading.d.ts +20 -0
  67. package/lib/typescript/src/DataLoading.d.ts.map +1 -0
  68. package/lib/typescript/src/NavigationBuilderContext.d.ts +2 -2
  69. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  70. package/lib/typescript/src/NavigationProvider.d.ts +0 -1
  71. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  72. package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
  73. package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
  74. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  75. package/lib/typescript/src/StaticNavigation.d.ts +68 -42
  76. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  77. package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
  78. package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
  79. package/lib/typescript/src/checkSerializable.d.ts +4 -2
  80. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  81. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  82. package/lib/typescript/src/createNavigatorFactory.d.ts +11 -5
  83. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  84. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  85. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  86. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  87. package/lib/typescript/src/index.d.ts +2 -1
  88. package/lib/typescript/src/index.d.ts.map +1 -1
  89. package/lib/typescript/src/types.d.ts +84 -29
  90. package/lib/typescript/src/types.d.ts.map +1 -1
  91. package/lib/typescript/src/useEventEmitter.d.ts +1 -1
  92. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  93. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  94. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  95. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -1
  96. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  97. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  98. package/lib/typescript/src/useNavigationHelpers.d.ts +3 -4
  99. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  100. package/lib/typescript/src/useNavigationState.d.ts +11 -1
  101. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  102. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  103. package/lib/typescript/src/useOnPreventRemove.d.ts +2 -6
  104. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  105. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  106. package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
  107. package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -0
  108. package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
  109. package/lib/typescript/src/useRoute.d.ts +1 -1
  110. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  111. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  112. package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
  113. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  114. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  115. package/lib/typescript/src/utilities.d.ts +21 -38
  116. package/lib/typescript/src/utilities.d.ts.map +1 -1
  117. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  118. package/package.json +6 -8
  119. package/src/BaseNavigationContainer.tsx +9 -2
  120. package/src/DataLoading.tsx +139 -0
  121. package/src/NavigationBuilderContext.tsx +5 -1
  122. package/src/NavigationIndependentTree.tsx +11 -11
  123. package/src/NavigationProvider.tsx +13 -16
  124. package/src/PreventRemoveContext.tsx +1 -0
  125. package/src/PreventRemoveProvider.tsx +48 -43
  126. package/src/StaticNavigation.tsx +164 -99
  127. package/src/StaticTreeContext.tsx +7 -0
  128. package/src/checkSerializable.tsx +20 -14
  129. package/src/createNavigationContainerRef.tsx +12 -9
  130. package/src/createNavigatorFactory.tsx +27 -18
  131. package/src/getActionFromState.tsx +18 -16
  132. package/src/getPathFromState.tsx +167 -132
  133. package/src/getPatternParts.tsx +2 -2
  134. package/src/getStateFromPath.tsx +260 -193
  135. package/src/index.tsx +3 -0
  136. package/src/types.tsx +298 -62
  137. package/src/useEventEmitter.tsx +7 -2
  138. package/src/useKeyedChildListeners.tsx +4 -2
  139. package/src/useNavigation.tsx +2 -21
  140. package/src/useNavigationBuilder.tsx +7 -24
  141. package/src/useNavigationCache.tsx +26 -19
  142. package/src/useNavigationHelpers.tsx +26 -9
  143. package/src/useNavigationState.tsx +90 -60
  144. package/src/useOnAction.tsx +55 -3
  145. package/src/useOnPreventRemove.tsx +68 -36
  146. package/src/useOptionsGetters.tsx +4 -1
  147. package/src/usePreventRemove.tsx +11 -2
  148. package/src/useRoute.tsx +3 -5
  149. package/src/useRouteCache.tsx +19 -11
  150. package/src/useScheduleUpdate.tsx +8 -7
  151. package/src/useSyncState.tsx +5 -1
  152. package/src/utilities.tsx +36 -41
  153. package/src/validatePathConfig.tsx +86 -58
@@ -31,8 +31,11 @@ export function useOptionsGetters({ key, options, navigation }: Options) {
31
31
  }
32
32
  }, [navigation, onOptionsChange]);
33
33
 
34
- React.useEffect(() => {
34
+ React.useInsertionEffect(() => {
35
35
  optionsRef.current = options;
36
+ }, [options]);
37
+
38
+ React.useEffect(() => {
36
39
  optionsChangeListener();
37
40
 
38
41
  return navigation?.addListener('focus', optionsChangeListener);
@@ -23,15 +23,24 @@ export function usePreventRemove(
23
23
  const navigation = useNavigation();
24
24
  const { key: routeKey } = useRoute();
25
25
 
26
- const { setPreventRemove } = usePreventRemoveContext();
26
+ const { setPreventRemove, notifyPreventRemove } = usePreventRemoveContext();
27
27
 
28
- React.useEffect(() => {
28
+ React.useInsertionEffect(() => {
29
29
  setPreventRemove(id, routeKey, preventRemove);
30
+
30
31
  return () => {
31
32
  setPreventRemove(id, routeKey, false);
32
33
  };
33
34
  }, [setPreventRemove, id, routeKey, preventRemove]);
34
35
 
36
+ React.useEffect(() => {
37
+ notifyPreventRemove();
38
+
39
+ return () => {
40
+ notifyPreventRemove();
41
+ };
42
+ }, [id, routeKey, preventRemove, notifyPreventRemove]);
43
+
35
44
  const beforeRemoveListener = useLatestCallback<
36
45
  EventListenerCallback<EventMapCore<any>, 'beforeRemove'>
37
46
  >((e) => {
package/src/useRoute.tsx CHANGED
@@ -22,11 +22,9 @@ type AllRouteNames<ParamList extends {}> =
22
22
  | KeyOf<ParamList>
23
23
  | AllNestedRouteNames<NestedParamLists<ParamList>>;
24
24
 
25
- type AllNestedRouteNames<ParamLists> =
26
- // Distributes over the union of nested param lists
27
- ParamLists extends infer ParamList extends {}
28
- ? AllRouteNames<ParamList>
29
- : never;
25
+ type AllNestedRouteNames<ParamLists> = ParamLists extends {}
26
+ ? AllRouteNames<ParamLists>
27
+ : never;
30
28
 
31
29
  /**
32
30
  * Hook to access the route prop of the parent screen anywhere.
@@ -4,8 +4,6 @@ import * as React from 'react';
4
4
  import { isRecordEqual } from './isRecordEqual';
5
5
  import type { RouteProp } from './types';
6
6
 
7
- type RouteCache = Map<string, RouteProp<ParamListBase>>;
8
-
9
7
  /**
10
8
  * Utilities such as `getFocusedRouteNameFromRoute` need to access state.
11
9
  * So we need a way to suppress the warning for those use cases.
@@ -21,9 +19,12 @@ export function useRouteCache<State extends NavigationState>(
21
19
  routes: State['routes']
22
20
  ) {
23
21
  // Cache object which holds route objects for each screen
24
- const cache = React.useMemo(() => ({ current: new Map() as RouteCache }), []);
22
+ const cache = React.useMemo(
23
+ () => ({ current: new Map<string, RouteProp<ParamListBase>>() }),
24
+ []
25
+ );
25
26
 
26
- cache.current = routes.reduce((acc, route) => {
27
+ const next = routes.reduce((acc, route) => {
27
28
  const previous = cache.current.get(route.key);
28
29
  const { state, ...routeWithoutState } = route;
29
30
 
@@ -53,16 +54,23 @@ export function useRouteCache<State extends NavigationState>(
53
54
  }
54
55
  }
55
56
 
56
- Object.defineProperty(proxy, CHILD_STATE, {
57
- enumerable: false,
58
- configurable: true,
59
- value: state,
60
- });
57
+ // @ts-expect-error: this isn't in type definitions coz we want this private
58
+ if (proxy[CHILD_STATE] !== state) {
59
+ Object.defineProperty(proxy, CHILD_STATE, {
60
+ enumerable: false,
61
+ configurable: true,
62
+ value: state,
63
+ });
64
+ }
61
65
 
62
66
  acc.set(route.key, proxy);
63
67
 
64
68
  return acc;
65
- }, new Map() as RouteCache);
69
+ }, new Map<string, RouteProp<ParamListBase>>());
70
+
71
+ React.useInsertionEffect(() => {
72
+ cache.current = next;
73
+ });
66
74
 
67
- return Array.from(cache.current.values());
75
+ return Array.from(next.values());
68
76
  }
@@ -4,17 +4,18 @@ import { NavigationBuilderContext } from './NavigationBuilderContext';
4
4
 
5
5
  /**
6
6
  * When screen config changes, we want to update the navigator in the same update phase.
7
- * However, navigation state is in the root component and React won't let us update it from a child.
8
- * This is a workaround for that, the scheduled update is stored in the ref without actually calling setState.
9
- * It lets all subsequent updates access the latest state so it stays correct.
10
- * Then we call setState during after the component updates.
7
+ * However, navigation state is in the root component and React won't let us update it from a child during render.
8
+ * The render could also be interrupted in concurrent mode, so we need to update after commit.
9
+ * So we queue the update in an insertion effect and flush it in a layout effect.
10
+ * Insertion effects run before layout effects, so every navigator in the tree has queued its update before we flush.
11
+ * This lets nested updates be applied from the root down without clobbering each other.
11
12
  */
12
13
  export function useScheduleUpdate(callback: () => void) {
13
14
  const { scheduleUpdate, flushUpdates } = React.use(NavigationBuilderContext);
14
15
 
15
- // FIXME: This is potentially unsafe
16
- // However, since we are using sync store, it might be fine
17
- scheduleUpdate(callback);
16
+ React.useInsertionEffect(() => {
17
+ scheduleUpdate(callback);
18
+ });
18
19
 
19
20
  React.useLayoutEffect(flushUpdates);
20
21
  }
@@ -100,7 +100,11 @@ export function useSyncState<T>(getInitialState: () => T) {
100
100
 
101
101
  if (pendingUpdates.length !== 0) {
102
102
  store.batchUpdates(() => {
103
- // Flush all the pending updates
103
+ // Updates are queued child-first as insertion effects run bottom-up.
104
+ // The root navigator replaces the whole state while nested navigators merge into it.
105
+ // So we apply them root-first to avoid the root clobbering nested updates.
106
+ pendingUpdates.reverse();
107
+
104
108
  for (const update of pendingUpdates) {
105
109
  update();
106
110
  }
package/src/utilities.tsx CHANGED
@@ -15,6 +15,17 @@ export type KeysOf<T> = T extends {} ? keyof T : never;
15
15
  */
16
16
  export type KeyOf<T extends {}> = Extract<keyof T, string>;
17
17
 
18
+ /**
19
+ * TS only checks object literals for excess properties when they are not generic.
20
+ * When an object is captured as a generic type, unknown keys are not caught.
21
+ * This builds a type from the provided object where any key not in the allowed type has the type `never`.
22
+ * Checking the provided object against this type then produces an error for each unknown key.
23
+ */
24
+ export type NoExcessObject<Provided, Allowed> = Record<
25
+ Exclude<keyof Provided, keyof Allowed>,
26
+ never
27
+ >;
28
+
18
29
  /**
19
30
  * We get a union type when using keyof, but we want an intersection instead.
20
31
  * https://stackoverflow.com/a/50375286/1665026
@@ -25,8 +36,6 @@ export type UnionToIntersection<U> = (
25
36
  ? I
26
37
  : never;
27
38
 
28
- export type UnknownToUndefined<T> = unknown extends T ? undefined : T;
29
-
30
39
  /**
31
40
  * Exclude undefined from a type.
32
41
  * Similar to NonNullable but only excludes undefined, not null.
@@ -39,6 +48,23 @@ export type RequiredKeys<T extends object> = {
39
48
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
40
49
  }[keyof T];
41
50
 
51
+ type OptionalKeys<T extends object> = Exclude<keyof T, RequiredKeys<T>>;
52
+
53
+ /**
54
+ * Remove `| undefined` from the object type,
55
+ * and adjust the optionality of the keys based on the original type.
56
+ */
57
+ export type NotUndefinedObject<
58
+ T extends object | undefined,
59
+ O extends object = NotUndefined<T>,
60
+ > = undefined extends T
61
+ ? {
62
+ [K in RequiredKeys<O>]: O[K];
63
+ } & {
64
+ [K in OptionalKeys<O>]?: O[K] | undefined;
65
+ }
66
+ : T;
67
+
42
68
  export type UndefinedIfAllOptional<T> = T extends object
43
69
  ? RequiredKeys<T> extends never
44
70
  ? T | undefined
@@ -173,46 +199,15 @@ type QueryParamRequiredKeys<Params, Parse> = Exclude<
173
199
  QueryParamOptionalKeys<Params, Parse>
174
200
  >;
175
201
 
176
- /**
177
- * Infer the path string from a linking config.
178
- */
179
- export type InferPath<T> = T extends { path: infer P extends string }
180
- ? P
181
- : never;
182
-
183
- /**
184
- * Infer the parse functions from a linking config.
185
- */
186
- export type InferParse<T> = T extends { parse: infer P } ? P : {};
187
-
188
202
  export type InferParamsFromLinking<T> = T extends {
189
203
  path: infer P extends string;
204
+ parse: infer Parse;
190
205
  }
191
- ? UndefinedIfAllOptional<
192
- ExtractParamsType<ExtractParamStrings<P>, InferParse<T>>
193
- >
194
- : T extends string
195
- ? UndefinedIfAllOptional<
196
- ExtractParamsType<ExtractParamStrings<T>, undefined>
197
- >
198
- : undefined;
199
-
200
- /**
201
- * Infer the params type from a screen component or nested navigator.
202
- */
203
- export type InferScreenParams<T> =
204
- T extends React.ComponentType<{ route: { params: infer P } }>
205
- ? P
206
- : T extends { config: { screens: infer Screens } }
207
- ? import('./types').NavigatorScreenParams<{
208
- [K in keyof Screens]: Screens[K] extends React.ComponentType<{
209
- route: { params: infer P };
210
- }>
211
- ? P
212
- : Screens[K] extends { screen: infer S }
213
- ? S extends React.ComponentType<{ route: { params: infer P } }>
214
- ? P
215
- : undefined
216
- : undefined;
217
- }>
206
+ ? UndefinedIfAllOptional<ExtractParamsType<ExtractParamStrings<P>, Parse>>
207
+ : T extends {
208
+ path: infer P extends string;
209
+ }
210
+ ? UndefinedIfAllOptional<ExtractParamStrings<P>>
211
+ : T extends string
212
+ ? UndefinedIfAllOptional<ExtractParamStrings<T>>
218
213
  : undefined;
@@ -1,24 +1,39 @@
1
- const formatToList = (items: Record<string, string>) =>
2
- Object.entries(items)
3
- .map(([key, value]) => `- ${key} (${value})`)
4
- .join('\n');
1
+ const ROOT_VALIDATION_LIST = [
2
+ '- path (string)',
3
+ '- initialRouteName (string)',
4
+ '- screens (object)',
5
+ ].join('\n');
5
6
 
6
- export function validatePathConfig(config: unknown, root = true) {
7
- const validation = {
8
- path: 'string',
9
- initialRouteName: 'string',
10
- screens: 'object',
11
- ...(root
12
- ? null
13
- : {
14
- alias: 'array',
15
- exact: 'boolean',
16
- stringify: 'object',
17
- parse: 'object',
18
- shared: 'boolean',
19
- }),
20
- };
7
+ const NESTED_VALIDATION_LIST = [
8
+ ROOT_VALIDATION_LIST,
9
+ '- alias (array)',
10
+ '- exact (boolean)',
11
+ '- stringify (object)',
12
+ '- parse (object)',
13
+ '- shared (boolean)',
14
+ ].join('\n');
15
+
16
+ const getValidationType = (key: string, root: boolean) => {
17
+ switch (key) {
18
+ case 'path':
19
+ case 'initialRouteName':
20
+ return 'string';
21
+ case 'screens':
22
+ return 'object';
23
+ case 'alias':
24
+ return root ? undefined : 'array';
25
+ case 'exact':
26
+ case 'shared':
27
+ return root ? undefined : 'boolean';
28
+ case 'stringify':
29
+ case 'parse':
30
+ return root ? undefined : 'object';
31
+ default:
32
+ return undefined;
33
+ }
34
+ };
21
35
 
36
+ export function validatePathConfig(config: unknown, root = true) {
22
37
  if (typeof config !== 'object' || config === null) {
23
38
  throw new Error(
24
39
  `Expected the configuration to be an object, but got ${JSON.stringify(
@@ -27,58 +42,71 @@ export function validatePathConfig(config: unknown, root = true) {
27
42
  );
28
43
  }
29
44
 
30
- const validationErrors = Object.fromEntries(
31
- Object.keys(config)
32
- .map((key) => {
33
- if (key in validation) {
34
- const type = validation[key as keyof typeof validation];
35
- // @ts-expect-error: we know the key exists
36
- const value = config[key];
37
-
38
- if (value !== undefined) {
39
- if (type === 'array') {
40
- if (!Array.isArray(value)) {
41
- return [key, `expected 'Array', got '${typeof value}'`];
42
- }
43
- } else if (typeof value !== type) {
44
- return [key, `expected '${type}', got '${typeof value}'`];
45
- }
46
- }
47
- } else {
48
- return [key, 'extraneous'];
49
- }
50
-
51
- return null;
52
- })
53
- .filter(Boolean) as [string, string][]
54
- );
55
-
56
- if (Object.keys(validationErrors).length) {
45
+ const pathConfig = config as Record<string, unknown>;
46
+
47
+ let validationErrors: string[] | undefined;
48
+
49
+ for (const key in pathConfig) {
50
+ const type = getValidationType(key, root);
51
+
52
+ if (type === undefined) {
53
+ (validationErrors ??= []).push(`- ${key} (extraneous)`);
54
+ continue;
55
+ }
56
+
57
+ const value = pathConfig[key];
58
+
59
+ if (value === undefined) {
60
+ continue;
61
+ }
62
+
63
+ if (type === 'array') {
64
+ if (!Array.isArray(value)) {
65
+ (validationErrors ??= []).push(
66
+ `- ${key} (expected 'Array', got '${typeof value}')`
67
+ );
68
+ }
69
+ } else if (typeof value !== type) {
70
+ (validationErrors ??= []).push(
71
+ `- ${key} (expected '${type}', got '${typeof value}')`
72
+ );
73
+ }
74
+ }
75
+
76
+ if (validationErrors) {
57
77
  throw new Error(
58
- `Found invalid properties in the configuration:\n${formatToList(
59
- validationErrors
60
- )}\n\nYou can only specify the following properties:\n${formatToList(
61
- validation
62
- )}\n\nIf you want to specify configuration for screens, you need to specify them under a 'screens' property.\n\nSee https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration.`
78
+ `Found invalid properties in the configuration:\n${validationErrors.join(
79
+ '\n'
80
+ )}\n\nYou can only specify the following properties:\n${
81
+ root ? ROOT_VALIDATION_LIST : NESTED_VALIDATION_LIST
82
+ }\n\nIf you want to specify configuration for screens, you need to specify them under a 'screens' property.\n\nSee https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration.`
63
83
  );
64
84
  }
65
85
 
86
+ const path = pathConfig.path;
87
+
66
88
  if (
67
89
  root &&
68
- 'path' in config &&
69
- typeof config.path === 'string' &&
70
- config.path.includes(':')
90
+ 'path' in pathConfig &&
91
+ typeof path === 'string' &&
92
+ path.includes(':')
71
93
  ) {
72
94
  throw new Error(
73
- `Found invalid path '${config.path}'. The 'path' in the top-level configuration cannot contain patterns for params.`
95
+ `Found invalid path '${path}'. The 'path' in the top-level configuration cannot contain patterns for params.`
74
96
  );
75
97
  }
76
98
 
77
- if ('screens' in config && config.screens) {
78
- Object.entries(config.screens).forEach(([_, value]) => {
99
+ const screens = pathConfig.screens;
100
+
101
+ if (screens && typeof screens === 'object') {
102
+ const screenConfig = screens as Record<string, unknown>;
103
+
104
+ for (const name in screenConfig) {
105
+ const value = screenConfig[name];
106
+
79
107
  if (typeof value !== 'string') {
80
108
  validatePathConfig(value, false);
81
109
  }
82
- });
110
+ }
83
111
  }
84
112
  }