@react-navigation/core 8.0.0-alpha.32 → 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.
- package/lib/module/NavigationBuilderContext.js +8 -13
- package/lib/module/NavigationBuilderContext.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +42 -15
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/getPathFromState.js +41 -16
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +43 -12
- package/lib/module/getPatternParts.js.map +1 -1
- package/lib/module/getStateFromPath.js +176 -110
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useDescriptors.js +2 -2
- package/lib/module/useDescriptors.js.map +1 -1
- package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
- package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +2 -2
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +3 -3
- package/lib/module/useNavigationCache.js.map +1 -1
- package/lib/module/useNavigationHelpers.js +2 -2
- package/lib/module/useNavigationHelpers.js.map +1 -1
- package/lib/module/useOnAction.js +6 -4
- package/lib/module/useOnAction.js.map +1 -1
- package/lib/module/useOnGetNavigation.js +2 -2
- package/lib/module/useOnGetNavigation.js.map +1 -1
- package/lib/module/useOnGetState.js +2 -2
- package/lib/module/useOnGetState.js.map +1 -1
- package/lib/module/useOnPreventRemove.js +2 -2
- package/lib/module/useOnPreventRemove.js.map +1 -1
- package/lib/module/useOnRouteFocus.js +2 -2
- package/lib/module/useOnRouteFocus.js.map +1 -1
- package/lib/module/useOptionsGetters.js +2 -4
- package/lib/module/useOptionsGetters.js.map +1 -1
- package/lib/module/useScheduleUpdate.js +2 -2
- package/lib/module/useScheduleUpdate.js.map +1 -1
- package/lib/typescript/src/NavigationBuilderContext.d.ts +8 -5
- package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/src/getPatternParts.d.ts +4 -2
- package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +1 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useOnAction.d.ts.map +1 -1
- package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -1
- package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
- package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
- package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
- package/lib/typescript/src/utilities.d.ts +3 -0
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/package.json +2 -3
- package/src/NavigationBuilderContext.tsx +21 -17
- package/src/NavigationIndependentTree.tsx +48 -22
- package/src/getPathFromState.tsx +63 -15
- package/src/getPatternParts.tsx +75 -14
- package/src/getStateFromPath.tsx +251 -114
- package/src/index.tsx +1 -0
- package/src/types.tsx +1 -1
- package/src/useDescriptors.tsx +2 -1
- package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
- package/src/useNavigationBuilder.tsx +2 -4
- package/src/useNavigationCache.tsx +3 -3
- package/src/useNavigationHelpers.tsx +2 -2
- package/src/useOnAction.tsx +6 -5
- package/src/useOnGetNavigation.tsx +3 -2
- package/src/useOnGetState.tsx +4 -2
- package/src/useOnPreventRemove.tsx +4 -2
- package/src/useOnRouteFocus.tsx +2 -4
- package/src/useOptionsGetters.tsx +3 -2
- package/src/useScheduleUpdate.tsx +2 -2
- package/src/utilities.tsx +5 -2
package/src/getStateFromPath.tsx
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
? `%${
|
|
95
|
-
:
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
return -1;
|
|
570
|
+
if (rank !== 0) {
|
|
571
|
+
return rank;
|
|
494
572
|
}
|
|
573
|
+
}
|
|
495
574
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
579
|
+
const getSegmentRank = (segment: string | undefined) => {
|
|
580
|
+
if (segment === undefined) {
|
|
581
|
+
return 5;
|
|
582
|
+
}
|
|
512
583
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
584
|
+
if (segment === '*') {
|
|
585
|
+
return 8;
|
|
586
|
+
}
|
|
517
587
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
588
|
+
if (!segment.startsWith(':')) {
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
522
591
|
|
|
523
|
-
|
|
524
|
-
if (bParam && !aParam) {
|
|
525
|
-
return -1;
|
|
526
|
-
}
|
|
592
|
+
const regex = segment.includes('(');
|
|
527
593
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
594
|
+
if (segment.endsWith('+')) {
|
|
595
|
+
return regex ? 3 : 4;
|
|
596
|
+
}
|
|
532
597
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
}
|
|
598
|
+
if (segment.endsWith('*')) {
|
|
599
|
+
return regex ? 6 : 7;
|
|
600
|
+
}
|
|
538
601
|
|
|
539
|
-
|
|
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
|
-
|
|
672
|
+
let value = match.groups[`${PARAM_GROUP_PREFIX}${param.index}`];
|
|
611
673
|
|
|
612
|
-
if (value
|
|
613
|
-
|
|
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
|
-
|
|
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
|
|
629
|
-
|
|
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
|
|
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
|
-
):
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
849
|
-
|
|
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.
|
|
857
|
-
regex: part.regex
|
|
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.
|
|
997
|
+
pathParamNames.add(part.name);
|
|
862
998
|
}
|
|
863
|
-
} else if (part.
|
|
999
|
+
} else if (part.type === 'wildcard') {
|
|
864
1000
|
regexString += `.*\\/`;
|
|
865
1001
|
} else {
|
|
866
|
-
regexString += `${
|
|
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,
|
|
@@ -1089,7 +1226,7 @@ const createNestedStateObject = (
|
|
|
1089
1226
|
initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
|
|
1090
1227
|
|
|
1091
1228
|
const nestedStateIndex =
|
|
1092
|
-
nestedState.index
|
|
1229
|
+
nestedState.index ?? nestedState.routes.length - 1;
|
|
1093
1230
|
const nestedRoute = nestedState.routes[nestedStateIndex];
|
|
1094
1231
|
|
|
1095
1232
|
if (nestedRoute == null) {
|
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
|
*/
|
package/src/useDescriptors.tsx
CHANGED
|
@@ -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
|
-
} =
|
|
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
|
-
|
|
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 } =
|
|
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 {
|
|
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 } =
|
|
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 {
|
|
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
|
|
75
|
+
const { withStackTrace } = useNavigationBuilderContext();
|
|
76
76
|
|
|
77
|
-
const
|
|
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
|