@react-navigation/core 8.0.0-alpha.22 → 8.0.0-alpha.24

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 (63) hide show
  1. package/lib/module/NavigationBuilderContext.js.map +1 -1
  2. package/lib/module/StaticNavigation.js +1 -1
  3. package/lib/module/StaticNavigation.js.map +1 -1
  4. package/lib/module/createNavigatorFactory.js.map +1 -1
  5. package/lib/module/getPathFromState.js +87 -57
  6. package/lib/module/getPathFromState.js.map +1 -1
  7. package/lib/module/getStateFromPath.js +77 -46
  8. package/lib/module/getStateFromPath.js.map +1 -1
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/types.js +13 -0
  11. package/lib/module/types.js.map +1 -1
  12. package/lib/module/useEventEmitter.js +4 -1
  13. package/lib/module/useEventEmitter.js.map +1 -1
  14. package/lib/module/useNavigationBuilder.js +11 -14
  15. package/lib/module/useNavigationBuilder.js.map +1 -1
  16. package/lib/module/useOnAction.js +4 -1
  17. package/lib/module/useOnAction.js.map +1 -1
  18. package/lib/module/useOnPreventRemove.js +35 -24
  19. package/lib/module/useOnPreventRemove.js.map +1 -1
  20. package/lib/module/useRoute.js.map +1 -1
  21. package/lib/module/validatePathConfig.js +50 -37
  22. package/lib/module/validatePathConfig.js.map +1 -1
  23. package/lib/typescript/src/NavigationBuilderContext.d.ts +2 -2
  24. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  25. package/lib/typescript/src/StaticNavigation.d.ts +3 -3
  26. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  27. package/lib/typescript/src/createNavigatorFactory.d.ts +4 -2
  28. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  29. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  30. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  31. package/lib/typescript/src/index.d.ts +1 -1
  32. package/lib/typescript/src/index.d.ts.map +1 -1
  33. package/lib/typescript/src/types.d.ts +66 -33
  34. package/lib/typescript/src/types.d.ts.map +1 -1
  35. package/lib/typescript/src/useEventEmitter.d.ts +1 -1
  36. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  37. package/lib/typescript/src/useNavigationBuilder.d.ts +0 -7
  38. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  39. package/lib/typescript/src/useNavigationHelpers.d.ts +0 -7
  40. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  41. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  42. package/lib/typescript/src/useOnPreventRemove.d.ts +2 -6
  43. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  44. package/lib/typescript/src/useRoute.d.ts +1 -1
  45. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  46. package/lib/typescript/src/utilities.d.ts +4 -38
  47. package/lib/typescript/src/utilities.d.ts.map +1 -1
  48. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  49. package/package.json +5 -5
  50. package/src/NavigationBuilderContext.tsx +5 -1
  51. package/src/StaticNavigation.tsx +7 -6
  52. package/src/createNavigatorFactory.tsx +23 -2
  53. package/src/getPathFromState.tsx +97 -66
  54. package/src/getStateFromPath.tsx +168 -142
  55. package/src/index.tsx +1 -0
  56. package/src/types.tsx +257 -54
  57. package/src/useEventEmitter.tsx +6 -1
  58. package/src/useNavigationBuilder.tsx +11 -15
  59. package/src/useOnAction.tsx +6 -2
  60. package/src/useOnPreventRemove.tsx +68 -36
  61. package/src/useRoute.tsx +3 -5
  62. package/src/utilities.tsx +8 -41
  63. package/src/validatePathConfig.tsx +85 -58
