@react-navigation/core 8.0.0-alpha.19 → 8.0.0-alpha.20

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 (144) hide show
  1. package/lib/module/BaseNavigationContainer.js +6 -2
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/StaticNavigation.js +72 -61
  4. package/lib/module/StaticNavigation.js.map +1 -1
  5. package/lib/module/findFocusedRoute.js +7 -4
  6. package/lib/module/findFocusedRoute.js.map +1 -1
  7. package/lib/module/getActionFromState.js +25 -12
  8. package/lib/module/getActionFromState.js.map +1 -1
  9. package/lib/module/getPathFromState.js +24 -9
  10. package/lib/module/getPathFromState.js.map +1 -1
  11. package/lib/module/getPatternParts.js +3 -0
  12. package/lib/module/getPatternParts.js.map +1 -1
  13. package/lib/module/getStateFromPath.js +164 -78
  14. package/lib/module/getStateFromPath.js.map +1 -1
  15. package/lib/module/index.js +1 -1
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/types.js +8 -0
  18. package/lib/module/types.js.map +1 -1
  19. package/lib/module/useDescriptors.js +13 -6
  20. package/lib/module/useDescriptors.js.map +1 -1
  21. package/lib/module/useFocusEvents.js +5 -1
  22. package/lib/module/useFocusEvents.js.map +1 -1
  23. package/lib/module/useNavigation.js.map +1 -1
  24. package/lib/module/useNavigationBuilder.js +18 -5
  25. package/lib/module/useNavigationBuilder.js.map +1 -1
  26. package/lib/module/useNavigationCache.js +1 -1
  27. package/lib/module/useNavigationCache.js.map +1 -1
  28. package/lib/module/useNavigationState.js.map +1 -1
  29. package/lib/module/useOnAction.js +1 -1
  30. package/lib/module/useOnAction.js.map +1 -1
  31. package/lib/module/useRoute.js.map +1 -1
  32. package/lib/module/validatePathConfig.js +2 -1
  33. package/lib/module/validatePathConfig.js.map +1 -1
  34. package/lib/typescript/src/BaseNavigationContainer.d.ts +2 -2
  35. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  36. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  37. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  38. package/lib/typescript/src/Group.d.ts +1 -1
  39. package/lib/typescript/src/Group.d.ts.map +1 -1
  40. package/lib/typescript/src/NavigationBuilderContext.d.ts +1 -1
  41. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  42. package/lib/typescript/src/NavigationContainerRefContext.d.ts +1 -1
  43. package/lib/typescript/src/NavigationContainerRefContext.d.ts.map +1 -1
  44. package/lib/typescript/src/NavigationHelpersContext.d.ts +1 -1
  45. package/lib/typescript/src/NavigationHelpersContext.d.ts.map +1 -1
  46. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  47. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  48. package/lib/typescript/src/NavigationProvider.d.ts +6 -6
  49. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  50. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  51. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  52. package/lib/typescript/src/SceneView.d.ts +3 -2
  53. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  54. package/lib/typescript/src/Screen.d.ts +1 -1
  55. package/lib/typescript/src/Screen.d.ts.map +1 -1
  56. package/lib/typescript/src/StaticNavigation.d.ts +68 -73
  57. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  58. package/lib/typescript/src/createNavigationContainerRef.d.ts +1 -1
  59. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  60. package/lib/typescript/src/createNavigatorFactory.d.ts +3 -3
  61. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  62. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  63. package/lib/typescript/src/getActionFromState.d.ts +1 -1
  64. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  65. package/lib/typescript/src/getPathFromState.d.ts +1 -1
  66. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  67. package/lib/typescript/src/getPatternParts.d.ts +1 -0
  68. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  69. package/lib/typescript/src/getStateFromPath.d.ts +2 -2
  70. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  71. package/lib/typescript/src/index.d.ts +34 -33
  72. package/lib/typescript/src/index.d.ts.map +1 -1
  73. package/lib/typescript/src/theming/ThemeContext.d.ts +1 -1
  74. package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -1
  75. package/lib/typescript/src/theming/ThemeProvider.d.ts +2 -2
  76. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  77. package/lib/typescript/src/theming/useTheme.d.ts +1 -1
  78. package/lib/typescript/src/theming/useTheme.d.ts.map +1 -1
  79. package/lib/typescript/src/types.d.ts +72 -37
  80. package/lib/typescript/src/types.d.ts.map +1 -1
  81. package/lib/typescript/src/useChildListeners.d.ts +3 -3
  82. package/lib/typescript/src/useChildListeners.d.ts.map +1 -1
  83. package/lib/typescript/src/useComponent.d.ts +1 -1
  84. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  85. package/lib/typescript/src/useDescriptors.d.ts +5 -5
  86. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  87. package/lib/typescript/src/useEventEmitter.d.ts +1 -1
  88. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  89. package/lib/typescript/src/useFocusEvents.d.ts +2 -2
  90. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  91. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts +2 -2
  92. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts.map +1 -1
  93. package/lib/typescript/src/useKeyedChildListeners.d.ts +3 -3
  94. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  95. package/lib/typescript/src/useNavigation.d.ts +5 -3
  96. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  97. package/lib/typescript/src/useNavigationBuilder.d.ts +7 -7
  98. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  99. package/lib/typescript/src/useNavigationCache.d.ts +3 -3
  100. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  101. package/lib/typescript/src/useNavigationContainerRef.d.ts +1 -1
  102. package/lib/typescript/src/useNavigationContainerRef.d.ts.map +1 -1
  103. package/lib/typescript/src/useNavigationHelpers.d.ts +4 -4
  104. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  105. package/lib/typescript/src/useNavigationState.d.ts +2 -2
  106. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  107. package/lib/typescript/src/useOnAction.d.ts +3 -3
  108. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  109. package/lib/typescript/src/useOnGetState.d.ts +1 -1
  110. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  111. package/lib/typescript/src/useOnPreventRemove.d.ts +3 -3
  112. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  113. package/lib/typescript/src/useOptionsGetters.d.ts +1 -1
  114. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  115. package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -1
  116. package/lib/typescript/src/useRoute.d.ts +2 -2
  117. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  118. package/lib/typescript/src/useRouteCache.d.ts +2 -2
  119. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  120. package/lib/typescript/src/useStateForPath.d.ts +1 -1
  121. package/lib/typescript/src/useStateForPath.d.ts.map +1 -1
  122. package/lib/typescript/src/utilities.d.ts +4 -3
  123. package/lib/typescript/src/utilities.d.ts.map +1 -1
  124. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  125. package/package.json +18 -17
  126. package/src/BaseNavigationContainer.tsx +7 -2
  127. package/src/StaticNavigation.tsx +220 -248
  128. package/src/findFocusedRoute.tsx +10 -5
  129. package/src/getActionFromState.tsx +43 -30
  130. package/src/getPathFromState.tsx +40 -20
  131. package/src/getPatternParts.tsx +8 -0
  132. package/src/getStateFromPath.tsx +244 -67
  133. package/src/index.tsx +39 -1
  134. package/src/types.tsx +167 -122
  135. package/src/useDescriptors.tsx +22 -10
  136. package/src/useFocusEvents.tsx +7 -1
  137. package/src/useNavigation.tsx +8 -4
  138. package/src/useNavigationBuilder.tsx +22 -7
  139. package/src/useNavigationCache.tsx +4 -4
  140. package/src/useNavigationState.tsx +3 -3
  141. package/src/useOnAction.tsx +1 -1
  142. package/src/useRoute.tsx +4 -2
  143. package/src/utilities.tsx +17 -11
  144. package/src/validatePathConfig.tsx +1 -0
