@react-navigation/core 8.0.0-alpha.33 → 8.0.0-alpha.35
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/README.md +17 -14
- package/lib/module/BaseNavigationContainer.js +14 -2
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/NavigationBuilderContext.js +8 -13
- package/lib/module/NavigationBuilderContext.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +45 -15
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/NavigationProvider.js +9 -4
- package/lib/module/NavigationProvider.js.map +1 -1
- package/lib/module/PreventRemoveProvider.js +2 -2
- package/lib/module/PreventRemoveProvider.js.map +1 -1
- package/lib/module/SceneView.js +2 -1
- package/lib/module/SceneView.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 +175 -109
- 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 +9 -6
- package/lib/module/useDescriptors.js.map +1 -1
- package/lib/module/useFocusEffect.js +4 -2
- package/lib/module/useFocusEffect.js.map +1 -1
- package/lib/module/useFocusEvents.js +4 -2
- package/lib/module/useFocusEvents.js.map +1 -1
- package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
- package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
- package/lib/module/useIsFocused.js +1 -0
- package/lib/module/useIsFocused.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +42 -39
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +85 -81
- 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/useNavigationState.js +48 -23
- package/lib/module/useNavigationState.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 +19 -20
- package/lib/module/useOptionsGetters.js.map +1 -1
- package/lib/module/usePreventRemoveContext.js +1 -1
- package/lib/module/usePreventRemoveContext.js.map +1 -1
- package/lib/module/useRouteCache.js +43 -33
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/module/useScheduleUpdate.js +5 -3
- package/lib/module/useScheduleUpdate.js.map +1 -1
- package/lib/module/useSyncState.js +9 -10
- package/lib/module/useSyncState.js.map +1 -1
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/NavigationBuilderContext.d.ts +9 -6
- package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +2 -1
- package/lib/typescript/src/SceneView.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 +2 -2
- package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEffect.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
- package/lib/typescript/src/useIsFocused.d.ts +1 -0
- package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts +1 -3
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationState.d.ts +1 -0
- package/lib/typescript/src/useNavigationState.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 +2 -4
- package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts +7 -12
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
- package/lib/typescript/src/useSyncState.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/BaseNavigationContainer.tsx +19 -2
- package/src/NavigationBuilderContext.tsx +22 -18
- package/src/NavigationIndependentTree.tsx +56 -22
- package/src/NavigationProvider.tsx +13 -4
- package/src/PreventRemoveProvider.tsx +2 -8
- package/src/SceneView.tsx +3 -1
- package/src/getPathFromState.tsx +63 -15
- package/src/getPatternParts.tsx +75 -14
- package/src/getStateFromPath.tsx +250 -113
- package/src/index.tsx +1 -0
- package/src/types.tsx +1 -1
- package/src/useDescriptors.tsx +20 -6
- package/src/useFocusEffect.tsx +5 -2
- package/src/useFocusEvents.tsx +5 -2
- package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
- package/src/useIsFocused.tsx +4 -0
- package/src/useNavigationBuilder.tsx +54 -48
- package/src/useNavigationCache.tsx +130 -108
- package/src/useNavigationHelpers.tsx +2 -2
- package/src/useNavigationState.tsx +60 -29
- 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 +26 -25
- package/src/usePreventRemoveContext.tsx +1 -1
- package/src/useRouteCache.tsx +49 -33
- package/src/useScheduleUpdate.tsx +6 -3
- package/src/useSyncState.tsx +9 -11
- package/src/utilities.tsx +5 -2
- package/lib/module/useComponent.js +0 -34
- package/lib/module/useComponent.js.map +0 -1
- package/lib/typescript/src/useComponent.d.ts +0 -7
- package/lib/typescript/src/useComponent.d.ts.map +0 -1
- package/src/useComponent.tsx +0 -37
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,
|
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
|
@@ -6,11 +6,13 @@ import type {
|
|
|
6
6
|
Router,
|
|
7
7
|
} from '@react-navigation/routers';
|
|
8
8
|
import * as React from 'react';
|
|
9
|
+
import useLatestCallback from 'use-latest-callback';
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
type AddKeyedListener,
|
|
12
13
|
type AddListener,
|
|
13
14
|
NavigationBuilderContext,
|
|
15
|
+
useNavigationBuilderContext,
|
|
14
16
|
} from './NavigationBuilderContext';
|
|
15
17
|
import { NavigationProvider } from './NavigationProvider';
|
|
16
18
|
import { SceneView } from './SceneView';
|
|
@@ -67,11 +69,11 @@ type Options<
|
|
|
67
69
|
ScreenOptions extends {},
|
|
68
70
|
EventMap extends EventMapBase,
|
|
69
71
|
> = {
|
|
70
|
-
routes: State['routes'];
|
|
71
72
|
screens: Record<
|
|
72
73
|
string,
|
|
73
74
|
ScreenConfigWithParent<State, ScreenOptions, EventMap>
|
|
74
75
|
>;
|
|
76
|
+
state: State;
|
|
75
77
|
navigation: NavigationHelpers<ParamListBase>;
|
|
76
78
|
screenOptions: ScreenOptionsOrCallback<ScreenOptions> | undefined;
|
|
77
79
|
screenLayout: ScreenLayout<ScreenOptions> | undefined;
|
|
@@ -100,8 +102,8 @@ export function useDescriptors<
|
|
|
100
102
|
ScreenOptions extends {},
|
|
101
103
|
EventMap extends EventMapBase,
|
|
102
104
|
>({
|
|
103
|
-
routes,
|
|
104
105
|
screens,
|
|
106
|
+
state,
|
|
105
107
|
navigation,
|
|
106
108
|
screenOptions,
|
|
107
109
|
screenLayout,
|
|
@@ -116,6 +118,7 @@ export function useDescriptors<
|
|
|
116
118
|
emitter,
|
|
117
119
|
}: Options<State, ScreenOptions, EventMap>) {
|
|
118
120
|
const theme = React.use(ThemeContext);
|
|
121
|
+
|
|
119
122
|
const [options, setOptions] = React.useState<
|
|
120
123
|
Record<string, Partial<ScreenOptions> | undefined>
|
|
121
124
|
>({});
|
|
@@ -127,7 +130,7 @@ export function useDescriptors<
|
|
|
127
130
|
flushUpdates,
|
|
128
131
|
getIsStateEmitted,
|
|
129
132
|
withStackTrace,
|
|
130
|
-
} =
|
|
133
|
+
} = useNavigationBuilderContext();
|
|
131
134
|
|
|
132
135
|
const context = React.useMemo(
|
|
133
136
|
() => ({
|
|
@@ -166,7 +169,7 @@ export function useDescriptors<
|
|
|
166
169
|
EventMap,
|
|
167
170
|
ActionHelpers
|
|
168
171
|
>({
|
|
169
|
-
routes,
|
|
172
|
+
routes: state.routes,
|
|
170
173
|
getState,
|
|
171
174
|
navigation,
|
|
172
175
|
setOptions,
|
|
@@ -174,7 +177,11 @@ export function useDescriptors<
|
|
|
174
177
|
emitter,
|
|
175
178
|
});
|
|
176
179
|
|
|
177
|
-
const cachedRoutes = useRouteCache(routes);
|
|
180
|
+
const cachedRoutes = useRouteCache(state.routes);
|
|
181
|
+
|
|
182
|
+
const getFocusedRouteKey = useLatestCallback(
|
|
183
|
+
() => state.routes[state.index]?.key
|
|
184
|
+
);
|
|
178
185
|
|
|
179
186
|
const getOptions = (
|
|
180
187
|
route: RouteProp<ParamListBase, string>,
|
|
@@ -257,6 +264,7 @@ export function useDescriptors<
|
|
|
257
264
|
route={route}
|
|
258
265
|
screen={screen}
|
|
259
266
|
routeState={routeState}
|
|
267
|
+
getFocusedRouteKey={getFocusedRouteKey}
|
|
260
268
|
getState={getState}
|
|
261
269
|
setState={setState}
|
|
262
270
|
subscribe={subscribe}
|
|
@@ -312,7 +320,13 @@ export function useDescriptors<
|
|
|
312
320
|
}
|
|
313
321
|
|
|
314
322
|
const customOptions = getOptions(route, navigation, overrides);
|
|
315
|
-
|
|
323
|
+
|
|
324
|
+
const element = render(
|
|
325
|
+
route,
|
|
326
|
+
navigation,
|
|
327
|
+
customOptions,
|
|
328
|
+
state.routes[i]?.state
|
|
329
|
+
);
|
|
316
330
|
|
|
317
331
|
acc[route.key] = {
|
|
318
332
|
route,
|