@react-navigation/core 8.0.0-alpha.13 → 8.0.0-alpha.15
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/checkSerializable.js +11 -4
- package/lib/module/checkSerializable.js.map +1 -1
- package/lib/module/createNavigatorFactory.js.map +1 -1
- package/lib/module/getActionFromState.js +15 -2
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getStateFromPath.js +61 -45
- 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/useEventEmitter.js +43 -31
- package/lib/module/useEventEmitter.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +48 -47
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useRouteCache.js +2 -1
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/typescript/src/checkSerializable.d.ts +5 -3
- package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
- package/lib/typescript/src/createNavigatorFactory.d.ts +2 -1
- package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
- package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/checkSerializable.tsx +22 -12
- package/src/createNavigatorFactory.tsx +13 -1
- package/src/getActionFromState.tsx +20 -4
- package/src/getStateFromPath.tsx +110 -70
- package/src/index.tsx +1 -1
- package/src/useEventEmitter.tsx +45 -36
- package/src/useNavigationBuilder.tsx +77 -76
- package/src/useRouteCache.tsx +2 -1
package/src/getStateFromPath.tsx
CHANGED
|
@@ -29,11 +29,6 @@ type ParseConfigValue =
|
|
|
29
29
|
|
|
30
30
|
type ParseConfig = Record<string, ParseConfigValue | undefined>;
|
|
31
31
|
|
|
32
|
-
type RouteParseConfig = {
|
|
33
|
-
parseConfig?: ParseConfig | undefined;
|
|
34
|
-
pathParamNames: Set<string>;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
32
|
type RouteConfig = {
|
|
38
33
|
screen: string;
|
|
39
34
|
regex?: RegExp | undefined;
|
|
@@ -41,6 +36,8 @@ type RouteConfig = {
|
|
|
41
36
|
params: { screen: string; name?: string | undefined; index: number }[];
|
|
42
37
|
routeNames: string[];
|
|
43
38
|
parse?: ParseConfig | undefined;
|
|
39
|
+
explicitParamNames?: Set<string> | undefined;
|
|
40
|
+
hasNestedScreens: boolean;
|
|
44
41
|
};
|
|
45
42
|
|
|
46
43
|
type InitialRouteConfig = {
|
|
@@ -61,6 +58,7 @@ type ParsedRoute = {
|
|
|
61
58
|
type ConfigResources = {
|
|
62
59
|
initialRoutes: InitialRouteConfig[];
|
|
63
60
|
configs: RouteConfig[];
|
|
61
|
+
configsByScreen: Record<string, RouteConfig[]>;
|
|
64
62
|
};
|
|
65
63
|
|
|
66
64
|
const INVALID_SCHEMA_RESULT_ERROR =
|
|
@@ -71,6 +69,23 @@ const INVALID_PARSER_ERROR =
|
|
|
71
69
|
|
|
72
70
|
const PARAM_GROUP_PREFIX = 'param_';
|
|
73
71
|
|
|
72
|
+
const NESTED_SCREEN_PARAM_NAMES = [
|
|
73
|
+
'screen',
|
|
74
|
+
'params',
|
|
75
|
+
'initial',
|
|
76
|
+
'path',
|
|
77
|
+
'merge',
|
|
78
|
+
'pop',
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const getExplicitParamNames = (parse?: ParseConfig) => {
|
|
82
|
+
const names = Object.entries(parse ?? {})
|
|
83
|
+
.filter(([, parser]) => parser != null)
|
|
84
|
+
.map(([name]) => name);
|
|
85
|
+
|
|
86
|
+
return names.length ? new Set(names) : undefined;
|
|
87
|
+
};
|
|
88
|
+
|
|
74
89
|
const getStandardSchema = (parser: ParseConfigValue) => {
|
|
75
90
|
if (
|
|
76
91
|
'~standard' in parser &&
|
|
@@ -129,7 +144,8 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
129
144
|
path: string,
|
|
130
145
|
options?: Options<ParamList>
|
|
131
146
|
): ResultState | undefined {
|
|
132
|
-
const { initialRoutes, configs } =
|
|
147
|
+
const { initialRoutes, configs, configsByScreen } =
|
|
148
|
+
getConfigResources(options);
|
|
133
149
|
|
|
134
150
|
const screens = options?.screens;
|
|
135
151
|
|
|
@@ -156,15 +172,23 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
156
172
|
remaining = remaining.replace(normalizedPrefix, '');
|
|
157
173
|
}
|
|
158
174
|
|
|
175
|
+
const decodedSegments: string[] = [];
|
|
176
|
+
|
|
177
|
+
for (const segment of remaining.split('/')) {
|
|
178
|
+
if (!segment) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
decodedSegments.push(decodeURIComponent(segment));
|
|
184
|
+
} catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
159
189
|
if (screens === undefined) {
|
|
160
190
|
// When no config is specified, use the path segments as route names
|
|
161
|
-
const routes =
|
|
162
|
-
.split('/')
|
|
163
|
-
.filter(Boolean)
|
|
164
|
-
.map((segment) => {
|
|
165
|
-
const name = decodeURIComponent(segment);
|
|
166
|
-
return { name };
|
|
167
|
-
});
|
|
191
|
+
const routes = decodedSegments.map((name) => ({ name }));
|
|
168
192
|
|
|
169
193
|
if (routes.length) {
|
|
170
194
|
return createNestedStateObject(path, routes, initialRoutes);
|
|
@@ -183,7 +207,7 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
183
207
|
path,
|
|
184
208
|
match.routeNames.map((name) => ({ name })),
|
|
185
209
|
initialRoutes,
|
|
186
|
-
|
|
210
|
+
match
|
|
187
211
|
);
|
|
188
212
|
}
|
|
189
213
|
|
|
@@ -193,13 +217,13 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
193
217
|
// We match the whole path against the regex instead of segments
|
|
194
218
|
// This makes sure matches such as wildcard will catch any unmatched routes, even if nested
|
|
195
219
|
for (const config of configs) {
|
|
196
|
-
const routes = matchAgainstConfig(remaining, config,
|
|
220
|
+
const routes = matchAgainstConfig(remaining, config, configsByScreen);
|
|
197
221
|
|
|
198
222
|
if (routes === undefined) {
|
|
199
223
|
continue;
|
|
200
224
|
}
|
|
201
225
|
|
|
202
|
-
const state = createNestedStateObject(path, routes, initialRoutes,
|
|
226
|
+
const state = createNestedStateObject(path, routes, initialRoutes, config);
|
|
203
227
|
|
|
204
228
|
if (state !== undefined) {
|
|
205
229
|
return state;
|
|
@@ -240,12 +264,16 @@ function prepareConfigResources(options?: Options<{}>) {
|
|
|
240
264
|
|
|
241
265
|
checkForDuplicatedConfigs(configs);
|
|
242
266
|
|
|
243
|
-
const
|
|
267
|
+
const configsByScreen: Record<string, RouteConfig[]> = {};
|
|
268
|
+
|
|
269
|
+
for (const c of configs) {
|
|
270
|
+
(configsByScreen[c.screen] ??= []).push(c);
|
|
271
|
+
}
|
|
244
272
|
|
|
245
273
|
return {
|
|
246
274
|
initialRoutes,
|
|
247
275
|
configs,
|
|
248
|
-
|
|
276
|
+
configsByScreen,
|
|
249
277
|
};
|
|
250
278
|
}
|
|
251
279
|
|
|
@@ -388,18 +416,10 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
|
|
|
388
416
|
}, {});
|
|
389
417
|
}
|
|
390
418
|
|
|
391
|
-
function getConfigsWithRegexes(configs: RouteConfig[]) {
|
|
392
|
-
return configs.map((c) => ({
|
|
393
|
-
...c,
|
|
394
|
-
// Add `$` to the regex to make sure it matches till end of the path and not just beginning
|
|
395
|
-
regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
|
|
396
|
-
}));
|
|
397
|
-
}
|
|
398
|
-
|
|
399
419
|
const matchAgainstConfig = (
|
|
400
420
|
remaining: string,
|
|
401
421
|
config: RouteConfig,
|
|
402
|
-
|
|
422
|
+
configsByScreen: Record<string, RouteConfig[]>
|
|
403
423
|
) => {
|
|
404
424
|
if (!config.regex) {
|
|
405
425
|
return undefined;
|
|
@@ -416,9 +436,8 @@ const matchAgainstConfig = (
|
|
|
416
436
|
|
|
417
437
|
for (const routeName of config.routeNames) {
|
|
418
438
|
// Check matching name AND pattern in case same screen is used at different levels in config
|
|
419
|
-
const routeConfig =
|
|
420
|
-
(c)
|
|
421
|
-
c.screen === routeName && arrayStartsWith(config.segments, c.segments)
|
|
439
|
+
const routeConfig = configsByScreen[routeName]?.find((c) =>
|
|
440
|
+
arrayStartsWith(config.segments, c.segments)
|
|
422
441
|
);
|
|
423
442
|
|
|
424
443
|
let params: Record<string, unknown> | undefined;
|
|
@@ -439,7 +458,14 @@ const matchAgainstConfig = (
|
|
|
439
458
|
continue;
|
|
440
459
|
}
|
|
441
460
|
|
|
442
|
-
|
|
461
|
+
let decoded: string;
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
decoded = decodeURIComponent(value);
|
|
465
|
+
} catch {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
443
469
|
const parser = routeConfig.parse?.[param.name];
|
|
444
470
|
|
|
445
471
|
if (!parser) {
|
|
@@ -516,6 +542,9 @@ const createNormalizedConfigs = (
|
|
|
516
542
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
|
517
543
|
// it can have `path` property and
|
|
518
544
|
// it could have `screens` prop which has nested configs
|
|
545
|
+
const nestedScreens = 'screens' in config ? config.screens : undefined;
|
|
546
|
+
const hasNestedScreens = !!nestedScreens;
|
|
547
|
+
|
|
519
548
|
if (typeof config.path === 'string') {
|
|
520
549
|
if (config.exact && config.path == null) {
|
|
521
550
|
throw new Error(
|
|
@@ -535,7 +564,8 @@ const createNormalizedConfigs = (
|
|
|
535
564
|
screen,
|
|
536
565
|
[...routeNames],
|
|
537
566
|
[...paths, { screen, path: alias }],
|
|
538
|
-
config.parse
|
|
567
|
+
config.parse,
|
|
568
|
+
hasNestedScreens
|
|
539
569
|
)
|
|
540
570
|
);
|
|
541
571
|
} else if (typeof alias === 'object') {
|
|
@@ -546,7 +576,8 @@ const createNormalizedConfigs = (
|
|
|
546
576
|
alias.exact
|
|
547
577
|
? [{ screen, path: alias.path }]
|
|
548
578
|
: [...paths, { screen, path: alias.path }],
|
|
549
|
-
alias.parse
|
|
579
|
+
alias.parse,
|
|
580
|
+
hasNestedScreens
|
|
550
581
|
)
|
|
551
582
|
);
|
|
552
583
|
}
|
|
@@ -561,7 +592,13 @@ const createNormalizedConfigs = (
|
|
|
561
592
|
|
|
562
593
|
paths.push({ screen, path: config.path });
|
|
563
594
|
configs.push(
|
|
564
|
-
createConfigItem(
|
|
595
|
+
createConfigItem(
|
|
596
|
+
screen,
|
|
597
|
+
[...routeNames],
|
|
598
|
+
[...paths],
|
|
599
|
+
config.parse,
|
|
600
|
+
hasNestedScreens
|
|
601
|
+
)
|
|
565
602
|
);
|
|
566
603
|
|
|
567
604
|
configs.push(...aliasConfigs);
|
|
@@ -577,7 +614,7 @@ const createNormalizedConfigs = (
|
|
|
577
614
|
);
|
|
578
615
|
}
|
|
579
616
|
|
|
580
|
-
if (
|
|
617
|
+
if (hasNestedScreens) {
|
|
581
618
|
// property `initialRouteName` without `screens` has no purpose
|
|
582
619
|
if (
|
|
583
620
|
'initialRouteName' in config &&
|
|
@@ -589,10 +626,10 @@ const createNormalizedConfigs = (
|
|
|
589
626
|
});
|
|
590
627
|
}
|
|
591
628
|
|
|
592
|
-
Object.keys(
|
|
629
|
+
Object.keys(nestedScreens).forEach((nestedConfig) => {
|
|
593
630
|
const result = createNormalizedConfigs(
|
|
594
631
|
nestedConfig,
|
|
595
|
-
|
|
632
|
+
nestedScreens as Record<string, string | PathConfig<{}>>,
|
|
596
633
|
initials,
|
|
597
634
|
[...paths],
|
|
598
635
|
[...parentScreens],
|
|
@@ -613,7 +650,8 @@ const createConfigItem = (
|
|
|
613
650
|
screen: string,
|
|
614
651
|
routeNames: string[],
|
|
615
652
|
paths: { screen: string; path: string }[],
|
|
616
|
-
parse?: ParseConfig
|
|
653
|
+
parse?: ParseConfig,
|
|
654
|
+
hasNestedScreens = false
|
|
617
655
|
): RouteConfig => {
|
|
618
656
|
const parts: (PatternPart & { screen: string })[] = [];
|
|
619
657
|
|
|
@@ -658,34 +696,11 @@ const createConfigItem = (
|
|
|
658
696
|
params,
|
|
659
697
|
routeNames,
|
|
660
698
|
parse,
|
|
699
|
+
explicitParamNames: getExplicitParamNames(parse),
|
|
700
|
+
hasNestedScreens,
|
|
661
701
|
};
|
|
662
702
|
};
|
|
663
703
|
|
|
664
|
-
const findParseConfigForRoute = (
|
|
665
|
-
routeName: string,
|
|
666
|
-
flatConfig: RouteConfig[]
|
|
667
|
-
): RouteParseConfig | undefined => {
|
|
668
|
-
for (const config of flatConfig) {
|
|
669
|
-
if (routeName === config.routeNames[config.routeNames.length - 1]) {
|
|
670
|
-
return {
|
|
671
|
-
parseConfig: config.parse,
|
|
672
|
-
pathParamNames: new Set(
|
|
673
|
-
config.params
|
|
674
|
-
.filter(
|
|
675
|
-
(
|
|
676
|
-
param
|
|
677
|
-
): param is { screen: string; name: string; index: number } =>
|
|
678
|
-
param.screen === routeName && typeof param.name === 'string'
|
|
679
|
-
)
|
|
680
|
-
.map((param) => param.name)
|
|
681
|
-
),
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return undefined;
|
|
687
|
-
};
|
|
688
|
-
|
|
689
704
|
// Try to find an initial route connected with the one passed
|
|
690
705
|
const findInitialRoute = (
|
|
691
706
|
routeName: string,
|
|
@@ -747,7 +762,7 @@ const createNestedStateObject = (
|
|
|
747
762
|
path: string,
|
|
748
763
|
routes: ParsedRoute[],
|
|
749
764
|
initialRoutes: InitialRouteConfig[],
|
|
750
|
-
|
|
765
|
+
routeConfig?: RouteConfig
|
|
751
766
|
): InitialState | undefined => {
|
|
752
767
|
let route = routes.shift() as ParsedRoute;
|
|
753
768
|
const parentScreens: string[] = [];
|
|
@@ -789,14 +804,22 @@ const createNestedStateObject = (
|
|
|
789
804
|
route = findFocusedRoute(state) as ParsedRoute;
|
|
790
805
|
route.path = path.replace(/\/$/, '');
|
|
791
806
|
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
807
|
+
const pathParamNames = new Set(
|
|
808
|
+
routeConfig?.params
|
|
809
|
+
.filter(
|
|
810
|
+
(param): param is { screen: string; name: string; index: number } =>
|
|
811
|
+
param.screen === routeConfig.screen && typeof param.name === 'string'
|
|
812
|
+
)
|
|
813
|
+
.map((param) => param.name)
|
|
814
|
+
);
|
|
795
815
|
|
|
796
816
|
const queryParams = parseQueryParams(
|
|
797
817
|
path,
|
|
798
|
-
|
|
799
|
-
|
|
818
|
+
routeConfig?.parse,
|
|
819
|
+
pathParamNames,
|
|
820
|
+
routeConfig?.explicitParamNames,
|
|
821
|
+
routeConfig?.hasNestedScreens,
|
|
822
|
+
route.params
|
|
800
823
|
);
|
|
801
824
|
|
|
802
825
|
if (!queryParams.valid) {
|
|
@@ -813,7 +836,10 @@ const createNestedStateObject = (
|
|
|
813
836
|
const parseQueryParams = (
|
|
814
837
|
path: string,
|
|
815
838
|
parseConfig?: ParseConfig,
|
|
816
|
-
pathParamNames: Set<string> = new Set()
|
|
839
|
+
pathParamNames: Set<string> = new Set(),
|
|
840
|
+
explicitParamNames?: Set<string>,
|
|
841
|
+
hasNestedScreens = false,
|
|
842
|
+
routeParams?: Record<string, unknown>
|
|
817
843
|
):
|
|
818
844
|
| { valid: true; params?: Record<string, unknown> | undefined }
|
|
819
845
|
| { valid: false } => {
|
|
@@ -882,6 +908,20 @@ const parseQueryParams = (
|
|
|
882
908
|
}
|
|
883
909
|
}
|
|
884
910
|
|
|
911
|
+
if (
|
|
912
|
+
hasNestedScreens &&
|
|
913
|
+
!explicitParamNames?.has('screen') &&
|
|
914
|
+
(typeof params.screen === 'string' ||
|
|
915
|
+
typeof routeParams?.screen === 'string')
|
|
916
|
+
) {
|
|
917
|
+
for (const name of NESTED_SCREEN_PARAM_NAMES) {
|
|
918
|
+
if (!explicitParamNames?.has(name)) {
|
|
919
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
920
|
+
delete params[name];
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
885
925
|
return {
|
|
886
926
|
valid: true,
|
|
887
927
|
params: Object.keys(params).length ? params : undefined,
|
package/src/index.tsx
CHANGED
|
@@ -38,7 +38,7 @@ export { ThemeProvider } from './theming/ThemeProvider';
|
|
|
38
38
|
export { useTheme } from './theming/useTheme';
|
|
39
39
|
export * from './types';
|
|
40
40
|
export { useFocusEffect } from './useFocusEffect';
|
|
41
|
-
export { useIsFocused } from './useIsFocused';
|
|
41
|
+
export { IsFocusedContext, useIsFocused } from './useIsFocused';
|
|
42
42
|
export { useNavigation } from './useNavigation';
|
|
43
43
|
export { useNavigationBuilder } from './useNavigationBuilder';
|
|
44
44
|
export { useNavigationContainerRef } from './useNavigationContainerRef';
|
package/src/useEventEmitter.tsx
CHANGED
|
@@ -59,58 +59,67 @@ export function useEventEmitter<T extends Record<string, any>>(
|
|
|
59
59
|
target?: string;
|
|
60
60
|
canPreventDefault?: boolean;
|
|
61
61
|
}) => {
|
|
62
|
-
const items = listeners.current[type]
|
|
62
|
+
const items = listeners.current[type];
|
|
63
63
|
|
|
64
64
|
// Copy the current list of callbacks in case they are mutated during execution
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
|
|
65
|
+
let callbacks: Set<(e: any) => void> | undefined;
|
|
66
|
+
|
|
67
|
+
if (items) {
|
|
68
|
+
if (target !== undefined) {
|
|
69
|
+
const targetSet = items[target];
|
|
70
|
+
|
|
71
|
+
if (targetSet && targetSet.size) {
|
|
72
|
+
callbacks = new Set(targetSet);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
for (const key in items) {
|
|
76
|
+
const set = items[key];
|
|
77
|
+
|
|
78
|
+
if (set && set.size) {
|
|
79
|
+
callbacks ??= new Set();
|
|
80
|
+
|
|
81
|
+
for (const cb of set) {
|
|
82
|
+
callbacks.add(cb);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
70
88
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
return type;
|
|
74
|
-
},
|
|
89
|
+
const descriptors: PropertyDescriptorMap = {
|
|
90
|
+
type: { enumerable: true, value: type },
|
|
75
91
|
};
|
|
76
92
|
|
|
77
93
|
if (target !== undefined) {
|
|
78
|
-
|
|
79
|
-
enumerable: true,
|
|
80
|
-
get() {
|
|
81
|
-
return target;
|
|
82
|
-
},
|
|
83
|
-
});
|
|
94
|
+
descriptors.target = { enumerable: true, value: target };
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
if (data !== undefined) {
|
|
87
|
-
|
|
88
|
-
enumerable: true,
|
|
89
|
-
get() {
|
|
90
|
-
return data;
|
|
91
|
-
},
|
|
92
|
-
});
|
|
98
|
+
descriptors.data = { enumerable: true, value: data };
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
let defaultPrevented = false;
|
|
102
|
+
|
|
95
103
|
if (canPreventDefault) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
enumerable: true,
|
|
101
|
-
get() {
|
|
102
|
-
return defaultPrevented;
|
|
103
|
-
},
|
|
104
|
+
descriptors.defaultPrevented = {
|
|
105
|
+
enumerable: true,
|
|
106
|
+
get() {
|
|
107
|
+
return defaultPrevented;
|
|
104
108
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
};
|
|
110
|
+
descriptors.preventDefault = {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
value() {
|
|
113
|
+
defaultPrevented = true;
|
|
110
114
|
},
|
|
111
|
-
}
|
|
115
|
+
};
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
const event: EventArg<any, any, any> = Object.defineProperties(
|
|
119
|
+
{} as EventArg<any, any, any>,
|
|
120
|
+
descriptors
|
|
121
|
+
);
|
|
122
|
+
|
|
114
123
|
listenRef.current?.(event);
|
|
115
124
|
|
|
116
125
|
callbacks?.forEach((cb) => cb(event));
|
|
@@ -63,6 +63,14 @@ type NavigatorRoute = {
|
|
|
63
63
|
params?: NavigatorScreenParams<ParamListBase> | undefined;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
const isNavigationState = (
|
|
67
|
+
state: unknown
|
|
68
|
+
): state is NavigationState | PartialState<NavigationState> =>
|
|
69
|
+
state != null &&
|
|
70
|
+
typeof state === 'object' &&
|
|
71
|
+
'routes' in state &&
|
|
72
|
+
Array.isArray(state.routes);
|
|
73
|
+
|
|
66
74
|
const isScreen = (
|
|
67
75
|
child: React.ReactElement<unknown>
|
|
68
76
|
): child is React.ReactElement<{
|
|
@@ -279,8 +287,10 @@ const getRouteConfigsFromChildren = <
|
|
|
279
287
|
};
|
|
280
288
|
|
|
281
289
|
const getStateFromParams = (params: NavigatorRoute['params']) => {
|
|
282
|
-
|
|
283
|
-
|
|
290
|
+
const state = params?.state;
|
|
291
|
+
|
|
292
|
+
if (isNavigationState(state)) {
|
|
293
|
+
return state;
|
|
284
294
|
} else if (typeof params?.screen === 'string' && params?.initial !== false) {
|
|
285
295
|
return {
|
|
286
296
|
routes: [
|
|
@@ -373,44 +383,7 @@ export function useNavigationBuilder<
|
|
|
373
383
|
return original;
|
|
374
384
|
});
|
|
375
385
|
|
|
376
|
-
const screens = routeConfigs.reduce<
|
|
377
|
-
Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
|
|
378
|
-
>((acc, config) => {
|
|
379
|
-
if (config.props.name in acc) {
|
|
380
|
-
throw new Error(
|
|
381
|
-
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.props.name}')`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
acc[config.props.name] = config;
|
|
386
|
-
return acc;
|
|
387
|
-
}, {});
|
|
388
|
-
|
|
389
386
|
const routeNames = routeConfigs.map((config) => config.props.name);
|
|
390
|
-
const routeKeyList = routeNames.reduce<Record<string, React.Key | undefined>>(
|
|
391
|
-
(acc, curr) => {
|
|
392
|
-
acc[curr] = screens[curr].keys.map((key) => key ?? '').join(':');
|
|
393
|
-
return acc;
|
|
394
|
-
},
|
|
395
|
-
{}
|
|
396
|
-
);
|
|
397
|
-
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
|
398
|
-
(acc, curr) => {
|
|
399
|
-
const { initialParams } = screens[curr].props;
|
|
400
|
-
acc[curr] = initialParams;
|
|
401
|
-
return acc;
|
|
402
|
-
},
|
|
403
|
-
{}
|
|
404
|
-
);
|
|
405
|
-
const routeGetIdList = routeNames.reduce<
|
|
406
|
-
RouterConfigOptions['routeGetIdList']
|
|
407
|
-
>(
|
|
408
|
-
(acc, curr) =>
|
|
409
|
-
Object.assign(acc, {
|
|
410
|
-
[curr]: screens[curr].props.getId,
|
|
411
|
-
}),
|
|
412
|
-
{}
|
|
413
|
-
);
|
|
414
387
|
|
|
415
388
|
if (!routeNames.length) {
|
|
416
389
|
throw new Error(
|
|
@@ -418,6 +391,31 @@ export function useNavigationBuilder<
|
|
|
418
391
|
);
|
|
419
392
|
}
|
|
420
393
|
|
|
394
|
+
const screens: Record<
|
|
395
|
+
string,
|
|
396
|
+
ScreenConfigWithParent<State, ScreenOptions, EventMap>
|
|
397
|
+
> = {};
|
|
398
|
+
|
|
399
|
+
const routeKeyList: Record<string, React.Key | undefined> = {};
|
|
400
|
+
const routeParamList: Record<string, object | undefined> = {};
|
|
401
|
+
const routeGetIdList: RouterConfigOptions['routeGetIdList'] = {};
|
|
402
|
+
|
|
403
|
+
for (const config of routeConfigs) {
|
|
404
|
+
const name = config.props.name;
|
|
405
|
+
|
|
406
|
+
if (name in screens) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${name}')`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
screens[name] = config;
|
|
413
|
+
routeKeyList[name] = config.keys.map((key) => key ?? '').join(':');
|
|
414
|
+
routeParamList[name] = config.props.initialParams;
|
|
415
|
+
|
|
416
|
+
Object.assign(routeGetIdList, { [name]: config.props.getId });
|
|
417
|
+
}
|
|
418
|
+
|
|
421
419
|
const isStateValid = React.useCallback(
|
|
422
420
|
(state: NavigationState | PartialState<NavigationState>) =>
|
|
423
421
|
state.type === undefined || state.type === router.type,
|
|
@@ -656,24 +654,21 @@ export function useNavigationBuilder<
|
|
|
656
654
|
|
|
657
655
|
if (route?.params && !didConsumeNestedParams) {
|
|
658
656
|
let action: CommonActions.Action | undefined;
|
|
657
|
+
const stateFromParams = route.params.state;
|
|
659
658
|
|
|
660
|
-
if (
|
|
661
|
-
typeof route.params.state === 'object' &&
|
|
662
|
-
route.params.state != null &&
|
|
663
|
-
!isNestedParamsConsumed
|
|
664
|
-
) {
|
|
659
|
+
if (isNavigationState(stateFromParams) && !isNestedParamsConsumed) {
|
|
665
660
|
didConsumeNestedParams = true;
|
|
666
661
|
|
|
667
662
|
if (
|
|
668
663
|
options.routeNamesChangeBehavior === 'lastUnhandled' &&
|
|
669
|
-
doesStateHaveOnlyInvalidRoutes(
|
|
664
|
+
doesStateHaveOnlyInvalidRoutes(stateFromParams)
|
|
670
665
|
) {
|
|
671
|
-
if (
|
|
672
|
-
setUnhandledState(
|
|
666
|
+
if (stateFromParams !== unhandledState) {
|
|
667
|
+
setUnhandledState(stateFromParams);
|
|
673
668
|
}
|
|
674
669
|
} else {
|
|
675
670
|
// If the route was updated with new state, we should reset to it
|
|
676
|
-
action = CommonActions.reset(
|
|
671
|
+
action = CommonActions.reset(stateFromParams);
|
|
677
672
|
}
|
|
678
673
|
} else if (
|
|
679
674
|
typeof route.params.screen === 'string' &&
|
|
@@ -834,35 +829,41 @@ export function useNavigationBuilder<
|
|
|
834
829
|
return;
|
|
835
830
|
}
|
|
836
831
|
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
832
|
+
const hasPerScreenListeners = routeNames.some(
|
|
833
|
+
(name) => screens[name].props.listeners != null
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
if (screenListeners != null || hasPerScreenListeners) {
|
|
837
|
+
const navigation = descriptors[route.key].navigation;
|
|
838
|
+
|
|
839
|
+
const listeners = ([] as (((e: any) => void) | undefined)[])
|
|
840
|
+
.concat(
|
|
841
|
+
// Get an array of listeners for all screens + common listeners on navigator
|
|
842
|
+
...[
|
|
843
|
+
screenListeners,
|
|
844
|
+
...routeNames.map((name) => {
|
|
845
|
+
const { listeners } = screens[name].props;
|
|
846
|
+
return listeners;
|
|
847
|
+
}),
|
|
848
|
+
].map((listeners) => {
|
|
849
|
+
const map =
|
|
850
|
+
typeof listeners === 'function'
|
|
851
|
+
? listeners({ route: route as any, navigation })
|
|
852
|
+
: listeners;
|
|
853
|
+
|
|
854
|
+
return map
|
|
855
|
+
? Object.keys(map)
|
|
856
|
+
.filter((type) => type === e.type)
|
|
857
|
+
.map((type) => map?.[type])
|
|
858
|
+
: undefined;
|
|
859
|
+
})
|
|
860
|
+
)
|
|
861
|
+
// We don't want same listener to be called multiple times for same event
|
|
862
|
+
// So we remove any duplicate functions from the array
|
|
863
|
+
.filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
|
|
864
864
|
|
|
865
|
-
|
|
865
|
+
listeners.forEach((listener) => listener?.(e));
|
|
866
|
+
}
|
|
866
867
|
|
|
867
868
|
onEmitEvent({
|
|
868
869
|
type: e.type,
|
package/src/useRouteCache.tsx
CHANGED
|
@@ -36,9 +36,10 @@ export function useRouteCache<State extends NavigationState>(
|
|
|
36
36
|
proxy = routeWithoutState;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
39
|
+
if (process.env.NODE_ENV !== 'production' && proxy !== previous) {
|
|
40
40
|
// FIXME: since the state is updated with mutation, the route object cannot be frozen
|
|
41
41
|
// As a workaround, loop through the object and make the properties readonly
|
|
42
|
+
// Only needed once per proxy - skip if we're reusing a previously-frozen one
|
|
42
43
|
for (const key in proxy) {
|
|
43
44
|
// @ts-expect-error: this is fine since we are looping through the object
|
|
44
45
|
const value = proxy[key];
|