@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.
- package/lib/module/SceneView.js +18 -4
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/StaticNavigation.js +0 -19
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/checkSerializable.js +11 -4
- package/lib/module/checkSerializable.js.map +1 -1
- package/lib/module/getActionFromState.js +15 -2
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getStateFromPath.js +11 -16
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +55 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/useEventEmitter.js +28 -29
- package/lib/module/useEventEmitter.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +43 -47
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useRouteCache.js +2 -1
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/typescript/src/ConsumedParamsContext.d.ts +2 -2
- package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/StaticNavigation.d.ts +2 -63
- package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/src/checkSerializable.d.ts +5 -3
- package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
- package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +102 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/ConsumedParamsContext.tsx +2 -2
- package/src/SceneView.tsx +27 -4
- package/src/StaticNavigation.tsx +1 -91
- package/src/checkSerializable.tsx +22 -12
- package/src/getActionFromState.tsx +20 -4
- package/src/getStateFromPath.tsx +23 -20
- package/src/index.tsx +0 -1
- package/src/types.tsx +138 -4
- package/src/useEventEmitter.tsx +33 -38
- package/src/useNavigationBuilder.tsx +64 -72
- 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
|
-
|
|
17
|
-
interface RootParamList {}
|
|
152
|
+
interface RootParamList extends ParamListForRootNavigator<RootNavigator> {}
|
|
18
153
|
|
|
19
|
-
|
|
20
|
-
interface Theme {}
|
|
154
|
+
interface Theme extends RootTheme {}
|
|
21
155
|
}
|
|
22
156
|
}
|
|
23
157
|
|
package/src/useEventEmitter.tsx
CHANGED
|
@@ -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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
enumerable: true,
|
|
118
|
-
get() {
|
|
119
|
-
return defaultPrevented;
|
|
120
|
-
},
|
|
107
|
+
descriptors.defaultPrevented = {
|
|
108
|
+
enumerable: true,
|
|
109
|
+
get() {
|
|
110
|
+
return defaultPrevented;
|
|
121
111
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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?.
|
|
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
|
|
716
|
+
const setConsumedParams = consumedParams?.setConsumed;
|
|
729
717
|
|
|
730
718
|
React.useEffect(() => {
|
|
731
719
|
if (
|
|
732
|
-
|
|
720
|
+
setConsumedParams &&
|
|
733
721
|
didConsumeNestedParams &&
|
|
734
722
|
typeof route?.params === 'object' &&
|
|
735
723
|
route.params != null
|
|
736
724
|
) {
|
|
737
|
-
|
|
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,
|
|
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
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
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 });
|
package/src/useRouteCache.tsx
CHANGED
|
@@ -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];
|