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