@react-navigation/core 7.17.2 → 7.17.3

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 (40) hide show
  1. package/lib/module/StaticNavigation.js +0 -19
  2. package/lib/module/StaticNavigation.js.map +1 -1
  3. package/lib/module/checkSerializable.js +11 -4
  4. package/lib/module/checkSerializable.js.map +1 -1
  5. package/lib/module/getActionFromState.js +15 -2
  6. package/lib/module/getActionFromState.js.map +1 -1
  7. package/lib/module/getStateFromPath.js +11 -16
  8. package/lib/module/getStateFromPath.js.map +1 -1
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/types.js +55 -0
  11. package/lib/module/types.js.map +1 -1
  12. package/lib/module/useEventEmitter.js +28 -29
  13. package/lib/module/useEventEmitter.js.map +1 -1
  14. package/lib/module/useNavigationBuilder.js +38 -40
  15. package/lib/module/useNavigationBuilder.js.map +1 -1
  16. package/lib/module/useRouteCache.js +2 -1
  17. package/lib/module/useRouteCache.js.map +1 -1
  18. package/lib/typescript/src/StaticNavigation.d.ts +2 -63
  19. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  20. package/lib/typescript/src/checkSerializable.d.ts +5 -3
  21. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  22. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  23. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  24. package/lib/typescript/src/index.d.ts +1 -1
  25. package/lib/typescript/src/index.d.ts.map +1 -1
  26. package/lib/typescript/src/types.d.ts +102 -2
  27. package/lib/typescript/src/types.d.ts.map +1 -1
  28. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  29. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  30. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  31. package/package.json +3 -3
  32. package/src/StaticNavigation.tsx +1 -91
  33. package/src/checkSerializable.tsx +22 -12
  34. package/src/getActionFromState.tsx +20 -4
  35. package/src/getStateFromPath.tsx +23 -20
  36. package/src/index.tsx +0 -1
  37. package/src/types.tsx +138 -4
  38. package/src/useEventEmitter.tsx +33 -38
  39. package/src/useNavigationBuilder.tsx +59 -65
  40. package/src/useRouteCache.tsx +2 -1
@@ -6,7 +6,6 @@ import type {
6
6
  DefaultNavigatorOptions,
7
7
  EventMapBase,
8
8
  NavigationListBase,
9
- NavigatorScreenParams,
10
9
  NavigatorTypeBagBase,
11
10
  PathConfig,
12
11
  RouteConfigComponent,
@@ -15,77 +14,7 @@ import type {
15
14
  } from './types';
16
15
  import { useRoute } from './useRoute';
17
16
 
18
- /**
19
- * Flatten a type to remove all type alias names, unions etc.
20
- * This will show a plain object when hovering over the type.
21
- */
22
- type FlatType<T> = { [K in keyof T]: T[K] } & {};
23
-
24
- /**
25
- * keyof T doesn't work for union types. We can use distributive conditional types instead.
26
- * https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
27
- */
28
- type KeysOf<T> = T extends {} ? keyof T : never;
29
-
30
- /**
31
- * We get a union type when using keyof, but we want an intersection instead.
32
- * https://stackoverflow.com/a/50375286/1665026
33
- */
34
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
35
- k: infer I
36
- ) => void
37
- ? I
38
- : never;
39
-
40
- type UnknownToUndefined<T> = unknown extends T ? undefined : T;
41
-
42
- type ParamsForScreenComponent<T> = T extends {
43
- screen: React.ComponentType<{ route: { params: infer P } }>;
44
- }
45
- ? P
46
- : T extends React.ComponentType<{ route: { params: infer P } }>
47
- ? P
48
- : undefined;
49
-
50
- type ParamsForScreen<T> = T extends {
51
- screen: { config: StaticConfig<NavigatorTypeBagBase> };
52
- }
53
- ? NavigatorScreenParams<StaticParamList<T['screen']>> | undefined
54
- : T extends { config: StaticConfig<NavigatorTypeBagBase> }
55
- ? NavigatorScreenParams<StaticParamList<T>> | undefined
56
- : UnknownToUndefined<ParamsForScreenComponent<T>>;
57
-
58
- type ParamListForScreens<Screens> = {
59
- [Key in KeysOf<Screens>]: ParamsForScreen<Screens[Key]>;
60
- };
61
-
62
- type ParamListForGroups<
63
- Groups extends
64
- | Readonly<{
65
- [key: string]: {
66
- screens: StaticConfigScreens<
67
- ParamListBase,
68
- NavigationState,
69
- {},
70
- EventMapBase,
71
- any
72
- >;
73
- };
74
- }>
75
- | undefined,
76
- > = Groups extends {
77
- [key: string]: {
78
- screens: StaticConfigScreens<
79
- ParamListBase,
80
- NavigationState,
81
- {},
82
- EventMapBase,
83
- any
84
- >;
85
- };
86
- }
87
- ? ParamListForScreens<UnionToIntersection<Groups[keyof Groups]['screens']>>
88
- : {};
17
+ export type { StaticParamList } from './types';
89
18
 
