@react-navigation/core 7.17.1 → 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 (47) hide show
  1. package/lib/module/SceneView.js +18 -4
  2. package/lib/module/SceneView.js.map +1 -1
  3. package/lib/module/StaticNavigation.js +0 -19
  4. package/lib/module/StaticNavigation.js.map +1 -1
  5. package/lib/module/checkSerializable.js +11 -4
  6. package/lib/module/checkSerializable.js.map +1 -1
  7. package/lib/module/getActionFromState.js +15 -2
  8. package/lib/module/getActionFromState.js.map +1 -1
  9. package/lib/module/getStateFromPath.js +11 -16
  10. package/lib/module/getStateFromPath.js.map +1 -1
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/types.js +55 -0
  13. package/lib/module/types.js.map +1 -1
  14. package/lib/module/useEventEmitter.js +28 -29
  15. package/lib/module/useEventEmitter.js.map +1 -1
  16. package/lib/module/useNavigationBuilder.js +43 -47
  17. package/lib/module/useNavigationBuilder.js.map +1 -1
  18. package/lib/module/useRouteCache.js +2 -1
  19. package/lib/module/useRouteCache.js.map +1 -1
  20. package/lib/typescript/src/ConsumedParamsContext.d.ts +2 -2
  21. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
  22. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  23. package/lib/typescript/src/StaticNavigation.d.ts +2 -63
  24. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  25. package/lib/typescript/src/checkSerializable.d.ts +5 -3
  26. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  27. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  28. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  29. package/lib/typescript/src/index.d.ts +1 -1
  30. package/lib/typescript/src/index.d.ts.map +1 -1
  31. package/lib/typescript/src/types.d.ts +102 -2
  32. package/lib/typescript/src/types.d.ts.map +1 -1
  33. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  34. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  35. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  36. package/package.json +3 -3
  37. package/src/ConsumedParamsContext.tsx +2 -2
  38. package/src/SceneView.tsx +27 -4
  39. package/src/StaticNavigation.tsx +1 -91
  40. package/src/checkSerializable.tsx +22 -12
  41. package/src/getActionFromState.tsx +20 -4
  42. package/src/getStateFromPath.tsx +23 -20
  43. package/src/index.tsx +0 -1
  44. package/src/types.tsx +138 -4
  45. package/src/useEventEmitter.tsx +33 -38
  46. package/src/useNavigationBuilder.tsx +64 -72
  47. package/src/useRouteCache.tsx +2 -1
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));
@@ -331,7 +331,7 @@ export function useNavigationBuilder<
331
331
 
332
332
  const isNestedParamsConsumed =
333
333
  typeof route?.params === 'object' && route.params != null
334
- ? consumedParams?.ref?.deref() === route.params
334
+ ? consumedParams?.isConsumed(route.params)
335
335
  : false;
336
336
 
337
337
  const {
@@ -376,44 +376,7 @@ export function useNavigationBuilder<
376
376
  return original;
377
377
  });
378
378
 
379
- const screens = routeConfigs.reduce<
380
- Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
381
- >((acc, config) => {
382
- if (config.props.name in acc) {
383
- throw new Error(
384
- `A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.props.name}')`
385
- );
386
- }
387
-
388
- acc[config.props.name] = config;
389
- return acc;
390
- }, {});
391
-
392
379
  const routeNames = routeConfigs.map((config) => config.props.name);
393
- const routeKeyList = routeNames.reduce<Record<string, React.Key | undefined>>(
394
- (acc, curr) => {
395
- acc[curr] = screens[curr].keys.map((key) => key ?? '').join(':');
396
- return acc;
397
- },
398
- {}
399
- );
400
- const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
401
- (acc, curr) => {
402
- const { initialParams } = screens[curr].props;
403
- acc[curr] = initialParams;
404
- return acc;
405
- },
406
- {}
407
- );
408
- const routeGetIdList = routeNames.reduce<
409
- RouterConfigOptions['routeGetIdList']
410
- >(
411
- (acc, curr) =>
412
- Object.assign(acc, {
413
- [curr]: screens[curr].props.getId,
414
- }),
415
- {}
416
- );
417
380
 
418
381
  if (!routeNames.length) {
419
382
  throw new Error(
@@ -421,6 +384,31 @@ export function useNavigationBuilder<
421
384
  );
422
385
  }
423
386
 
