@react-navigation/native 7.2.5 → 7.3.0

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 (50) hide show
  1. package/lib/module/NavigationContainer.js +9 -44
  2. package/lib/module/NavigationContainer.js.map +1 -1
  3. package/lib/module/UnhandledLinkingContext.js +9 -7
  4. package/lib/module/UnhandledLinkingContext.js.map +1 -1
  5. package/lib/module/createStandardNavigationFactories.js +97 -0
  6. package/lib/module/createStandardNavigationFactories.js.map +1 -0
  7. package/lib/module/index.js +1 -0
  8. package/lib/module/index.js.map +1 -1
  9. package/lib/module/useLinking.js +2 -7
  10. package/lib/module/useLinking.js.map +1 -1
  11. package/lib/module/useLinking.native.js +14 -14
  12. package/lib/module/useLinking.native.js.map +1 -1
  13. package/lib/module/useMemoArray.js +25 -0
  14. package/lib/module/useMemoArray.js.map +1 -0
  15. package/lib/typescript/src/Link.d.ts +1 -1
  16. package/lib/typescript/src/Link.d.ts.map +1 -1
  17. package/lib/typescript/src/LinkingContext.d.ts +1 -1
  18. package/lib/typescript/src/LinkingContext.d.ts.map +1 -1
  19. package/lib/typescript/src/LocaleDirContext.d.ts +1 -1
  20. package/lib/typescript/src/LocaleDirContext.d.ts.map +1 -1
  21. package/lib/typescript/src/NavigationContainer.d.ts +1 -1
  22. package/lib/typescript/src/NavigationContainer.d.ts.map +1 -1
  23. package/lib/typescript/src/ServerContainer.d.ts +2 -2
  24. package/lib/typescript/src/ServerContainer.d.ts.map +1 -1
  25. package/lib/typescript/src/UnhandledLinkingContext.d.ts +6 -0
  26. package/lib/typescript/src/UnhandledLinkingContext.d.ts.map +1 -1
  27. package/lib/typescript/src/createStandardNavigationFactories.d.ts +47 -0
  28. package/lib/typescript/src/createStandardNavigationFactories.d.ts.map +1 -0
  29. package/lib/typescript/src/createStaticNavigation.d.ts +2 -2
  30. package/lib/typescript/src/createStaticNavigation.d.ts.map +1 -1
  31. package/lib/typescript/src/index.d.ts +17 -16
  32. package/lib/typescript/src/index.d.ts.map +1 -1
  33. package/lib/typescript/src/useDocumentTitle.d.ts +1 -1
  34. package/lib/typescript/src/useDocumentTitle.d.ts.map +1 -1
  35. package/lib/typescript/src/useLinkBuilder.d.ts.map +1 -1
  36. package/lib/typescript/src/useLinking.d.ts +2 -2
  37. package/lib/typescript/src/useLinking.d.ts.map +1 -1
  38. package/lib/typescript/src/useLinking.native.d.ts +2 -2
  39. package/lib/typescript/src/useLinking.native.d.ts.map +1 -1
  40. package/lib/typescript/src/useLocale.d.ts +1 -1
  41. package/lib/typescript/src/useMemoArray.d.ts +2 -0
  42. package/lib/typescript/src/useMemoArray.d.ts.map +1 -0
  43. package/package.json +5 -4
  44. package/src/NavigationContainer.tsx +15 -62
  45. package/src/UnhandledLinkingContext.tsx +8 -9
  46. package/src/createStandardNavigationFactories.tsx +294 -0
  47. package/src/index.tsx +5 -0
  48. package/src/useLinking.native.tsx +30 -29
  49. package/src/useLinking.tsx +2 -14
  50. package/src/useMemoArray.tsx +41 -0
