@react-navigation/core 8.0.0-alpha.33 → 8.0.0-alpha.34

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 (78) hide show
  1. package/lib/module/NavigationBuilderContext.js +8 -13
  2. package/lib/module/NavigationBuilderContext.js.map +1 -1
  3. package/lib/module/NavigationIndependentTree.js +42 -15
  4. package/lib/module/NavigationIndependentTree.js.map +1 -1
  5. package/lib/module/getPathFromState.js +41 -16
  6. package/lib/module/getPathFromState.js.map +1 -1
  7. package/lib/module/getPatternParts.js +43 -12
  8. package/lib/module/getPatternParts.js.map +1 -1
  9. package/lib/module/getStateFromPath.js +175 -109
  10. package/lib/module/getStateFromPath.js.map +1 -1
  11. package/lib/module/index.js +1 -0
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/useDescriptors.js +2 -2
  14. package/lib/module/useDescriptors.js.map +1 -1
  15. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  16. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  17. package/lib/module/useNavigationBuilder.js +2 -2
  18. package/lib/module/useNavigationBuilder.js.map +1 -1
  19. package/lib/module/useNavigationCache.js +3 -3
  20. package/lib/module/useNavigationCache.js.map +1 -1
  21. package/lib/module/useNavigationHelpers.js +2 -2
  22. package/lib/module/useNavigationHelpers.js.map +1 -1
  23. package/lib/module/useOnAction.js +6 -4
  24. package/lib/module/useOnAction.js.map +1 -1
  25. package/lib/module/useOnGetNavigation.js +2 -2
  26. package/lib/module/useOnGetNavigation.js.map +1 -1
  27. package/lib/module/useOnGetState.js +2 -2
  28. package/lib/module/useOnGetState.js.map +1 -1
  29. package/lib/module/useOnPreventRemove.js +2 -2
  30. package/lib/module/useOnPreventRemove.js.map +1 -1
  31. package/lib/module/useOnRouteFocus.js +2 -2
  32. package/lib/module/useOnRouteFocus.js.map +1 -1
  33. package/lib/module/useOptionsGetters.js +2 -4
  34. package/lib/module/useOptionsGetters.js.map +1 -1
  35. package/lib/module/useScheduleUpdate.js +2 -2
  36. package/lib/module/useScheduleUpdate.js.map +1 -1
  37. package/lib/typescript/src/NavigationBuilderContext.d.ts +8 -5
  38. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  39. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  40. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  41. package/lib/typescript/src/getPatternParts.d.ts +4 -2
  42. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  43. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  44. package/lib/typescript/src/index.d.ts +1 -0
  45. package/lib/typescript/src/index.d.ts.map +1 -1
  46. package/lib/typescript/src/types.d.ts +1 -1
  47. package/lib/typescript/src/types.d.ts.map +1 -1
  48. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  49. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  50. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  51. package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -1
  52. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  53. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  54. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  55. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  56. package/lib/typescript/src/utilities.d.ts +3 -0
  57. package/lib/typescript/src/utilities.d.ts.map +1 -1
  58. package/package.json +2 -3
  59. package/src/NavigationBuilderContext.tsx +21 -17
  60. package/src/NavigationIndependentTree.tsx +48 -22
  61. package/src/getPathFromState.tsx +63 -15
  62. package/src/getPatternParts.tsx +75 -14
  63. package/src/getStateFromPath.tsx +250 -113
  64. package/src/index.tsx +1 -0
  65. package/src/types.tsx +1 -1
  66. package/src/useDescriptors.tsx +2 -1
  67. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  68. package/src/useNavigationBuilder.tsx +2 -4
  69. package/src/useNavigationCache.tsx +3 -3
  70. package/src/useNavigationHelpers.tsx +2 -2
  71. package/src/useOnAction.tsx +6 -5
  72. package/src/useOnGetNavigation.tsx +3 -2
  73. package/src/useOnGetState.tsx +4 -2
  74. package/src/useOnPreventRemove.tsx +4 -2
  75. package/src/useOnRouteFocus.tsx +2 -4
  76. package/src/useOptionsGetters.tsx +3 -2
  77. package/src/useScheduleUpdate.tsx +2 -2
  78. package/src/utilities.tsx +5 -2
@@ -1,12 +1,11 @@
1
1
  import type { NavigationState, PartialState } from '@react-navigation/routers';