90
19
  type StaticRouteConfig<
91
20
  ParamList extends ParamListBase,
@@ -287,25 +216,6 @@ export type StaticScreenProps<T extends Record<string, unknown> | undefined> = {
287
216
  };
288
217
  };
289
218
 
290
- /**
291
- * Infer the param list from the static navigation config.
292
- */
293
- export type StaticParamList<
294
- T extends {
295
- readonly config: {
296
- readonly screens?: Record<string, any>;
297
- readonly groups?: {
298
- [key: string]: {
299
- screens: Record<string, any>;
300
- };
301
- };
302
- };
303
- },
304
- > = FlatType<
305
- ParamListForScreens<T['config']['screens']> &
306
- ParamListForGroups<T['config']['groups']>
307
- >;
308
-
309
219
  type StaticNavigationBase = {
310
220
  config: StaticConfig<NavigatorTypeBagBase>;
311
221
  getComponent: () => React.ComponentType<{}>;
@@ -1,14 +1,16 @@
1
- const checkSerializableWithoutCircularReference = (
2
- o: { [key: string]: any },
3
- seen: Set<any>,
4
- location: (string | number)[]
5
- ):
1
+ type Result =
6
2
  | { serializable: true }
7
3
  | {
8
4
  serializable: false;
9
5
  location: (string | number)[];
10
6
  reason: string;
11
- } => {
7
+ };
8
+
9
+ const checkSerializableWithoutCircularReference = (
10
+ o: { [key: string]: any },
11
+ seen: Set<any>,
12
+ location: (string | number)[]
13
+ ): Result => {
12
14
  if (
13
15
  o === undefined ||
14
16
  o === null ||
@@ -25,7 +27,7 @@ const checkSerializableWithoutCircularReference = (
25
27
  ) {
26
28
  return {
27
29
  serializable: false,
28
- location,
30
+ location: location.slice(),
29
31
  reason: typeof o === 'function' ? 'Function' : String(o),
30
32
  };
31
33
  }
@@ -34,7 +36,7 @@ const checkSerializableWithoutCircularReference = (
34
36
  return {
35
37
  serializable: false,
36
38
  reason: 'Circular reference',
37
- location,
39
+ location: location.slice(),
38
40
  };
39
41
  }
40
42
 
@@ -42,30 +44,38 @@ const checkSerializableWithoutCircularReference = (
42
44
 
43
45
  if (Array.isArray(o)) {
44
46
  for (let i = 0; i < o.length; i++) {
47
+ location.push(i);
45
48
  const childResult = checkSerializableWithoutCircularReference(
46
49
  o[i],
47
- new Set<any>(seen),
48
- [...location, i]
50
+ seen,
51
+ location
49
52
  );
53
+ location.pop();
50
54
 
51
55
  if (!childResult.serializable) {
56
+ seen.delete(o);
52
57
  return childResult;
53
58
  }
54
59
  }
55
60
  } else {
56
61
  for (const key in o) {
62
+ location.push(key);
57
63
  const childResult = checkSerializableWithoutCircularReference(
58
64
  o[key],
59
- new Set<any>(seen),
60
- [...location, key]
65
+ seen,
66
+ location
61
67
  );
68
+ location.pop();
62
69
 
63
70
  if (!childResult.serializable) {
71
+ seen.delete(o);
64
72
  return childResult;
65
73
  }
66
74
  }
67
75
  }
68
76
 
77
+ seen.delete(o);
78
+
69
79
  return { serializable: true };
70
80
  };
71
81
 
@@ -28,14 +28,30 @@ type NavigateAction<State extends NavigationState> = {
28
28
  };
29
29
  };
30
30
 
31
+ // Cache the normalized config across calls for the same `options` reference,
32
+ // so we don't re-walk the config tree on every action conversion.
33
+ const cachedNormalizedConfig = new WeakMap<Options, ConfigItem>();
34
+
31
35
  export function getActionFromState(
32
36
  state: PartialState<NavigationState>,
33
37
  options?: Options
34
38
  ): NavigateAction<NavigationState> | CommonActions.Action | undefined {
35
- // Create a normalized configs object which will be easier to use
36
- const normalizedConfig = options
37
- ? createNormalizedConfigItem(options as PathConfig<object> | string)
38
- : {};
39
+ let normalizedConfig;
40
+
41
+ if (options) {
42
+ const cached = cachedNormalizedConfig.get(options);
43
+
44
+ if (cached) {
45
+ normalizedConfig = cached;
46
+ } else {
47
+ normalizedConfig = createNormalizedConfigItem(
48
+ options as PathConfig<object> | string
49
+ );
50
+ cachedNormalizedConfig.set(options, normalizedConfig as ConfigItem);
51
+ }
52
+ } else {
53
+ normalizedConfig = {};
54
+ }
39
55
 
40
56
  const routes =
41
57
  state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
@@ -49,6 +49,7 @@ type ParsedRoute = {
49
49
  type ConfigResources = {
50
50
  initialRoutes: InitialRouteConfig[];
51
51
  configs: RouteConfig[];
52
+ configsByScreen: Record<string, RouteConfig[]>;
52
53
  };
53
54
 
54
55
  /**
@@ -76,7 +77,8 @@ export function getStateFromPath<ParamList extends {}>(
76
77
  path: string,
77
78
  options?: Options<ParamList>
78
79
  ): ResultState | undefined {
79
- const { initialRoutes, configs } = getConfigResources(options);
80
+ const { initialRoutes, configs, configsByScreen } =
81
+ getConfigResources(options);
80
82
 
81
83
  const screens = options?.screens;
82
84
 
@@ -142,7 +144,11 @@ export function getStateFromPath<ParamList extends {}>(
142
144
 
143
145
  // We match the whole path against the regex instead of segments
144
146
  // This makes sure matches such as wildcard will catch any unmatched routes, even if nested
145
- const { routes, remainingPath } = matchAgainstConfigs(remaining, configs);
147
+ const { routes, remainingPath } = matchAgainstConfigs(
148
+ remaining,
149
+ configs,
150
+ configsByScreen
151
+ );
146
152
 
147
153
  if (routes !== undefined) {
148
154
  // This will always be empty if full path matched
@@ -189,12 +195,16 @@ function prepareConfigResources(options?: Options<{}>) {
189
195
 
190
196
  checkForDuplicatedConfigs(configs);
191
197
 
192
- const configWithRegexes = getConfigsWithRegexes(configs);
198
+ const configsByScreen: Record<string, RouteConfig[]> = {};
199
+
200
+ for (const c of configs) {
201
+ (configsByScreen[c.screen] ??= []).push(c);
202
+ }
193
203
 
194
204
  return {
195
205
  initialRoutes,
196
206
  configs,
197
- configWithRegexes,
207
+ configsByScreen,
198
208
  };
199
209
  }
200
210
 
@@ -337,15 +347,11 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
337
347
  }, {});
338
348
  }
339
349
 
340
- function getConfigsWithRegexes(configs: RouteConfig[]) {
341
- return configs.map((c) => ({
342
- ...c,
343
- // Add `$` to the regex to make sure it matches till end of the path and not just beginning
344
- regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
345
- }));
346
- }
347
-
348
- const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
350
+ const matchAgainstConfigs = (
351
+ remaining: string,
352
+ configs: RouteConfig[],
353
+ configsByScreen: Record<string, RouteConfig[]>
354
+ ) => {
349
355
  let routes: ParsedRoute[] | undefined;
350
356
  let remainingPath = remaining;
351
357
 
@@ -360,13 +366,10 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
360
366
  // If our regex matches, we need to extract params from the path
361
367
  if (match) {
362
368
  routes = config.routeNames.map((routeName) => {
363
- const routeConfig = configs.find((c) => {
364
- // Check matching name AND pattern in case same screen is used at different levels in config
365
- return (
366
- c.screen === routeName &&
367
- arrayStartsWith(config.segments, c.segments)
368
- );
369
- });
369
+ // Check matching name AND pattern in case same screen is used at different levels in config
370
+ const routeConfig = configsByScreen[routeName]?.find((c) =>
371
+ arrayStartsWith(config.segments, c.segments)
372
+ );
370
373
 
371
374
  const params =
372
375
  routeConfig && match.groups
package/src/index.tsx CHANGED
@@ -25,7 +25,6 @@ export {
25
25
  type StaticConfigGroup,
26
26
  type StaticConfigScreens,
27
27
  type StaticNavigation,
28
- type StaticParamList,
29
28
  type StaticScreenProps,
30
29
  } from './StaticNavigation';
31
30
  export { ThemeContext } from './theming/ThemeContext';
package/src/types.tsx CHANGED
@@ -10,14 +10,148 @@ import type {
10
10
  } from '@react-navigation/routers';
11
11
  import type * as React from 'react';
12
12
 
13
+ /**
14
+ * Flatten a type to remove all type alias names, unions etc.
15
+ * This will show a plain object when hovering over the type.
16
+ */
17
+ type FlatType<T> = { [K in keyof T]: T[K] } & {};
18
+
19
+ /**
20
+ * keyof T doesn't work for union types. We can use distributive conditional types instead.
21
+ * https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
22
+ */
23
+ type KeysOf<T> = T extends {} ? keyof T : never;
24
+
25
+ /**
26
+ * We get a union type when using keyof, but we want an intersection instead.
27
+ * https://stackoverflow.com/a/50375286/1665026
28
+ */
29
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
30
+ k: infer I
31
+ ) => void
32
+ ? I
33
+ : never;
34
+
35
+ type UnknownToUndefined<T> = unknown extends T ? undefined : T;
36
+
37
+ type ParamsForScreenComponent<T> = T extends {
38
+ screen: React.ComponentType<{ route: { params: infer P } }>;
39
+ }
40
+ ? P
41
+ : T extends React.ComponentType<{ route: { params: infer P } }>
42
+ ? P
43
+ : undefined;
44
+
45
+ type StaticNavigationConfig = {
46
+ readonly config: {
47
+ readonly screens?: Record<string, any>;
48
+ readonly groups?: {
49
+ [key: string]: {
50
+ screens: Record<string, any>;
51
+ };
52
+ };
53
+ };
54
+ };
55
+
56
+ type ParamsForScreen<T> = T extends {
57
+ screen: infer Screen;
58
+ }
59
+ ? Screen extends StaticNavigationConfig
60
+ ? NavigatorScreenParams<StaticParamList<Screen>> | undefined
61
+ : UnknownToUndefined<ParamsForScreenComponent<T>>
62
+ : T extends StaticNavigationConfig
63
+ ? NavigatorScreenParams<StaticParamList<T>> | undefined
64
+ : UnknownToUndefined<ParamsForScreenComponent<T>>;
65
+
66
+ type ParamListForScreens<Screens> = {
67
+ [Key in KeysOf<Screens>]: ParamsForScreen<Screens[Key]>;
68
+ };
69
+
70
+ type ParamListForGroups<
71
+ Groups extends
72
+ | Readonly<{
73
+ [key: string]: {
74
+ screens: Record<string, any>;
75
+ };
76
+ }>
77
+ | undefined,
78
+ > = Groups extends {
79
+ [key: string]: {
80
+ screens: Record<string, any>;
81
+ };
82
+ }
83
+ ? ParamListForScreens<UnionToIntersection<Groups[keyof Groups]['screens']>>
84
+ : {};
85
+
86
+ /**
87
+ * Infer the param list from the static navigation config.
88
+ */
89
+ export type StaticParamList<T extends StaticNavigationConfig> = FlatType<
90
+ ParamListForScreens<T['config']['screens']> &
91
+ ParamListForGroups<T['config']['groups']>
92
+ >;
93
+
94
+ type ParamListForStaticNavigator<T> = T extends StaticNavigationConfig
95
+ ? StaticParamList<T>
96
+ : {};
97
+
98
+ type ParamListForTypedNavigator<T> = T extends {
99
+ Screen: any;
100
+ } & PrivateValueStore<infer Value>
101
+ ? Value[0]
102
+ : {};
103
+
104
+ type ParamListForRootNavigator<T> =
105
+ string extends keyof ParamListForTypedNavigator<T>
106
+ ? ParamListForStaticNavigator<T>
107
+ : ParamListForTypedNavigator<T>;
108
+
109
+ /**
110
+ * Root navigator used in the app.
111
+ * It's used for the global types in the app.
112
+ *
113
+ * Users need to use module augmentation to add their navigator type:
114
+ *
115
+ * ```ts
116
+ * // Navigator created with static or dynamic API
117
+ * const RootStack = createStackNavigator({
118
+ * // ...
119
+ * });
120
+ *
121
+ * type RootStackType = typeof RootStack;
122
+ *
123
+ * declare module '@react-navigation/core' {
124
+ * interface RootNavigator extends RootStackType {}
125
+ * }
126
+ * ```
127
+ */
128
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
129
+ export interface RootNavigator {}
130
+
131
+ /**
132
+ * Theme object for the navigation components.
133
+ *
134
+ * Custom properties can be added using declaration merging:
135
+ *
136
+ * ```ts
137
+ * declare module '@react-navigation/core' {
138
+ * interface Theme extends NativeTheme {
139
+ * myCustomProperty: string;
140
+ * }
141
+ * }
142
+ * ```
143
+ */
144
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
145
+ export interface Theme {}
146
+
147
+ type RootTheme = Theme;
148
+
13
149
  declare global {
14
150
  // eslint-disable-next-line @typescript-eslint/no-namespace
15
151
  namespace ReactNavigation {
16
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
17
- interface RootParamList {}
152
+ interface RootParamList extends ParamListForRootNavigator<RootNavigator> {}
18
153
 
19
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
20
- interface Theme {}
154
+ interface Theme extends RootTheme {}
21
155
  }
22
156
  }
23
157
 
@@ -75,59 +75,54 @@ export function useEventEmitter<T extends Record<string, any>>(
75
75
  target?: string;
76
76
  canPreventDefault?: boolean;
77
77
  }) => {
78
- const items = listeners.current[type] || {};
78
+ const items = listeners.current[type];
79
79
 
80
80
  // Copy the current list of callbacks in case they are mutated during execution
81
- const callbacks =
82
- target !== undefined
83
- ? items[target]?.slice()
84
- : ([] as Listeners)
85
- .concat(...Object.keys(items).map((t) => items[t]))
86
- .filter((cb, i, self) => self.lastIndexOf(cb) === i);
87
-
88
- const event: EventArg<any, any, any> = {
89
- get type() {
90
- return type;
91
- },
81
+ let callbacks: Listeners | undefined;
82
+
83
+ if (items !== undefined) {
84
+ callbacks =
85
+ target !== undefined
86
+ ? items[target]?.slice()
87
+ : ([] as Listeners)
88
+ .concat(...Object.keys(items).map((t) => items[t]))
89
+ .filter((cb, i, self) => self.lastIndexOf(cb) === i);
90
+ }
91
+
92
+ const descriptors: PropertyDescriptorMap = {
93
+ type: { enumerable: true, value: type },
92
94
  };
93
95
 
94
96
  if (target !== undefined) {
95
- Object.defineProperty(event, 'target', {
96
- enumerable: true,
97
- get() {
98
- return target;
99
- },
100
- });
97
+ descriptors.target = { enumerable: true, value: target };
101
98
  }
102
99
 
103
100
  if (data !== undefined) {
104
- Object.defineProperty(event, 'data', {
105
- enumerable: true,
106
- get() {
107
- return data;
108
- },
109
- });
101
+ descriptors.data = { enumerable: true, value: data };
110
102
  }
111
103
 
104
+ let defaultPrevented = false;
105
+
112
106
  if (canPreventDefault) {
113
- let defaultPrevented = false;
114
-
115
- Object.defineProperties(event, {
116
- defaultPrevented: {
117
- enumerable: true,
118
- get() {
119
- return defaultPrevented;
120
- },
107
+ descriptors.defaultPrevented = {
108
+ enumerable: true,
109
+ get() {
110
+ return defaultPrevented;
121
111
  },
122
- preventDefault: {
123
- enumerable: true,
124
- value() {
125
- defaultPrevented = true;
126
- },
112
+ };
113
+ descriptors.preventDefault = {
114
+ enumerable: true,
115
+ value() {
116
+ defaultPrevented = true;
127
117
  },
128
- });
118
+ };
129
119
  }
130
120
 
121
+ const event: EventArg<any, any, any> = Object.defineProperties(
122
+ {} as EventArg<any, any, any>,
123
+ descriptors
124
+ );
125
+
131
126
  listenRef.current?.(event);
132
127
 
133
128
  callbacks?.forEach((cb) => cb(event));