@react-navigation/core 7.2.2 → 7.3.1

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 (39) hide show
  1. package/lib/commonjs/StaticNavigation.js +2 -2
  2. package/lib/commonjs/StaticNavigation.js.map +1 -1
  3. package/lib/commonjs/getPatternParts.js +1 -1
  4. package/lib/commonjs/getPatternParts.js.map +1 -1
  5. package/lib/commonjs/getStateFromPath.js +33 -10
  6. package/lib/commonjs/getStateFromPath.js.map +1 -1
  7. package/lib/commonjs/useScheduleUpdate.js +2 -1
  8. package/lib/commonjs/useScheduleUpdate.js.map +1 -1
  9. package/lib/commonjs/validatePathConfig.js +9 -2
  10. package/lib/commonjs/validatePathConfig.js.map +1 -1
  11. package/lib/module/StaticNavigation.js +2 -2
  12. package/lib/module/StaticNavigation.js.map +1 -1
  13. package/lib/module/getPatternParts.js +1 -1
  14. package/lib/module/getPatternParts.js.map +1 -1
  15. package/lib/module/getStateFromPath.js +33 -10
  16. package/lib/module/getStateFromPath.js.map +1 -1
  17. package/lib/module/useScheduleUpdate.js +3 -1
  18. package/lib/module/useScheduleUpdate.js.map +1 -1
  19. package/lib/module/validatePathConfig.js +9 -2
  20. package/lib/module/validatePathConfig.js.map +1 -1
  21. package/lib/typescript/commonjs/src/StaticNavigation.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/src/getStateFromPath.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/src/types.d.ts +8 -6
  24. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/src/useScheduleUpdate.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/src/validatePathConfig.d.ts.map +1 -1
  27. package/lib/typescript/module/src/StaticNavigation.d.ts.map +1 -1
  28. package/lib/typescript/module/src/getStateFromPath.d.ts.map +1 -1
  29. package/lib/typescript/module/src/types.d.ts +8 -6
  30. package/lib/typescript/module/src/types.d.ts.map +1 -1
  31. package/lib/typescript/module/src/useScheduleUpdate.d.ts.map +1 -1
  32. package/lib/typescript/module/src/validatePathConfig.d.ts.map +1 -1
  33. package/package.json +4 -4
  34. package/src/StaticNavigation.tsx +2 -4
  35. package/src/getPatternParts.tsx +1 -1
  36. package/src/getStateFromPath.tsx +55 -26
  37. package/src/types.tsx +9 -8
  38. package/src/useScheduleUpdate.tsx +3 -1
  39. package/src/validatePathConfig.tsx +9 -2
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  InitialState,
3
3
  NavigationState,
4
+ ParamListBase,
4
5
  PartialState,
5
6
  } from '@react-navigation/routers';
6
7
  import escape from 'escape-string-regexp';
@@ -10,7 +11,7 @@ import { arrayStartsWith } from './arrayStartsWith';
10
11
  import { findFocusedRoute } from './findFocusedRoute';
11
12
  import { getPatternParts, type PatternPart } from './getPatternParts';
12
13
  import { isArrayEqual } from './isArrayEqual';
13
- import type { PathConfigMap } from './types';
14
+ import type { PathConfig, PathConfigMap } from './types';
14
15
  import { validatePathConfig } from './validatePathConfig';
15
16
 