2
- import escape from 'escape-string-regexp';
3
2
  import queryString from 'query-string';
4
3
 
5
4
  import { arrayStartsWith } from './arrayStartsWith';
6
5
  import {
7
6
  combinePatternParts,
8
7
  getPatternParts,
9
- type PatternPart,
8
+ type PatternPartRepeat,
10
9
  } from './getPatternParts';
11
10
  import { isArrayEqual } from './isArrayEqual';
12
11
  import type { PathConfig, PathConfigMap } from './types';
@@ -34,11 +33,13 @@ type RouteConfig = {
34
33
  regex?: RegExp | undefined;
35
34
  pattern: string;
36
35
  segments: string[];
36
+ segmentRanks: number[];
37
37
  params: {
38
38
  screen: string;
39
39
  name: string;
40
40
  index: number;
41
41
  regex?: RegExp | undefined;
42
+ repeat?: PatternPartRepeat;
42
43
  }[];
43
44
  routeNames: string[];
44
45
  parse?: ParseConfig | undefined;
@@ -59,11 +60,34 @@ type ParsedRoute = {
59
60
  path?: string | undefined;
60
61
  };
61
62
 
62
- type RoutePatternPart = PatternPart & { screen: string };
63
+ type RoutePatternPart =
64
+ | {
65
+ type: 'static';
66
+ segment: string;
67
+ rank: number;
68
+ pattern: string;
69
+ }
70
+ | {
71
+ type: 'param';
72
+ screen: string;
73
+ segment: string;
74
+ rank: number;
75
+ name: string;
76
+ pattern?: string;
77
+ regex?: RegExp;
78
+ optional?: boolean;
79
+ repeat?: PatternPartRepeat;
80
+ }
81
+ | {
82
+ type: 'wildcard';
83
+ segment: '*';
84
+ rank: number;
85
+ };
63
86
 
64
87
  type ConfigResources = {
65
88
  initialRoutes: InitialRouteConfig[];
66
89
  configs: RouteConfig[];
90
+ configsWithZeroOrMore: RouteConfig[];
67
91
  configsByScreen: Record<string, RouteConfig[]>;
68
92
  configsByPattern: Record<string, RouteConfig[]>;
69
93
  prefixRegex?: RegExp | undefined;
@@ -86,16 +110,42 @@ const NESTED_SCREEN_PARAM_NAMES = [
86
110
  'pop',
87
111
  ];
88
112
 
89
- const getStaticSegmentPattern = (segment: string) =>
90
- Array.from(segment, (char) => {
91
- const encoded = encodeURIComponent(char);
113
+ const REGEX_SYNTAX_CHAR = /[|\\{}()[\]^$+*?.]/;
114
+ const HEX_DIGITS = '0123456789ABCDEF';
115
+
116
+ const getStaticSegmentPattern = (segment: string) => {
117
+ let pattern = '';
118
+
119
+ for (const char of segment) {
120
+ const code = char.charCodeAt(0);
121
+
122
+ let escaped = char;
123
+
124
+ // A hyphen uses a hexadecimal escape because a regular backslash escape
125
+ // isn't valid outside character classes when a regex uses Unicode mode.
126
+ if (char === '-') {
127
+ escaped = '\\x2d';
128
+ } else {
129
+ // Other characters with special meaning in a regex can use a regular
130
+ // backslash escape so they are matched as literal characters.
131
+ if (REGEX_SYNTAX_CHAR.test(char)) {
132
+ escaped = `\\${char}`;
133
+ }
134
+ }
135
+
136
+ // ASCII characters have a single-byte percent-encoded form that can be
137
+ // built directly. Other characters need encodeURIComponent to encode all
138
+ // bytes in their UTF-8 representation.
92
139
  const percentEncoded =
93
- encoded === char
94
- ? `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`
95
- : encoded;
140
+ char.length === 1 && code < 128
141
+ ? `%${HEX_DIGITS.charAt(code >> 4)}${HEX_DIGITS.charAt(code & 15)}`
142
+ : encodeURIComponent(char);
143
+
144
+ pattern += `(?:${escaped}|${percentEncoded})`;
145
+ }
96
146
 
97
- return `(?:${escape(char)}|${escape(percentEncoded)})`;
98
- }).join('');
147
+ return pattern;
148
+ };
99
149
 
100
150
  const getExplicitParamNames = (parse?: ParseConfig) => {
101
151
  const names: string[] = [];
@@ -173,6 +223,7 @@ export function getStateFromPath<ParamList extends {}>(
173
223
  const {
174
224
  initialRoutes,
175
225
  configs,
226
+ configsWithZeroOrMore,
176
227
  configsByScreen,
177
228
  configsByPattern,
178
229
  prefixRegex,
@@ -244,12 +295,50 @@ export function getStateFromPath<ParamList extends {}>(
244
295
  if (match) {
245
296
  const config = resolveSharedConfig(match);
246
297
 
247
- return createNestedStateObject(
298
+ const state = createNestedStateObject(
248
299
  path,
249
300
  config.routeNames.map((name) => ({ name })),
250
301
  initialRoutes,
251
302
  config
252
303
  );
304
+
305
+ if (state) {
306
+ return state;
307
+ }
308
+ }
309
+
310
+ for (const config of configsWithZeroOrMore) {
311
+ const routes = matchAgainstConfig('', config, configsByScreen);
312
+
313
+ if (routes) {
314
+ let selectedConfig = resolveSharedConfig(config);
315
+ let selectedRoutes = routes;
316
+
317
+ if (selectedConfig !== config) {
318
+ const rematchedRoutes = matchAgainstConfig(
319
+ '',
320
+ selectedConfig,
321
+ configsByScreen
322
+ );
323
+
324
+ if (rematchedRoutes === undefined) {
325
+ selectedConfig = config;
326
+ } else {
327
+ selectedRoutes = rematchedRoutes;
328
+ }
329
+ }
330
+
331
+ const state = createNestedStateObject(
332
+ path,
333
+ selectedRoutes,
334
+ initialRoutes,
335
+ selectedConfig
336
+ );
337
+
338
+ if (state) {
339
+ return state;
340
+ }
341
+ }
253
342
  }
254
343
 
255
344
  return undefined;
@@ -384,8 +473,13 @@ function prepareConfigResources(options?: Options<{}>) {
384
473
 
385
474
  const configsByScreen: Record<string, RouteConfig[]> = {};
386
475
  const configsByPattern: Record<string, RouteConfig[]> = {};
476
+ const configsWithZeroOrMore: RouteConfig[] = [];
387
477
 
388
478
  for (const c of configs) {
479
+ if (c.params.some((param) => param.repeat === 'zero-or-more')) {
480
+ configsWithZeroOrMore.push(c);
481
+ }
482
+
389
483
  (configsByScreen[c.screen] ??= []).push(c);
390
484
 
391
485
  const existing = configsByPattern[c.pattern];
@@ -401,6 +495,7 @@ function prepareConfigResources(options?: Options<{}>) {
401
495
  return {
402
496
  initialRoutes,
403
497
  configs,
498
+ configsWithZeroOrMore,
404
499
  configsByScreen,
405
500
  configsByPattern,
406
501
  prefixRegex,
@@ -428,9 +523,7 @@ function getSortedNormalizedConfigs(
428
523
  const configs: RouteConfig[] = [];
429
524
 
430
525
  for (const key in screens) {
431
- configs.push(
432
- ...createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
433
- );
526
+ setupNormalizedConfigs(key, screens, initialRoutes, configs, [], [], []);
434
527
  }
435
528
 
436
529
  for (let order = 0; order < configs.length; order++) {
@@ -443,15 +536,17 @@ function getSortedNormalizedConfigs(
443
536
 
444
537
  return configs.sort((a, b) => {
445
538
  // Sort config from most specific to least specific:
446
- // - more segments
447
539
  // - static segments
448
540
  // - params with regex
449
541
  // - regular params
542
+ // - one-or-more params
543
+ // - the end of the pattern
544
+ // - zero-or-more params
450
545
  // - wildcard
451
546
 
452
547
  // If 2 patterns are same, move the one with less route names up
453
548
  // This is an error state, so it's only useful for consistent error messages
454
- if (isArrayEqual(a.segments, b.segments)) {
549
+ if (a.pattern === b.pattern && isArrayEqual(a.segments, b.segments)) {
455
550
  if (
456
551
  a.routeNames.length > b.routeNames.length &&
457
552
  arrayStartsWith(a.routeNames, b.routeNames)
@@ -469,76 +564,43 @@ function getSortedNormalizedConfigs(
469
564
  return a.routeNames.length - b.routeNames.length || a.order - b.order;
470
565
  }
471
566
 
472
- // If one of the patterns starts with the other, it's more exhaustive
473
- // So move it up
474
- if (arrayStartsWith(a.segments, b.segments)) {
475
- return -1;
476
- }
477
-
478
- if (arrayStartsWith(b.segments, a.segments)) {
479
- return 1;
480
- }
481
-
482
567
  for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
483
- const aSegment = a.segments[i];
484
- const bSegment = b.segments[i];
485
-
486
- // if b is longer, b gets higher priority
487
- if (aSegment == null) {
488
- return 1;
489
- }
568
+ const rank = (a.segmentRanks[i] ?? 5) - (b.segmentRanks[i] ?? 5);
490
569
 
491
- // if a is longer, a gets higher priority
492
- if (bSegment == null) {
493
- return -1;
570
+ if (rank !== 0) {
571
+ return rank;
494
572
  }
573
+ }
495
574
 
496
- const aWildCard = aSegment === '*';
497
- const bWildCard = bSegment === '*';
498
- const aParam = aSegment.startsWith(':');
499
- const bParam = bSegment.startsWith(':');
500
- const aRegex = aParam && aSegment.includes('(');
501
- const bRegex = bParam && bSegment.includes('(');
502
-
503
- // if both are wildcard or regex, we compare next component
504
- if ((aWildCard && bWildCard) || (aRegex && bRegex)) {
505
- continue;
506
- }
575
+ return a.order - b.order;
576
+ });
577
+ }
507
578
 
508
- // if only a is wildcard, b gets higher priority
509
- if (aWildCard && !bWildCard) {
510
- return 1;
511
- }
579
+ const getSegmentRank = (segment: string | undefined) => {
580
+ if (segment === undefined) {
581
+ return 5;
582
+ }
512
583
 
513
- // if only b is wildcard, a gets higher priority
514
- if (bWildCard && !aWildCard) {
515
- return -1;
516
- }
584
+ if (segment === '*') {
585
+ return 8;
586
+ }
517
587
 
518
- // If only a has a param, b gets higher priority
519
- if (aParam && !bParam) {
520
- return 1;
521
- }
588
+ if (!segment.startsWith(':')) {
589
+ return 0;
590
+ }
522
591
 
523
- // If only b has a param, a gets higher priority
524
- if (bParam && !aParam) {
525
- return -1;
526
- }
592
+ const regex = segment.includes('(');
527
593
 
528
- // if only a has regex, a gets higher priority
529
- if (aRegex && !bRegex) {
530
- return -1;
531
- }
594
+ if (segment.endsWith('+')) {
595
+ return regex ? 3 : 4;
596
+ }
532
597
 
533
- // if only b has regex, b gets higher priority
534
- if (bRegex && !aRegex) {
535
- return 1;
536
- }
537
- }
598
+ if (segment.endsWith('*')) {
599
+ return regex ? 6 : 7;
600
+ }
538
601
 
539
- return a.segments.length - b.segments.length;
540
- });
541
- }
602
+ return regex ? 1 : 2;
603
+ };
542
604
 
543
605
  // Throw if two configs resolve to the same pattern but conflicting screens
544
606
  function checkForDuplicatedConfigs(
@@ -607,26 +669,51 @@ const matchAgainstConfig = (
607
669
  continue;
608
670
  }
609
671
 
610
- const value = match.groups[`${PARAM_GROUP_PREFIX}${param.index}`];
672
+ let value = match.groups[`${PARAM_GROUP_PREFIX}${param.index}`];
611
673
 
612
- if (value == null) {
613
- params[param.name] = undefined;
614
- hasParams = true;
615
- continue;
674
+ if (param.repeat && value != null) {
675
+ value = value.replace(/\/$/, '');
616
676
  }
617
677
 
618
678
  let decoded: string;
679
+ let decodedSegments: string[] | undefined;
619
680
 
620
681
  try {
621
- decoded = decodeURIComponent(value);
682
+ if (param.repeat) {
683
+ if (value == null) {
684
+ decodedSegments = [];
685
+ } else {
686
+ decodedSegments = value.split('/').map(decodeURIComponent);
687
+ }
688
+
689
+ decoded = decodedSegments.join('/');
690
+ } else {
691
+ if (value == null) {
692
+ params[param.name] = undefined;
693
+ hasParams = true;
694
+ continue;
695
+ }
696
+
697
+ decoded = decodeURIComponent(value);
698
+ }
622
699
  } catch {
623
700
  return undefined;
624
701
  }
625
702
 
626
703
  // Percent-encoded values are matched permissively
627
704
  // So validate the decoded value against the custom regex
628
- if (param.regex && value !== decoded && !param.regex.test(decoded)) {
629
- return undefined;
705
+ if (param.regex) {
706
+ const regex = param.regex;
707
+ const values = decodedSegments ?? [decoded];
708
+ const rawValues = param.repeat ? (value?.split('/') ?? []) : [value];
709
+
710
+ if (
711
+ values.some(
712
+ (item, index) => item !== rawValues[index] && !regex.test(item)
713
+ )
714
+ ) {
715
+ return undefined;
716
+ }
630
717
  }
631
718
 
632
719
  const parser = routeConfig.parse?.[param.name];
@@ -681,16 +768,56 @@ const matchAgainstConfig = (
681
768
  return matchedRoutes;
682
769
  };
683
770
 
684
- const createNormalizedConfigs = (
771
+ const getRoutePatternParts = (
772
+ path: string,
773
+ screen: string
774
+ ): RoutePatternPart[] =>
775
+ getPatternParts(path).map((part) => {
776
+ const rank = getSegmentRank(part.segment);
777
+
778
+ if (part.name) {
779
+ return {
780
+ type: 'param',
781
+ screen,
782
+ segment: part.segment,
783
+ rank,
784
+ name: part.name,
785
+ ...(part.pattern
786
+ ? {
787
+ pattern: part.pattern,
788
+ regex: new RegExp(`^(?:${part.pattern})$`),
789
+ }
790
+ : null),
791
+ ...(part.optional ? { optional: true } : null),
792
+ ...(part.repeat ? { repeat: part.repeat } : null),
793
+ };
794
+ }
795
+
796
+ if (part.segment === '*') {
797
+ return {
798
+ type: 'wildcard',
799
+ segment: part.segment,
800
+ rank,
801
+ };
802
+ }
803
+
804
+ return {
805
+ type: 'static',
806
+ segment: part.segment,
807
+ rank,
808
+ pattern: getStaticSegmentPattern(part.segment),
809
+ };
810
+ });
811
+
812
+ const setupNormalizedConfigs = (
685
813
  screen: string,
686
814
  routeConfig: Record<string, string | PathConfig<{}>>,
687
815
  initials: InitialRouteConfig[],
816
+ configs: RouteConfig[],
688
817
  parts: RoutePatternPart[],
689
818
  parentScreens: string[],
690
819
  routeNames: string[]
691
- ): RouteConfig[] => {
692
- const configs: RouteConfig[] = [];
693
-
820
+ ): void => {
694
821
  routeNames.push(screen);
695
822
 
696
823
  parentScreens.push(screen);
@@ -700,7 +827,7 @@ const createNormalizedConfigs = (
700
827
  if (typeof config === 'string') {
701
828
  const configParts = combinePatternParts(
702
829
  parts,
703
- getPatternParts(config).map((part) => ({ ...part, screen }))
830
+ getRoutePatternParts(config, screen)
704
831
  );
705
832
 
706
833
  configs.push(createConfigItem(screen, [...routeNames], configParts));
@@ -717,7 +844,7 @@ const createNormalizedConfigs = (
717
844
  typeof config.path === 'string'
718
845
  ? combinePatternParts(
719
846
  parts,
720
- getPatternParts(config.path).map((part) => ({ ...part, screen })),
847
+ getRoutePatternParts(config.path, screen),
721
848
  config.exact
722
849
  )
723
850
  : parts;
@@ -734,10 +861,7 @@ const createNormalizedConfigs = (
734
861
  createConfigItem(
735
862
  screen,
736
863
  [...routeNames],
737
- combinePatternParts(
738
- parts,
739
- getPatternParts(alias).map((part) => ({ ...part, screen }))
740
- ),
864
+ combinePatternParts(parts, getRoutePatternParts(alias, screen)),
741
865
  config.parse,
742
866
  hasNestedScreens,
743
867
  false
@@ -750,10 +874,7 @@ const createNormalizedConfigs = (
750
874
  [...routeNames],
751
875
  combinePatternParts(
752
876
  parts,
753
- getPatternParts(alias.path).map((part) => ({
754
- ...part,
755
- screen,
756
- })),
877
+ getRoutePatternParts(alias.path, screen),
757
878
  alias.exact
758
879
  ),
759
880
  alias.parse,
@@ -802,23 +923,20 @@ const createNormalizedConfigs = (
802
923
  }
803
924
 
804
925
  for (const nestedConfig in nestedScreens) {
805
- const result = createNormalizedConfigs(
926
+ setupNormalizedConfigs(
806
927
  nestedConfig,
807
928
  nestedScreens,
808
929
  initials,
930
+ configs,
809
931
  currentParts,
810
932
  [...parentScreens],
811
933
  routeNames
812
934
  );
813
-
814
- configs.push(...result);
815
935
  }
816
936
  }
817
937
  }
818
938
 
819
939
  routeNames.pop();
820
-
821
- return configs;
822
940
  };
823
941
 
824
942
  const createConfigItem = (
@@ -832,6 +950,7 @@ const createConfigItem = (
832
950
  let regex: RegExp | undefined;
833
951
 
834
952
  const segments: string[] = [];
953
+ const segmentRanks: number[] = [];
835
954
  const params: RouteConfig['params'] = [];
836
955
  const pathParamNames = new Set<string>();
837
956
 
@@ -841,29 +960,46 @@ const createConfigItem = (
841
960
 
842
961
  for (const part of parts) {
843
962
  segments.push(part.segment);
963
+ segmentRanks.push(part.rank);
844
964
 
845
- if (part.param) {
965
+ if (part.type === 'param') {
846
966
  // A custom regex may not match the percent-encoded form of the value
847
967
  // So also accept segments containing an encoded character and validate them after decoding
848
- const reg = part.regex
849
- ? `(?:${part.regex})|(?=[^/]*%[0-9A-F]{2})[^/]+`
850
- : '[^/]+';
968
+ let reg = '[^/]+';
969
+
970
+ if (part.pattern) {
971
+ const encodedReg = `(?=[^/]*%[0-9A-F]{2})[^/]+`;
972
+
973
+ reg = part.repeat
974
+ ? `(?:${encodedReg})|(?![^/]*%[0-9A-F]{2})(?:${part.pattern})`
975
+ : `(?:${part.pattern})|${encodedReg}`;
976
+ }
977
+
978
+ if (part.repeat) {
979
+ const repeatedReg = `(?:(?:${reg})\\/)+`;
980
+
981
+ regexString += `(?<${PARAM_GROUP_PREFIX}${index}>${repeatedReg})${
982
+ part.repeat === 'zero-or-more' ? '?' : ''
983
+ }`;
984
+ } else {
985
+ regexString += `(((?<${PARAM_GROUP_PREFIX}${index}>${reg})\\/)${part.optional ? '?' : ''})`;
986
+ }
851
987
 
852
- regexString += `(((?<${PARAM_GROUP_PREFIX}${index}>${reg})\\/)${part.optional ? '?' : ''})`;
853
988
  params.push({
854
989
  index,
855
990
  screen: part.screen,
856
- name: part.param,
857
- regex: part.regex ? new RegExp(`^(?:${part.regex})$`) : undefined,
991
+ name: part.name,
992
+ regex: part.regex,
993
+ ...(part.repeat ? { repeat: part.repeat } : {}),
858
994
  });
859
995
 
860
996
  if (part.screen === screen) {
861
- pathParamNames.add(part.param);
997
+ pathParamNames.add(part.name);
862
998
  }
863
- } else if (part.segment === '*') {
999
+ } else if (part.type === 'wildcard') {
864
1000
  regexString += `.*\\/`;
865
1001
  } else {
866
- regexString += `${getStaticSegmentPattern(part.segment)}\\/`;
1002
+ regexString += `${part.pattern}\\/`;
867
1003
  }
868
1004
 
869
1005
  index++;
@@ -881,6 +1017,7 @@ const createConfigItem = (
881
1017
  regex,
882
1018
  pattern,
883
1019
  segments,
1020
+ segmentRanks,
884
1021
  params,
885
1022
  routeNames,
886
1023
  parse,
package/src/index.tsx CHANGED
@@ -24,6 +24,7 @@ export {
24
24
  NavigationProvider,
25
25
  NavigationRouteContext,
26
26
  } from './NavigationProvider';
27
+ export { NavigationRootContext } from './NavigationRootContext';
27
28
  export { PreventRemoveContext } from './PreventRemoveContext';
28
29
  export { PreventRemoveProvider } from './PreventRemoveProvider';
29
30
  export {
package/src/types.tsx CHANGED
@@ -1373,7 +1373,7 @@ export type NavigationContainerRef<ParamList extends {}> = Omit<
1373
1373
  /**
1374
1374
  * Get the rehydrated navigation state of the navigation tree.
1375
1375
  */
1376
- getRootState(): NavigationState;
1376
+ getRootState(): NavigationState | undefined;
1377
1377
  /**
1378
1378
  * Get the currently focused navigation route.
1379
1379
  */
@@ -11,6 +11,7 @@ import {
11
11
  type AddKeyedListener,
12
12
  type AddListener,
13
13
  NavigationBuilderContext,
14
+ useNavigationBuilderContext,
14
15
  } from './NavigationBuilderContext';
15
16
  import { NavigationProvider } from './NavigationProvider';
16
17
  import { SceneView } from './SceneView';
@@ -127,7 +128,7 @@ export function useDescriptors<
127
128
  flushUpdates,
128
129
  getIsStateEmitted,
129
130
  withStackTrace,
130
- } = React.use(NavigationBuilderContext);
131
+ } = useNavigationBuilderContext();
131
132
 
132
133
  const context = React.useMemo(
133
134
  () => ({
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import {
5
5
  type FocusedNavigationCallback,
6
6
  type FocusedNavigationListener,
7
- NavigationBuilderContext,
7
+ useNavigationBuilderContext,
8
8
  } from './NavigationBuilderContext';
9
9
  import type { NavigationHelpers } from './types';
10
10
 
@@ -20,7 +20,7 @@ export function useFocusedListenersChildrenAdapter({
20
20
  navigation,
21
21
  focusedListeners,
22
22
  }: Options) {
23
- const { addListener } = React.use(NavigationBuilderContext);
23
+ const { addListener } = useNavigationBuilderContext();
24
24
 
25
25
  const listener = React.useCallback(
26
26
  (callback: FocusedNavigationCallback<any>) => {
@@ -19,7 +19,7 @@ import { ConsumedParamsContext } from './ConsumedParamsContext';
19
19
  import { deepFreeze } from './deepFreeze';
20
20
  import { Group } from './Group';
21
21
  import { isArrayEqual } from './isArrayEqual';
22
- import { NavigationBuilderContext } from './NavigationBuilderContext';
22
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
23
23
  import { NavigationHelpersContext } from './NavigationHelpersContext';
24
24
  import { NavigationMetaContext } from './NavigationMetaContext';
25
25
  import { IsScreenContext, NavigationRouteContext } from './NavigationProvider';
@@ -460,9 +460,7 @@ export function useNavigationBuilder<
460
460
  getIsInitial,
461
461
  } = React.use(NavigationStateContext);
462
462
 
463
- const { onEmitEvent, getIsStateEmitted } = React.use(
464
- NavigationBuilderContext
465
- );
463
+ const { onEmitEvent, getIsStateEmitted } = useNavigationBuilderContext();
466
464
 
467
465
  const stateCleanupRef = React.useRef<boolean>(false);
468
466
  const lastStateRef = React.useRef<State | PartialState<State> | undefined>(
@@ -7,7 +7,7 @@ import {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
- import { NavigationBuilderContext } from './NavigationBuilderContext';
10
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
11
11
  import { NavigationContext } from './NavigationProvider';
12
12
  import type { NavigationHelpers, NavigationProp } from './types';
13
13
  import type { NavigationEventEmitter } from './useEventEmitter';
@@ -72,9 +72,9 @@ export function useNavigationCache<
72
72
  router,
73
73
  emitter,
74
74
  }: Options<State, ScreenOptions, EventMap>) {
75
- const parentNavigation = React.use(NavigationContext);
75
+ const { withStackTrace } = useNavigationBuilderContext();
76
76
 
77
- const { withStackTrace } = React.use(NavigationBuilderContext);
77
+ const parentNavigation = React.use(NavigationContext);
78
78
 
79
79
  // Cache object which holds navigation objects for each screen
80
80
  // We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change