387
+ const screens: Record<
388
+ string,
389
+ ScreenConfigWithParent<State, ScreenOptions, EventMap>
390
+ > = {};
391
+
392
+ const routeKeyList: Record<string, React.Key | undefined> = {};
393
+ const routeParamList: Record<string, object | undefined> = {};
394
+ const routeGetIdList: RouterConfigOptions['routeGetIdList'] = {};
395
+
396
+ for (const config of routeConfigs) {
397
+ const name = config.props.name;
398
+
399
+ if (name in screens) {
400
+ throw new Error(
401
+ `A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${name}')`
402
+ );
403
+ }
404
+
405
+ screens[name] = config;
406
+ routeKeyList[name] = config.keys.map((key) => key ?? '').join(':');
407
+ routeParamList[name] = config.props.initialParams;
408
+
409
+ Object.assign(routeGetIdList, { [name]: config.props.getId });
410
+ }
411
+
424
412
  const isStateValid = React.useCallback(
425
413
  (state: NavigationState | PartialState<NavigationState>) =>
426
414
  state.type === undefined || state.type === router.type,
@@ -725,20 +713,18 @@ export function useNavigationBuilder<
725
713
  : nextState;
726
714
  }
727
715
 
728
- const setConsumedParamsRef = consumedParams?.setRef;
716
+ const setConsumedParams = consumedParams?.setConsumed;
729
717
 
730
718
  React.useEffect(() => {
731
719
  if (
732
- setConsumedParamsRef &&
720
+ setConsumedParams &&
733
721
  didConsumeNestedParams &&
734
722
  typeof route?.params === 'object' &&
735
723
  route.params != null
736
724
  ) {
737
- // Track whether the params have been already consumed
738
- // Set it to the same object, so merged params can be handled again
739
- setConsumedParamsRef(new WeakRef(route.params));
725
+ setConsumedParams(route.params);
740
726
  }
741
- }, [didConsumeNestedParams, route?.params, setConsumedParamsRef]);
727
+ }, [didConsumeNestedParams, route?.params, setConsumedParams]);
742
728
 
743
729
  const shouldUpdate = state !== nextState;
744
730
 
@@ -837,35 +823,41 @@ export function useNavigationBuilder<
837
823
  return;
838
824
  }
839
825
 
840
- const navigation = descriptors[route.key].navigation;
841
-
842
- const listeners = ([] as (((e: any) => void) | undefined)[])
843
- .concat(
844
- // Get an array of listeners for all screens + common listeners on navigator
845
- ...[
846
- screenListeners,
847
- ...routeNames.map((name) => {
848
- const { listeners } = screens[name].props;
849
- return listeners;
850
- }),
851
- ].map((listeners) => {
852
- const map =
853
- typeof listeners === 'function'
854
- ? listeners({ route: route as any, navigation })
855
- : listeners;
856
-
857
- return map
858
- ? Object.keys(map)
859
- .filter((type) => type === e.type)
860
- .map((type) => map?.[type])
861
- : undefined;
862
- })
863
- )
864
- // We don't want same listener to be called multiple times for same event
865
- // So we remove any duplicate functions from the array
866
- .filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
826
+ const hasPerScreenListeners = routeNames.some(
827
+ (name) => screens[name].props.listeners != null
828
+ );
867
829
 
868
- listeners.forEach((listener) => listener?.(e));
830
+ if (screenListeners != null || hasPerScreenListeners) {
831
+ const navigation = descriptors[route.key].navigation;
832
+
833
+ const listeners = ([] as (((e: any) => void) | undefined)[])
834
+ .concat(
835
+ // Get an array of listeners for all screens + common listeners on navigator
836
+ ...[
837
+ screenListeners,
838
+ ...routeNames.map((name) => {
839
+ const { listeners } = screens[name].props;
840
+ return listeners;
841
+ }),
842
+ ].map((listeners) => {
843
+ const map =
844
+ typeof listeners === 'function'
845
+ ? listeners({ route: route as any, navigation })
846
+ : listeners;
847
+
848
+ return map
849
+ ? Object.keys(map)
850
+ .filter((type) => type === e.type)
851
+ .map((type) => map?.[type])
852
+ : undefined;
853
+ })
854
+ )
855
+ // We don't want same listener to be called multiple times for same event
856
+ // So we remove any duplicate functions from the array
857
+ .filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
858
+
859
+ listeners.forEach((listener) => listener?.(e));
860
+ }
869
861
  });
870
862
 
871
863
  useFocusEvents({ state, emitter });
@@ -36,9 +36,10 @@ export function useRouteCache<State extends NavigationState>(
36
36
  proxy = routeWithoutState;
37
37
  }
38
38
 
39
- if (process.env.NODE_ENV !== 'production') {
39
+ if (process.env.NODE_ENV !== 'production' && proxy !== previous) {
40
40
  // FIXME: since the state is updated with mutation, the route object cannot be frozen
41
41
  // As a workaround, loop through the object and make the properties readonly
42
+ // Only needed once per proxy - skip if we're reusing a previously-frozen one
42
43
  for (const key in proxy) {
43
44
  // @ts-expect-error: this is fine since we are looping through the object
44
45
  const value = proxy[key];