@react-navigation/core 7.21.5 → 7.21.6
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 +14 -8
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/ConsumedParamsContext.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 +1 -1
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/findFocusedRoute.js +3 -3
- package/lib/module/findFocusedRoute.js.map +1 -1
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getPathFromState.js +75 -45
- 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 +47 -21
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/getStateFromRouteParams.js +24 -0
- package/lib/module/getStateFromRouteParams.js.map +1 -0
- 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/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/EnsureSingleNavigator.d.ts +1 -1
- package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/NavigationProvider.d.ts +1 -1
- package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +2 -1
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/StaticContainer.d.ts +1 -1
- package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
- package/lib/typescript/src/findFocusedRoute.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/getStateFromRouteParams.d.ts +3 -0
- package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
- package/lib/typescript/src/theming/ThemeProvider.d.ts +1 -1
- package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +15 -9
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useComponent.d.ts +1 -1
- package/lib/typescript/src/useComponent.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
- package/lib/typescript/src/useLazyValue.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/useNavigationState.d.ts +1 -1
- package/lib/typescript/src/useNavigationState.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/package.json +7 -7
- package/src/BaseNavigationContainer.tsx +16 -10
- package/src/ConsumedParamsContext.tsx +1 -6
- package/src/NavigationIndependentTree.tsx +8 -5
- package/src/SceneView.tsx +18 -51
- package/src/StaticNavigation.tsx +2 -2
- package/src/findFocusedRoute.tsx +5 -3
- package/src/getActionFromState.tsx +3 -1
- package/src/getPathFromState.tsx +130 -59
- package/src/getPatternParts.tsx +15 -2
- package/src/getStateFromPath.tsx +76 -17
- package/src/getStateFromRouteParams.tsx +60 -0
- package/src/types.tsx +42 -24
- 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/useOnPreventRemove.tsx +4 -3
- package/src/useRouteCache.tsx +1 -4
- package/src/useSyncState.tsx +3 -2
|
@@ -12,6 +12,7 @@ import useLatestCallback from 'use-latest-callback';
|
|
|
12
12
|
|
|
13
13
|
import { checkDuplicateRouteNames } from './checkDuplicateRouteNames';
|
|
14
14
|
import { checkSerializable } from './checkSerializable';
|
|
15
|
+
import { ConsumedParamsContext } from './ConsumedParamsContext';
|
|
15
16
|
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
|
|
16
17
|
import { DeprecatedNavigationInChildContext } from './DeprecatedNavigationInChildContext';
|
|
17
18
|
import { EnsureSingleNavigator } from './EnsureSingleNavigator';
|
|
@@ -30,6 +31,7 @@ import { UnhandledActionContext } from './UnhandledActionContext';
|
|
|
30
31
|
import { useChildListeners } from './useChildListeners';
|
|
31
32
|
import { useEventEmitter } from './useEventEmitter';
|
|
32
33
|
import { useKeyedChildListeners } from './useKeyedChildListeners';
|
|
34
|
+
import { useLazyValue } from './useLazyValue';
|
|
33
35
|
import { useNavigationIndependentTree } from './useNavigationIndependentTree';
|
|
34
36
|
import { useOptionsGetters } from './useOptionsGetters';
|
|
35
37
|
import { useSyncState } from './useSyncState';
|
|
@@ -108,6 +110,8 @@ export const BaseNavigationContainer = React.forwardRef(
|
|
|
108
110
|
getPartialState(initialState == null ? undefined : initialState)
|
|
109
111
|
);
|
|
110
112
|
|
|
113
|
+
const consumedParams = useLazyValue(() => new WeakMap<object, true>());
|
|
114
|
+
|
|
111
115
|
const isFirstMountRef = React.useRef<boolean>(true);
|
|
112
116
|
|
|
113
117
|
const navigatorKeyRef = React.useRef<string | undefined>(undefined);
|
|
@@ -458,17 +462,19 @@ export const BaseNavigationContainer = React.forwardRef(
|
|
|
458
462
|
<NavigationContainerRefContext.Provider value={navigation}>
|
|
459
463
|
<NavigationBuilderContext.Provider value={builderContext}>
|
|
460
464
|
<NavigationStateContext.Provider value={context}>
|
|
461
|
-
<
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
<DeprecatedNavigationInChildContext.Provider
|
|
465
|
-
value={navigationInChildEnabled}
|
|
465
|
+
<ConsumedParamsContext.Provider value={consumedParams}>
|
|
466
|
+
<UnhandledActionContext.Provider
|
|
467
|
+
value={onUnhandledAction ?? defaultOnUnhandledAction}
|
|
466
468
|
>
|
|
467
|
-
<
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
469
|
+
<DeprecatedNavigationInChildContext.Provider
|
|
470
|
+
value={navigationInChildEnabled}
|
|
471
|
+
>
|
|
472
|
+
<EnsureSingleNavigator>
|
|
473
|
+
<ThemeProvider value={theme}>{children}</ThemeProvider>
|
|
474
|
+
</EnsureSingleNavigator>
|
|
475
|
+
</DeprecatedNavigationInChildContext.Provider>
|
|
476
|
+
</UnhandledActionContext.Provider>
|
|
477
|
+
</ConsumedParamsContext.Provider>
|
|
472
478
|
</NavigationStateContext.Provider>
|
|
473
479
|
</NavigationBuilderContext.Provider>
|
|
474
480
|
</NavigationContainerRefContext.Provider>
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
type ConsumedParamsContextValue = {
|
|
4
|
-
isConsumed: (params: object) => boolean;
|
|
5
|
-
setConsumed: (params: object) => void;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
3
|
export const ConsumedParamsContext = React.createContext<
|
|
9
|
-
|
|
4
|
+
WeakMap<object, true> | undefined
|
|
10
5
|
>(undefined);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
3
|
import { NavigationContext } from './NavigationContext';
|
|
4
|
+
import { NavigationFocusedRouteStateContext } from './NavigationFocusedRouteStateContext';
|
|
4
5
|
import { NavigationIndependentTreeContext } from './NavigationIndependentTreeContext';
|
|
5
6
|
import { NavigationRouteContext } from './NavigationProvider';
|
|
6
7
|
import { IsFocusedContext } from './useIsFocused';
|
|
@@ -17,11 +18,13 @@ export function NavigationIndependentTree({
|
|
|
17
18
|
// We need to clear any existing contexts for nested independent container to work correctly
|
|
18
19
|
<NavigationRouteContext.Provider value={undefined}>
|
|
19
20
|
<NavigationContext.Provider value={undefined}>
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
<NavigationFocusedRouteStateContext.Provider value={undefined}>
|
|
22
|
+
<IsFocusedContext.Provider value={undefined}>
|
|
23
|
+
<NavigationIndependentTreeContext.Provider value>
|
|
24
|
+
{children}
|
|
25
|
+
</NavigationIndependentTreeContext.Provider>
|
|
26
|
+
</IsFocusedContext.Provider>
|
|
27
|
+
</NavigationFocusedRouteStateContext.Provider>
|
|
25
28
|
</NavigationContext.Provider>
|
|
26
29
|
</NavigationRouteContext.Provider>
|
|
27
30
|
);
|
package/src/SceneView.tsx
CHANGED
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
} from '@react-navigation/routers';
|
|
7
7
|
import * as React from 'react';
|
|
8
8
|
|
|
9
|
-
import { ConsumedParamsContext } from './ConsumedParamsContext';
|
|
10
9
|
import { EnsureSingleNavigator } from './EnsureSingleNavigator';
|
|
11
10
|
import { isArrayEqual } from './isArrayEqual';
|
|
12
11
|
import {
|
|
@@ -114,36 +113,6 @@ export function SceneView<
|
|
|
114
113
|
const parentFocusedRouteState = React.useContext(
|
|
115
114
|
NavigationFocusedRouteStateContext
|
|
116
115
|
);
|
|
117
|
-
const [consumedParams, setConsumedParams] = React.useState<WeakMap<
|
|
118
|
-
object,
|
|
119
|
-
true
|
|
120
|
-
> | null>(null);
|
|
121
|
-
|
|
122
|
-
const consumedParamsContext = React.useMemo(
|
|
123
|
-
() => ({
|
|
124
|
-
isConsumed: (params: object) => {
|
|
125
|
-
if (consumedParams) {
|
|
126
|
-
return consumedParams.has(params);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return false;
|
|
130
|
-
},
|
|
131
|
-
setConsumed: (params: object) => {
|
|
132
|
-
setConsumedParams((prev) => {
|
|
133
|
-
if (prev && prev.has(params)) {
|
|
134
|
-
return prev;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const map = new WeakMap();
|
|
138
|
-
|
|
139
|
-
map.set(params, true);
|
|
140
|
-
|
|
141
|
-
return map;
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
}),
|
|
145
|
-
[consumedParams]
|
|
146
|
-
);
|
|
147
116
|
|
|
148
117
|
const focusedRouteState = React.useMemo(() => {
|
|
149
118
|
const state: FocusedRouteState = {
|
|
@@ -212,25 +181,23 @@ export function SceneView<
|
|
|
212
181
|
: screen.component;
|
|
213
182
|
|
|
214
183
|
return (
|
|
215
|
-
<
|
|
216
|
-
<
|
|
217
|
-
<
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
</NavigationStateContext.Provider>
|
|
234
|
-
</ConsumedParamsContext.Provider>
|
|
184
|
+
<NavigationStateContext.Provider value={context}>
|
|
185
|
+
<NavigationFocusedRouteStateContext.Provider value={focusedRouteState}>
|
|
186
|
+
<EnsureSingleNavigator>
|
|
187
|
+
<StaticContainer
|
|
188
|
+
name={screen.name}
|
|
189
|
+
render={ScreenComponent || screen.children}
|
|
190
|
+
navigation={navigation}
|
|
191
|
+
route={route}
|
|
192
|
+
>
|
|
193
|
+
{ScreenComponent !== undefined ? (
|
|
194
|
+
<ScreenComponent navigation={navigation} route={route} />
|
|
195
|
+
) : screen.children !== undefined ? (
|
|
196
|
+
screen.children({ navigation, route })
|
|
197
|
+
) : null}
|
|
198
|
+
</StaticContainer>
|
|
199
|
+
</EnsureSingleNavigator>
|
|
200
|
+
</NavigationFocusedRouteStateContext.Provider>
|
|
201
|
+
</NavigationStateContext.Provider>
|
|
235
202
|
);
|
|
236
203
|
}
|
package/src/StaticNavigation.tsx
CHANGED
|
@@ -787,8 +787,8 @@ export function createPathConfigForStaticNavigation(
|
|
|
787
787
|
|
|
788
788
|
screenConfig.path = normalizePath(
|
|
789
789
|
key
|
|
790
|
-
.replace(/([A-Z]
|
|
791
|
-
.replace(
|
|
790
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
791
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
792
792
|
.toLowerCase()
|
|
793
793
|
);
|
|
794
794
|
}
|
package/src/findFocusedRoute.tsx
CHANGED
|
@@ -12,11 +12,13 @@ type Result =
|
|
|
12
12
|
export function findFocusedRoute(state: InitialState): Result {
|
|
13
13
|
let current: InitialState | undefined = state;
|
|
14
14
|
|
|
15
|
-
while (
|
|
16
|
-
current
|
|
15
|
+
while (
|
|
16
|
+
current?.routes[current.index ?? current.routes.length - 1]?.state != null
|
|
17
|
+
) {
|
|
18
|
+
current = current.routes[current.index ?? current.routes.length - 1]?.state;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const route = current?.routes[current?.index ??
|
|
21
|
+
const route = current?.routes[current?.index ?? current.routes.length - 1];
|
|
20
22
|
|
|
21
23
|
return route;
|
|
22
24
|
}
|
|
@@ -180,7 +180,9 @@ export function getActionFromState(
|
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
const createNormalizedConfigItem = (
|
|
183
|
+
const createNormalizedConfigItem = (
|
|
184
|
+
config: PathConfig<object> | string
|
|
185
|
+
): ConfigItem =>
|
|
184
186
|
typeof config === 'object' && config != null
|
|
185
187
|
? {
|
|
186
188
|
initialRouteName: config.initialRouteName,
|
package/src/getPathFromState.tsx
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
NavigationState,
|
|
3
|
-
PartialState,
|
|
4
|
-
Route,
|
|
5
|
-
} from '@react-navigation/routers';
|
|
1
|
+
import type { NavigationState, PartialState } from '@react-navigation/routers';
|
|
6
2
|
import * as queryString from 'query-string';
|
|
7
3
|
|
|
8
4
|
import { getPatternParts, type PatternPart } from './getPatternParts';
|
|
5
|
+
import { getStateFromRouteParams } from './getStateFromRouteParams';
|
|
9
6
|
import type { PathConfig, PathConfigMap } from './types';
|
|
10
7
|
import { validatePathConfig } from './validatePathConfig';
|
|
11
8
|
|
|
@@ -19,20 +16,40 @@ type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
|
|
19
16
|
|
|
20
17
|
type StringifyConfig = Record<string, (value: unknown) => string>;
|
|
21
18
|
|
|
19
|
+
type SerializedParamValue = string | string[] | null;
|
|
20
|
+
|
|
22
21
|
type ConfigItem = {
|
|
23
22
|
parts?: PatternPart[];
|
|
23
|
+
ownParts: PatternPart[];
|
|
24
24
|
stringify?: StringifyConfig;
|
|
25
25
|
screens?: Record<string, ConfigItem>;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const serializeParamValue = (value: unknown): SerializedParamValue =>
|
|
29
|
+
value === null
|
|
30
|
+
? null
|
|
31
|
+
: Array.isArray(value)
|
|
32
|
+
? value.map(String)
|
|
33
|
+
: String(value);
|
|
34
|
+
|
|
35
|
+
const getActiveRoute = (
|
|
36
|
+
state: State,
|
|
37
|
+
configs: Record<string, ConfigItem> | undefined,
|
|
38
|
+
getRouteState: (
|
|
39
|
+
route: State['routes'][number],
|
|
40
|
+
config: ConfigItem | undefined
|
|
41
|
+
) => State | undefined
|
|
42
|
+
): { name: string; params?: object } => {
|
|
29
43
|
const route =
|
|
30
44
|
typeof state.index === 'number'
|
|
31
45
|
? state.routes[state.index]
|
|
32
46
|
: state.routes[state.routes.length - 1];
|
|
33
47
|
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
const config = configs?.[route.name];
|
|
49
|
+
const routeState = getRouteState(route, config);
|
|
50
|
+
|
|
51
|
+
if (routeState) {
|
|
52
|
+
return getActiveRoute(routeState, config?.screens, getRouteState);
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
return route;
|
|
@@ -101,24 +118,55 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
const configs = getNormalizedConfigs(options);
|
|
121
|
+
const cachedRouteStates = new Map<object, State | undefined>();
|
|
122
|
+
|
|
123
|
+
const getRouteState = (
|
|
124
|
+
route: State['routes'][number],
|
|
125
|
+
config: ConfigItem | undefined
|
|
126
|
+
) => {
|
|
127
|
+
if (route.state) {
|
|
128
|
+
return route.state;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const hasScreenParams =
|
|
132
|
+
route.params &&
|
|
133
|
+
'screen' in route.params &&
|
|
134
|
+
typeof route.params.screen === 'string' &&
|
|
135
|
+
config?.screens?.[route.params.screen];
|
|
136
|
+
|
|
137
|
+
const hasStateParams =
|
|
138
|
+
route.params && 'state' in route.params && config?.screens;
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
!route.params ||
|
|
142
|
+
!config?.screens ||
|
|
143
|
+
(!hasScreenParams && !hasStateParams)
|
|
144
|
+
) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!cachedRouteStates.has(route)) {
|
|
149
|
+
cachedRouteStates.set(route, getStateFromRouteParams(route.params));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return cachedRouteStates.get(route);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const focusedRoute = getActiveRoute(state, configs, getRouteState);
|
|
104
156
|
|
|
105
157
|
let path = '/';
|
|
106
158
|
let current: State | undefined = state;
|
|
107
159
|
|
|
108
|
-
const allParams: Record<string, string> = {};
|
|
109
|
-
|
|
110
160
|
while (current) {
|
|
111
161
|
let index = typeof current.index === 'number' ? current.index : 0;
|
|
112
|
-
let route = current.routes[index]
|
|
113
|
-
state?: State;
|
|
114
|
-
};
|
|
162
|
+
let route = current.routes[index];
|
|
115
163
|
|
|
116
164
|
let parts: PatternPart[] | undefined;
|
|
117
165
|
|
|
118
|
-
|
|
119
|
-
let currentOptions = configs;
|
|
166
|
+
const partValues = new Map<PatternPart, SerializedParamValue | undefined>();
|
|
120
167
|
|
|
121
|
-
|
|
168
|
+
let focusedParams: Record<string, SerializedParamValue> | undefined;
|
|
169
|
+
let currentOptions = configs;
|
|
122
170
|
|
|
123
171
|
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
|
124
172
|
const nestedRouteNames = [];
|
|
@@ -126,19 +174,21 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
126
174
|
let hasNext = true;
|
|
127
175
|
|
|
128
176
|
while (route.name in currentOptions && hasNext) {
|
|
129
|
-
|
|
177
|
+
const config = currentOptions[route.name];
|
|
178
|
+
parts = config.parts;
|
|
179
|
+
const ownParts = config.ownParts;
|
|
130
180
|
|
|
131
181
|
nestedRouteNames.push(route.name);
|
|
132
182
|
|
|
133
183
|
if (route.params) {
|
|
134
|
-
const options =
|
|
184
|
+
const options = config;
|
|
135
185
|
|
|
136
186
|
const currentParams = Object.fromEntries(
|
|
137
187
|
Object.entries(route.params)
|
|
138
|
-
.map(([key, value]): [string,
|
|
188
|
+
.map(([key, value]): [string, SerializedParamValue] | null => {
|
|
139
189
|
if (value === undefined) {
|
|
140
190
|
if (options) {
|
|
141
|
-
const optional =
|
|
191
|
+
const optional = ownParts.find(
|
|
142
192
|
(part) => part.param === key
|
|
143
193
|
)?.optional;
|
|
144
194
|
|
|
@@ -150,51 +200,71 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
150
200
|
}
|
|
151
201
|
}
|
|
152
202
|
|
|
153
|
-
const stringify = options?.stringify?.[key]
|
|
203
|
+
const stringify = options?.stringify?.[key];
|
|
154
204
|
|
|
155
|
-
return [
|
|
205
|
+
return [
|
|
206
|
+
key,
|
|
207
|
+
stringify ? stringify(value) : serializeParamValue(value),
|
|
208
|
+
];
|
|
156
209
|
})
|
|
157
210
|
.filter((entry) => entry != null)
|
|
158
211
|
);
|
|
159
212
|
|
|
160
|
-
|
|
161
|
-
|
|
213
|
+
const claimedParams = new Set<string>();
|
|
214
|
+
|
|
215
|
+
for (const part of ownParts) {
|
|
216
|
+
if (part.param && part.param in currentParams) {
|
|
217
|
+
const value = currentParams[part.param];
|
|
218
|
+
|
|
219
|
+
if (value !== undefined) {
|
|
220
|
+
partValues.set(part, value);
|
|
221
|
+
claimedParams.add(part.param);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
162
224
|
}
|
|
163
225
|
|
|
164
226
|
if (focusedRoute === route) {
|
|
165
227
|
// If this is the focused route, keep the params for later use
|
|
166
228
|
// We save it here since it's been stringified already
|
|
167
|
-
focusedParams = {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
229
|
+
focusedParams = {};
|
|
230
|
+
|
|
231
|
+
for (const key in currentParams) {
|
|
232
|
+
const value = currentParams[key];
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
!claimedParams.has(key) &&
|
|
236
|
+
value !== undefined &&
|
|
237
|
+
value !== 'undefined'
|
|
238
|
+
) {
|
|
239
|
+
focusedParams[key] = value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (const part of ownParts) {
|
|
246
|
+
if (part.param && !partValues.has(part)) {
|
|
247
|
+
partValues.set(part, undefined);
|
|
180
248
|
}
|
|
181
249
|
}
|
|
182
250
|
|
|
251
|
+
const routeState = getRouteState(route, config);
|
|
252
|
+
|
|
183
253
|
// If there is no `screens` property or no nested state, we return pattern
|
|
184
|
-
if (!
|
|
254
|
+
if (!config.screens || routeState === undefined) {
|
|
185
255
|
hasNext = false;
|
|
186
256
|
} else {
|
|
187
257
|
index =
|
|
188
|
-
typeof
|
|
189
|
-
?
|
|
190
|
-
:
|
|
258
|
+
typeof routeState.index === 'number'
|
|
259
|
+
? routeState.index
|
|
260
|
+
: routeState.routes.length - 1;
|
|
191
261
|
|
|
192
|
-
const nextRoute =
|
|
193
|
-
const nestedConfig =
|
|
262
|
+
const nextRoute = routeState.routes[index];
|
|
263
|
+
const nestedConfig = config.screens;
|
|
194
264
|
|
|
195
265
|
// if there is config for next route name, we go deeper
|
|
196
266
|
if (nestedConfig && nextRoute.name in nestedConfig) {
|
|
197
|
-
route = nextRoute
|
|
267
|
+
route = nextRoute;
|
|
198
268
|
currentOptions = nestedConfig;
|
|
199
269
|
} else {
|
|
200
270
|
// If not, there is no sense in going deeper in config
|
|
@@ -203,9 +273,12 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
203
273
|
}
|
|
204
274
|
}
|
|
205
275
|
|
|
276
|
+
const routeState = getRouteState(route, currentOptions[route.name]);
|
|
277
|
+
|
|
206
278
|
if (currentOptions[route.name] !== undefined) {
|
|
207
279
|
path += parts
|
|
208
|
-
?.map((
|
|
280
|
+
?.map((part) => {
|
|
281
|
+
const { segment, param, optional } = part;
|
|
209
282
|
// We don't know what to show for wildcard patterns
|
|
210
283
|
// Showing the route name seems ok, though whatever we show here will be incorrect
|
|
211
284
|
// Since the page doesn't actually exist
|
|
@@ -215,7 +288,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
215
288
|
|
|
216
289
|
// If the path has a pattern for a param, put the param in the path
|
|
217
290
|
if (param) {
|
|
218
|
-
const value =
|
|
291
|
+
const value = partValues.get(part);
|
|
219
292
|
|
|
220
293
|
if (value === undefined && optional) {
|
|
221
294
|
// Optional params without value assigned in route.params should be ignored
|
|
@@ -244,12 +317,12 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
244
317
|
focusedParams = Object.fromEntries(
|
|
245
318
|
Object.entries(focusedRoute.params).map(([key, value]) => [
|
|
246
319
|
key,
|
|
247
|
-
|
|
320
|
+
serializeParamValue(value),
|
|
248
321
|
])
|
|
249
322
|
);
|
|
250
323
|
}
|
|
251
324
|
|
|
252
|
-
if (
|
|
325
|
+
if (routeState) {
|
|
253
326
|
path += '/';
|
|
254
327
|
} else if (focusedParams) {
|
|
255
328
|
for (const param in focusedParams) {
|
|
@@ -266,7 +339,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
266
339
|
}
|
|
267
340
|
}
|
|
268
341
|
|
|
269
|
-
current =
|
|
342
|
+
current = routeState;
|
|
270
343
|
}
|
|
271
344
|
|
|
272
345
|
// Include the root path if specified
|
|
@@ -292,14 +365,13 @@ const createConfigItem = (
|
|
|
292
365
|
parentParts?: PatternPart[]
|
|
293
366
|
): ConfigItem => {
|
|
294
367
|
if (typeof config === 'string') {
|
|
295
|
-
|
|
296
|
-
const parts = getPatternParts(config);
|
|
368
|
+
const ownParts = getPatternParts(config);
|
|
297
369
|
|
|
298
370
|
if (parentParts) {
|
|
299
|
-
return { parts: [...parentParts, ...
|
|
371
|
+
return { parts: [...parentParts, ...ownParts], ownParts };
|
|
300
372
|
}
|
|
301
373
|
|
|
302
|
-
return { parts };
|
|
374
|
+
return { parts: ownParts, ownParts };
|
|
303
375
|
}
|
|
304
376
|
|
|
305
377
|
if (config.exact && config.path === undefined) {
|
|
@@ -310,14 +382,12 @@ const createConfigItem = (
|
|
|
310
382
|
|
|
311
383
|
// If an object is specified as the value (e.g. Foo: { ... }),
|
|
312
384
|
// It can have `path` property and `screens` prop which has nested configs
|
|
385
|
+
const ownParts = config.path ? getPatternParts(config.path) : [];
|
|
313
386
|
const parts =
|
|
314
387
|
config.exact !== true
|
|
315
|
-
? [
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
]
|
|
319
|
-
: config.path
|
|
320
|
-
? getPatternParts(config.path)
|
|
388
|
+
? [...(parentParts || []), ...ownParts]
|
|
389
|
+
: ownParts.length
|
|
390
|
+
? ownParts
|
|
321
391
|
: undefined;
|
|
322
392
|
|
|
323
393
|
const screens = config.screens
|
|
@@ -326,6 +396,7 @@ const createConfigItem = (
|
|
|
326
396
|
|
|
327
397
|
return {
|
|
328
398
|
parts,
|
|
399
|
+
ownParts,
|
|
329
400
|
stringify: config.stringify,
|
|
330
401
|
screens,
|
|
331
402
|
};
|
package/src/getPatternParts.tsx
CHANGED
|
@@ -15,6 +15,8 @@ export function getPatternParts(path: string): PatternPart[] {
|
|
|
15
15
|
|
|
16
16
|
let isRegex = false;
|
|
17
17
|
let isParam = false;
|
|
18
|
+
let isEscaped = false;
|
|
19
|
+
let isInCharClass = false;
|
|
18
20
|
let regexInnerParens = 0;
|
|
19
21
|
|
|
20
22
|
// One extra iteration to add the last character
|
|
@@ -34,7 +36,7 @@ export function getPatternParts(path: string): PatternPart[] {
|
|
|
34
36
|
`Encountered ':' in the middle of a segment in path: ${path}`
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
|
-
} else if (char === '(') {
|
|
39
|
+
} else if (char === '(' && !isEscaped && !isInCharClass) {
|
|
38
40
|
if (isParam) {
|
|
39
41
|
if (isRegex) {
|
|
40
42
|
// The '(' is part of the regex if we're already inside one
|
|
@@ -47,7 +49,7 @@ export function getPatternParts(path: string): PatternPart[] {
|
|
|
47
49
|
`Encountered '(' without preceding ':' in path: ${path}`
|
|
48
50
|
);
|
|
49
51
|
}
|
|
50
|
-
} else if (char === ')') {
|
|
52
|
+
} else if (char === ')' && !isEscaped && !isInCharClass) {
|
|
51
53
|
if (isParam && isRegex) {
|
|
52
54
|
if (regexInnerParens) {
|
|
53
55
|
// The ')' is part of the regex if we're already inside one
|
|
@@ -102,6 +104,17 @@ export function getPatternParts(path: string): PatternPart[] {
|
|
|
102
104
|
if (isRegex) {
|
|
103
105
|
current.regex = current.regex || '';
|
|
104
106
|
current.regex += char;
|
|
107
|
+
|
|
108
|
+
// Track escapes and character classes so parens inside them are not special
|
|
109
|
+
if (isEscaped) {
|
|
110
|
+
isEscaped = false;
|
|
111
|
+
} else if (char === '\\') {
|
|
112
|
+
isEscaped = true;
|
|
113
|
+
} else if (char === '[') {
|
|
114
|
+
isInCharClass = true;
|
|
115
|
+
} else if (char === ']') {
|
|
116
|
+
isInCharClass = false;
|
|
117
|
+
}
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
if (isParam && !isRegex) {
|