@@ -8,7 +8,11 @@ import queryString from 'query-string';
8
8
 
9
9
  import { arrayStartsWith } from './arrayStartsWith';
10
10
  import { findFocusedRoute } from './findFocusedRoute';
11
- import { getPatternParts, type PatternPart } from './getPatternParts';
11
+ import {
12
+ combinePatternParts,
13
+ getPatternParts,
14
+ type PatternPart,
15
+ } from './getPatternParts';
12
16
  import { isArrayEqual } from './isArrayEqual';
13
17
  import type { PathConfig, PathConfigMap } from './types';
14
18
  import type {
@@ -31,6 +35,7 @@ type ParseConfig = Record<string, ParseConfigValue | undefined>;
31
35
 
32
36
  type RouteConfig = {
33
37
  screen: string;
38
+ order: number;
34
39
  regex?: RegExp | undefined;
35
40
  segments: string[];
36
41
  params: { screen: string; name?: string | undefined; index: number }[];
@@ -38,6 +43,7 @@ type RouteConfig = {
38
43
  parse?: ParseConfig | undefined;
39
44
  explicitParamNames?: Set<string> | undefined;
40
45
  hasNestedScreens: boolean;
46
+ shared: boolean;
41
47
  };
42
48
 
43
49
  type InitialRouteConfig = {
@@ -55,10 +61,13 @@ type ParsedRoute = {
55
61
  params?: Record<string, unknown> | undefined;
56
62
  };
57
63
 
64
+ type RoutePatternPart = PatternPart & { screen: string };
65
+
58
66
  type ConfigResources = {
59
67
  initialRoutes: InitialRouteConfig[];
60
68
  configs: RouteConfig[];
61
69
  configsByScreen: Record<string, RouteConfig[]>;
70
+ configsByPattern: Record<string, RouteConfig[]>;
62
71
  };
63
72
 
64
73
  const INVALID_SCHEMA_RESULT_ERROR =
@@ -142,13 +151,28 @@ const getValidationResult = (
142
151
  */
143
152
  export function getStateFromPath<ParamList extends {}>(
144
153
  path: string,
145
- options?: Options<ParamList>
154
+ options?: Options<ParamList>,
155
+ previous?: NavigationState
146
156
  ): ResultState | undefined {
147
- const { initialRoutes, configs, configsByScreen } =
157
+ const { initialRoutes, configs, configsByScreen, configsByPattern } =
148
158
  getConfigResources(options);
149
159
 
150
160
  const screens = options?.screens;
151
161
 
162
+ // When a matched config is shared, the same pattern can resolve to multiple branches
163
+ // pick the one that best matches the current navigation state
164
+ const resolveSharedConfig = (config: RouteConfig) => {
165
+ if (config.shared) {
166
+ const candidates = configsByPattern[config.segments.join('/')];
167
+
168
+ if (candidates) {
169
+ return selectSharedConfig(config, candidates, previous, initialRoutes);
170
+ }
171
+ }
172
+
173
+ return config;
174
+ };
175
+
152
176
  let remaining = path
153
177
  .replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
154
178
  .replace(/^\//, '') // Remove extra leading slash
@@ -203,11 +227,13 @@ export function getStateFromPath<ParamList extends {}>(
203
227
  const match = configs.find((config) => config.segments.join('/') === '');
204
228
 
205
229
  if (match) {
230
+ const config = resolveSharedConfig(match);
231
+
206
232
  return createNestedStateObject(
207
233
  path,
208
- match.routeNames.map((name) => ({ name })),
234
+ config.routeNames.map((name) => ({ name })),
209
235
  initialRoutes,
210
- match
236
+ config
211
237
  );
212
238
  }
213
239
 
@@ -223,7 +249,29 @@ export function getStateFromPath<ParamList extends {}>(
223
249
  continue;
224
250
  }
225
251
 
226
- const state = createNestedStateObject(path, routes, initialRoutes, config);
252
+ let selectedConfig = resolveSharedConfig(config);
253
+ let selectedRoutes = routes;
254
+
255
+ if (selectedConfig !== config) {
256
+ const rematchedRoutes = matchAgainstConfig(
257
+ remaining,
258
+ selectedConfig,
259
+ configsByScreen
260
+ );
261
+
262
+ if (rematchedRoutes === undefined) {
263
+ selectedConfig = config;
264
+ } else {
265
+ selectedRoutes = rematchedRoutes;
266
+ }
267
+ }
268
+
269
+ const state = createNestedStateObject(
270
+ path,
271
+ selectedRoutes,
272
+ initialRoutes,
273
+ selectedConfig
274
+ );
227
275
 
228
276
  if (state !== undefined) {
229
277
  return state;
@@ -265,15 +313,18 @@ function prepareConfigResources(options?: Options<{}>) {
265
313
  checkForDuplicatedConfigs(configs);
266
314
 
267
315
  const configsByScreen: Record<string, RouteConfig[]> = {};
316
+ const configsByPattern: Record<string, RouteConfig[]> = {};
268
317
 
269
318
  for (const c of configs) {
270
319
  (configsByScreen[c.screen] ??= []).push(c);
320
+ (configsByPattern[c.segments.join('/')] ??= []).push(c);
271
321
  }
272
322
 
273
323
  return {
274
324
  initialRoutes,
275
325
  configs,
276
326
  configsByScreen,
327
+ configsByPattern,
277
328
  };
278
329
  }
279
330
 
@@ -301,6 +352,7 @@ function getSortedNormalizedConfigs(
301
352
  createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
302
353
  )
303
354
  )
355
+ .map((config, order) => ({ ...config, order }))
304
356
  .sort((a, b) => {
305
357
  // Sort config from most specific to least specific:
306
358
  // - more segments
@@ -326,22 +378,25 @@ function getSortedNormalizedConfigs(
326
378
  }
327
379
 
328
380
  for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
381
+ const aSegment = a.segments[i];
382
+ const bSegment = b.segments[i];
383
+
329
384
  // if b is longer, b gets higher priority
330
- if (a.segments[i] == null) {
385
+ if (aSegment == null) {
331
386
  return 1;
332
387
  }
333
388
 
334
389
  // if a is longer, a gets higher priority
335
- if (b.segments[i] == null) {
390
+ if (bSegment == null) {
336
391
  return -1;
337
392
  }
338
393
 
339
- const aWildCard = a.segments[i] === '*';
340
- const bWildCard = b.segments[i] === '*';
341
- const aParam = a.segments[i].startsWith(':');
342
- const bParam = b.segments[i].startsWith(':');
343
- const aRegex = aParam && a.segments[i].includes('(');
344
- const bRegex = bParam && b.segments[i].includes('(');
394
+ const aWildCard = aSegment === '*';
395
+ const bWildCard = bSegment === '*';
396
+ const aParam = aSegment.startsWith(':');
397
+ const bParam = bSegment.startsWith(':');
398
+ const aRegex = aParam && aSegment.includes('(');
399
+ const bRegex = bParam && bSegment.includes('(');
345
400
 
346
401
  // if both are wildcard or regex, we compare next component
347
402
  if ((aWildCard && bWildCard) || (aRegex && bRegex)) {
@@ -399,13 +454,13 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
399
454
  ? b.every((it, i) => a[i] === it)
400
455
  : a.every((it, i) => b[i] === it);
401
456
 
402
- if (!intersects) {
457
+ if (!intersects && (!acc[pattern].shared || !config.shared)) {
403
458
  throw new Error(
404
459
  `Found conflicting screens with the same pattern. The pattern '${
405
460
  pattern
406
461
  }' resolves to both '${a.join(' > ')}' and '${b.join(
407
462
  ' > '
408
- )}'. Patterns must be unique and cannot resolve to more than one screen.`
463
+ )}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
409
464
  );
410
465
  }
411
466
  }
@@ -434,10 +489,14 @@ const matchAgainstConfig = (
434
489
  let validationFailed = false;
435
490
  const matchedRoutes: ParsedRoute[] = [];
436
491
 
437
- for (const routeName of config.routeNames) {
438
- // Check matching name AND pattern in case same screen is used at different levels in config
439
- const routeConfig = configsByScreen[routeName]?.find((c) =>
440
- arrayStartsWith(config.segments, c.segments)
492
+ for (const [index, routeName] of config.routeNames.entries()) {
493
+ // Check matching name, route names and pattern in case same screen is used
494
+ // at different levels or branches in config
495
+ const routeConfig = configsByScreen[routeName]?.find(
496
+ (c) =>
497
+ index === c.routeNames.length - 1 &&
498
+ arrayStartsWith(config.routeNames, c.routeNames) &&
499
+ arrayStartsWith(config.segments, c.segments)
441
500
  );
442
501
 
443
502
  let params: Record<string, unknown> | undefined;
@@ -523,7 +582,7 @@ const createNormalizedConfigs = (
523
582
  screen: string,
524
583
  routeConfig: Record<string, string | PathConfig<{}>>,
525
584
  initials: InitialRouteConfig[],
526
- paths: { screen: string; path: string }[],
585
+ parts: RoutePatternPart[],
527
586
  parentScreens: string[],
528
587
  routeNames: string[]
529
588
  ): RouteConfig[] => {
@@ -536,22 +595,28 @@ const createNormalizedConfigs = (
536
595
  const config = routeConfig[screen];
537
596
 
538
597
  if (typeof config === 'string') {
539
- paths.push({ screen, path: config });
540
- configs.push(createConfigItem(screen, [...routeNames], [...paths]));
598
+ const configParts = combinePatternParts(
599
+ parts,
600
+ getPatternParts(config).map((part) => ({ ...part, screen }))
601
+ );
602
+
603
+ configs.push(createConfigItem(screen, [...routeNames], configParts));
541
604
  } else if (typeof config === 'object') {
542
605
  // if an object is specified as the value (e.g. Foo: { ... }),
543
606
  // it can have `path` property and
544
607
  // it could have `screens` prop which has nested configs
545
608
  const nestedScreens = 'screens' in config ? config.screens : undefined;
546
609
  const hasNestedScreens = !!nestedScreens;
610
+ const currentParts =
611
+ typeof config.path === 'string'
612
+ ? combinePatternParts(
613
+ parts,
614
+ getPatternParts(config.path).map((part) => ({ ...part, screen })),
615
+ config.exact
616
+ )
617
+ : parts;
547
618
 
548
619
  if (typeof config.path === 'string') {
549
- if (config.exact && config.path == null) {
550
- throw new Error(
551
- `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: ''\`.`
552
- );
553
- }
554
-
555
620
  // We should add alias configs after the main config
556
621
  // So unless they are more specific, main config will be matched first
557
622
  const aliasConfigs = [];
@@ -563,9 +628,13 @@ const createNormalizedConfigs = (
563
628
  createConfigItem(
564
629
  screen,
565
630
  [...routeNames],
566
- [...paths, { screen, path: alias }],
631
+ combinePatternParts(
632
+ parts,
633
+ getPatternParts(alias).map((part) => ({ ...part, screen }))
634
+ ),
567
635
  config.parse,
568
- hasNestedScreens
636
+ hasNestedScreens,
637
+ false
569
638
  )
570
639
  );
571
640
  } else if (typeof alias === 'object') {
@@ -573,31 +642,31 @@ const createNormalizedConfigs = (
573
642
  createConfigItem(
574
643
  screen,
575
644
  [...routeNames],
576
- alias.exact
577
- ? [{ screen, path: alias.path }]
578
- : [...paths, { screen, path: alias.path }],
645
+ combinePatternParts(
646
+ parts,
647
+ getPatternParts(alias.path).map((part) => ({
648
+ ...part,
649
+ screen,
650
+ })),
651
+ alias.exact
652
+ ),
579
653
  alias.parse,
580
- hasNestedScreens
654
+ hasNestedScreens,
655
+ alias.shared === true
581
656
  )
582
657
  );
583
658
  }
584
659
  }
585
660
  }
586
661
 
587
- if (config.exact) {
588
- // If it's an exact path, we don't need to keep track of the parent screens
589
- // So we can clear it
590
- paths.length = 0;
591
- }
592
-
593
- paths.push({ screen, path: config.path });
594
662
  configs.push(
595
663
  createConfigItem(
596
664
  screen,
597
665
  [...routeNames],
598
- [...paths],
666
+ currentParts,
599
667
  config.parse,
600
- hasNestedScreens
668
+ hasNestedScreens,
669
+ config.shared === true
601
670
  )
602
671
  );
603
672
 
@@ -631,7 +700,7 @@ const createNormalizedConfigs = (
631
700
  nestedConfig,
632
701
  nestedScreens as Record<string, string | PathConfig<{}>>,
633
702
  initials,
634
- [...paths],
703
+ currentParts,
635
704
  [...parentScreens],
636
705
  routeNames
637
706
  );
@@ -649,17 +718,11 @@ const createNormalizedConfigs = (
649
718
  const createConfigItem = (
650
719
  screen: string,
651
720
  routeNames: string[],
652
- paths: { screen: string; path: string }[],
721
+ parts: RoutePatternPart[],
653
722
  parse?: ParseConfig,
654
- hasNestedScreens = false
723
+ hasNestedScreens = false,
724
+ shared = false
655
725
  ): RouteConfig => {
656
- const parts: (PatternPart & { screen: string })[] = [];
657
-
658
- // Parse the path string into parts for easier matching
659
- for (const { screen, path } of paths) {
660
- parts.push(...getPatternParts(path).map((part) => ({ ...part, screen })));
661
- }
662
-
663
726
  const regex = parts.length
664
727
  ? new RegExp(
665
728
  `^(${parts
@@ -691,6 +754,7 @@ const createConfigItem = (
691
754
 
692
755
  return {
693
756
  screen,
757
+ order: 0,
694
758
  regex,
695
759
  segments,
696
760
  params,
@@ -698,7 +762,87 @@ const createConfigItem = (
698
762
  parse,
699
763
  explicitParamNames: getExplicitParamNames(parse),
700
764
  hasNestedScreens,
765
+ shared,
766
+ };
767
+ };
768
+
769
+ const selectSharedConfig = (
770
+ match: RouteConfig,
771
+ candidates: RouteConfig[],
772
+ previous: NavigationState | PartialState<NavigationState> | undefined,
773
+ initialRoutes: InitialRouteConfig[]
774
+ ) => {
775
+ // Walk the previous state down to the focused leaf to get the active route path
776
+ const focusedRouteNames: string[] = [];
777
+
778
+ let current = previous;
779
+
780
+ while (current?.routes.length) {
781
+ const index =
782
+ typeof current.index === 'number'
783
+ ? current.index
784
+ : current.routes.length - 1;
785
+
786
+ const route = current.routes[index];
787
+
788
+ if (route == null) {
789
+ break;
790
+ }
791
+
792
+ focusedRouteNames.push(route.name);
793
+ current = route.state;
794
+ }
795
+
796
+ // Rank a candidate by how far it aligns with the focused path, then by
797
+ // how many of its levels match a configured initial route
798
+ const score = (routeNames: string[]) => {
799
+ let focused = 0;
800
+
801
+ while (
802
+ routeNames[focused] !== undefined &&
803
+ routeNames[focused] === focusedRouteNames[focused]
804
+ ) {
805
+ focused++;
806
+ }
807
+
808
+ let initial = 0;
809
+
810
+ for (const config of initialRoutes) {
811
+ if (
812
+ arrayStartsWith(routeNames, config.parentScreens) &&
813
+ routeNames[config.parentScreens.length] === config.initialRouteName
814
+ ) {
815
+ initial++;
816
+ }
817
+ }
818
+
819
+ return { focused, initial };
701
820
  };
821
+
822
+ let selected = match;
823
+ let best = { focused: -1, initial: -1 };
824
+
825
+ for (const candidate of candidates) {
826
+ if (!candidate.shared) {
827
+ continue;
828
+ }
829
+
830
+ const { focused, initial } = score(candidate.routeNames);
831
+
832
+ const better =
833
+ focused !== best.focused
834
+ ? focused > best.focused
835
+ : initial !== best.initial
836
+ ? initial > best.initial
837
+ : candidate.order < selected.order;
838
+
839
+ if (better) {
840
+ selected = candidate;
841
+ best = { focused, initial };
842
+ }
843
+ }
844
+
845
+ return selected;
702
846
  };
703
847
 
704
848
  // Try to find an initial route connected with the one passed
@@ -711,7 +855,14 @@ const findInitialRoute = (
711
855
  if (parentScreens.length === config.parentScreens.length) {
712
856
  let sameParents = true;
713
857
  for (let i = 0; i < parentScreens.length; i++) {
714
- if (parentScreens[i].localeCompare(config.parentScreens[i]) !== 0) {
858
+ const parentScreen = parentScreens[i];
859
+ const configParentScreen = config.parentScreens[i];
860
+
861
+ if (
862
+ parentScreen == null ||
863
+ configParentScreen == null ||
864
+ parentScreen.localeCompare(configParentScreen) !== 0
865
+ ) {
715
866
  sameParents = false;
716
867
  break;
717
868
  }
@@ -764,7 +915,12 @@ const createNestedStateObject = (
764
915
  initialRoutes: InitialRouteConfig[],
765
916
  routeConfig?: RouteConfig
766
917
  ): InitialState | undefined => {
767
- let route = routes.shift() as ParsedRoute;
918
+ let route = routes.shift();
919
+
920
+ if (route == null) {
921
+ return undefined;
922
+ }
923
+
768
924
  const parentScreens: string[] = [];
769
925
 
770
926
  let initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
@@ -780,29 +936,48 @@ const createNestedStateObject = (
780
936
  if (routes.length > 0) {
781
937
  let nestedState = state;
782
938
 
783
- while ((route = routes.shift() as ParsedRoute)) {
939
+ let nextRoute = routes.shift();
940
+
941
+ while (nextRoute != null) {
942
+ route = nextRoute;
784
943
  initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
785
944
 
786
945
  const nestedStateIndex =
787
946
  nestedState.index || nestedState.routes.length - 1;
947
+ const nestedRoute = nestedState.routes[nestedStateIndex];
948
+
949
+ if (nestedRoute == null) {
950
+ throw new Error(`Couldn't find a route at index ${nestedStateIndex}.`);
951
+ }
788
952
 
789
- nestedState.routes[nestedStateIndex].state = createStateObject(
953
+ nestedRoute.state = createStateObject(
790
954
  initialRoute,
791
955
  route,
792
956
  routes.length === 0
793
957
  );
794
958
 
795
959
  if (routes.length > 0) {
796
- nestedState = nestedState.routes[nestedStateIndex]
797
- .state as InitialState;
960
+ if (nestedRoute.state == null) {
961
+ throw new Error(
962
+ `Couldn't find nested state for route '${route.name}'.`
963
+ );
964
+ }
965
+
966
+ nestedState = nestedRoute.state;
798
967
  }
799
968
 
800
969
  parentScreens.push(route.name);
970
+ nextRoute = routes.shift();
801
971
  }
802
972
  }
803
973
 
804
- route = findFocusedRoute(state) as ParsedRoute;
805
- route.path = path.replace(/\/$/, '');
974
+ const focusedRoute = findFocusedRoute(state);
975
+
976
+ if (focusedRoute == null) {
977
+ return undefined;
978
+ }
979
+
980
+ focusedRoute.path = path.replace(/\/$/, '');
806
981
 
807
982
  const pathParamNames = new Set(
808
983
  routeConfig?.params
@@ -819,7 +994,7 @@ const createNestedStateObject = (
819
994
  pathParamNames,
820
995
  routeConfig?.explicitParamNames,
821
996
  routeConfig?.hasNestedScreens,
822
- route.params
997
+ focusedRoute.params
823
998
  );
824
999
 
825
1000
  if (!queryParams.valid) {
@@ -827,7 +1002,7 @@ const createNestedStateObject = (
827
1002
  }
828
1003
 
829
1004
  if (queryParams.params) {
830
- route.params = { ...route.params, ...queryParams.params };
1005
+ focusedRoute.params = { ...focusedRoute.params, ...queryParams.params };
831
1006
  }
832
1007
 
833
1008
  return state;
@@ -839,12 +1014,12 @@ const parseQueryParams = (
839
1014
  pathParamNames: Set<string> = new Set(),
840
1015
  explicitParamNames?: Set<string>,
841
1016
  hasNestedScreens = false,
842
- routeParams?: Record<string, unknown>
1017
+ routeParams?: object
843
1018
  ):
844
1019
  | { valid: true; params?: Record<string, unknown> | undefined }
845
1020
  | { valid: false } => {
846
1021
  const query = path.split('?')[1];
847
- const params: Record<string, unknown> = queryString.parse(query);
1022
+ const params: Record<string, unknown> = query ? queryString.parse(query) : {};
848
1023
 
849
1024
  // Path params should always win over same-named query params.
850
1025
  for (const name of pathParamNames) {
@@ -912,7 +1087,9 @@ const parseQueryParams = (
912
1087
  hasNestedScreens &&
913
1088
  !explicitParamNames?.has('screen') &&
914
1089
  (typeof params.screen === 'string' ||
915
- typeof routeParams?.screen === 'string')
1090
+ (routeParams != null &&
1091
+ 'screen' in routeParams &&
1092
+ typeof routeParams.screen === 'string'))
916
1093
  ) {
917
1094
  for (const name of NESTED_SCREEN_PARAM_NAMES) {
918
1095
  if (!explicitParamNames?.has(name)) {
package/src/index.tsx CHANGED
@@ -35,7 +35,45 @@ export {
35
35
  export { ThemeContext } from './theming/ThemeContext';
36
36
  export { ThemeProvider } from './theming/ThemeProvider';
37
37
  export { useTheme } from './theming/useTheme';
38
- export * from './types';
38
+ export type {
39
+ CompositeNavigationProp,
40
+ CompositeScreenProps,
41
+ DefaultNavigatorOptions,
42
+ Descriptor,
43
+ EventArg,
44
+ EventConsumer,
45
+ EventEmitter,
46
+ EventListenerCallback,
47
+ EventMapBase,
48
+ EventMapCore,
49
+ GenericNavigation,
50
+ NavigationContainerProps,
51
+ NavigationContainerRef,
52
+ NavigationContainerRefWithCurrent,
53
+ NavigationHelpers,
54
+ NavigationListBase,
55
+ NavigationListForNavigator,
56
+ NavigationListForNested,
57
+ NavigationProp,
58
+ NavigatorScreenParams,
59
+ NavigatorTypeBagBase,
60
+ NavigatorTypeBagFor,
61
+ PathConfig,
62
+ PathConfigMap,
63
+ RootNavigator,
64
+ RootParamList,
65
+ RouteConfig,
66
+ RouteConfigComponent,
67
+ RouteConfigProps,
68
+ RouteForName,
69
+ RouteGroupConfig,
70
+ RouteProp,
71
+ ScreenLayoutArgs,
72
+ ScreenListeners,
73
+ Theme,
74
+ TypedNavigator,
75
+ } from './types';
76
+ export { PrivateValueStore } from './types';
39
77
  export { useFocusEffect } from './useFocusEffect';
40
78
  export { IsFocusedContext, useIsFocused } from './useIsFocused';
41
79
  export { useNavigation } from './useNavigation';