@@ -0,0 +1,294 @@
1
+ import {
2
+ CommonActions,
3
+ createNavigatorFactory,
4
+ createScreenFactory,
5
+ type DefaultNavigatorOptions,
6
+ type DefaultRouterOptions,
7
+ type EventMapBase,
8
+ type NavigationAction,
9
+ type NavigationProp,
10
+ type NavigatorTypeBagBase,
11
+ type ParamListBase,
12
+ type RouterFactory,
13
+ type StaticConfig,
14
+ type StaticParamList,
15
+ type StaticScreenFactory,
16
+ type TypedNavigator,
17
+ useNavigationBuilder,
18
+ } from '@react-navigation/core';
19
+ import * as React from 'react';
20
+ import type {
21
+ createStandardNavigator,
22
+ NavigatorArgs as StandardNavigationArgs,
23
+ } from 'standard-navigation';
24
+
25
+ import { useBuildHref } from './useLinkBuilder';
26
+ import { useMemoArray } from './useMemoArray';
27
+
28
+ type StandardEventMap<EventMap extends EventMapBase> = {
29
+ [EventName in keyof EventMap]: {
30
+ data: EventMap[EventName] extends { data?: infer Data }
31
+ ? Data extends object | undefined
32
+ ? Data
33
+ : object | undefined
34
+ : undefined;
35
+ canPreventDefault: EventMap[EventName] extends { canPreventDefault: true }
36
+ ? true
37
+ : false;
38
+ };
39
+ };
40
+
41
+ type ActionHelpersOf<T> =
42
+ T extends Record<string, (...args: never[]) => unknown> ? T : {};
43
+
44
+ type StandardNavigationNavigatorProps<
45
+ TypeBag extends StandardNavigationTypeBagBase,
46
+ > = TypeBag['RouterOptions'] &
47
+ DefaultNavigatorOptions<
48
+ ParamListBase,
49
+ string | undefined,
50
+ StandardNavigationTypeBagFor<TypeBag, ParamListBase>['State'],
51
+ TypeBag['ScreenOptions'],
52
+ TypeBag['EventMap'],
53
+ StandardNavigation<TypeBag>
54
+ >;
55
+
56
+ type StandardNavigationTypeBagFor<
57
+ TypeBag extends StandardNavigationTypeBagBase,
58
+ ParamList extends ParamListBase,
59
+ NavigatorID extends string | undefined = string | undefined,
60
+ > = TypeBag & {
61
+ ParamList: ParamList;
62
+ NavigatorID: NavigatorID;
63
+ };
64
+
65
+ type StandardNavigation<TypeBag extends StandardNavigationTypeBagBase> =
66
+ StandardNavigationTypeBagFor<
67
+ TypeBag,
68
+ ParamListBase
69
+ >['NavigationList'][keyof StandardNavigationTypeBagFor<
70
+ TypeBag,
71
+ ParamListBase
72
+ >['NavigationList']];
73
+
74
+ type StandardNavigationPropsMapper<
75
+ TypeBag extends StandardNavigationTypeBagBase,
76
+ NavigatorProps extends object,
77
+ > = (props: {
78
+ state: StandardNavigationTypeBagFor<TypeBag, ParamListBase>['State'];
79
+ navigation: StandardNavigation<TypeBag>;
80
+ }) => Partial<NavigatorProps>;
81
+
82
+ type StandardNavigationDefaultBag<
83
+ TypeBag extends StandardNavigationTypeBagBase,
84
+ > = StandardNavigationTypeBagFor<TypeBag, ParamListBase, string | undefined>;
85
+
86
+ type StandardNavigationFactories<
87
+ TypeBag extends StandardNavigationTypeBagBase,
88
+ > = {
89
+ createNavigator: StandardNavigationCreateNavigator<TypeBag>;
90
+ createScreen: StaticScreenFactory<StandardNavigationDefaultBag<TypeBag>>;
91
+ };
92
+
93
+ export type StandardNavigationCreateNavigator<
94
+ TypeBag extends StandardNavigationTypeBagBase,
95
+ > = {
96
+ <
97
+ const ParamList extends ParamListBase,
98
+ const NavigatorID extends string | undefined = string | undefined,
99
+ >(): TypedNavigator<
100
+ StandardNavigationTypeBagFor<TypeBag, ParamList, NavigatorID>,
101
+ undefined
102
+ >;
103
+ <const Config extends StaticConfig<StandardNavigationDefaultBag<TypeBag>>>(
104
+ config: Config & StaticConfig<StandardNavigationDefaultBag<TypeBag>>
105
+ ): TypedNavigator<
106
+ StandardNavigationTypeBagFor<
107
+ TypeBag,
108
+ StaticParamList<{ config: Config }> & ParamListBase,
109
+ string | undefined
110
+ >,
111
+ Config
112
+ >;
113
+ };
114
+
115
+ export interface StandardNavigationTypeBagBase extends NavigatorTypeBagBase {
116
+ ActionHelpers: {};
117
+ ScreenOptions: {};
118
+ EventMap: EventMapBase;
119
+ RouterOptions: DefaultRouterOptions;
120
+ NavigationList: {
121
+ [RouteName in keyof this['ParamList']]: NavigationProp<
122
+ this['ParamList'],
123
+ RouteName,
124
+ this['NavigatorID'],
125
+ this['State'],
126
+ this['ScreenOptions'],
127
+ this['EventMap']
128
+ > &
129
+ ActionHelpersOf<this['ActionHelpers']>;
130
+ };
131
+ Navigator: React.ComponentType<{}>;
132
+ }
133
+
134
+ export function createStandardNavigationFactories<
135
+ TypeBag extends StandardNavigationTypeBagBase,
136
+ NavigatorProps extends object = {},
137
+ >(
138
+ navigator: ReturnType<
139
+ typeof createStandardNavigator<
140
+ TypeBag['ScreenOptions'],
141
+ StandardEventMap<TypeBag['EventMap']>,
142
+ NavigatorProps
143
+ >
144
+ >,
145
+ router: RouterFactory<
146
+ StandardNavigationTypeBagFor<TypeBag, ParamListBase>['State'],
147
+ NavigationAction,
148
+ TypeBag['RouterOptions']
149
+ >,
150
+ mapper?: StandardNavigationPropsMapper<TypeBag, NavigatorProps>
151
+ ): StandardNavigationFactories<TypeBag> {
152
+ const { type, version, NavigatorContent } = navigator;
153
+
154
+ if (type !== 'standard') {
155
+ throw new Error(
156
+ `createStandardNavigationFactories only works with standard navigator objects, but got navigator of ${typeof type === 'string' ? `type "${type}".` : 'unknown type.'}`
157
+ );
158
+ }
159
+
160
+ if (version !== 1) {
161
+ throw new Error(
162
+ `createStandardNavigationFactories only works with version 1 of standard navigator objects, but got version ${version}.`
163
+ );
164
+ }
165
+
166
+ type Bag = StandardNavigationTypeBagFor<TypeBag, ParamListBase>;
167
+ type StandardArgs = StandardNavigationArgs<
168
+ TypeBag['ScreenOptions'],
169
+ StandardEventMap<TypeBag['EventMap']>
170
+ >;
171
+
172
+ function StandardNavigationNavigator(
173
+ props: StandardNavigationNavigatorProps<TypeBag>
174
+ ) {
175
+ const builder = useNavigationBuilder<
176
+ Bag['State'],
177
+ TypeBag['RouterOptions'],
178
+ ActionHelpersOf<Bag['ActionHelpers']>,
179
+ TypeBag['ScreenOptions'],
180
+ TypeBag['EventMap']
181
+ >(router, props);
182
+
183
+ const buildHref = useBuildHref();
184
+
185
+ const routes = useMemoArray(
186
+ ('preloadedRoutes' in builder.state &&
187
+ Array.isArray(builder.state.preloadedRoutes)
188
+ ? builder.state.routes.concat(builder.state.preloadedRoutes)
189
+ : builder.state.routes
190
+ ).map((route) => {
191
+ const href = buildHref(route.name, route.params);
192
+
193
+ return [
194
+ {
195
+ key: route.key,
196
+ name: route.name,
197
+ params: route.params,
198
+ href,
199
+ },
200
+ [route.key, route.name, route.params, href],
201
+ ];
202
+ })
203
+ );
204
+
205
+ const state = React.useMemo(
206
+ (): StandardArgs['state'] => ({
207
+ index: builder.state.index,
208
+ routes,
209
+ }),
210
+ [builder.state.index, routes]
211
+ );
212
+
213
+ const descriptors: StandardArgs['descriptors'] = {};
214
+
215
+ for (const route of state.routes) {
216
+ const descriptor =
217
+ builder.descriptors[route.key] ?? builder.describe(route, true);
218
+
219
+ descriptors[route.key] = {
220
+ options: descriptor.options,
221
+ render: descriptor.render,
222
+ };
223
+ }
224
+
225
+ const actions = React.useMemo<StandardArgs['actions']>(
226
+ () => ({
227
+ navigate(name, params) {
228
+ builder.navigation.dispatch({
229
+ ...CommonActions.navigate(name, params),
230
+ target: builder.state.key,
231
+ });
232
+ },
233
+ back() {
234
+ builder.navigation.goBack();
235
+ },
236
+ }),
237
+ [builder.navigation, builder.state.key]
238
+ );
239
+
240
+ const emitter = React.useMemo<StandardArgs['emitter']>(
241
+ () => ({
242
+ // @ts-expect-error - they are compatible
243
+ emit: builder.navigation.emit,
244
+ }),
245
+ [builder.navigation]
246
+ );
247
+
248
+ const mapped = mapper?.({
249
+ state: builder.state,
250
+ navigation: builder.navigation as StandardNavigation<TypeBag>,
251
+ });
252
+
253
+ // Omit props used by useNavigationBuilder and routers internally
254
+ const {
255
+ /* eslint-disable @typescript-eslint/no-unused-vars */
256
+ children,
257
+ id,
258
+ initialRouteName,
259
+ layout,
260
+ screenLayout,
261
+ screenListeners,
262
+ screenOptions,
263
+ UNSTABLE_routeNamesChangeBehavior,
264
+ UNSTABLE_router,
265
+ /* eslint-enable @typescript-eslint/no-unused-vars */
266
+ ...rest
267
+ } = props;
268
+
269
+ return (
270
+ <builder.NavigationContent>
271
+ <NavigatorContent
272
+ {...(rest as NavigatorProps)}
273
+ {...mapped}
274
+ state={state}
275
+ descriptors={descriptors}
276
+ actions={actions}
277
+ emitter={emitter}
278
+ />
279
+ </builder.NavigationContent>
280
+ );
281
+ }
282
+
283
+ const createNavigator = createNavigatorFactory(
284
+ StandardNavigationNavigator
285
+ ) as StandardNavigationCreateNavigator<TypeBag>;
286
+
287
+ const createScreen =
288
+ createScreenFactory<StandardNavigationDefaultBag<TypeBag>>();
289
+
290
+ return {
291
+ createNavigator,
292
+ createScreen,
293
+ };
294
+ }
package/src/index.tsx CHANGED
@@ -1,3 +1,8 @@
1
+ export {
2
+ createStandardNavigationFactories,
3
+ type StandardNavigationCreateNavigator,
4
+ type StandardNavigationTypeBagBase,
5
+ } from './createStandardNavigationFactories';
1
6
  export { createStaticNavigation } from './createStaticNavigation';
