@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/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 {
|
|
@@ -105,37 +104,6 @@ export function SceneView<
|
|
|
105
104
|
|
|
106
105
|
const getIsInitial = React.useCallback(() => isInitialRef.current, []);
|
|
107
106
|
|
|
108
|
-
const [consumedParams, setConsumedParams] = React.useState<WeakMap<
|
|
109
|
-
object,
|
|
110
|
-
true
|
|
111
|
-
> | null>(null);
|
|
112
|
-
|
|
113
|
-
const consumedParamsContext = React.useMemo(
|
|
114
|
-
() => ({
|
|
115
|
-
isConsumed: (params: object) => {
|
|
116
|
-
if (consumedParams) {
|
|
117
|
-
return consumedParams.has(params);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return false;
|
|
121
|
-
},
|
|
122
|
-
setConsumed: (params: object) => {
|
|
123
|
-
setConsumedParams((prev) => {
|
|
124
|
-
if (prev && prev.has(params)) {
|
|
125
|
-
return prev;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const map = new WeakMap<object, true>();
|
|
129
|
-
|
|
130
|
-
map.set(params, true);
|
|
131
|
-
|
|
132
|
-
return map;
|
|
133
|
-
});
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
[consumedParams]
|
|
137
|
-
);
|
|
138
|
-
|
|
139
107
|
const parentFocusedRouteState = React.use(NavigationFocusedRouteStateContext);
|
|
140
108
|
|
|
141
109
|
const focusedRouteState = React.useMemo(() => {
|
|
@@ -205,25 +173,23 @@ export function SceneView<
|
|
|
205
173
|
: screen.component;
|
|
206
174
|
|
|
207
175
|
return (
|
|
208
|
-
<
|
|
209
|
-
<
|
|
210
|
-
<
|
|
211
|
-
<
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
</NavigationStateContext.Provider>
|
|
227
|
-
</ConsumedParamsContext.Provider>
|
|
176
|
+
<NavigationStateContext.Provider value={context}>
|
|
177
|
+
<NavigationFocusedRouteStateContext.Provider value={focusedRouteState}>
|
|
178
|
+
<EnsureSingleNavigator>
|
|
179
|
+
<StaticContainer
|
|
180
|
+
name={screen.name}
|
|
181
|
+
render={ScreenComponent || screen.children}
|
|
182
|
+
navigation={navigation}
|
|
183
|
+
route={route}
|
|
184
|
+
>
|
|
185
|
+
{ScreenComponent !== undefined ? (
|
|
186
|
+
<ScreenComponent navigation={navigation} route={route} />
|
|
187
|
+
) : screen.children !== undefined ? (
|
|
188
|
+
screen.children({ navigation, route })
|
|
189
|
+
) : null}
|
|
190
|
+
</StaticContainer>
|
|
191
|
+
</EnsureSingleNavigator>
|
|
192
|
+
</NavigationFocusedRouteStateContext.Provider>
|
|
193
|
+
</NavigationStateContext.Provider>
|
|
228
194
|
);
|
|
229
195
|
}
|
package/src/StaticNavigation.tsx
CHANGED
|
@@ -32,7 +32,9 @@ import type {
|
|
|
32
32
|
HasArguments,
|
|
33
33
|
InferParamsFromLinking,
|
|
34
34
|
KeysOf,
|
|
35
|
+
NoExcessCallbackReturn,
|
|
35
36
|
NoExcessObject,
|
|
37
|
+
NoExcessValue,
|
|
36
38
|
NotUndefinedObject,
|
|
37
39
|
StandardSchemaV1,
|
|
38
40
|
UnionToIntersection,
|
|
@@ -53,19 +55,23 @@ type ParamsForScreenComponent<T> = [T] extends [(...args: any[]) => any]
|
|
|
53
55
|
: undefined
|
|
54
56
|
: ScreenComponentParams<T>;
|
|
55
57
|
|
|
56
|
-
// If
|
|
57
|
-
//
|
|
58
|
+
// If the initial route's params include `undefined`, the nested navigator itself can be optional.
|
|
59
|
+
// If no initial route is specified, check if every nested route's params include `undefined`.
|
|
60
|
+
// Otherwise the nested navigator needs to be required.
|
|
58
61
|
type ParamsForNestedNavigator<
|
|
59
62
|
T extends { config: any },
|
|
60
63
|
ParamList extends {} = StaticParamList<T>,
|
|
61
64
|
Params = NavigatorScreenParams<ParamList>,
|
|
65
|
+
RouteNames extends keyof ParamList = T['config'] extends {
|
|
66
|
+
initialRouteName: infer RouteName extends keyof ParamList;
|
|
67
|
+
}
|
|
68
|
+
? RouteName
|
|
69
|
+
: keyof ParamList,
|
|
62
70
|
> = {
|
|
63
|
-
|
|
64
|
-
// If every route has optional params, the resulting union is `never`
|
|
65
|
-
[RouteName in keyof ParamList]-?: undefined extends ParamList[RouteName]
|
|
71
|
+
[RouteName in RouteNames]-?: undefined extends ParamList[RouteName]
|
|
66
72
|
? never
|
|
67
73
|
: RouteName;
|
|
68
|
-
}[
|
|
74
|
+
}[RouteNames] extends never
|
|
69
75
|
? Params | undefined
|
|
70
76
|
: Params;
|
|
71
77
|
|
|
@@ -132,6 +138,8 @@ type ParamListForScreens<Screens> = {
|
|
|
132
138
|
any,
|
|
133
139
|
any,
|
|
134
140
|
any,
|
|
141
|
+
any,
|
|
142
|
+
any,
|
|
135
143
|
any
|
|
136
144
|
>
|
|
137
145
|
? ParamsForConfig<Linking, Screen>
|
|
@@ -146,22 +154,22 @@ type ParamListForGroups<Groups> = Groups extends {
|
|
|
146
154
|
? ParamListForScreens<UnionToIntersection<Screens>>
|
|
147
155
|
: {};
|
|
148
156
|
|
|
149
|
-
type
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
type StaticRouteBase = Readonly<Omit<Route<string>, 'params'>>;
|
|
158
|
+
|
|
159
|
+
type StaticRoute<Params, P = AnyToUnknown<Params>> = StaticRouteBase &
|
|
160
|
+
(undefined extends Params
|
|
161
|
+
? {
|
|
162
|
+
/**
|
|
163
|
+
* Params for this route
|
|
164
|
+
*/
|
|
165
|
+
readonly params?: P extends object ? Readonly<P> : P;
|
|
166
|
+
}
|
|
167
|
+
: {
|
|
168
|
+
/**
|
|
169
|
+
* Params for this route
|
|
170
|
+
*/
|
|
171
|
+
readonly params: Readonly<P>;
|
|
172
|
+
});
|
|
165
173
|
|
|
166
174
|
type StaticScreenConfigLinkingAlias = {
|
|
167
175
|
/**
|
|
@@ -193,7 +201,7 @@ type StaticScreenConfigLinkingAlias = {
|
|
|
193
201
|
>;
|
|
194
202
|
/**
|
|
195
203
|
* An object mapping the param name to a function which converts the param value to a string.
|
|
196
|
-
* By default,
|
|
204
|
+
* By default, arrays and null are preserved for query params, and other values are converted using `String(value)`.
|
|
197
205
|
*
|
|
198
206
|
* @example
|
|
199
207
|
* ```js
|
|
@@ -246,15 +254,17 @@ export type StaticScreenConfigScreen =
|
|
|
246
254
|
| React.ComponentType<any>
|
|
247
255
|
| StaticNavigation<any>;
|
|
248
256
|
|
|
249
|
-
export
|
|
250
|
-
Linking extends StaticScreenConfigLinking,
|
|
251
|
-
Screen,
|
|
252
|
-
State extends NavigationState,
|
|
253
|
-
ScreenOptions extends {},
|
|
254
|
-
EventMap extends EventMapBase,
|
|
255
|
-
Navigation,
|
|
256
|
-
Params = ParamsForConfig<Linking, Screen>,
|
|
257
|
-
|
|
257
|
+
export interface StaticScreenConfig<
|
|
258
|
+
out Linking extends StaticScreenConfigLinking,
|
|
259
|
+
out Screen,
|
|
260
|
+
in out State extends NavigationState,
|
|
261
|
+
in out ScreenOptions extends {},
|
|
262
|
+
in out EventMap extends EventMapBase,
|
|
263
|
+
in out Navigation,
|
|
264
|
+
in out Params = ParamsForConfig<Linking, Screen>,
|
|
265
|
+
in out ProvidedOptions extends {} = {},
|
|
266
|
+
in out ProvidedListeners extends {} = {},
|
|
267
|
+
> {
|
|
258
268
|
/**
|
|
259
269
|
* Static navigation config or Component to render for the screen.
|
|
260
270
|
*/
|
|
@@ -291,7 +301,7 @@ export type StaticScreenConfig<
|
|
|
291
301
|
route: StaticRoute<Params>;
|
|
292
302
|
navigation: Navigation;
|
|
293
303
|
theme: Theme;
|
|
294
|
-
}) => ScreenOptions);
|
|
304
|
+
}) => NoExcessCallbackReturn<ProvidedOptions, ScreenOptions>);
|
|
295
305
|
|
|
296
306
|
/**
|
|
297
307
|
* Event listeners for this screen.
|
|
@@ -310,7 +320,10 @@ export type StaticScreenConfig<
|
|
|
310
320
|
| ((props: {
|
|
311
321
|
route: StaticRoute<Params>;
|
|
312
322
|
navigation: Navigation;
|
|
313
|
-
}) =>
|
|
323
|
+
}) => NoExcessCallbackReturn<
|
|
324
|
+
ProvidedListeners,
|
|
325
|
+
ScreenListeners<State, EventMap>
|
|
326
|
+
>);
|
|
314
327
|
|
|
315
328
|
/**
|
|
316
329
|
* Layout for this screen.
|
|
@@ -392,14 +405,13 @@ export type StaticScreenConfig<
|
|
|
392
405
|
name: string;
|
|
393
406
|
params: AnyToUnknown<Params>;
|
|
394
407
|
}) => Promise<void>;
|
|
395
|
-
}
|
|
408
|
+
}
|
|
396
409
|
|
|
397
410
|
export type StaticScreenFactory<in out Bag extends NavigatorTypeBagBase> = <
|
|
398
411
|
const Linking extends StaticScreenConfigLinking,
|
|
399
412
|
const Screen extends StaticScreenConfigScreen,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
| ((...args: never[]) => unknown) = Bag['ScreenOptions'],
|
|
413
|
+
ProvidedOptions extends {} = {},
|
|
414
|
+
ProvidedListeners extends {} = {},
|
|
403
415
|
>(
|
|
404
416
|
config: StaticScreenConfig<
|
|
405
417
|
Linking,
|
|
@@ -407,11 +419,11 @@ export type StaticScreenFactory<in out Bag extends NavigatorTypeBagBase> = <
|
|
|
407
419
|
Bag['State'],
|
|
408
420
|
Bag['ScreenOptions'],
|
|
409
421
|
Bag['EventMap'],
|
|
410
|
-
Bag['NavigationList'][keyof Bag['ParamList']]
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
422
|
+
Bag['NavigationList'][keyof Bag['ParamList']],
|
|
423
|
+
ParamsForConfig<Linking, Screen>,
|
|
424
|
+
ProvidedOptions,
|
|
425
|
+
ProvidedListeners
|
|
426
|
+
>
|
|
415
427
|
) => StaticScreenConfig<
|
|
416
428
|
Linking,
|
|
417
429
|
Screen,
|
|
@@ -446,7 +458,9 @@ type StaticConfigScreens<
|
|
|
446
458
|
ScreenOptions,
|
|
447
459
|
EventMap,
|
|
448
460
|
NavigationList[RouteName],
|
|
449
|
-
any
|
|
461
|
+
any,
|
|
462
|
+
{},
|
|
463
|
+
{}
|
|
450
464
|
>;
|
|
451
465
|
};
|
|
452
466
|
|
|
@@ -529,23 +543,26 @@ export type StaticConfig<
|
|
|
529
543
|
}
|
|
530
544
|
);
|
|
531
545
|
|
|
532
|
-
type
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
screen: unknown;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
? { options: NoExcessOptions<Screen['options'], ScreenOptions> }
|
|
543
|
-
: unknown;
|
|
546
|
+
type NoExcessScreen<
|
|
547
|
+
Screen,
|
|
548
|
+
ScreenOptions extends {},
|
|
549
|
+
Listeners extends {},
|
|
550
|
+
> = (Screen extends { screen: unknown; options: unknown }
|
|
551
|
+
? { options: NoExcessValue<Screen['options'], ScreenOptions> }
|
|
552
|
+
: unknown) &
|
|
553
|
+
(Screen extends { screen: unknown; listeners: unknown }
|
|
554
|
+
? { listeners: NoExcessValue<Screen['listeners'], Listeners> }
|
|
555
|
+
: unknown);
|
|
544
556
|
|
|
545
|
-
type NoExcessScreens<
|
|
557
|
+
type NoExcessScreens<
|
|
558
|
+
Screens,
|
|
559
|
+
ScreenOptions extends {},
|
|
560
|
+
Listeners extends {},
|
|
561
|
+
> = {
|
|
546
562
|
[RouteName in keyof Screens]: NoExcessScreen<
|
|
547
563
|
Screens[RouteName],
|
|
548
|
-
ScreenOptions
|
|
564
|
+
ScreenOptions,
|
|
565
|
+
Listeners
|
|
549
566
|
>;
|
|
550
567
|
};
|
|
551
568
|
|
|
@@ -553,6 +570,7 @@ type NoExcessGroups<
|
|
|
553
570
|
Groups,
|
|
554
571
|
Bag extends NavigatorTypeBagBase,
|
|
555
572
|
ScreenOptions extends {},
|
|
573
|
+
Listeners extends {},
|
|
556
574
|
> = {
|
|
557
575
|
[GroupName in keyof Groups]: NoExcessObject<
|
|
558
576
|
Groups[GroupName],
|
|
@@ -564,7 +582,7 @@ type NoExcessGroups<
|
|
|
564
582
|
NavigationListBase<ParamListBase>
|
|
565
583
|
>
|
|
566
584
|
> &
|
|
567
|
-
NoExcessStaticConfig<Groups[GroupName], Bag, ScreenOptions>;
|
|
585
|
+
NoExcessStaticConfig<Groups[GroupName], Bag, ScreenOptions, Listeners>;
|
|
568
586
|
};
|
|
569
587
|
|
|
570
588
|
// Each property is probed with `Config extends { key: infer X }` and the results are intersected.
|
|
@@ -574,19 +592,25 @@ export type NoExcessStaticConfig<
|
|
|
574
592
|
Config,
|
|
575
593
|
Bag extends NavigatorTypeBagBase,
|
|
576
594
|
ScreenOptions extends {} = Bag['ScreenOptions'],
|
|
595
|
+
Listeners extends {} = ScreenListeners<Bag['State'], Bag['EventMap']>,
|
|
577
596
|
> = (Config extends { screenOptions: infer ProvidedOptions }
|
|
578
597
|
? {
|
|
579
|
-
screenOptions:
|
|
598
|
+
screenOptions: NoExcessValue<ProvidedOptions, ScreenOptions>;
|
|
580
599
|
}
|
|
581
600
|
: unknown) &
|
|
601
|
+
(Config extends { screenListeners: infer ProvidedListeners }
|
|
602
|
+
? {
|
|
603
|
+
screenListeners: NoExcessValue<ProvidedListeners, Listeners>;
|
|
604
|
+
}
|
|
605
|
+
: unknown) &
|
|
582
606
|
(Config extends { screens: infer Screens }
|
|
583
607
|
? {
|
|
584
|
-
screens: NoExcessScreens<Screens, ScreenOptions>;
|
|
608
|
+
screens: NoExcessScreens<Screens, ScreenOptions, Listeners>;
|
|
585
609
|
}
|
|
586
610
|
: unknown) &
|
|
587
611
|
(Config extends { groups: infer Groups }
|
|
588
612
|
? {
|
|
589
|
-
groups: NoExcessGroups<Groups, Bag, ScreenOptions>;
|
|
613
|
+
groups: NoExcessGroups<Groups, Bag, ScreenOptions, Listeners>;
|
|
590
614
|
}
|
|
591
615
|
: unknown);
|
|
592
616
|
|
|
@@ -1138,8 +1162,8 @@ export function createPathConfigForStaticNavigation<ParamList extends {}>(
|
|
|
1138
1162
|
|
|
1139
1163
|
screenConfig.path = normalizePath(
|
|
1140
1164
|
key
|
|
1141
|
-
.replace(/([A-Z]
|
|
1142
|
-
.replace(
|
|
1165
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
1166
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
1143
1167
|
.toLowerCase()
|
|
1144
1168
|
);
|
|
1145
1169
|
}
|
package/src/findFocusedRoute.tsx
CHANGED
|
@@ -14,7 +14,7 @@ export function findFocusedRoute(state: InitialState): Result {
|
|
|
14
14
|
|
|
15
15
|
while (current != null) {
|
|
16
16
|
const route: InitialState['routes'][number] | undefined =
|
|
17
|
-
current.routes[current.index ??
|
|
17
|
+
current.routes[current.index ?? current.routes.length - 1];
|
|
18
18
|
|
|
19
19
|
if (route?.state == null) {
|
|
20
20
|
return route;
|
|
@@ -195,11 +195,16 @@ export function getActionFromState(
|
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
const createNormalizedConfigItem = (
|
|
198
|
+
const createNormalizedConfigItem = (
|
|
199
|
+
config: PathConfig<object> | string
|
|
200
|
+
): ConfigItem =>
|
|
199
201
|
typeof config === 'object' && config != null
|
|
200
202
|
? {
|
|
201
203
|
initialRouteName:
|
|
202
|
-
'initialRouteName' in config
|
|
204
|
+
'initialRouteName' in config &&
|
|
205
|
+
typeof config.initialRouteName === 'string'
|
|
206
|
+
? config.initialRouteName
|
|
207
|
+
: undefined,
|
|
203
208
|
screens:
|
|
204
209
|
'screens' in config && config.screens != null
|
|
205
210
|
? createNormalizedConfigs(config.screens)
|
|
@@ -209,7 +214,6 @@ const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
|
|
|
209
214
|
|
|
210
215
|
const createNormalizedConfigs = (options: PathConfigMap<object>) =>
|
|
211
216
|
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
|
|
212
|
-
// @ts-expect-error -we can't properly type this
|
|
213
217
|
acc[k] = createNormalizedConfigItem(v);
|
|
214
218
|
return acc;
|
|
215
219
|
}, {});
|
package/src/getPathFromState.tsx
CHANGED
|
@@ -16,12 +16,22 @@ type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
|
|
16
16
|
|
|
17
17
|
type StringifyConfig = Record<string, ((value: unknown) => string) | undefined>;
|
|
18
18
|
|
|
19
|
+
type SerializedParamValue = string | string[] | null;
|
|
20
|
+
|
|
19
21
|
type ConfigItem = {
|
|
20
22
|
parts?: PatternPart[] | undefined;
|
|
23
|
+
ownParts: PatternPart[];
|
|
21
24
|
stringify?: StringifyConfig | undefined;
|
|
22
25
|
screens?: Record<string, ConfigItem> | undefined;
|
|
23
26
|
};
|
|
24
27
|
|
|
28
|
+
const serializeParamValue = (value: unknown): SerializedParamValue =>
|
|
29
|
+
value === null
|
|
30
|
+
? null
|
|
31
|
+
: Array.isArray(value)
|
|
32
|
+
? value.map(String)
|
|
33
|
+
: String(value);
|
|
34
|
+
|
|
25
35
|
const encodePathParam = (value: string) => {
|
|
26
36
|
let result = '';
|
|
27
37
|
|
|
@@ -170,8 +180,6 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
170
180
|
let path = '/';
|
|
171
181
|
let current: State | undefined = state;
|
|
172
182
|
|
|
173
|
-
const allParams: Record<string, string> = {};
|
|
174
|
-
|
|
175
183
|
while (current) {
|
|
176
184
|
let index: number = typeof current.index === 'number' ? current.index : 0;
|
|
177
185
|
|
|
@@ -186,7 +194,11 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
186
194
|
|
|
187
195
|
let parts: PatternPart[] | undefined;
|
|
188
196
|
|
|
189
|
-
|
|
197
|
+
// Values for each pattern part object, so the same param name
|
|
198
|
+
// at different nesting levels gets the value from its own level
|
|
199
|
+
const partValues = new Map<PatternPart, SerializedParamValue | undefined>();
|
|
200
|
+
|
|
201
|
+
let focusedParams: Record<string, SerializedParamValue> | undefined;
|
|
190
202
|
let currentOptions = configs;
|
|
191
203
|
|
|
192
204
|
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
|
@@ -203,12 +215,14 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
203
215
|
|
|
204
216
|
parts = config.parts;
|
|
205
217
|
|
|
218
|
+
const ownParts = config.ownParts;
|
|
219
|
+
|
|
206
220
|
nestedRouteNames.push(route.name);
|
|
207
221
|
|
|
208
222
|
if (route.params) {
|
|
209
223
|
const options = config;
|
|
210
224
|
const params = route.params as Record<string, unknown>;
|
|
211
|
-
const currentParams: Record<string,
|
|
225
|
+
const currentParams: Record<string, SerializedParamValue> = {};
|
|
212
226
|
|
|
213
227
|
for (const key in params) {
|
|
214
228
|
const value = params[key];
|
|
@@ -216,7 +230,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
216
230
|
if (value === undefined) {
|
|
217
231
|
let optional = false;
|
|
218
232
|
|
|
219
|
-
for (const part of
|
|
233
|
+
for (const part of ownParts) {
|
|
220
234
|
if (part.param === key) {
|
|
221
235
|
optional = part.optional === true;
|
|
222
236
|
break;
|
|
@@ -228,39 +242,52 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
244
|
|
|
231
|
-
const stringify = options.stringify?.[key]
|
|
245
|
+
const stringify = options.stringify?.[key];
|
|
232
246
|
|
|
233
|
-
currentParams[key] = stringify
|
|
247
|
+
currentParams[key] = stringify
|
|
248
|
+
? stringify(value)
|
|
249
|
+
: serializeParamValue(value);
|
|
234
250
|
}
|
|
235
251
|
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
const claimedParams = new Set<string>();
|
|
253
|
+
|
|
254
|
+
for (const part of ownParts) {
|
|
255
|
+
if (part.param && part.param in currentParams) {
|
|
256
|
+
const value = currentParams[part.param];
|
|
257
|
+
|
|
258
|
+
if (value !== undefined) {
|
|
259
|
+
partValues.set(part, value);
|
|
260
|
+
claimedParams.add(part.param);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
238
263
|
}
|
|
239
264
|
|
|
240
265
|
if (focusedRoute === route) {
|
|
241
266
|
// If this is the focused route, keep the params for later use
|
|
242
267
|
// We save it here since it's been stringified already
|
|
268
|
+
// Params claimed by the pattern shouldn't be repeated in the query string
|
|
243
269
|
focusedParams = {};
|
|
244
270
|
|
|
245
271
|
for (const key in currentParams) {
|
|
246
|
-
let inPattern = false;
|
|
247
|
-
|
|
248
|
-
for (const part of parts ?? []) {
|
|
249
|
-
if (part.param === key) {
|
|
250
|
-
inPattern = true;
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
272
|
const value = currentParams[key];
|
|
256
273
|
|
|
257
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
!claimedParams.has(key) &&
|
|
276
|
+
value !== undefined &&
|
|
277
|
+
value !== 'undefined'
|
|
278
|
+
) {
|
|
258
279
|
focusedParams[key] = value;
|
|
259
280
|
}
|
|
260
281
|
}
|
|
261
282
|
}
|
|
262
283
|
}
|
|
263
284
|
|
|
285
|
+
for (const part of ownParts) {
|
|
286
|
+
if (part.param && !partValues.has(part)) {
|
|
287
|
+
partValues.set(part, undefined);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
264
291
|
// If there is no `screens` property or no nested state, we return pattern
|
|
265
292
|
const routeState = getRouteState(route, config);
|
|
266
293
|
|
|
@@ -298,7 +325,9 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
298
325
|
if (parts) {
|
|
299
326
|
let index = 0;
|
|
300
327
|
|
|
301
|
-
for (const
|
|
328
|
+
for (const part of parts) {
|
|
329
|
+
const { segment, param, optional } = part;
|
|
330
|
+
|
|
302
331
|
if (index > 0) {
|
|
303
332
|
path += '/';
|
|
304
333
|
}
|
|
@@ -315,7 +344,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
315
344
|
|
|
316
345
|
// If the path has a pattern for a param, put the param in the path
|
|
317
346
|
if (param) {
|
|
318
|
-
const value =
|
|
347
|
+
const value = partValues.get(part);
|
|
319
348
|
|
|
320
349
|
if (value === undefined && optional) {
|
|
321
350
|
// Optional params without value assigned in route.params should be ignored
|
|
@@ -341,7 +370,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
341
370
|
const params = focusedRoute.params as Record<string, unknown>;
|
|
342
371
|
|
|
343
372
|
for (const key in params) {
|
|
344
|
-
const value =
|
|
373
|
+
const value = serializeParamValue(params[key]);
|
|
345
374
|
|
|
346
375
|
if (value !== 'undefined') {
|
|
347
376
|
focusedParams[key] = value;
|
|
@@ -385,14 +414,13 @@ const createConfigItem = (
|
|
|
385
414
|
parentParts?: PatternPart[]
|
|
386
415
|
): ConfigItem => {
|
|
387
416
|
if (typeof config === 'string') {
|
|
388
|
-
|
|
389
|
-
const parts = getPatternParts(config);
|
|
417
|
+
const ownParts = getPatternParts(config);
|
|
390
418
|
|
|
391
419
|
if (parentParts) {
|
|
392
|
-
return { parts: [...parentParts, ...
|
|
420
|
+
return { parts: [...parentParts, ...ownParts], ownParts };
|
|
393
421
|
}
|
|
394
422
|
|
|
395
|
-
return { parts };
|
|
423
|
+
return { parts: ownParts, ownParts };
|
|
396
424
|
}
|
|
397
425
|
|
|
398
426
|
if (config.exact && config.path === undefined) {
|
|
@@ -403,14 +431,12 @@ const createConfigItem = (
|
|
|
403
431
|
|
|
404
432
|
// If an object is specified as the value (e.g. Foo: { ... }),
|
|
405
433
|
// It can have `path` property and `screens` prop which has nested configs
|
|
434
|
+
const ownParts = config.path ? getPatternParts(config.path) : [];
|
|
406
435
|
const parts =
|
|
407
436
|
config.exact !== true
|
|
408
|
-
? [
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
]
|
|
412
|
-
: config.path
|
|
413
|
-
? getPatternParts(config.path)
|
|
437
|
+
? [...(parentParts || []), ...ownParts]
|
|
438
|
+
: ownParts.length
|
|
439
|
+
? ownParts
|
|
414
440
|
: undefined;
|
|
415
441
|
|
|
416
442
|
const screens =
|
|
@@ -420,6 +446,7 @@ const createConfigItem = (
|
|
|
420
446
|
|
|
421
447
|
return {
|
|
422
448
|
parts,
|
|
449
|
+
ownParts,
|
|
423
450
|
stringify: config.stringify,
|
|
424
451
|
screens,
|
|
425
452
|
};
|
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) {
|