@react-navigation/core 8.0.0-alpha.23 → 8.0.0-alpha.25
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 +5 -2
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/DataLoading.js +102 -0
- package/lib/module/DataLoading.js.map +1 -0
- package/lib/module/NavigationBuilderContext.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +7 -6
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/NavigationProvider.js +12 -12
- package/lib/module/NavigationProvider.js.map +1 -1
- package/lib/module/PreventRemoveContext.js.map +1 -1
- package/lib/module/PreventRemoveProvider.js +34 -24
- package/lib/module/PreventRemoveProvider.js.map +1 -1
- package/lib/module/StaticNavigation.js +15 -7
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/StaticTreeContext.js +5 -0
- package/lib/module/StaticTreeContext.js.map +1 -0
- package/lib/module/checkSerializable.js +7 -9
- package/lib/module/checkSerializable.js.map +1 -1
- package/lib/module/createNavigationContainerRef.js +6 -8
- package/lib/module/createNavigationContainerRef.js.map +1 -1
- package/lib/module/createNavigatorFactory.js.map +1 -1
- package/lib/module/getActionFromState.js +11 -11
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getPathFromState.js +125 -96
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +2 -2
- package/lib/module/getPatternParts.js.map +1 -1
- package/lib/module/getStateFromPath.js +136 -78
- 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/types.js +13 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/useEventEmitter.js +5 -2
- package/lib/module/useEventEmitter.js.map +1 -1
- package/lib/module/useKeyedChildListeners.js +4 -2
- package/lib/module/useKeyedChildListeners.js.map +1 -1
- package/lib/module/useNavigation.js +2 -15
- package/lib/module/useNavigation.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +5 -21
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +19 -14
- package/lib/module/useNavigationCache.js.map +1 -1
- package/lib/module/useNavigationHelpers.js +27 -9
- package/lib/module/useNavigationHelpers.js.map +1 -1
- package/lib/module/useNavigationState.js +69 -36
- package/lib/module/useNavigationState.js.map +1 -1
- package/lib/module/useOnAction.js +25 -3
- package/lib/module/useOnAction.js.map +1 -1
- package/lib/module/useOnPreventRemove.js +35 -24
- package/lib/module/useOnPreventRemove.js.map +1 -1
- package/lib/module/useOptionsGetters.js +3 -1
- package/lib/module/useOptionsGetters.js.map +1 -1
- package/lib/module/usePreventRemove.js +9 -2
- package/lib/module/usePreventRemove.js.map +1 -1
- package/lib/module/useRoute.js.map +1 -1
- package/lib/module/useRouteCache.js +14 -7
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/module/useScheduleUpdate.js +8 -8
- package/lib/module/useScheduleUpdate.js.map +1 -1
- package/lib/module/useSyncState.js +4 -1
- package/lib/module/useSyncState.js.map +1 -1
- package/lib/module/validatePathConfig.js +50 -37
- package/lib/module/validatePathConfig.js.map +1 -1
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/DataLoading.d.ts +20 -0
- package/lib/typescript/src/DataLoading.d.ts.map +1 -0
- package/lib/typescript/src/NavigationBuilderContext.d.ts +2 -2
- package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationProvider.d.ts +0 -1
- package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
- package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
- package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
- package/lib/typescript/src/StaticNavigation.d.ts +68 -42
- package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
- package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
- package/lib/typescript/src/checkSerializable.d.ts +4 -2
- package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
- package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
- package/lib/typescript/src/createNavigatorFactory.d.ts +11 -5
- package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
- package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
- package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +84 -29
- package/lib/typescript/src/types.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/useKeyedChildListeners.d.ts.map +1 -1
- package/lib/typescript/src/useNavigation.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationHelpers.d.ts +3 -4
- package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationState.d.ts +11 -1
- package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
- package/lib/typescript/src/useOnAction.d.ts.map +1 -1
- package/lib/typescript/src/useOnPreventRemove.d.ts +2 -6
- package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
- package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -0
- package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
- package/lib/typescript/src/useRoute.d.ts +1 -1
- package/lib/typescript/src/useRoute.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
- 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 +21 -38
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
- package/package.json +6 -8
- package/src/BaseNavigationContainer.tsx +9 -2
- package/src/DataLoading.tsx +139 -0
- package/src/NavigationBuilderContext.tsx +5 -1
- package/src/NavigationIndependentTree.tsx +11 -11
- package/src/NavigationProvider.tsx +13 -16
- package/src/PreventRemoveContext.tsx +1 -0
- package/src/PreventRemoveProvider.tsx +48 -43
- package/src/StaticNavigation.tsx +164 -99
- package/src/StaticTreeContext.tsx +7 -0
- package/src/checkSerializable.tsx +20 -14
- package/src/createNavigationContainerRef.tsx +12 -9
- package/src/createNavigatorFactory.tsx +27 -18
- package/src/getActionFromState.tsx +18 -16
- package/src/getPathFromState.tsx +167 -132
- package/src/getPatternParts.tsx +2 -2
- package/src/getStateFromPath.tsx +260 -193
- package/src/index.tsx +3 -0
- package/src/types.tsx +298 -62
- package/src/useEventEmitter.tsx +7 -2
- package/src/useKeyedChildListeners.tsx +4 -2
- package/src/useNavigation.tsx +2 -21
- package/src/useNavigationBuilder.tsx +7 -24
- package/src/useNavigationCache.tsx +26 -19
- package/src/useNavigationHelpers.tsx +26 -9
- package/src/useNavigationState.tsx +90 -60
- package/src/useOnAction.tsx +55 -3
- package/src/useOnPreventRemove.tsx +68 -36
- package/src/useOptionsGetters.tsx +4 -1
- package/src/usePreventRemove.tsx +11 -2
- package/src/useRoute.tsx +3 -5
- package/src/useRouteCache.tsx +19 -11
- package/src/useScheduleUpdate.tsx +8 -7
- package/src/useSyncState.tsx +5 -1
- package/src/utilities.tsx +36 -41
- package/src/validatePathConfig.tsx +86 -58
package/src/getStateFromPath.tsx
CHANGED
|
@@ -37,11 +37,13 @@ type RouteConfig = {
|
|
|
37
37
|
screen: string;
|
|
38
38
|
order: number;
|
|
39
39
|
regex?: RegExp | undefined;
|
|
40
|
+
pattern: string;
|
|
40
41
|
segments: string[];
|
|
41
|
-
params: { screen: string; name
|
|
42
|
+
params: { screen: string; name: string; index: number }[];
|
|
42
43
|
routeNames: string[];
|
|
43
44
|
parse?: ParseConfig | undefined;
|
|
44
45
|
explicitParamNames?: Set<string> | undefined;
|
|
46
|
+
pathParamNames: Set<string>;
|
|
45
47
|
hasNestedScreens: boolean;
|
|
46
48
|
shared: boolean;
|
|
47
49
|
};
|
|
@@ -57,7 +59,6 @@ type ResultState = PartialState<NavigationState> & {
|
|
|
57
59
|
|
|
58
60
|
type ParsedRoute = {
|
|
59
61
|
name: string;
|
|
60
|
-
path?: string | undefined;
|
|
61
62
|
params?: Record<string, unknown> | undefined;
|
|
62
63
|
};
|
|
63
64
|
|
|
@@ -88,9 +89,15 @@ const NESTED_SCREEN_PARAM_NAMES = [
|
|
|
88
89
|
];
|
|
89
90
|
|
|
90
91
|
const getExplicitParamNames = (parse?: ParseConfig) => {
|
|
91
|
-
const names =
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const names: string[] = [];
|
|
93
|
+
|
|
94
|
+
if (parse) {
|
|
95
|
+
for (const name in parse) {
|
|
96
|
+
if (parse[name] != null) {
|
|
97
|
+
names.push(name);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
94
101
|
|
|
95
102
|
return names.length ? new Set(names) : undefined;
|
|
96
103
|
};
|
|
@@ -163,7 +170,7 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
163
170
|
// pick the one that best matches the current navigation state
|
|
164
171
|
const resolveSharedConfig = (config: RouteConfig) => {
|
|
165
172
|
if (config.shared) {
|
|
166
|
-
const candidates = configsByPattern[config.
|
|
173
|
+
const candidates = configsByPattern[config.pattern];
|
|
167
174
|
|
|
168
175
|
if (candidates) {
|
|
169
176
|
return selectSharedConfig(config, candidates, previous, initialRoutes);
|
|
@@ -196,23 +203,21 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
196
203
|
remaining = remaining.replace(normalizedPrefix, '');
|
|
197
204
|
}
|
|
198
205
|
|
|
199
|
-
|
|
206
|
+
if (screens === undefined) {
|
|
207
|
+
// When no config is specified, use the path segments as route names
|
|
208
|
+
const routes: ParsedRoute[] = [];
|
|
200
209
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
for (const segment of remaining.split('/')) {
|
|
211
|
+
if (!segment) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
205
214
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
try {
|
|
216
|
+
routes.push({ name: decodeURIComponent(segment) });
|
|
217
|
+
} catch {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
210
220
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (screens === undefined) {
|
|
214
|
-
// When no config is specified, use the path segments as route names
|
|
215
|
-
const routes = decodedSegments.map((name) => ({ name }));
|
|
216
221
|
|
|
217
222
|
if (routes.length) {
|
|
218
223
|
return createNestedStateObject(path, routes, initialRoutes);
|
|
@@ -224,7 +229,7 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
224
229
|
if (remaining === '/') {
|
|
225
230
|
// We need to add special handling of empty path so navigation to empty path also works
|
|
226
231
|
// When handling empty path, we should only look at the root level config
|
|
227
|
-
const match = configs.find((config) => config.
|
|
232
|
+
const match = configs.find((config) => config.pattern === '');
|
|
228
233
|
|
|
229
234
|
if (match) {
|
|
230
235
|
const config = resolveSharedConfig(match);
|
|
@@ -242,7 +247,29 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
242
247
|
|
|
243
248
|
// We match the whole path against the regex instead of segments
|
|
244
249
|
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
|
250
|
+
const firstRawSegment = remaining.split('/')[0];
|
|
251
|
+
|
|
252
|
+
let firstDecodedSegment: string | undefined;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
firstDecodedSegment =
|
|
256
|
+
firstRawSegment != null ? decodeURIComponent(firstRawSegment) : undefined;
|
|
257
|
+
} catch {
|
|
258
|
+
firstDecodedSegment = undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
245
261
|
for (const config of configs) {
|
|
262
|
+
// Regex matching is expensive, so skip configs that can't match this path.
|
|
263
|
+
if (
|
|
264
|
+
!canMatchFirstSegment(
|
|
265
|
+
config.segments[0],
|
|
266
|
+
firstDecodedSegment,
|
|
267
|
+
firstRawSegment
|
|
268
|
+
)
|
|
269
|
+
) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
246
273
|
const routes = matchAgainstConfig(remaining, config, configsByScreen);
|
|
247
274
|
|
|
248
275
|
if (routes === undefined) {
|
|
@@ -281,6 +308,26 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
281
308
|
return undefined;
|
|
282
309
|
}
|
|
283
310
|
|
|
311
|
+
const canMatchFirstSegment = (
|
|
312
|
+
configSegment: string | undefined,
|
|
313
|
+
decodedSegment: string | undefined,
|
|
314
|
+
rawSegment: string | undefined
|
|
315
|
+
) => {
|
|
316
|
+
if (decodedSegment === undefined) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (configSegment === undefined) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (configSegment === '*' || configSegment.startsWith(':')) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return configSegment === decodedSegment || configSegment === rawSegment;
|
|
329
|
+
};
|
|
330
|
+
|
|
284
331
|
/**
|
|
285
332
|
* Reference to the last used config resources. This is used to avoid recomputing the config resources when the options are the same.
|
|
286
333
|
*/
|
|
@@ -289,11 +336,17 @@ const cachedConfigResources = new WeakMap<Options<{}>, ConfigResources>();
|
|
|
289
336
|
function getConfigResources<ParamList extends {}>(
|
|
290
337
|
options: Options<ParamList> | undefined
|
|
291
338
|
) {
|
|
292
|
-
if (!options)
|
|
339
|
+
if (!options) {
|
|
340
|
+
return prepareConfigResources();
|
|
341
|
+
}
|
|
293
342
|
|
|
294
343
|
const cached = cachedConfigResources.get(options);
|
|
295
344
|
|
|
296
|
-
if (cached)
|
|
345
|
+
if (cached) {
|
|
346
|
+
return cached;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
validatePathConfig(options);
|
|
297
350
|
|
|
298
351
|
const resources = prepareConfigResources(options);
|
|
299
352
|
|
|
@@ -303,21 +356,23 @@ function getConfigResources<ParamList extends {}>(
|
|
|
303
356
|
}
|
|
304
357
|
|
|
305
358
|
function prepareConfigResources(options?: Options<{}>) {
|
|
306
|
-
if (options) {
|
|
307
|
-
validatePathConfig(options);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
359
|
const initialRoutes = getInitialRoutes(options);
|
|
311
360
|
const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
|
|
312
361
|
|
|
313
|
-
checkForDuplicatedConfigs(configs);
|
|
314
|
-
|
|
315
362
|
const configsByScreen: Record<string, RouteConfig[]> = {};
|
|
316
363
|
const configsByPattern: Record<string, RouteConfig[]> = {};
|
|
317
364
|
|
|
318
365
|
for (const c of configs) {
|
|
319
366
|
(configsByScreen[c.screen] ??= []).push(c);
|
|
320
|
-
|
|
367
|
+
|
|
368
|
+
const existing = configsByPattern[c.pattern];
|
|
369
|
+
|
|
370
|
+
if (existing) {
|
|
371
|
+
checkForDuplicatedConfigs(existing[existing.length - 1], c);
|
|
372
|
+
existing.push(c);
|
|
373
|
+
} else {
|
|
374
|
+
configsByPattern[c.pattern] = [c];
|
|
375
|
+
}
|
|
321
376
|
}
|
|
322
377
|
|
|
323
378
|
return {
|
|
@@ -346,129 +401,133 @@ function getSortedNormalizedConfigs(
|
|
|
346
401
|
screens: Record<string, string | PathConfig<{}>> = {}
|
|
347
402
|
) {
|
|
348
403
|
// Create a normalized configs array which will be easier to use
|
|
349
|
-
|
|
350
|
-
.concat(
|
|
351
|
-
...Object.keys(screens).map((key) =>
|
|
352
|
-
createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
|
|
353
|
-
)
|
|
354
|
-
)
|
|
355
|
-
.map((config, order) => ({ ...config, order }))
|
|
356
|
-
.sort((a, b) => {
|
|
357
|
-
// Sort config from most specific to least specific:
|
|
358
|
-
// - more segments
|
|
359
|
-
// - static segments
|
|
360
|
-
// - params with regex
|
|
361
|
-
// - regular params
|
|
362
|
-
// - wildcard
|
|
363
|
-
|
|
364
|
-
// If 2 patterns are same, move the one with less route names up
|
|
365
|
-
// This is an error state, so it's only useful for consistent error messages
|
|
366
|
-
if (isArrayEqual(a.segments, b.segments)) {
|
|
367
|
-
return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
|
|
368
|
-
}
|
|
404
|
+
const configs: RouteConfig[] = [];
|
|
369
405
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
406
|
+
for (const key in screens) {
|
|
407
|
+
configs.push(
|
|
408
|
+
...createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
|
|
409
|
+
);
|
|
410
|
+
}
|
|
375
411
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
412
|
+
for (let order = 0; order < configs.length; order++) {
|
|
413
|
+
const config = configs[order];
|
|
379
414
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
415
|
+
if (config) {
|
|
416
|
+
config.order = order;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
383
419
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
420
|
+
return configs.sort((a, b) => {
|
|
421
|
+
// Sort config from most specific to least specific:
|
|
422
|
+
// - more segments
|
|
423
|
+
// - static segments
|
|
424
|
+
// - params with regex
|
|
425
|
+
// - regular params
|
|
426
|
+
// - wildcard
|
|
427
|
+
|
|
428
|
+
// If 2 patterns are same, move the one with less route names up
|
|
429
|
+
// This is an error state, so it's only useful for consistent error messages
|
|
430
|
+
if (isArrayEqual(a.segments, b.segments)) {
|
|
431
|
+
return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
|
|
432
|
+
}
|
|
388
433
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
434
|
+
// If one of the patterns starts with the other, it's more exhaustive
|
|
435
|
+
// So move it up
|
|
436
|
+
if (arrayStartsWith(a.segments, b.segments)) {
|
|
437
|
+
return -1;
|
|
438
|
+
}
|
|
393
439
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const bParam = bSegment.startsWith(':');
|
|
398
|
-
const aRegex = aParam && aSegment.includes('(');
|
|
399
|
-
const bRegex = bParam && bSegment.includes('(');
|
|
440
|
+
if (arrayStartsWith(b.segments, a.segments)) {
|
|
441
|
+
return 1;
|
|
442
|
+
}
|
|
400
443
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
444
|
+
for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
|
|
445
|
+
const aSegment = a.segments[i];
|
|
446
|
+
const bSegment = b.segments[i];
|
|
405
447
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
448
|
+
// if b is longer, b gets higher priority
|
|
449
|
+
if (aSegment == null) {
|
|
450
|
+
return 1;
|
|
451
|
+
}
|
|
410
452
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
453
|
+
// if a is longer, a gets higher priority
|
|
454
|
+
if (bSegment == null) {
|
|
455
|
+
return -1;
|
|
456
|
+
}
|
|
415
457
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
458
|
+
const aWildCard = aSegment === '*';
|
|
459
|
+
const bWildCard = bSegment === '*';
|
|
460
|
+
const aParam = aSegment.startsWith(':');
|
|
461
|
+
const bParam = bSegment.startsWith(':');
|
|
462
|
+
const aRegex = aParam && aSegment.includes('(');
|
|
463
|
+
const bRegex = bParam && bSegment.includes('(');
|
|
420
464
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
465
|
+
// if both are wildcard or regex, we compare next component
|
|
466
|
+
if ((aWildCard && bWildCard) || (aRegex && bRegex)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
425
469
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
470
|
+
// if only a is wildcard, b gets higher priority
|
|
471
|
+
if (aWildCard && !bWildCard) {
|
|
472
|
+
return 1;
|
|
473
|
+
}
|
|
430
474
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
475
|
+
// if only b is wildcard, a gets higher priority
|
|
476
|
+
if (bWildCard && !aWildCard) {
|
|
477
|
+
return -1;
|
|
435
478
|
}
|
|
436
479
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
480
|
+
// If only a has a param, b gets higher priority
|
|
481
|
+
if (aParam && !bParam) {
|
|
482
|
+
return 1;
|
|
483
|
+
}
|
|
440
484
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
? b.every((it, i) => a[i] === it)
|
|
455
|
-
: a.every((it, i) => b[i] === it);
|
|
456
|
-
|
|
457
|
-
if (!intersects && (!acc[pattern].shared || !config.shared)) {
|
|
458
|
-
throw new Error(
|
|
459
|
-
`Found conflicting screens with the same pattern. The pattern '${
|
|
460
|
-
pattern
|
|
461
|
-
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
|
462
|
-
' > '
|
|
463
|
-
)}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
|
|
464
|
-
);
|
|
485
|
+
// If only b has a param, a gets higher priority
|
|
486
|
+
if (bParam && !aParam) {
|
|
487
|
+
return -1;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// if only a has regex, a gets higher priority
|
|
491
|
+
if (aRegex && !bRegex) {
|
|
492
|
+
return -1;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// if only b has regex, b gets higher priority
|
|
496
|
+
if (bRegex && !aRegex) {
|
|
497
|
+
return 1;
|
|
465
498
|
}
|
|
466
499
|
}
|
|
467
500
|
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
501
|
+
return a.segments.length - b.segments.length;
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Throw if two configs resolve to the same pattern but conflicting screens
|
|
506
|
+
function checkForDuplicatedConfigs(
|
|
507
|
+
existing: RouteConfig | undefined,
|
|
508
|
+
config: RouteConfig
|
|
509
|
+
) {
|
|
510
|
+
if (!existing) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const a = existing.routeNames;
|
|
515
|
+
const b = config.routeNames;
|
|
516
|
+
|
|
517
|
+
// It's not a problem if the path string omitted from a inner most screen
|
|
518
|
+
// For example, it's ok if a path resolves to `A > B > C` or `A > B`
|
|
519
|
+
const intersects =
|
|
520
|
+
a.length > b.length ? arrayStartsWith(a, b) : arrayStartsWith(b, a);
|
|
521
|
+
|
|
522
|
+
if (!intersects && (!existing.shared || !config.shared)) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`Found conflicting screens with the same pattern. The pattern '${
|
|
525
|
+
config.pattern
|
|
526
|
+
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
|
527
|
+
' > '
|
|
528
|
+
)}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
472
531
|
}
|
|
473
532
|
|
|
474
533
|
const matchAgainstConfig = (
|
|
@@ -500,20 +559,21 @@ const matchAgainstConfig = (
|
|
|
500
559
|
);
|
|
501
560
|
|
|
502
561
|
let params: Record<string, unknown> | undefined;
|
|
562
|
+
let hasParams = false;
|
|
503
563
|
|
|
504
564
|
if (routeConfig && match.groups) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
for (const [key, value] of Object.entries(match.groups)) {
|
|
508
|
-
const index = Number(key.replace(PARAM_GROUP_PREFIX, ''));
|
|
509
|
-
const param = routeConfig.params.find((it) => it.index === index);
|
|
565
|
+
params = {};
|
|
510
566
|
|
|
511
|
-
|
|
567
|
+
for (const param of routeConfig.params) {
|
|
568
|
+
if (param.screen !== routeName) {
|
|
512
569
|
continue;
|
|
513
570
|
}
|
|
514
571
|
|
|
572
|
+
const value = match.groups[`${PARAM_GROUP_PREFIX}${param.index}`];
|
|
573
|
+
|
|
515
574
|
if (value == null) {
|
|
516
|
-
|
|
575
|
+
params[param.name] = undefined;
|
|
576
|
+
hasParams = true;
|
|
517
577
|
continue;
|
|
518
578
|
}
|
|
519
579
|
|
|
@@ -528,7 +588,8 @@ const matchAgainstConfig = (
|
|
|
528
588
|
const parser = routeConfig.parse?.[param.name];
|
|
529
589
|
|
|
530
590
|
if (!parser) {
|
|
531
|
-
|
|
591
|
+
params[param.name] = decoded;
|
|
592
|
+
hasParams = true;
|
|
532
593
|
continue;
|
|
533
594
|
}
|
|
534
595
|
|
|
@@ -542,12 +603,14 @@ const matchAgainstConfig = (
|
|
|
542
603
|
break;
|
|
543
604
|
}
|
|
544
605
|
|
|
545
|
-
|
|
606
|
+
params[param.name] = result.value;
|
|
607
|
+
hasParams = true;
|
|
546
608
|
continue;
|
|
547
609
|
}
|
|
548
610
|
|
|
549
611
|
if (typeof parser === 'function') {
|
|
550
|
-
|
|
612
|
+
params[param.name] = parser(decoded);
|
|
613
|
+
hasParams = true;
|
|
551
614
|
continue;
|
|
552
615
|
}
|
|
553
616
|
|
|
@@ -558,13 +621,9 @@ const matchAgainstConfig = (
|
|
|
558
621
|
// A failed param validation invalidates the whole nested route chain for this config.
|
|
559
622
|
break;
|
|
560
623
|
}
|
|
561
|
-
|
|
562
|
-
if (paramEntries.length) {
|
|
563
|
-
params = Object.fromEntries(paramEntries);
|
|
564
|
-
}
|
|
565
624
|
}
|
|
566
625
|
|
|
567
|
-
if (params &&
|
|
626
|
+
if (params && hasParams) {
|
|
568
627
|
matchedRoutes.push({ name: routeName, params });
|
|
569
628
|
} else {
|
|
570
629
|
matchedRoutes.push({ name: routeName });
|
|
@@ -605,7 +664,10 @@ const createNormalizedConfigs = (
|
|
|
605
664
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
|
606
665
|
// it can have `path` property and
|
|
607
666
|
// it could have `screens` prop which has nested configs
|
|
608
|
-
const nestedScreens =
|
|
667
|
+
const nestedScreens =
|
|
668
|
+
'screens' in config
|
|
669
|
+
? (config.screens as Record<string, string | PathConfig<{}>>)
|
|
670
|
+
: undefined;
|
|
609
671
|
const hasNestedScreens = !!nestedScreens;
|
|
610
672
|
const currentParts =
|
|
611
673
|
typeof config.path === 'string'
|
|
@@ -683,7 +745,7 @@ const createNormalizedConfigs = (
|
|
|
683
745
|
);
|
|
684
746
|
}
|
|
685
747
|
|
|
686
|
-
if (
|
|
748
|
+
if (nestedScreens) {
|
|
687
749
|
// property `initialRouteName` without `screens` has no purpose
|
|
688
750
|
if (
|
|
689
751
|
'initialRouteName' in config &&
|
|
@@ -695,10 +757,10 @@ const createNormalizedConfigs = (
|
|
|
695
757
|
});
|
|
696
758
|
}
|
|
697
759
|
|
|
698
|
-
|
|
760
|
+
for (const nestedConfig in nestedScreens) {
|
|
699
761
|
const result = createNormalizedConfigs(
|
|
700
762
|
nestedConfig,
|
|
701
|
-
nestedScreens
|
|
763
|
+
nestedScreens,
|
|
702
764
|
initials,
|
|
703
765
|
currentParts,
|
|
704
766
|
[...parentScreens],
|
|
@@ -706,7 +768,7 @@ const createNormalizedConfigs = (
|
|
|
706
768
|
);
|
|
707
769
|
|
|
708
770
|
configs.push(...result);
|
|
709
|
-
}
|
|
771
|
+
}
|
|
710
772
|
}
|
|
711
773
|
}
|
|
712
774
|
|
|
@@ -723,44 +785,56 @@ const createConfigItem = (
|
|
|
723
785
|
hasNestedScreens = false,
|
|
724
786
|
shared = false
|
|
725
787
|
): RouteConfig => {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
788
|
+
let regex: RegExp | undefined;
|
|
789
|
+
|
|
790
|
+
const segments: string[] = [];
|
|
791
|
+
const params: RouteConfig['params'] = [];
|
|
792
|
+
const pathParamNames = new Set<string>();
|
|
793
|
+
|
|
794
|
+
if (parts.length) {
|
|
795
|
+
let regexString = '^(';
|
|
796
|
+
let index = 0;
|
|
797
|
+
|
|
798
|
+
for (const part of parts) {
|
|
799
|
+
segments.push(part.segment);
|
|
800
|
+
|
|
801
|
+
if (part.param) {
|
|
802
|
+
const reg = part.regex || '[^/]+';
|
|
803
|
+
|
|
804
|
+
regexString += `(((?<${PARAM_GROUP_PREFIX}${index}>${reg})\\/)${part.optional ? '?' : ''})`;
|
|
805
|
+
params.push({
|
|
806
|
+
index,
|
|
807
|
+
screen: part.screen,
|
|
808
|
+
name: part.param,
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
if (part.screen === screen) {
|
|
812
|
+
pathParamNames.add(part.param);
|
|
813
|
+
}
|
|
814
|
+
} else {
|
|
815
|
+
regexString += `${part.segment === '*' ? '.*' : escape(part.segment)}\\/`;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
index++;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
regexString += ')$';
|
|
822
|
+
regex = new RegExp(regexString);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const pattern = segments.join('/');
|
|
754
826
|
|
|
755
827
|
return {
|
|
756
828
|
screen,
|
|
757
829
|
order: 0,
|
|
758
830
|
regex,
|
|
831
|
+
pattern,
|
|
759
832
|
segments,
|
|
760
833
|
params,
|
|
761
834
|
routeNames,
|
|
762
835
|
parse,
|
|
763
836
|
explicitParamNames: getExplicitParamNames(parse),
|
|
837
|
+
pathParamNames,
|
|
764
838
|
hasNestedScreens,
|
|
765
839
|
shared,
|
|
766
840
|
};
|
|
@@ -979,19 +1053,10 @@ const createNestedStateObject = (
|
|
|
979
1053
|
|
|
980
1054
|
focusedRoute.path = path.replace(/\/$/, '');
|
|
981
1055
|
|
|
982
|
-
const pathParamNames = new Set(
|
|
983
|
-
routeConfig?.params
|
|
984
|
-
.filter(
|
|
985
|
-
(param): param is { screen: string; name: string; index: number } =>
|
|
986
|
-
param.screen === routeConfig.screen && typeof param.name === 'string'
|
|
987
|
-
)
|
|
988
|
-
.map((param) => param.name)
|
|
989
|
-
);
|
|
990
|
-
|
|
991
1056
|
const queryParams = parseQueryParams(
|
|
992
1057
|
path,
|
|
993
1058
|
routeConfig?.parse,
|
|
994
|
-
pathParamNames,
|
|
1059
|
+
routeConfig?.pathParamNames,
|
|
995
1060
|
routeConfig?.explicitParamNames,
|
|
996
1061
|
routeConfig?.hasNestedScreens,
|
|
997
1062
|
focusedRoute.params
|
|
@@ -1028,7 +1093,9 @@ const parseQueryParams = (
|
|
|
1028
1093
|
}
|
|
1029
1094
|
|
|
1030
1095
|
if (parseConfig) {
|
|
1031
|
-
for (const
|
|
1096
|
+
for (const name in parseConfig) {
|
|
1097
|
+
const parser = parseConfig[name];
|
|
1098
|
+
|
|
1032
1099
|
if (!parser || pathParamNames.has(name)) {
|
|
1033
1100
|
continue;
|
|
1034
1101
|
}
|