16
17
  type Options<ParamList extends {}> = {
@@ -48,7 +49,6 @@ type ParsedRoute = {
48
49
  type ConfigResources = {
49
50
  initialRoutes: InitialRouteConfig[];
50
51
  configs: RouteConfig[];
51
- configWithRegexes: RouteConfig[];
52
52
  };
53
53
 
54
54
  /**
@@ -76,8 +76,7 @@ export function getStateFromPath<ParamList extends {}>(
76
76
  path: string,
77
77
  options?: Options<ParamList>
78
78
  ): ResultState | undefined {
79
- const { initialRoutes, configs, configWithRegexes } =
80
- getConfigResources(options);
79
+ const { initialRoutes, configs } = getConfigResources(options);
81
80
 
82
81
  const screens = options?.screens;
83
82
 
@@ -143,10 +142,7 @@ export function getStateFromPath<ParamList extends {}>(
143
142
 
144
143
  // We match the whole path against the regex instead of segments
145
144
  // This makes sure matches such as wildcard will catch any unmatched routes, even if nested
146
- const { routes, remainingPath } = matchAgainstConfigs(
147
- remaining,
148
- configWithRegexes
149
- );
145
+ const { routes, remainingPath } = matchAgainstConfigs(remaining, configs);
150
146
 
151
147
  if (routes !== undefined) {
152
148
  // This will always be empty if full path matched
@@ -189,8 +185,7 @@ function prepareConfigResources(options?: Options<{}>) {
189
185
  }
190
186
 
191
187
  const initialRoutes = getInitialRoutes(options);
192
-
193
- const configs = getNormalizedConfigs(initialRoutes, options?.screens);
188
+ const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
194
189
 
195
190
  checkForDuplicatedConfigs(configs);
196
191
 
@@ -216,22 +211,15 @@ function getInitialRoutes(options?: Options<{}>) {
216
211
  return initialRoutes;
217
212
  }
218
213
 
219
- function getNormalizedConfigs(
214
+ function getSortedNormalizedConfigs(
220
215
  initialRoutes: InitialRouteConfig[],
221
- screens: PathConfigMap<object> = {}
216
+ screens: Record<string, string | PathConfig<ParamListBase>> = {}
222
217
  ) {
223
218
  // Create a normalized configs array which will be easier to use
224
219
  return ([] as RouteConfig[])
225
220
  .concat(
226
221
  ...Object.keys(screens).map((key) =>
227
- createNormalizedConfigs(
228
- key,
229
- screens as PathConfigMap<object>,
230
- initialRoutes,
231
- [],
232
- [],
233
- []
234
- )
222
+ createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
235
223
  )
236
224
  )
237
225
  .sort((a, b) => {
@@ -430,7 +418,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
430
418
 
431
419
  const createNormalizedConfigs = (
432
420
  screen: string,
433
- routeConfig: PathConfigMap<object>,
421
+ routeConfig: Record<string, string | PathConfig<ParamListBase>>,
434
422
  initials: InitialRouteConfig[],
435
423
  paths: { screen: string; path: string }[],
436
424
  parentScreens: string[],
@@ -442,7 +430,6 @@ const createNormalizedConfigs = (
442
430
 
443
431
  parentScreens.push(screen);
444
432
 
445
- // @ts-expect-error: we can't strongly typecheck this for now
446
433
  const config = routeConfig[screen];
447
434
 
448
435
  if (typeof config === 'string') {
@@ -453,12 +440,42 @@ const createNormalizedConfigs = (
453
440
  // it can have `path` property and
454
441
  // it could have `screens` prop which has nested configs
455
442
  if (typeof config.path === 'string') {
456
- if (config.exact && config.path === undefined) {
443
+ if (config.exact && config.path == null) {
457
444
  throw new Error(
458
- "A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`."
445
+ `Screen '${screen}' doesn't specify a 'path'. A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. \`path: ''\`.`
459
446
  );
460
447
  }
461
448
 
449
+ // We should add alias configs after the main config
450
+ // So unless they are more specific, main config will be matched first
451
+ const aliasConfigs = [];
452
+
453
+ if (config.alias) {
454
+ for (const alias of config.alias) {
455
+ if (typeof alias === 'string') {
456
+ aliasConfigs.push(
457
+ createConfigItem(
458
+ screen,
459
+ [...routeNames],
460
+ [...paths, { screen, path: alias }],
461
+ config.parse
462
+ )
463
+ );
464
+ } else if (typeof alias === 'object') {
465
+ aliasConfigs.push(
466
+ createConfigItem(
467
+ screen,
468
+ [...routeNames],
469
+ alias.exact
470
+ ? [{ screen, path: alias.path }]
471
+ : [...paths, { screen, path: alias.path }],
472
+ alias.parse
473
+ )
474
+ );
475
+ }
476
+ }
477
+ }
478
+
462
479
  if (config.exact) {
463
480
  // If it's an exact path, we don't need to keep track of the parent screens
464
481
  // So we can clear it
@@ -469,6 +486,18 @@ const createNormalizedConfigs = (
469
486
  configs.push(
470
487
  createConfigItem(screen, [...routeNames], [...paths], config.parse)
471
488
  );
489
+
490
+ configs.push(...aliasConfigs);
491
+ }
492
+
493
+ if (
494
+ typeof config !== 'string' &&
495
+ typeof config.path !== 'string' &&
496
+ config.alias?.length
497
+ ) {
498
+ throw new Error(
499
+ `Screen '${screen}' doesn't specify a 'path'. A 'path' needs to be specified in order to use 'alias'.`
500
+ );
472
501
  }
473
502
 
474
503
  if (config.screens) {
@@ -483,7 +512,7 @@ const createNormalizedConfigs = (
483
512
  Object.keys(config.screens).forEach((nestedConfig) => {
484
513
  const result = createNormalizedConfigs(
485
514
  nestedConfig,
486
- config.screens as PathConfigMap<object>,
515
+ config.screens as Record<string, string | PathConfig<ParamListBase>>,
487
516
  initials,
488
517
  [...paths],
489
518
  [...parentScreens],
@@ -525,7 +554,7 @@ const createConfigItem = (
525
554
 
526
555
  return `${it.segment === '*' ? '.*' : escape(it.segment)}\\/`;
527
556
  })
528
- .join('')})`
557
+ .join('')})$`
529
558
  )
530
559
  : undefined;
531
560
 
package/src/types.tsx CHANGED
@@ -971,12 +971,12 @@ export type NavigatorScreenParams<ParamList extends {}> =
971
971
  };
972
972
  }[keyof ParamList];
973
973
 
974
- export type PathConfig<ParamList extends {}> = {
974
+ type PathConfigAlias = {
975
975
  /**
976
976
  * Path string to match against.
977
977
  * e.g. `/users/:id` will match `/users/1` and extract `id` param as `1`.
978
978
  */
979
- path?: string;
979
+ path: string;
980
980
  /**
981
981
  * Whether the path should be consider parent paths or use the exact path.
982
982
  * By default, paths are relating to the path config on the parent screen.
@@ -995,6 +995,9 @@ export type PathConfig<ParamList extends {}> = {
995
995
  * ```
996
996
  */
997
997
  parse?: Record<string, (value: string) => any>;
998
+ };
999
+
1000
+ export type PathConfig<ParamList extends {}> = Partial<PathConfigAlias> & {
998
1001
  /**
999
1002
  * An object mapping the param name to a function which converts the param value to a string.
1000
1003
  * By default, all params are converted to strings using `String(value)`.
@@ -1007,6 +1010,10 @@ export type PathConfig<ParamList extends {}> = {
1007
1010
  * ```
1008
1011
  */
1009
1012
  stringify?: Record<string, (value: any) => string>;
1013
+ /**
1014
+ * Additional path alias that will be matched to the same screen.
1015
+ */
1016
+ alias?: (string | PathConfigAlias)[];
1010
1017
  /**
1011
1018
  * Path configuration for child screens.
1012
1019
  */
@@ -1015,12 +1022,6 @@ export type PathConfig<ParamList extends {}> = {
1015
1022
  * Name of the initial route to use for the navigator when the path matches.
1016
1023
  */
1017
1024
  initialRouteName?: keyof ParamList;
1018
- /**
1019
- * A function returning a state, which may be set after modifying the routes name.
1020
- */
1021
- getStateForRouteNamesChange?: (
1022
- state: NavigationState
1023
- ) => PartialState<NavigationState> | undefined;
1024
1025
  };
1025
1026
 
1026
1027
  export type PathConfigMap<ParamList extends {}> = {
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { NavigationBuilderContext } from './NavigationBuilderContext';
4
+ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
5
+
4
6
  /**
5
7
  * When screen config changes, we want to update the navigator in the same update phase.
6
8
  * However, navigation state is in the root component and React won't let us update it from a child.
@@ -17,5 +19,5 @@ export function useScheduleUpdate(callback: () => void) {
17
19
  // However, since we are using sync store, it might be fine
18
20
  scheduleUpdate(callback);
19
21
 
20
- React.useEffect(flushUpdates);
22
+ useIsomorphicLayoutEffect(flushUpdates);
21
23
  }
@@ -11,6 +11,7 @@ export function validatePathConfig(config: unknown, root = true) {
11
11
  ...(root
12
12
  ? null
13
13
  : {
14
+ alias: 'array',
14
15
  exact: 'boolean',
15
16
  stringify: 'object',
16
17
  parse: 'object',
@@ -33,8 +34,14 @@ export function validatePathConfig(config: unknown, root = true) {
33
34
  // @ts-expect-error: we know the key exists
34
35
  const value = config[key];
35
36
 
36
- if (value !== undefined && typeof value !== type) {
37
- return [key, `expected '${type}', got '${typeof value}'`];
37
+ if (value !== undefined) {
38
+ if (type === 'array') {
39
+ if (!Array.isArray(value)) {
40
+ return [key, `expected 'Array', got '${typeof value}'`];
41
+ }
42
+ } else if (typeof value !== type) {
43
+ return [key, `expected '${type}', got '${typeof value}'`];
44
+ }
38
45
  }
39
46
  } else {
40
47
  return [key, 'extraneous'];