2
7
  export { Link } from './Link';
3
8
  export { LinkingContext } from './LinkingContext';
@@ -55,8 +55,7 @@ export function useLinking(
55
55
  },
56
56
  getStateFromPath = getStateFromPathDefault,
57
57
  getActionFromState = getActionFromStateDefault,
58
- }: Options,
59
- onUnhandledLinking: (lastUnhandledLining: string | undefined) => void
58
+ }: Options
60
59
  ) {
61
60
  const independent = useNavigationIndependentTree();
62
61
 
@@ -69,28 +68,40 @@ export function useLinking(
69
68
  return undefined;
70
69
  }
71
70
 
72
- if (enabled !== false && linkingHandlers.length) {
73
- console.error(
74
- [
75
- 'Looks like you have configured linking in multiple places. This is likely an error since deep links should only be handled in one place to avoid conflicts. Make sure that:',
76
- "- You don't have multiple NavigationContainers in the app each with 'linking' enabled",
77
- '- Only a single instance of the root component is rendered',
78
- Platform.OS === 'android'
79
- ? "- You have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple instances"
80
- : '',
81
- ]
82
- .join('\n')
83
- .trim()
84
- );
85
- }
86
-
87
71
  const handler = Symbol();
88
72
 
89
73
  if (enabled !== false) {
90
74
  linkingHandlers.push(handler);
91
75
  }
92
76
 
77
+ // In some cases, the effect cleanup may get called out of order
78
+ // This may result in multiple linking handlers being registered
79
+ // For example, when changing the wallpaper on Android
80
+ // Showing the error in a delay avoids false positives
81
+ const timer = setTimeout(() => {
82
+ if (
83
+ enabled !== false &&
84
+ linkingHandlers.length &&
85
+ !(linkingHandlers.length === 1 && linkingHandlers.includes(handler))
86
+ ) {
87
+ console.error(
88
+ [
89
+ 'Looks like you have configured linking in multiple places. This is likely an error since deep links should only be handled in one place to avoid conflicts. Make sure that:',
90
+ "- You don't have multiple NavigationContainers in the app each with 'linking' enabled",
91
+ '- Only a single instance of the root component is rendered',
92
+ Platform.OS === 'android'
93
+ ? "- You have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple instances"
94
+ : '',
95
+ ]
96
+ .join('\n')
97
+ .trim()
98
+ );
99
+ }
100
+ }, 1000);
101
+
93
102
  return () => {
103
+ clearTimeout(timer);
104
+
94
105
  const index = linkingHandlers.indexOf(handler);
95
106
 
96
107
  if (index > -1) {
@@ -152,15 +163,8 @@ export function useLinking(
152
163
  return url.then((url) => {
153
164
  const state = getStateFromURL(url);
154
165
 
155
- if (typeof url === 'string') {
156
- // If the link were handled, it gets cleared in NavigationContainer
157
- onUnhandledLinking(extractPathFromURL(prefixes, url));
158
- }
159
-
160
166
  return state;
161
167
  });
162
- } else {
163
- onUnhandledLinking(extractPathFromURL(prefixes, url));
164
168
  }
165
169
  }
166
170
 
@@ -177,7 +181,7 @@ export function useLinking(
177
181
  };
178
182
 
179
183
  return thenable as PromiseLike<ResultState | undefined>;
180
- }, [getStateFromURL, onUnhandledLinking, prefixes]);
184
+ }, [getStateFromURL]);
181
185
 