@@ -37,8 +37,9 @@ type RouteConfig = {
37
37
  screen: string;
38
38
  order: number;
39
39
  regex?: RegExp | undefined;
40
+ pattern: string;
40
41
  segments: string[];
41
- params: { screen: string; name?: string | undefined; index: number }[];
42
+ params: { screen: string; name: string; index: number }[];
42
43
  routeNames: string[];
43
44
  parse?: ParseConfig | undefined;
44
45
  explicitParamNames?: Set<string> | undefined;
@@ -57,7 +58,6 @@ type ResultState = PartialState<NavigationState> & {
57
58
 
58
59
  type ParsedRoute = {
59
60
  name: string;
60
- path?: string | undefined;
61
61
  params?: Record<string, unknown> | undefined;
62
62
  };
63
63
 
@@ -88,9 +88,15 @@ const NESTED_SCREEN_PARAM_NAMES = [
88
88
  ];
89
89
 
90
90
  const getExplicitParamNames = (parse?: ParseConfig) => {
91
- const names = Object.entries(parse ?? {})
92
- .filter(([, parser]) => parser != null)
93
- .map(([name]) => name);
91
+ const names: string[] = [];
92
+
93
+ if (parse) {
94
+ for (const name in parse) {
95
+ if (parse[name] != null) {
96
+ names.push(name);
97
+ }
98
+ }
99
+ }
94
100
 
95
101
  return names.length ? new Set(names) : undefined;
96
102
  };
@@ -163,7 +169,7 @@ export function getStateFromPath<ParamList extends {}>(
163
169
  // pick the one that best matches the current navigation state
164
170
  const resolveSharedConfig = (config: RouteConfig) => {
165
171
  if (config.shared) {
166
- const candidates = configsByPattern[config.segments.join('/')];
172
+ const candidates = configsByPattern[config.pattern];
167
173
 
168
174
  if (candidates) {
169
175
  return selectSharedConfig(config, candidates, previous, initialRoutes);
@@ -224,7 +230,7 @@ export function getStateFromPath<ParamList extends {}>(
224
230
  if (remaining === '/') {
225
231
  // We need to add special handling of empty path so navigation to empty path also works
226
232
  // When handling empty path, we should only look at the root level config
227
- const match = configs.find((config) => config.segments.join('/') === '');
233
+ const match = configs.find((config) => config.pattern === '');
228
234
 
229
235
  if (match) {
230
236
  const config = resolveSharedConfig(match);
@@ -317,7 +323,7 @@ function prepareConfigResources(options?: Options<{}>) {
317
323
 
318
324
  for (const c of configs) {
319
325
  (configsByScreen[c.screen] ??= []).push(c);
320
- (configsByPattern[c.segments.join('/')] ??= []).push(c);
326
+ (configsByPattern[c.pattern] ??= []).push(c);
321
327
  }
322
328
 
323
329
  return {
@@ -346,105 +352,117 @@ function getSortedNormalizedConfigs(
346
352
  screens: Record<string, string | PathConfig<{}>> = {}
347
353
  ) {
348
354
  // Create a normalized configs array which will be easier to use
349
- return ([] as RouteConfig[])
350
- .concat(
351
- ...Object.keys(screens).map((key) =>
352
- createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
353
- )
354
- )
355
- .map((config, order) => ({ ...config, order }))
356
- .sort((a, b) => {
357
- // Sort config from most specific to least specific:
358
- // - more segments
359
- // - static segments
360
- // - params with regex
361
- // - regular params
362
- // - wildcard
363
-
364
- // If 2 patterns are same, move the one with less route names up
365
- // This is an error state, so it's only useful for consistent error messages
366
- if (isArrayEqual(a.segments, b.segments)) {
367
- return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
368
- }
355
+ const configs: RouteConfig[] = [];
369
356
 
370
- // If one of the patterns starts with the other, it's more exhaustive
371
- // So move it up
372
- if (arrayStartsWith(a.segments, b.segments)) {
373
- return -1;
374
- }
357
+ for (const key in screens) {
358
+ configs.push(
359
+ ...createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
360
+ );
361
+ }
375
362
 
376
- if (arrayStartsWith(b.segments, a.segments)) {
377
- return 1;
378
- }
363
+ for (let order = 0; order < configs.length; order++) {
364
+ const config = configs[order];
379
365
 
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];
366
+ if (config) {
367
+ config.order = order;
368
+ }
369
+ }
383
370
 
384
- // if b is longer, b gets higher priority
385
- if (aSegment == null) {
386
- return 1;
387
- }
371
+ return configs.sort((a, b) => {
372
+ // Sort config from most specific to least specific:
373
+ // - more segments
374
+ // - static segments
375
+ // - params with regex
376
+ // - regular params
377
+ // - wildcard
378
+
379
+ // If 2 patterns are same, move the one with less route names up
380
+ // This is an error state, so it's only useful for consistent error messages
381
+ if (isArrayEqual(a.segments, b.segments)) {
382
+ return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
383
+ }
388
384
 
389
- // if a is longer, a gets higher priority
390
- if (bSegment == null) {
391
- return -1;
392
- }
385
+ // If one of the patterns starts with the other, it's more exhaustive
386
+ // So move it up
387
+ if (arrayStartsWith(a.segments, b.segments)) {
388
+ return -1;
389
+ }
393
390
 
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('(');
391
+ if (arrayStartsWith(b.segments, a.segments)) {
392
+ return 1;
393
+ }
400
394
 
401
- // if both are wildcard or regex, we compare next component
402
- if ((aWildCard && bWildCard) || (aRegex && bRegex)) {
403
- continue;
404
- }
395
+ for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
396
+ const aSegment = a.segments[i];
397
+ const bSegment = b.segments[i];
405
398
 
406
- // if only a is wildcard, b gets higher priority
407
- if (aWildCard && !bWildCard) {
408
- return 1;
409
- }
399
+ // if b is longer, b gets higher priority
400
+ if (aSegment == null) {
401
+ return 1;
402
+ }
410
403
 
411
- // if only b is wildcard, a gets higher priority
412
- if (bWildCard && !aWildCard) {
413
- return -1;
414
- }
404
+ // if a is longer, a gets higher priority
405
+ if (bSegment == null) {
406
+ return -1;
407
+ }
415
408
 
416
- // If only a has a param, b gets higher priority
417
- if (aParam && !bParam) {
418
- return 1;
419
- }
409
+ const aWildCard = aSegment === '*';
410
+ const bWildCard = bSegment === '*';
411
+ const aParam = aSegment.startsWith(':');
412
+ const bParam = bSegment.startsWith(':');
413
+ const aRegex = aParam && aSegment.includes('(');
414
+ const bRegex = bParam && bSegment.includes('(');
420
415
 
421
- // If only b has a param, a gets higher priority
422
- if (bParam && !aParam) {
423
- return -1;
424
- }
416
+ // if both are wildcard or regex, we compare next component
417
+ if ((aWildCard && bWildCard) || (aRegex && bRegex)) {
418
+ continue;
419
+ }
425
420
 
426
- // if only a has regex, a gets higher priority
427
- if (aRegex && !bRegex) {
428
- return -1;
429
- }
421
+ // if only a is wildcard, b gets higher priority
422
+ if (aWildCard && !bWildCard) {
423
+ return 1;
424
+ }
430
425
 
431
- // if only b has regex, b gets higher priority
432
- if (bRegex && !aRegex) {
433
- return 1;
434
- }
426
+ // if only b is wildcard, a gets higher priority
427
+ if (bWildCard && !aWildCard) {
428
+ return -1;
435
429
  }
436
430
 
437
- return a.segments.length - b.segments.length;
438
- });
431
+ // If only a has a param, b gets higher priority
432
+ if (aParam && !bParam) {
433
+ return 1;
434
+ }
435
+
436
+ // If only b has a param, a gets higher priority
437
+ if (bParam && !aParam) {
438
+ return -1;
439
+ }
440
+
441
+ // if only a has regex, a gets higher priority
442
+ if (aRegex && !bRegex) {
443
+ return -1;
444
+ }
445
+
446
+ // if only b has regex, b gets higher priority
447
+ if (bRegex && !aRegex) {
448
+ return 1;
449
+ }
450
+ }
451
+
452
+ return a.segments.length - b.segments.length;
453
+ });
439
454
  }
440
455
 
441
456
  function checkForDuplicatedConfigs(configs: RouteConfig[]) {
442
457
  // Check for duplicate patterns in the config
443
- configs.reduce<Record<string, RouteConfig>>((acc, config) => {
444
- const pattern = config.segments.join('/');
458
+ const patterns: Record<string, RouteConfig> = {};
445
459
 
446
- if (acc[pattern]) {
447
- const a = acc[pattern].routeNames;
460
+ for (const config of configs) {
461
+ const pattern = config.pattern;
462
+ const existing = patterns[pattern];
463
+
464
+ if (existing) {
465
+ const a = existing.routeNames;
448
466
  const b = config.routeNames;
449
467
 
450
468
  // It's not a problem if the path string omitted from a inner most screen
@@ -454,7 +472,7 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
454
472
  ? b.every((it, i) => a[i] === it)
455
473
  : a.every((it, i) => b[i] === it);
456
474
 
457
- if (!intersects && (!acc[pattern].shared || !config.shared)) {
475
+ if (!intersects && (!existing.shared || !config.shared)) {
458
476
  throw new Error(
459
477
  `Found conflicting screens with the same pattern. The pattern '${
460
478
  pattern
@@ -465,10 +483,8 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
465
483
  }
466
484
  }
467
485
 
468
- return Object.assign(acc, {
469
- [pattern]: config,
470
- });
471
- }, {});
486
+ patterns[pattern] = config;
487
+ }
472
488
  }
473
489
 
474
490
  const matchAgainstConfig = (
@@ -500,20 +516,23 @@ const matchAgainstConfig = (
500
516
  );
501
517
 
502
518
  let params: Record<string, unknown> | undefined;
519
+ let hasParams = false;
503
520
 
504
521
  if (routeConfig && match.groups) {
505
- const paramEntries: [string, unknown][] = [];
522
+ params = {};
506
523
 
507
- for (const [key, value] of Object.entries(match.groups)) {
524
+ for (const key in match.groups) {
525
+ const value = match.groups[key];
508
526
  const index = Number(key.replace(PARAM_GROUP_PREFIX, ''));
509
527
  const param = routeConfig.params.find((it) => it.index === index);
510
528
 
511
- if (param?.screen !== routeName || !param.name) {
529
+ if (!param || param.screen !== routeName) {
512
530
  continue;
513
531
  }
514
532
 
515
533
  if (value == null) {
516
- paramEntries.push([param.name, undefined]);
534
+ params[param.name] = undefined;
535
+ hasParams = true;
517
536
  continue;
518
537
  }
519
538
 
@@ -528,7 +547,8 @@ const matchAgainstConfig = (
528
547
  const parser = routeConfig.parse?.[param.name];
529
548
 
530
549
  if (!parser) {
531
- paramEntries.push([param.name, decoded]);
550
+ params[param.name] = decoded;
551
+ hasParams = true;
532
552
  continue;
533
553
  }
534
554
 
@@ -542,12 +562,14 @@ const matchAgainstConfig = (
542
562
  break;
543
563
  }
544
564
 
545
- paramEntries.push([param.name, result.value]);
565
+ params[param.name] = result.value;
566
+ hasParams = true;
546
567
  continue;
547
568
  }
548
569
 
549
570
  if (typeof parser === 'function') {
550
- paramEntries.push([param.name, parser(decoded)]);
571
+ params[param.name] = parser(decoded);
572
+ hasParams = true;
551
573
  continue;
552
574
  }
553
575
 
@@ -558,13 +580,9 @@ const matchAgainstConfig = (
558
580
  // A failed param validation invalidates the whole nested route chain for this config.
559
581
  break;
560
582
  }
561
-
562
- if (paramEntries.length) {
563
- params = Object.fromEntries(paramEntries);
564
- }
565
583
  }
566
584
 
567
- if (params && Object.keys(params).length) {
585
+ if (params && hasParams) {
568
586
  matchedRoutes.push({ name: routeName, params });
569
587
  } else {
570
588
  matchedRoutes.push({ name: routeName });
@@ -605,7 +623,10 @@ const createNormalizedConfigs = (
605
623
  // if an object is specified as the value (e.g. Foo: { ... }),
606
624
  // it can have `path` property and
607
625
  // it could have `screens` prop which has nested configs
608
- const nestedScreens = 'screens' in config ? config.screens : undefined;
626
+ const nestedScreens =
627
+ 'screens' in config
628
+ ? (config.screens as Record<string, string | PathConfig<{}>>)
629
+ : undefined;
609
630
  const hasNestedScreens = !!nestedScreens;
610
631
  const currentParts =
611
632
  typeof config.path === 'string'
@@ -683,7 +704,7 @@ const createNormalizedConfigs = (
683
704
  );
684
705
  }
685
706
 
686
- if (hasNestedScreens) {
707
+ if (nestedScreens) {
687
708
  // property `initialRouteName` without `screens` has no purpose
688
709
  if (
689
710
  'initialRouteName' in config &&
@@ -695,10 +716,10 @@ const createNormalizedConfigs = (
695
716
  });
696
717
  }
697
718
 
698
- Object.keys(nestedScreens).forEach((nestedConfig) => {
719
+ for (const nestedConfig in nestedScreens) {
699
720
  const result = createNormalizedConfigs(
700
721
  nestedConfig,
701
- nestedScreens as Record<string, string | PathConfig<{}>>,
722
+ nestedScreens,
702
723
  initials,
703
724
  currentParts,
704
725
  [...parentScreens],
@@ -706,7 +727,7 @@ const createNormalizedConfigs = (
706
727
  );
707
728
 
708
729
  configs.push(...result);
709
- });
730
+ }
710
731
  }
711
732
  }
712
733
 
@@ -723,39 +744,45 @@ const createConfigItem = (
723
744
  hasNestedScreens = false,
724
745
  shared = false
725
746
  ): RouteConfig => {
726
- const regex = parts.length
727
- ? new RegExp(
728
- `^(${parts
729
- .map((it, i) => {
730
- if (it.param) {
731
- const reg = it.regex || '[^/]+';
732
-
733
- return `(((?<${PARAM_GROUP_PREFIX}${i}>${reg})\\/)${it.optional ? '?' : ''})`;
734
- }
735
-
736
- return `${it.segment === '*' ? '.*' : escape(it.segment)}\\/`;
737
- })
738
- .join('')})$`
739
- )
740
- : undefined;
741
-
742
- const segments = parts.map((it) => it.segment);
743
- const params = parts
744
- .map((it, i) =>
745
- it.param
746
- ? {
747
- index: i,
748
- screen: it.screen,
749
- name: it.param,
750
- }
751
- : null
752
- )
753
- .filter((it) => it != null);
747
+ let regex: RegExp | undefined;
748
+
749
+ const segments: string[] = [];
750
+ const params: RouteConfig['params'] = [];
751
+
752
+ if (parts.length) {
753
+ let regexString = '^(';
754
+ let index = 0;
755
+
756
+ for (const part of parts) {
757
+ segments.push(part.segment);
758
+
759
+ if (part.param) {
760
+ const reg = part.regex || '[^/]+';
761
+
762
+ regexString += `(((?<${PARAM_GROUP_PREFIX}${index}>${reg})\\/)${part.optional ? '?' : ''})`;
763
+ params.push({
764
+ index,
765
+ screen: part.screen,
766
+ name: part.param,
767
+ });
768
+ } else {
769
+ regexString += `${part.segment === '*' ? '.*' : escape(part.segment)}\\/`;
770
+ }
771
+
772
+ index++;
773
+ }
774
+
775
+ regexString += ')$';
776
+ regex = new RegExp(regexString);
777
+ }
778
+
779
+ const pattern = segments.join('/');
754
780
 
755
781
  return {
756
782
  screen,
757
783
  order: 0,
758
784
  regex,
785
+ pattern,
759
786
  segments,
760
787
  params,
761
788
  routeNames,
@@ -981,10 +1008,7 @@ const createNestedStateObject = (
981
1008
 
982
1009
  const pathParamNames = new Set(
983
1010
  routeConfig?.params
984
- .filter(
985
- (param): param is { screen: string; name: string; index: number } =>
986
- param.screen === routeConfig.screen && typeof param.name === 'string'
987
- )
1011
+ .filter((param) => param.screen === routeConfig.screen)
988
1012
  .map((param) => param.name)
989
1013
  );
990
1014
 
@@ -1028,7 +1052,9 @@ const parseQueryParams = (
1028
1052
  }
1029
1053
 
1030
1054
  if (parseConfig) {
1031
- for (const [name, parser] of Object.entries(parseConfig)) {
1055
+ for (const name in parseConfig) {
1056
+ const parser = parseConfig[name];
1057
+
1032
1058
  if (!parser || pathParamNames.has(name)) {
1033
1059
  continue;
1034
1060
  }
package/src/index.tsx CHANGED
@@ -47,6 +47,7 @@ export type {
47
47
  EventMapBase,
48
48
  EventMapCore,
49
49
  GenericNavigation,
50
+ NavigationContainerEventMap,
50
51
  NavigationContainerProps,
51
52
  NavigationContainerRef,
52
53
  NavigationContainerRefWithCurrent,