@react-navigation/core 8.0.0-alpha.26 → 8.0.0-alpha.27
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 +12 -6
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/ConsumedParamsContext.js.map +1 -1
- package/lib/module/DataLoading.js +29 -4
- package/lib/module/DataLoading.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +8 -4
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/SceneView.js +16 -39
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/StaticNavigation.js +4 -3
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/findFocusedRoute.js +1 -1
- package/lib/module/findFocusedRoute.js.map +1 -1
- package/lib/module/getActionFromState.js +1 -2
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getPathFromState.js +41 -26
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +15 -2
- package/lib/module/getPatternParts.js.map +1 -1
- package/lib/module/getStateFromPath.js +45 -19
- 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/useFocusEvents.js +6 -2
- package/lib/module/useFocusEvents.js.map +1 -1
- package/lib/module/useLazyValue.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +8 -6
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useOnAction.js +6 -16
- package/lib/module/useOnAction.js.map +1 -1
- package/lib/module/useOnPreventRemove.js +1 -3
- package/lib/module/useOnPreventRemove.js.map +1 -1
- package/lib/module/useRouteCache.js +1 -3
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/module/useSyncState.js +3 -2
- package/lib/module/useSyncState.js.map +1 -1
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/ConsumedParamsContext.d.ts +1 -6
- package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
- package/lib/typescript/src/DataLoading.d.ts +1 -0
- package/lib/typescript/src/DataLoading.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/StaticNavigation.d.ts +39 -30
- package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
- package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useOnAction.d.ts.map +1 -1
- package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/lib/typescript/src/useSyncState.d.ts.map +1 -1
- package/lib/typescript/src/utilities.d.ts +10 -0
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BaseNavigationContainer.tsx +13 -7
- package/src/ConsumedParamsContext.tsx +1 -6
- package/src/DataLoading.tsx +68 -4
- package/src/NavigationIndependentTree.tsx +8 -5
- package/src/SceneView.tsx +18 -52
- package/src/StaticNavigation.tsx +88 -64
- package/src/findFocusedRoute.tsx +1 -1
- package/src/getActionFromState.tsx +7 -3
- package/src/getPathFromState.tsx +59 -32
- package/src/getPatternParts.tsx +15 -2
- package/src/getStateFromPath.tsx +77 -17
- package/src/index.tsx +5 -0
- package/src/types.tsx +2 -2
- package/src/useDescriptors.tsx +1 -1
- package/src/useFocusEvents.tsx +7 -5
- package/src/useLazyValue.tsx +2 -2
- package/src/useNavigationBuilder.tsx +12 -7
- package/src/useOnAction.tsx +11 -40
- package/src/useOnPreventRemove.tsx +4 -3
- package/src/useRouteCache.tsx +1 -4
- package/src/useSyncState.tsx +3 -2
- package/src/utilities.tsx +19 -0
package/src/getStateFromPath.tsx
CHANGED
|
@@ -39,7 +39,12 @@ type RouteConfig = {
|
|
|
39
39
|
regex?: RegExp | undefined;
|
|
40
40
|
pattern: string;
|
|
41
41
|
segments: string[];
|
|
42
|
-
params: {
|
|
42
|
+
params: {
|
|
43
|
+
screen: string;
|
|
44
|
+
name: string;
|
|
45
|
+
index: number;
|
|
46
|
+
regex?: RegExp | undefined;
|
|
47
|
+
}[];
|
|
43
48
|
routeNames: string[];
|
|
44
49
|
parse?: ParseConfig | undefined;
|
|
45
50
|
explicitParamNames?: Set<string> | undefined;
|
|
@@ -69,6 +74,7 @@ type ConfigResources = {
|
|
|
69
74
|
configs: RouteConfig[];
|
|
70
75
|
configsByScreen: Record<string, RouteConfig[]>;
|
|
71
76
|
configsByPattern: Record<string, RouteConfig[]>;
|
|
77
|
+
prefixRegex?: RegExp | undefined;
|
|
72
78
|
};
|
|
73
79
|
|
|
74
80
|
const INVALID_SCHEMA_RESULT_ERROR =
|
|
@@ -88,6 +94,17 @@ const NESTED_SCREEN_PARAM_NAMES = [
|
|
|
88
94
|
'pop',
|
|
89
95
|
];
|
|
90
96
|
|
|
97
|
+
const getStaticSegmentPattern = (segment: string) =>
|
|
98
|
+
Array.from(segment, (char) => {
|
|
99
|
+
const encoded = encodeURIComponent(char);
|
|
100
|
+
const percentEncoded =
|
|
101
|
+
encoded === char
|
|
102
|
+
? `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`
|
|
103
|
+
: encoded;
|
|
104
|
+
|
|
105
|
+
return `(?:${escape(char)}|${escape(percentEncoded)})`;
|
|
106
|
+
}).join('');
|
|
107
|
+
|
|
91
108
|
const getExplicitParamNames = (parse?: ParseConfig) => {
|
|
92
109
|
const names: string[] = [];
|
|
93
110
|
|
|
@@ -161,8 +178,13 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
161
178
|
options?: Options<ParamList>,
|
|
162
179
|
previous?: NavigationState
|
|
163
180
|
): ResultState | undefined {
|
|
164
|
-
const {
|
|
165
|
-
|
|
181
|
+
const {
|
|
182
|
+
initialRoutes,
|
|
183
|
+
configs,
|
|
184
|
+
configsByScreen,
|
|
185
|
+
configsByPattern,
|
|
186
|
+
prefixRegex,
|
|
187
|
+
} = getConfigResources(options);
|
|
166
188
|
|
|
167
189
|
const screens = options?.screens;
|
|
168
190
|
|
|
@@ -183,24 +205,20 @@ export function getStateFromPath<ParamList extends {}>(
|
|
|
183
205
|
let remaining = path
|
|
184
206
|
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
|
185
207
|
.replace(/^\//, '') // Remove extra leading slash
|
|
186
|
-
.replace(/\?.*$/, '')
|
|
208
|
+
.replace(/\?.*$/, '') // Remove query params which we will handle later
|
|
209
|
+
.replace(/%[0-9a-f]{2}/gi, (m) => m.toUpperCase()); // Normalize percent-encoding as hex digits are case-insensitive
|
|
187
210
|
|
|
188
211
|
// Make sure there is a trailing slash
|
|
189
212
|
remaining = remaining.endsWith('/') ? remaining : `${remaining}/`;
|
|
190
213
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (prefix) {
|
|
194
|
-
// Make sure there is a trailing slash
|
|
195
|
-
const normalizedPrefix = prefix.endsWith('/') ? prefix : `${prefix}/`;
|
|
214
|
+
if (prefixRegex) {
|
|
215
|
+
const prefixMatch = remaining.match(prefixRegex);
|
|
196
216
|
|
|
197
|
-
|
|
198
|
-
if (!remaining.startsWith(normalizedPrefix)) {
|
|
217
|
+
if (prefixMatch == null) {
|
|
199
218
|
return undefined;
|
|
200
219
|
}
|
|
201
220
|
|
|
202
|
-
|
|
203
|
-
remaining = remaining.replace(normalizedPrefix, '');
|
|
221
|
+
remaining = remaining.slice(prefixMatch[0].length);
|
|
204
222
|
}
|
|
205
223
|
|
|
206
224
|
if (screens === undefined) {
|
|
@@ -358,6 +376,19 @@ function getConfigResources<ParamList extends {}>(
|
|
|
358
376
|
function prepareConfigResources(options?: Options<{}>) {
|
|
359
377
|
const initialRoutes = getInitialRoutes(options);
|
|
360
378
|
const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
|
|
379
|
+
const prefix = options?.path?.replace(/^\//, '');
|
|
380
|
+
|
|
381
|
+
let prefixRegex: RegExp | undefined;
|
|
382
|
+
|
|
383
|
+
if (prefix) {
|
|
384
|
+
const normalizedPrefix = prefix.endsWith('/') ? prefix : `${prefix}/`;
|
|
385
|
+
const prefixPattern = normalizedPrefix
|
|
386
|
+
.split('/')
|
|
387
|
+
.map(getStaticSegmentPattern)
|
|
388
|
+
.join('/');
|
|
389
|
+
|
|
390
|
+
prefixRegex = new RegExp(`^${prefixPattern}`);
|
|
391
|
+
}
|
|
361
392
|
|
|
362
393
|
const configsByScreen: Record<string, RouteConfig[]> = {};
|
|
363
394
|
const configsByPattern: Record<string, RouteConfig[]> = {};
|
|
@@ -380,6 +411,7 @@ function prepareConfigResources(options?: Options<{}>) {
|
|
|
380
411
|
configs,
|
|
381
412
|
configsByScreen,
|
|
382
413
|
configsByPattern,
|
|
414
|
+
prefixRegex,
|
|
383
415
|
};
|
|
384
416
|
}
|
|
385
417
|
|
|
@@ -428,7 +460,21 @@ function getSortedNormalizedConfigs(
|
|
|
428
460
|
// If 2 patterns are same, move the one with less route names up
|
|
429
461
|
// This is an error state, so it's only useful for consistent error messages
|
|
430
462
|
if (isArrayEqual(a.segments, b.segments)) {
|
|
431
|
-
|
|
463
|
+
if (
|
|
464
|
+
a.routeNames.length > b.routeNames.length &&
|
|
465
|
+
arrayStartsWith(a.routeNames, b.routeNames)
|
|
466
|
+
) {
|
|
467
|
+
return -1;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (
|
|
471
|
+
b.routeNames.length > a.routeNames.length &&
|
|
472
|
+
arrayStartsWith(b.routeNames, a.routeNames)
|
|
473
|
+
) {
|
|
474
|
+
return 1;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return a.routeNames.length - b.routeNames.length || a.order - b.order;
|
|
432
478
|
}
|
|
433
479
|
|
|
434
480
|
// If one of the patterns starts with the other, it's more exhaustive
|
|
@@ -585,6 +631,12 @@ const matchAgainstConfig = (
|
|
|
585
631
|
return undefined;
|
|
586
632
|
}
|
|
587
633
|
|
|
634
|
+
// Percent-encoded values are matched permissively
|
|
635
|
+
// So validate the decoded value against the custom regex
|
|
636
|
+
if (param.regex && value !== decoded && !param.regex.test(decoded)) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
|
|
588
640
|
const parser = routeConfig.parse?.[param.name];
|
|
589
641
|
|
|
590
642
|
if (!parser) {
|
|
@@ -799,20 +851,27 @@ const createConfigItem = (
|
|
|
799
851
|
segments.push(part.segment);
|
|
800
852
|
|
|
801
853
|
if (part.param) {
|
|
802
|
-
|
|
854
|
+
// A custom regex may not match the percent-encoded form of the value
|
|
855
|
+
// So also accept segments containing an encoded character and validate them after decoding
|
|
856
|
+
const reg = part.regex
|
|
857
|
+
? `(?:${part.regex})|(?=[^/]*%[0-9A-F]{2})[^/]+`
|
|
858
|
+
: '[^/]+';
|
|
803
859
|
|
|
804
860
|
regexString += `(((?<${PARAM_GROUP_PREFIX}${index}>${reg})\\/)${part.optional ? '?' : ''})`;
|
|
805
861
|
params.push({
|
|
806
862
|
index,
|
|
807
863
|
screen: part.screen,
|
|
808
864
|
name: part.param,
|
|
865
|
+
regex: part.regex ? new RegExp(`^(?:${part.regex})$`) : undefined,
|
|
809
866
|
});
|
|
810
867
|
|
|
811
868
|
if (part.screen === screen) {
|
|
812
869
|
pathParamNames.add(part.param);
|
|
813
870
|
}
|
|
871
|
+
} else if (part.segment === '*') {
|
|
872
|
+
regexString += `.*\\/`;
|
|
814
873
|
} else {
|
|
815
|
-
regexString += `${
|
|
874
|
+
regexString += `${getStaticSegmentPattern(part.segment)}\\/`;
|
|
816
875
|
}
|
|
817
876
|
|
|
818
877
|
index++;
|
|
@@ -1083,7 +1142,8 @@ const parseQueryParams = (
|
|
|
1083
1142
|
):
|
|
1084
1143
|
| { valid: true; params?: Record<string, unknown> | undefined }
|
|
1085
1144
|
| { valid: false } => {
|
|
1086
|
-
const
|
|
1145
|
+
const queryIndex = path.indexOf('?');
|
|
1146
|
+
const query = queryIndex === -1 ? undefined : path.slice(queryIndex + 1);
|
|
1087
1147
|
const params: Record<string, unknown> = query ? queryString.parse(query) : {};
|
|
1088
1148
|
|
|
1089
1149
|
// Path params should always win over same-named query params.
|
package/src/index.tsx
CHANGED
|
@@ -11,6 +11,10 @@ export { getFocusedRouteNameFromRoute } from './getFocusedRouteNameFromRoute';
|
|
|
11
11
|
export { getPathFromState } from './getPathFromState';
|
|
12
12
|
export { getStateFromPath } from './getStateFromPath';
|
|
13
13
|
export { NavigationContainerRefContext } from './NavigationContainerRefContext';
|
|
14
|
+
export {
|
|
15
|
+
type FocusedRouteState,
|
|
16
|
+
NavigationFocusedRouteStateContext,
|
|
17
|
+
} from './NavigationFocusedRouteStateContext';
|
|
14
18
|
export { NavigationHelpersContext } from './NavigationHelpersContext';
|
|
15
19
|
export { NavigationIndependentTree } from './NavigationIndependentTree';
|
|
16
20
|
export { NavigationMetaContext } from './NavigationMetaContext';
|
|
@@ -61,6 +65,7 @@ export type {
|
|
|
61
65
|
NavigatorTypeBagBase,
|
|
62
66
|
NavigatorTypeBagFor,
|
|
63
67
|
NestedNavigatorsForParamList,
|
|
68
|
+
ParamListForNavigator,
|
|
64
69
|
PathConfig,
|
|
65
70
|
PathConfigMap,
|
|
66
71
|
RootNavigator,
|
package/src/types.tsx
CHANGED
|
@@ -298,7 +298,7 @@ type NavigatorLike =
|
|
|
298
298
|
getComponent: () => React.ComponentType<any>;
|
|
299
299
|
};
|
|
300
300
|
|
|
301
|
-
type ParamListForNavigator<T extends {}> = T extends NavigatorLike
|
|
301
|
+
export type ParamListForNavigator<T extends {}> = T extends NavigatorLike
|
|
302
302
|
? T extends PrivateValueStore<[infer ParamList extends {}, any, any, any]>
|
|
303
303
|
? ParamList
|
|
304
304
|
: T
|
|
@@ -1654,7 +1654,7 @@ export type PathConfig<Params> = FlatType<
|
|
|
1654
1654
|
path?: string | undefined;
|
|
1655
1655
|
/**
|
|
1656
1656
|
* An object mapping the param name to a function which converts the param value to a string.
|
|
1657
|
-
* By default,
|
|
1657
|
+
* By default, arrays and null are preserved for query params, and other values are converted using `String(value)`.
|
|
1658
1658
|
* Keys are constrained to valid param names when Params type is provided.
|
|
1659
1659
|
*
|
|
1660
1660
|
* @example
|
package/src/useDescriptors.tsx
CHANGED
package/src/useFocusEvents.tsx
CHANGED
|
@@ -51,7 +51,12 @@ export function useFocusEvents<State extends NavigationState>({
|
|
|
51
51
|
React.useEffect(() => {
|
|
52
52
|
const lastFocusedKey = lastFocusedKeyRef.current;
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const isFocused = navigation ? navigation.isFocused() : true;
|
|
55
|
+
|
|
56
|
+
if (isFocused) {
|
|
57
|
+
// Remember the current route as focused if the navigator is focused
|
|
58
|
+
lastFocusedKeyRef.current = currentFocusedKey;
|
|
59
|
+
}
|
|
55
60
|
|
|
56
61
|
// We wouldn't have `lastFocusedKey` on initial mount
|
|
57
62
|
// Fire focus event for the current route on mount if there's no parent navigator
|
|
@@ -61,10 +66,7 @@ export function useFocusEvents<State extends NavigationState>({
|
|
|
61
66
|
|
|
62
67
|
// We should only emit events when the focused key changed and navigator is focused
|
|
63
68
|
// When navigator is not focused, screens inside shouldn't receive focused status either
|
|
64
|
-
if (
|
|
65
|
-
lastFocusedKey === currentFocusedKey ||
|
|
66
|
-
!(navigation ? navigation.isFocused() : true)
|
|
67
|
-
) {
|
|
69
|
+
if (lastFocusedKey === currentFocusedKey || !isFocused) {
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
72
|
|
package/src/useLazyValue.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
export function useLazyValue<T>(create: () => T) {
|
|
3
|
+
export function useLazyValue<T>(create: () => T): T {
|
|
4
4
|
const lazyRef = React.useRef<T>(undefined);
|
|
5
5
|
|
|
6
6
|
if (lazyRef.current === undefined) {
|
|
7
7
|
lazyRef.current = create();
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
return lazyRef.current
|
|
10
|
+
return lazyRef.current;
|
|
11
11
|
}
|
|
@@ -337,7 +337,7 @@ export function useNavigationBuilder<
|
|
|
337
337
|
|
|
338
338
|
const isNestedParamsConsumed =
|
|
339
339
|
typeof route?.params === 'object' && route.params != null
|
|
340
|
-
? consumedParams?.
|
|
340
|
+
? consumedParams?.has(route.params)
|
|
341
341
|
: false;
|
|
342
342
|
|
|
343
343
|
const {
|
|
@@ -571,7 +571,14 @@ export function useNavigationBuilder<
|
|
|
571
571
|
return [stateBeforeInitialization, hydratedState, true, paramsForState];
|
|
572
572
|
}
|
|
573
573
|
|
|
574
|
-
return [
|
|
574
|
+
return [
|
|
575
|
+
undefined,
|
|
576
|
+
hydratedState,
|
|
577
|
+
false,
|
|
578
|
+
// Current state is already initialized
|
|
579
|
+
// So params weren't used, and need to be handled later
|
|
580
|
+
isStateInitialized(currentState) ? undefined : paramsForState,
|
|
581
|
+
];
|
|
575
582
|
}
|
|
576
583
|
// We explicitly don't include routeNames, route.params etc. in the dep list
|
|
577
584
|
// below. We want to avoid forcing a new state to be calculated in those cases
|
|
@@ -719,18 +726,16 @@ export function useNavigationBuilder<
|
|
|
719
726
|
: nextState;
|
|
720
727
|
}
|
|
721
728
|
|
|
722
|
-
const setConsumedParams = consumedParams?.setConsumed;
|
|
723
|
-
|
|
724
729
|
React.useEffect(() => {
|
|
725
730
|
if (
|
|
726
|
-
|
|
731
|
+
consumedParams &&
|
|
727
732
|
didConsumeNestedParams &&
|
|
728
733
|
typeof route?.params === 'object' &&
|
|
729
734
|
route.params != null
|
|
730
735
|
) {
|
|
731
|
-
|
|
736
|
+
consumedParams.set(route.params, true);
|
|
732
737
|
}
|
|
733
|
-
}, [didConsumeNestedParams, route?.params
|
|
738
|
+
}, [consumedParams, didConsumeNestedParams, route?.params]);
|
|
734
739
|
|
|
735
740
|
const shouldUpdate = state !== nextState;
|
|
736
741
|
|
package/src/useOnAction.tsx
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
NavigationAction,
|
|
3
|
-
NavigationRoute,
|
|
4
3
|
NavigationState,
|
|
5
|
-
ParamListBase,
|
|
6
|
-
PartialRoute,
|
|
7
4
|
PartialState,
|
|
8
5
|
Router,
|
|
9
6
|
RouterConfigOptions,
|
|
10
7
|
} from '@react-navigation/routers';
|
|
11
8
|
import * as React from 'react';
|
|
12
9
|
|
|
13
|
-
import {
|
|
10
|
+
import { ConsumedParamsContext } from './ConsumedParamsContext';
|
|
11
|
+
import { getLoaderForStateChange } from './DataLoading';
|
|
14
12
|
import {
|
|
15
13
|
type ChildActionListener,
|
|
16
14
|
type ChildBeforeRemoveListener,
|
|
@@ -32,40 +30,6 @@ type Options<State extends NavigationState> = {
|
|
|
32
30
|
emitter: NavigationEventEmitter<EventMapCore<any>>;
|
|
33
31
|
};
|
|
34
32
|
|
|
35
|
-
type MaybePartialRoute =
|
|
36
|
-
| NavigationRoute<ParamListBase, string>
|
|
37
|
-
| PartialRoute<NavigationRoute<ParamListBase, string>>;
|
|
38
|
-
|
|
39
|
-
const didFocusedRouteChange = (
|
|
40
|
-
currentState: NavigationState | PartialState<NavigationState>,
|
|
41
|
-
nextState: NavigationState | PartialState<NavigationState>
|
|
42
|
-
) => {
|
|
43
|
-
let currentFocusedRoute: MaybePartialRoute | undefined =
|
|
44
|
-
currentState.routes[currentState.index ?? 0];
|
|
45
|
-
|
|
46
|
-
let nextFocusedRoute: MaybePartialRoute | undefined =
|
|
47
|
-
nextState.routes[nextState.index ?? 0];
|
|
48
|
-
|
|
49
|
-
let changed = currentFocusedRoute?.key !== nextFocusedRoute?.key;
|
|
50
|
-
|
|
51
|
-
while (
|
|
52
|
-
!changed &&
|
|
53
|
-
currentFocusedRoute?.state != null &&
|
|
54
|
-
nextFocusedRoute?.state != null &&
|
|
55
|
-
currentFocusedRoute.state !== nextFocusedRoute.state
|
|
56
|
-
) {
|
|
57
|
-
currentFocusedRoute =
|
|
58
|
-
currentFocusedRoute.state.routes[currentFocusedRoute.state.index ?? 0];
|
|
59
|
-
|
|
60
|
-
nextFocusedRoute =
|
|
61
|
-
nextFocusedRoute.state.routes[nextFocusedRoute.state.index ?? 0];
|
|
62
|
-
|
|
63
|
-
changed = currentFocusedRoute?.key !== nextFocusedRoute?.key;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return changed;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
33
|
/**
|
|
70
34
|
* Hook to handle actions for a navigator, including state updates and bubbling.
|
|
71
35
|
*
|
|
@@ -93,6 +57,7 @@ export function useOnAction<State extends NavigationState>({
|
|
|
93
57
|
} = React.use(NavigationBuilderContext);
|
|
94
58
|
|
|
95
59
|
const tree = React.use(StaticTreeContext);
|
|
60
|
+
const consumedParams = React.use(ConsumedParamsContext);
|
|
96
61
|
|
|
97
62
|
const routerConfigOptionsRef =
|
|
98
63
|
React.useRef<RouterConfigOptions>(routerConfigOptions);
|
|
@@ -146,8 +111,13 @@ export function useOnAction<State extends NavigationState>({
|
|
|
146
111
|
|
|
147
112
|
onDispatchAction(action, false);
|
|
148
113
|
|
|
149
|
-
if (tree
|
|
150
|
-
const loader =
|
|
114
|
+
if (tree) {
|
|
115
|
+
const loader = getLoaderForStateChange(
|
|
116
|
+
tree,
|
|
117
|
+
result,
|
|
118
|
+
state,
|
|
119
|
+
consumedParams
|
|
120
|
+
);
|
|
151
121
|
|
|
152
122
|
loader?.();
|
|
153
123
|
}
|
|
@@ -194,6 +164,7 @@ export function useOnAction<State extends NavigationState>({
|
|
|
194
164
|
[
|
|
195
165
|
actionListeners,
|
|
196
166
|
beforeRemoveListeners,
|
|
167
|
+
consumedParams,
|
|
197
168
|
emitter,
|
|
198
169
|
getState,
|
|
199
170
|
key,
|
|
@@ -39,9 +39,10 @@ export const shouldPreventRemove = (
|
|
|
39
39
|
.filter(([, route]) => route.key != null)
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
const visitedRouteKeys
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const visitedRouteKeys =
|
|
43
|
+
VISITED_ROUTE_KEYS in action && action[VISITED_ROUTE_KEYS] instanceof Set
|
|
44
|
+
? action[VISITED_ROUTE_KEYS]
|
|
45
|
+
: new Set<string>();
|
|
45
46
|
|
|
46
47
|
const beforeRemoveAction = {
|
|
47
48
|
...action,
|
package/src/useRouteCache.tsx
CHANGED
|
@@ -41,10 +41,7 @@ export function useRouteCache<State extends NavigationState>(
|
|
|
41
41
|
// FIXME: since the state is updated with mutation, the route object cannot be frozen
|
|
42
42
|
// As a workaround, loop through the object and make the properties readonly
|
|
43
43
|
// Only needed once per proxy - skip if we're reusing a previously-frozen one
|
|
44
|
-
for (const key
|
|
45
|
-
// @ts-expect-error: this is fine since we are looping through the object
|
|
46
|
-
const value = proxy[key];
|
|
47
|
-
|
|
44
|
+
for (const [key, value] of Object.entries(proxy)) {
|
|
48
45
|
Object.defineProperty(proxy, key, {
|
|
49
46
|
enumerable: true,
|
|
50
47
|
configurable: true,
|
package/src/useSyncState.tsx
CHANGED
|
@@ -26,9 +26,10 @@ const createStore = <T,>(getInitialState: () => T) => {
|
|
|
26
26
|
|
|
27
27
|
const setState = (newState: T) => {
|
|
28
28
|
state = deepFreeze(newState);
|
|
29
|
-
didUpdate = true;
|
|
30
29
|
|
|
31
|
-
if (
|
|
30
|
+
if (isBatching) {
|
|
31
|
+
didUpdate = true;
|
|
32
|
+
} else {
|
|
32
33
|
listeners.forEach((listener) => listener());
|
|
33
34
|
}
|
|
34
35
|
};
|
package/src/utilities.tsx
CHANGED
|
@@ -26,6 +26,25 @@ export type NoExcessObject<Provided, Allowed> = Record<
|
|
|
26
26
|
never
|
|
27
27
|
>;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Check an object captured as a generic type against an allowed type,
|
|
31
|
+
* e.g. an object returned from a callback isn't checked for excess properties.
|
|
32
|
+
*/
|
|
33
|
+
export type NoExcessCallbackReturn<
|
|
34
|
+
Provided extends {},
|
|
35
|
+
Allowed extends {},
|
|
36
|
+
> = Allowed & Provided & NoExcessObject<Provided, Allowed>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Apply excess property checking to a value,
|
|
40
|
+
* or to the return value if the value is a function.
|
|
41
|
+
*/
|
|
42
|
+
export type NoExcessValue<Provided, Allowed extends {}> = Provided extends (
|
|
43
|
+
...args: never[]
|
|
44
|
+
) => infer Return
|
|
45
|
+
? (...args: never[]) => Allowed & NoExcessObject<Return, Allowed>
|
|
46
|
+
: NoExcessObject<Provided, Allowed>;
|
|
47
|
+
|
|
29
48
|
/**
|
|
30
49
|
* We get a union type when using keyof, but we want an intersection instead.
|
|
31
50
|
* https://stackoverflow.com/a/50375286/1665026
|