@react-navigation/core 8.0.0-alpha.24 → 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/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 +14 -6
- 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 +38 -39
- 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 +72 -45
- 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.map +1 -1
- package/lib/module/useEventEmitter.js +1 -1
- 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 +4 -14
- 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 +21 -2
- package/lib/module/useOnAction.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/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.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/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 +65 -39
- 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 +9 -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 +19 -13
- package/lib/typescript/src/types.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/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/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 +17 -0
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
- package/package.json +3 -5
- package/src/BaseNavigationContainer.tsx +9 -2
- package/src/DataLoading.tsx +139 -0
- 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 +157 -93
- package/src/StaticTreeContext.tsx +7 -0
- package/src/checkSerializable.tsx +20 -14
- package/src/createNavigationContainerRef.tsx +12 -9
- package/src/createNavigatorFactory.tsx +16 -28
- package/src/getActionFromState.tsx +18 -16
- package/src/getPathFromState.tsx +69 -65
- package/src/getPatternParts.tsx +2 -2
- package/src/getStateFromPath.tsx +107 -66
- package/src/index.tsx +2 -0
- package/src/types.tsx +62 -50
- package/src/useEventEmitter.tsx +1 -1
- package/src/useKeyedChildListeners.tsx +4 -2
- package/src/useNavigation.tsx +2 -21
- package/src/useNavigationBuilder.tsx +6 -16
- package/src/useNavigationCache.tsx +26 -19
- package/src/useNavigationHelpers.tsx +26 -9
- package/src/useNavigationState.tsx +90 -60
- package/src/useOnAction.tsx +49 -1
- package/src/useOptionsGetters.tsx +4 -1
- package/src/usePreventRemove.tsx +11 -2
- package/src/useRouteCache.tsx +19 -11
- package/src/useScheduleUpdate.tsx +8 -7
- package/src/useSyncState.tsx +5 -1
- package/src/utilities.tsx +28 -0
- package/src/validatePathConfig.tsx +1 -0
package/src/getStateFromPath.tsx
CHANGED
|
@@ -43,6 +43,7 @@ type RouteConfig = {
|
|
|
43
43
|
routeNames: string[];
|
|
44
44
|
parse?: ParseConfig | undefined;
|
|
45
45
|
explicitParamNames?: Set<string> | undefined;
|
|
46
|
+
pathParamNames: Set<string>;
|
|
46
47
|
hasNestedScreens: boolean;
|
|
47
48
|
shared: boolean;
|
|
48
49
|
};
|
|
@@ -202,23 +203,21 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
202
203
|
remaining = remaining.replace(normalizedPrefix, '');
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
if (screens === undefined) {
|
|
207
|
+
// When no config is specified, use the path segments as route names
|
|
208
|
+
const routes: ParsedRoute[] = [];
|
|
206
209
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
for (const segment of remaining.split('/')) {
|
|
211
|
+
if (!segment) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
211
214
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
try {
|
|
216
|
+
routes.push({ name: decodeURIComponent(segment) });
|
|
217
|
+
} catch {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
216
220
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (screens === undefined) {
|
|
220
|
-
// When no config is specified, use the path segments as route names
|
|
221
|
-
const routes = decodedSegments.map((name) => ({ name }));
|
|
222
221
|
|
|
223
222
|
if (routes.length) {
|
|
224
223
|
return createNestedStateObject(path, routes, initialRoutes);
|
|
@@ -248,7 +247,29 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
248
247
|
|
|
249
248
|
// We match the whole path against the regex instead of segments
|
|
250
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
|
+
|
|
251
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
|
+
|
|
252
273
|
const routes = matchAgainstConfig(remaining, config, configsByScreen);
|
|
253
274
|
|
|
254
275
|
if (routes === undefined) {
|
|
@@ -287,6 +308,26 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
287
308
|
return undefined;
|
|
288
309
|
}
|
|
289
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
|
+
|
|
290
331
|
/**
|
|
291
332
|
* Reference to the last used config resources. This is used to avoid recomputing the config resources when the options are the same.
|
|
292
333
|
*/
|
|
@@ -295,11 +336,17 @@ const cachedConfigResources = new WeakMap<Options<{}>, ConfigResources>();
|
|
|
295
336
|
function getConfigResources<ParamList extends {}>(
|
|
296
337
|
options: Options<ParamList> | undefined
|
|
297
338
|
) {
|
|
298
|
-
if (!options)
|
|
339
|
+
if (!options) {
|
|
340
|
+
return prepareConfigResources();
|
|
341
|
+
}
|
|
299
342
|
|
|
300
343
|
const cached = cachedConfigResources.get(options);
|
|
301
344
|
|
|
302
|
-
if (cached)
|
|
345
|
+
if (cached) {
|
|
346
|
+
return cached;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
validatePathConfig(options);
|
|
303
350
|
|
|
304
351
|
const resources = prepareConfigResources(options);
|
|
305
352
|
|
|
@@ -309,21 +356,23 @@ function getConfigResources<ParamList extends {}>(
|
|
|
309
356
|
}
|
|
310
357
|
|
|
311
358
|
function prepareConfigResources(options?: Options<{}>) {
|
|
312
|
-
if (options) {
|
|
313
|
-
validatePathConfig(options);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
359
|
const initialRoutes = getInitialRoutes(options);
|
|
317
360
|
const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
|
|
318
361
|
|
|
319
|
-
checkForDuplicatedConfigs(configs);
|
|
320
|
-
|
|
321
362
|
const configsByScreen: Record<string, RouteConfig[]> = {};
|
|
322
363
|
const configsByPattern: Record<string, RouteConfig[]> = {};
|
|
323
364
|
|
|
324
365
|
for (const c of configs) {
|
|
325
366
|
(configsByScreen[c.screen] ??= []).push(c);
|
|
326
|
-
|
|
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
|
+
}
|
|
327
376
|
}
|
|
328
377
|
|
|
329
378
|
return {
|
|
@@ -453,37 +502,31 @@ function getSortedNormalizedConfigs(
|
|
|
453
502
|
});
|
|
454
503
|
}
|
|
455
504
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (existing) {
|
|
465
|
-
const a = existing.routeNames;
|
|
466
|
-
const b = config.routeNames;
|
|
467
|
-
|
|
468
|
-
// It's not a problem if the path string omitted from a inner most screen
|
|
469
|
-
// For example, it's ok if a path resolves to `A > B > C` or `A > B`
|
|
470
|
-
const intersects =
|
|
471
|
-
a.length > b.length
|
|
472
|
-
? b.every((it, i) => a[i] === it)
|
|
473
|
-
: a.every((it, i) => b[i] === it);
|
|
474
|
-
|
|
475
|
-
if (!intersects && (!existing.shared || !config.shared)) {
|
|
476
|
-
throw new Error(
|
|
477
|
-
`Found conflicting screens with the same pattern. The pattern '${
|
|
478
|
-
pattern
|
|
479
|
-
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
|
480
|
-
' > '
|
|
481
|
-
)}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
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
|
+
}
|
|
485
513
|
|
|
486
|
-
|
|
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
|
+
);
|
|
487
530
|
}
|
|
488
531
|
}
|
|
489
532
|
|
|
@@ -521,15 +564,13 @@ const matchAgainstConfig = (
|
|
|
521
564
|
if (routeConfig && match.groups) {
|
|
522
565
|
params = {};
|
|
523
566
|
|
|
524
|
-
for (const
|
|
525
|
-
|
|
526
|
-
const index = Number(key.replace(PARAM_GROUP_PREFIX, ''));
|
|
527
|
-
const param = routeConfig.params.find((it) => it.index === index);
|
|
528
|
-
|
|
529
|
-
if (!param || param.screen !== routeName) {
|
|
567
|
+
for (const param of routeConfig.params) {
|
|
568
|
+
if (param.screen !== routeName) {
|
|
530
569
|
continue;
|
|
531
570
|
}
|
|
532
571
|
|
|
572
|
+
const value = match.groups[`${PARAM_GROUP_PREFIX}${param.index}`];
|
|
573
|
+
|
|
533
574
|
if (value == null) {
|
|
534
575
|
params[param.name] = undefined;
|
|
535
576
|
hasParams = true;
|
|
@@ -748,6 +789,7 @@ const createConfigItem = (
|
|
|
748
789
|
|
|
749
790
|
const segments: string[] = [];
|
|
750
791
|
const params: RouteConfig['params'] = [];
|
|
792
|
+
const pathParamNames = new Set<string>();
|
|
751
793
|
|
|
752
794
|
if (parts.length) {
|
|
753
795
|
let regexString = '^(';
|
|
@@ -765,6 +807,10 @@ const createConfigItem = (
|
|
|
765
807
|
screen: part.screen,
|
|
766
808
|
name: part.param,
|
|
767
809
|
});
|
|
810
|
+
|
|
811
|
+
if (part.screen === screen) {
|
|
812
|
+
pathParamNames.add(part.param);
|
|
813
|
+
}
|
|
768
814
|
} else {
|
|
769
815
|
regexString += `${part.segment === '*' ? '.*' : escape(part.segment)}\\/`;
|
|
770
816
|
}
|
|
@@ -788,6 +834,7 @@ const createConfigItem = (
|
|
|
788
834
|
routeNames,
|
|
789
835
|
parse,
|
|
790
836
|
explicitParamNames: getExplicitParamNames(parse),
|
|
837
|
+
pathParamNames,
|
|
791
838
|
hasNestedScreens,
|
|
792
839
|
shared,
|
|
793
840
|
};
|
|
@@ -1006,16 +1053,10 @@ const createNestedStateObject = (
|
|
|
1006
1053
|
|
|
1007
1054
|
focusedRoute.path = path.replace(/\/$/, '');
|
|
1008
1055
|
|
|
1009
|
-
const pathParamNames = new Set(
|
|
1010
|
-
routeConfig?.params
|
|
1011
|
-
.filter((param) => param.screen === routeConfig.screen)
|
|
1012
|
-
.map((param) => param.name)
|
|
1013
|
-
);
|
|
1014
|
-
|
|
1015
1056
|
const queryParams = parseQueryParams(
|
|
1016
1057
|
path,
|
|
1017
1058
|
routeConfig?.parse,
|
|
1018
|
-
pathParamNames,
|
|
1059
|
+
routeConfig?.pathParamNames,
|
|
1019
1060
|
routeConfig?.explicitParamNames,
|
|
1020
1061
|
routeConfig?.hasNestedScreens,
|
|
1021
1062
|
focusedRoute.params
|
package/src/index.tsx
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
createNavigatorFactory,
|
|
5
5
|
type TypedNavigatorFactory,
|
|
6
6
|
} from './createNavigatorFactory';
|
|
7
|
+
export { getLoaderForState as UNSTABLE_getLoaderForState } from './DataLoading';
|
|
7
8
|
export { findFocusedRoute } from './findFocusedRoute';
|
|
8
9
|
export { getActionFromState } from './getActionFromState';
|
|
9
10
|
export { getFocusedRouteNameFromRoute } from './getFocusedRouteNameFromRoute';
|
|
@@ -59,6 +60,7 @@ export type {
|
|
|
59
60
|
NavigatorScreenParams,
|
|
60
61
|
NavigatorTypeBagBase,
|
|
61
62
|
NavigatorTypeBagFor,
|
|
63
|
+
NestedNavigatorsForParamList,
|
|
62
64
|
PathConfig,
|
|
63
65
|
PathConfigMap,
|
|
64
66
|
RootNavigator,
|
package/src/types.tsx
CHANGED
|
@@ -429,7 +429,7 @@ type NavigationHelpersCommon<
|
|
|
429
429
|
|
|
430
430
|
type ParamType<
|
|
431
431
|
ParamList extends {},
|
|
432
|
-
RouteName
|
|
432
|
+
RouteName,
|
|
433
433
|
IsPartial extends boolean = false,
|
|
434
434
|
> = RouteName extends keyof ParamList
|
|
435
435
|
? ParamList[RouteName] extends undefined
|
|
@@ -724,16 +724,16 @@ export type RouteConfigComponent<
|
|
|
724
724
|
* React component to render for this screen.
|
|
725
725
|
*/
|
|
726
726
|
component: ScreenComponentType<ParamList, RouteName>;
|
|
727
|
-
getComponent?: never
|
|
728
|
-
children?: never
|
|
727
|
+
getComponent?: never;
|
|
728
|
+
children?: never;
|
|
729
729
|
}
|
|
730
730
|
| {
|
|
731
731
|
/**
|
|
732
732
|
* Lazily get a React component to render for this screen.
|
|
733
733
|
*/
|
|
734
734
|
getComponent: () => ScreenComponentType<ParamList, RouteName>;
|
|
735
|
-
component?: never
|
|
736
|
-
children?: never
|
|
735
|
+
component?: never;
|
|
736
|
+
children?: never;
|
|
737
737
|
}
|
|
738
738
|
| {
|
|
739
739
|
/**
|
|
@@ -743,8 +743,8 @@ export type RouteConfigComponent<
|
|
|
743
743
|
route: RouteProp<ParamList, RouteName>;
|
|
744
744
|
navigation: any;
|
|
745
745
|
}) => React.ReactNode;
|
|
746
|
-
component?: never
|
|
747
|
-
getComponent?: never
|
|
746
|
+
component?: never;
|
|
747
|
+
getComponent?: never;
|
|
748
748
|
};
|
|
749
749
|
|
|
750
750
|
export type RouteConfigProps<
|
|
@@ -1147,27 +1147,18 @@ type NavigationListForScreenParams<
|
|
|
1147
1147
|
RouteName extends keyof ParamList,
|
|
1148
1148
|
ExcludedRouteNames,
|
|
1149
1149
|
Parent extends NavigationProp<any, any, any, any, any> | undefined,
|
|
1150
|
-
> =
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
Parent
|
|
1163
|
-
>
|
|
1164
|
-
: NavigationListForParamListScreenParams<
|
|
1165
|
-
T,
|
|
1166
|
-
ParamList,
|
|
1167
|
-
RouteName,
|
|
1168
|
-
ExcludedRouteNames,
|
|
1169
|
-
Parent
|
|
1170
|
-
>;
|
|
1150
|
+
> = T extends PrivateValueStore<[any, any, any, any]> & NavigatorLike
|
|
1151
|
+
? NavigationListForNestedInternal<
|
|
1152
|
+
T,
|
|
1153
|
+
GenericNavigationComposite<ParamList, RouteName, Parent>
|
|
1154
|
+
>
|
|
1155
|
+
: NavigationListForParamListScreenParams<
|
|
1156
|
+
T,
|
|
1157
|
+
ParamList,
|
|
1158
|
+
RouteName,
|
|
1159
|
+
ExcludedRouteNames,
|
|
1160
|
+
Parent
|
|
1161
|
+
>;
|
|
1171
1162
|
|
|
1172
1163
|
type NavigationListForParamListScreenParams<
|
|
1173
1164
|
T extends {},
|
|
@@ -1435,20 +1426,39 @@ export type NavigatorTypeBagFor<
|
|
|
1435
1426
|
ParamList extends {},
|
|
1436
1427
|
> = TypeBag & { ParamList: ParamList };
|
|
1437
1428
|
|
|
1438
|
-
type
|
|
1429
|
+
export type NavigatorProps<
|
|
1430
|
+
ParamList extends ParamListBase,
|
|
1431
|
+
State extends NavigationState,
|
|
1432
|
+
ScreenOptions extends {},
|
|
1433
|
+
EventMap extends EventMapBase,
|
|
1434
|
+
Navigation,
|
|
1435
|
+
Navigator extends React.ComponentType<any>,
|
|
1436
|
+
> = Omit<
|
|
1437
|
+
React.ComponentProps<Navigator>,
|
|
1438
|
+
keyof DefaultNavigatorOptions<any, any, any, any, any>
|
|
1439
|
+
> &
|
|
1440
|
+
DefaultNavigatorOptions<
|
|
1441
|
+
ParamList,
|
|
1442
|
+
State,
|
|
1443
|
+
ScreenOptions,
|
|
1444
|
+
EventMap,
|
|
1445
|
+
Navigation
|
|
1446
|
+
>;
|
|
1447
|
+
|
|
1448
|
+
type TypedNavigatorStaticComponent<Bag extends NavigatorTypeBagBase> =
|
|
1439
1449
|
React.ComponentType<
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1450
|
+
Partial<
|
|
1451
|
+
Omit<
|
|
1452
|
+
NavigatorProps<
|
|
1443
1453
|
Bag['ParamList'],
|
|
1444
1454
|
Bag['State'],
|
|
1445
1455
|
Bag['ScreenOptions'],
|
|
1446
1456
|
Bag['EventMap'],
|
|
1447
|
-
Bag['NavigationList'],
|
|
1457
|
+
Bag['NavigationList'][keyof Bag['ParamList']],
|
|
1448
1458
|
Bag['Navigator']
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1459
|
+
>,
|
|
1460
|
+
'children'
|
|
1461
|
+
>
|
|
1452
1462
|
>
|
|
1453
1463
|
>;
|
|
1454
1464
|
|
|
@@ -1466,10 +1476,10 @@ type TypedNavigatorStatic<
|
|
|
1466
1476
|
config: Config;
|
|
1467
1477
|
with: (
|
|
1468
1478
|
Component: React.ComponentType<{
|
|
1469
|
-
Navigator:
|
|
1479
|
+
Navigator: TypedNavigatorStaticComponent<Bag>;
|
|
1470
1480
|
}>
|
|
1471
1481
|
) => TypedNavigatorStaticDecorated<Bag, Config>;
|
|
1472
|
-
getComponent: () =>
|
|
1482
|
+
getComponent: () => TypedNavigatorStaticComponent<Bag>;
|
|
1473
1483
|
};
|
|
1474
1484
|
|
|
1475
1485
|
export type TypedNavigator<
|
|
@@ -1502,17 +1512,14 @@ type TypedNavigatorInternal<
|
|
|
1502
1512
|
* Navigator component which manages the child screens.
|
|
1503
1513
|
*/
|
|
1504
1514
|
Navigator: React.ComponentType<
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
EventMap,
|
|
1514
|
-
NavigationList[keyof ParamList]
|
|
1515
|
-
>
|
|
1515
|
+
NavigatorProps<
|
|
1516
|
+
ParamList,
|
|
1517
|
+
State,
|
|
1518
|
+
ScreenOptions,
|
|
1519
|
+
EventMap,
|
|
1520
|
+
NavigationList[keyof ParamList],
|
|
1521
|
+
Navigator
|
|
1522
|
+
>
|
|
1516
1523
|
>;
|
|
1517
1524
|
/**
|
|
1518
1525
|
* Component used for grouping multiple route configuration.
|
|
@@ -1632,7 +1639,12 @@ type PathConfigAlias<Params> = {
|
|
|
1632
1639
|
};
|
|
1633
1640
|
|
|
1634
1641
|
export type PathConfig<Params> = FlatType<
|
|
1635
|
-
Partial<PathConfigAlias<Params>> & {
|
|
1642
|
+
Partial<Omit<PathConfigAlias<Params>, 'path'>> & {
|
|
1643
|
+
/**
|
|
1644
|
+
* Path string to match against.
|
|
1645
|
+
* e.g. `/users/:id` will match `/users/1` and extract `id` param as `1`.
|
|
1646
|
+
*/
|
|
1647
|
+
path?: string | undefined;
|
|
1636
1648
|
/**
|
|
1637
1649
|
* An object mapping the param name to a function which converts the param value to a string.
|
|
1638
1650
|
* By default, all params are converted to strings using `String(value)`.
|
package/src/useEventEmitter.tsx
CHANGED
|
@@ -19,7 +19,7 @@ export function useEventEmitter<T extends Record<string, any>>(
|
|
|
19
19
|
const listenRef = React.useRef(listen);
|
|
20
20
|
const onPostEmitRef = React.useRef(onPostEmit);
|
|
21
21
|
|
|
22
|
-
React.
|
|
22
|
+
React.useInsertionEffect(() => {
|
|
23
23
|
listenRef.current = listen;
|
|
24
24
|
onPostEmitRef.current = onPostEmit;
|
|
25
25
|
});
|
|
@@ -28,8 +28,10 @@ export function useKeyedChildListeners() {
|
|
|
28
28
|
keyedListeners[type][key] = listener;
|
|
29
29
|
|
|
30
30
|
return () => {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if (keyedListeners[type][key] === listener) {
|
|
32
|
+
// @ts-expect-error: according to ref stated above you can use `key` to index type
|
|
33
|
+
keyedListeners[type][key] = undefined;
|
|
34
|
+
}
|
|
33
35
|
};
|
|
34
36
|
},
|
|
35
37
|
[keyedListeners]
|
package/src/useNavigation.tsx
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
3
|
import { NavigationContainerRefContext } from './NavigationContainerRefContext';
|
|
4
|
-
import {
|
|
5
|
-
NavigationContext,
|
|
6
|
-
NavigationRouteNameContext,
|
|
7
|
-
} from './NavigationProvider';
|
|
4
|
+
import { NavigationContext } from './NavigationProvider';
|
|
8
5
|
import type {
|
|
9
6
|
GenericNavigation,
|
|
10
7
|
NavigationListForNavigator,
|
|
@@ -46,23 +43,7 @@ export function useNavigation(name?: string): unknown {
|
|
|
46
43
|
return navigation ?? root;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
// Generally, this condition isn't needed as `getParent(name)` works for current screen
|
|
52
|
-
// However, it'll throw if the hook is used in a preloaded screen
|
|
53
|
-
if (routeName === name) {
|
|
54
|
-
if (navigation === undefined) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Couldn't find a navigation object for '${name}'. This is not expected.`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return navigation;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// We get parent first so that `getParent(name)` is not called in preloaded screens
|
|
64
|
-
// It's ok since `getParent` also works for current screen, so parent is not skipped
|
|
65
|
-
const parent = navigation?.getParent()?.getParent(name);
|
|
46
|
+
const parent = navigation?.getParent(name);
|
|
66
47
|
|
|
67
48
|
if (parent === undefined) {
|
|
68
49
|
throw new Error(
|
|
@@ -583,7 +583,7 @@ export function useNavigationBuilder<
|
|
|
583
583
|
|
|
584
584
|
const previousRouteKeyListRef = React.useRef(routeKeyList);
|
|
585
585
|
|
|
586
|
-
React.
|
|
586
|
+
React.useInsertionEffect(() => {
|
|
587
587
|
previousRouteKeyListRef.current = routeKeyList;
|
|
588
588
|
});
|
|
589
589
|
|
|
@@ -789,19 +789,6 @@ export function useNavigationBuilder<
|
|
|
789
789
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
790
790
|
}, []);
|
|
791
791
|
|
|
792
|
-
// In some cases (e.g. route names change), internal state might have changed
|
|
793
|
-
// But it hasn't been committed yet, so hasn't propagated to the sync external store
|
|
794
|
-
// During this time, we need to return the internal state in `getState`
|
|
795
|
-
// Otherwise it can result in inconsistent state during render in children
|
|
796
|
-
// To avoid this, we use a ref for render phase, and immediately clear it on commit
|
|
797
|
-
const stateRef = React.useRef<State | null>(state);
|
|
798
|
-
|
|
799
|
-
stateRef.current = state;
|
|
800
|
-
|
|
801
|
-
React.useLayoutEffect(() => {
|
|
802
|
-
stateRef.current = null;
|
|
803
|
-
});
|
|
804
|
-
|
|
805
792
|
const getState = useLatestCallback((): State => {
|
|
806
793
|
const currentState = getCurrentState();
|
|
807
794
|
|
|
@@ -956,9 +943,9 @@ export function useNavigationBuilder<
|
|
|
956
943
|
onAction,
|
|
957
944
|
onUnhandledAction,
|
|
958
945
|
getState,
|
|
946
|
+
state,
|
|
959
947
|
emitter,
|
|
960
948
|
router,
|
|
961
|
-
stateRef,
|
|
962
949
|
});
|
|
963
950
|
|
|
964
951
|
useFocusedListenersChildrenAdapter({
|
|
@@ -1013,7 +1000,10 @@ export function useNavigationBuilder<
|
|
|
1013
1000
|
return (
|
|
1014
1001
|
<NavigationMetaContext.Provider value={undefined}>
|
|
1015
1002
|
<NavigationHelpersContext.Provider value={navigation}>
|
|
1016
|
-
<NavigationStateListenerProvider
|
|
1003
|
+
<NavigationStateListenerProvider
|
|
1004
|
+
state={state}
|
|
1005
|
+
getState={navigation.getState}
|
|
1006
|
+
>
|
|
1017
1007
|
<FocusedRouteKeyContext.Provider value={focusedRoute.key}>
|
|
1018
1008
|
<PreventRemoveProvider>{element}</PreventRemoveProvider>
|
|
1019
1009
|
</FocusedRouteKeyContext.Provider>
|
|
@@ -91,27 +91,15 @@ export function useNavigationCache<
|
|
|
91
91
|
[getState, parentNavigationHelpers, navigation, setOptions, emitter]
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
const next = routes.reduce<
|
|
95
95
|
NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>
|
|
96
96
|
>((acc, route) => {
|
|
97
97
|
const previous = cache.current[route.key];
|
|
98
98
|
|
|
99
|
-
type Thunk =
|
|
100
|
-
| NavigationAction
|
|
101
|
-
| ((state: State) => NavigationAction | null | undefined);
|
|
102
|
-
|
|
103
99
|
if (previous) {
|
|
104
100
|
// If a cached navigation object already exists, reuse it
|
|
105
101
|
acc[route.key] = previous;
|
|
106
102
|
} else {
|
|
107
|
-
const dispatch = (thunk: Thunk) => {
|
|
108
|
-
const action = typeof thunk === 'function' ? thunk(getState()) : thunk;
|
|
109
|
-
|
|
110
|
-
if (action != null) {
|
|
111
|
-
navigation.dispatch({ source: route.key, ...action });
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
103
|
const withStack = (callback: () => void) => {
|
|
116
104
|
let isStackSet = false;
|
|
117
105
|
|
|
@@ -134,6 +122,23 @@ export function useNavigationCache<
|
|
|
134
122
|
}
|
|
135
123
|
};
|
|
136
124
|
|
|
125
|
+
const dispatch = (
|
|
126
|
+
thunk:
|
|
127
|
+
| NavigationAction
|
|
128
|
+
| ((state: State) => NavigationAction | null | undefined)
|
|
129
|
+
) => {
|
|
130
|
+
withStack(() =>
|
|
131
|
+
React.startTransition(() => {
|
|
132
|
+
const action =
|
|
133
|
+
typeof thunk === 'function' ? thunk(getState()) : thunk;
|
|
134
|
+
|
|
135
|
+
if (action != null) {
|
|
136
|
+
navigation.dispatch({ source: route.key, ...action });
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
137
142
|
const actions = {
|
|
138
143
|
...router.actionCreators,
|
|
139
144
|
...CommonActions,
|
|
@@ -142,10 +147,8 @@ export function useNavigationCache<
|
|
|
142
147
|
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
|
143
148
|
(acc, name) => {
|
|
144
149
|
acc[name] = (...args: any) =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
dispatch(actions[name](...args))
|
|
148
|
-
);
|
|
150
|
+
// @ts-expect-error: name is a valid key, but TypeScript is dumb
|
|
151
|
+
dispatch(actions[name](...args));
|
|
149
152
|
|
|
150
153
|
return acc;
|
|
151
154
|
},
|
|
@@ -160,7 +163,7 @@ export function useNavigationCache<
|
|
|
160
163
|
...helpers,
|
|
161
164
|
// FIXME: too much work to fix the types for now
|
|
162
165
|
...(emitter.create(route.key) as any),
|
|
163
|
-
dispatch
|
|
166
|
+
dispatch,
|
|
164
167
|
getParent: (routeName) => {
|
|
165
168
|
if (routeName === route.name) {
|
|
166
169
|
// If the passed route name is the same as the current route's name,
|
|
@@ -197,5 +200,9 @@ export function useNavigationCache<
|
|
|
197
200
|
return acc;
|
|
198
201
|
}, {});
|
|
199
202
|
|
|
200
|
-
|
|
203
|
+
React.useInsertionEffect(() => {
|
|
204
|
+
cache.current = next;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return next;
|
|
201
208
|
}
|