182
186
  React.useEffect(() => {
183
187
  const listener = (url: string) => {
@@ -189,9 +193,6 @@ export function useLinking(
189
193
  const state = navigation ? getStateFromURL(url) : undefined;
190
194
 
191
195
  if (navigation && state) {
192
- // If the link were handled, it gets cleared in NavigationContainer
193
- onUnhandledLinking(extractPathFromURL(prefixes, url));
194
-
195
196
  const action = getActionFromStateRef.current(state, configRef.current);
196
197
 
197
198
  if (action !== undefined) {
@@ -215,7 +216,7 @@ export function useLinking(
215
216
  };
216
217
 
217
218
  return subscribe(listener);
218
- }, [enabled, getStateFromURL, onUnhandledLinking, prefixes, ref, subscribe]);
219
+ }, [enabled, getStateFromURL, ref, subscribe]);
219
220
 
220
221
  return {
221
222
  getInitialState,
@@ -110,8 +110,7 @@ export function useLinking(
110
110
  getStateFromPath = getStateFromPathDefault,
111
111
  getPathFromState = getPathFromStateDefault,
112
112
  getActionFromState = getActionFromStateDefault,
113
- }: Options,
114
- onUnhandledLinking: (lastUnhandledLining: string | undefined) => void
113
+ }: Options
115
114
  ) {
116
115
  const independent = useNavigationIndependentTree();
117
116
 
@@ -202,9 +201,6 @@ export function useLinking(
202
201
  value = undefined;
203
202
  }
204
203
  }
205
-
206
- // If the link were handled, it gets cleared in NavigationContainer
207
- onUnhandledLinking(path);
208
204
  }
209
205
 
210
206
  const thenable = {
@@ -287,8 +283,6 @@ export function useLinking(
287
283
  // We should only dispatch an action when going forward
288
284
  // Otherwise the action will likely add items to history, which would mess things up
289
285
  if (state) {
290
- // If the link were handled, it gets cleared in NavigationContainer
291
- onUnhandledLinking(path);
292
286
  // Make sure that the routes in the state exist in the root navigator
293
287
  // Otherwise there's an error in the linking configuration
294
288
  if (validateRoutesNotExistInRootState(state)) {
@@ -326,13 +320,7 @@ export function useLinking(
326
320
  navigation.resetRoot(state);
327
321
  }
328
322
  });
329
- }, [
330
- enabled,
331
- history,
332
- onUnhandledLinking,
333
- ref,
334
- validateRoutesNotExistInRootState,
335
- ]);
323
+ }, [enabled, history, ref, validateRoutesNotExistInRootState]);
336
324
 
337
325
  React.useEffect(() => {
338
326
  if (!enabled) {
@@ -0,0 +1,41 @@
1
+ import * as React from 'react';
2
+
3
+ export function useMemoArray<T>(
4
+ entries: [item: T, deps: readonly unknown[]][]
5
+ ): T[] {
6
+ const previousRef = React.useRef<
7
+ | {
8
+ entries: { item: T; deps: readonly unknown[] }[];
9
+ items: T[];
10
+ }
11
+ | undefined
12
+ >(undefined);
13
+
14
+ const previous = previousRef.current;
15
+ const next = entries.map(([item, deps], index) => {
16
+ const previousEntry = previous?.entries[index];
17
+ const depsEqual =
18
+ previousEntry &&
19
+ previousEntry.deps.length === deps.length &&
20
+ previousEntry.deps.every((it, index) => Object.is(it, deps[index]));
21
+
22
+ return depsEqual ? previousEntry : { item, deps };
23
+ });
24
+
25
+ if (
26
+ previous &&
27
+ previous.entries.length === next.length &&
28
+ next.every((entry, index) => entry === previous.entries[index])
29
+ ) {
30
+ return previous.items;
31
+ }
32
+
33
+ const items = next.map((entry) => entry.item);
34
+
35
+ previousRef.current = {
36
+ entries: next,
37
+ items,
38
+ };
39
+
40
+ return items;
41
+ }