@teardown/navigation 2.0.79 → 2.0.82
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/package.json +2 -2
- package/src/hooks/index.ts +14 -0
- package/src/hooks/scoped-hooks.ts +125 -0
- package/src/index.ts +35 -1
- package/src/primitives/define-layout.ts +179 -5
- package/src/primitives/define-screen.ts +144 -5
- package/src/primitives/index.ts +6 -0
- package/src/route-builder/create-root-layout.ts +42 -0
- package/src/route-builder/create-route-tree.ts +128 -0
- package/src/route-builder/index.ts +7 -0
- package/src/route-builder/route-builder-d.test.ts +100 -0
- package/src/route-builder/route-builder.test.ts +276 -0
- package/src/router/create-router.tsx +195 -6
- package/src/router/index.ts +7 -2
- package/src/types/index.ts +14 -0
- package/src/types/route-builder-types.ts +103 -0
- package/src/types/type-utils.ts +37 -10
|
@@ -8,6 +8,10 @@ import { NavigationContainer } from "@react-navigation/native";
|
|
|
8
8
|
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|
9
9
|
import type React from "react";
|
|
10
10
|
import type { ComponentType, ReactNode } from "react";
|
|
11
|
+
import { useEffect } from "react";
|
|
12
|
+
import { initializeScopedHooks } from "../hooks/scoped-hooks";
|
|
13
|
+
import type { RouteTree } from "../route-builder";
|
|
14
|
+
import type { AnyRouteDefinition } from "../types/route-builder-types";
|
|
11
15
|
import type { FlatRouteTree, NavigatorNode, ScreenEntry } from "./types";
|
|
12
16
|
import { isHierarchicalRouteTree } from "./types";
|
|
13
17
|
|
|
@@ -15,11 +19,16 @@ import { isHierarchicalRouteTree } from "./types";
|
|
|
15
19
|
let createBottomTabNavigator: (() => ReturnType<typeof createNativeStackNavigator>) | null = null;
|
|
16
20
|
let createDrawerNavigator: (() => ReturnType<typeof createNativeStackNavigator>) | null = null;
|
|
17
21
|
|
|
22
|
+
// Track which navigators are available
|
|
23
|
+
let bottomTabsAvailable = false;
|
|
24
|
+
let drawerAvailable = false;
|
|
25
|
+
|
|
18
26
|
// Try to load optional dependencies
|
|
19
27
|
try {
|
|
20
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
29
|
const bottomTabs = require("@react-navigation/bottom-tabs");
|
|
22
30
|
createBottomTabNavigator = bottomTabs.createBottomTabNavigator;
|
|
31
|
+
bottomTabsAvailable = true;
|
|
23
32
|
} catch {
|
|
24
33
|
// Bottom tabs not installed
|
|
25
34
|
}
|
|
@@ -28,10 +37,57 @@ try {
|
|
|
28
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
38
|
const drawer = require("@react-navigation/drawer");
|
|
30
39
|
createDrawerNavigator = drawer.createDrawerNavigator;
|
|
40
|
+
drawerAvailable = true;
|
|
31
41
|
} catch {
|
|
32
42
|
// Drawer not installed
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Recursively checks a navigator node tree for required navigator types
|
|
47
|
+
*/
|
|
48
|
+
function collectRequiredNavigatorTypes(node: NavigatorNode, types: Set<"tabs" | "drawer">): void {
|
|
49
|
+
if (node.type === "tabs") {
|
|
50
|
+
types.add("tabs");
|
|
51
|
+
} else if (node.type === "drawer") {
|
|
52
|
+
types.add("drawer");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check nested navigators
|
|
56
|
+
if (node.navigators) {
|
|
57
|
+
for (const nestedNode of Object.values(node.navigators)) {
|
|
58
|
+
collectRequiredNavigatorTypes(nestedNode, types);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Validates that required navigator dependencies are installed and throws if not
|
|
65
|
+
*/
|
|
66
|
+
function validateNavigatorDependencies(routeTree: NavigatorNode): void {
|
|
67
|
+
const requiredTypes = new Set<"tabs" | "drawer">();
|
|
68
|
+
collectRequiredNavigatorTypes(routeTree, requiredTypes);
|
|
69
|
+
|
|
70
|
+
const missingDeps: string[] = [];
|
|
71
|
+
|
|
72
|
+
if (requiredTypes.has("tabs") && !bottomTabsAvailable) {
|
|
73
|
+
missingDeps.push(
|
|
74
|
+
"Your route tree uses tabs navigation but @react-navigation/bottom-tabs is not installed.\n" +
|
|
75
|
+
" Install it with: npm install @react-navigation/bottom-tabs"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (requiredTypes.has("drawer") && !drawerAvailable) {
|
|
80
|
+
missingDeps.push(
|
|
81
|
+
"Your route tree uses drawer navigation but @react-navigation/drawer is not installed.\n" +
|
|
82
|
+
" Install it with: npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (missingDeps.length > 0) {
|
|
87
|
+
throw new Error(`[@teardown/navigation] Missing required navigation dependencies:\n\n${missingDeps.join("\n\n")}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
35
91
|
/**
|
|
36
92
|
* Options for creating a Teardown router
|
|
37
93
|
*/
|
|
@@ -69,6 +125,31 @@ export interface TeardownRouterOptions<T extends NavigatorNode | FlatRouteTree =
|
|
|
69
125
|
navigationContainerProps?: Omit<React.ComponentProps<typeof NavigationContainer>, "children">;
|
|
70
126
|
}
|
|
71
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Options for creating a Teardown router from a RouteTree
|
|
130
|
+
*/
|
|
131
|
+
export interface RouteTreeRouterOptions<TRoot extends AnyRouteDefinition> {
|
|
132
|
+
/**
|
|
133
|
+
* The route tree created with createRouteTree()
|
|
134
|
+
*/
|
|
135
|
+
routeTree: RouteTree<TRoot>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Deep linking configuration
|
|
139
|
+
*/
|
|
140
|
+
linking?: LinkingOptions<Record<string, unknown>>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Navigation theme
|
|
144
|
+
*/
|
|
145
|
+
theme?: Theme;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Custom navigation container wrapper
|
|
149
|
+
*/
|
|
150
|
+
navigationContainerProps?: Omit<React.ComponentProps<typeof NavigationContainer>, "children">;
|
|
151
|
+
}
|
|
152
|
+
|
|
72
153
|
/**
|
|
73
154
|
* Creates a navigator component based on type
|
|
74
155
|
*/
|
|
@@ -76,18 +157,18 @@ function createNavigator(type: "stack" | "tabs" | "drawer") {
|
|
|
76
157
|
switch (type) {
|
|
77
158
|
case "tabs":
|
|
78
159
|
if (!createBottomTabNavigator) {
|
|
79
|
-
|
|
80
|
-
"[@teardown/navigation] Tab navigator requested but @react-navigation/bottom-tabs is not installed
|
|
160
|
+
throw new Error(
|
|
161
|
+
"[@teardown/navigation] Tab navigator requested but @react-navigation/bottom-tabs is not installed.\n" +
|
|
162
|
+
"Install it with: npm install @react-navigation/bottom-tabs"
|
|
81
163
|
);
|
|
82
|
-
return createNativeStackNavigator();
|
|
83
164
|
}
|
|
84
165
|
return createBottomTabNavigator();
|
|
85
166
|
case "drawer":
|
|
86
167
|
if (!createDrawerNavigator) {
|
|
87
|
-
|
|
88
|
-
"[@teardown/navigation] Drawer navigator requested but @react-navigation/drawer is not installed
|
|
168
|
+
throw new Error(
|
|
169
|
+
"[@teardown/navigation] Drawer navigator requested but @react-navigation/drawer is not installed.\n" +
|
|
170
|
+
"Install it with: npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated"
|
|
89
171
|
);
|
|
90
|
-
return createNativeStackNavigator();
|
|
91
172
|
}
|
|
92
173
|
return createDrawerNavigator();
|
|
93
174
|
default:
|
|
@@ -197,6 +278,9 @@ export function createTeardownRouter<T extends NavigatorNode | FlatRouteTree>(
|
|
|
197
278
|
|
|
198
279
|
// Check if this is a hierarchical route tree
|
|
199
280
|
if (isHierarchicalRouteTree(routeTree)) {
|
|
281
|
+
// Validate that required navigator dependencies are installed (warns early)
|
|
282
|
+
validateNavigatorDependencies(routeTree);
|
|
283
|
+
|
|
200
284
|
// Set initial route name if provided
|
|
201
285
|
const treeWithInitial: NavigatorNode = {
|
|
202
286
|
...routeTree,
|
|
@@ -280,3 +364,108 @@ export type ExtractRoutePaths<T extends NavigatorNode> = T extends NavigatorNode
|
|
|
280
364
|
* Type helper to extract screen definitions from a route tree
|
|
281
365
|
*/
|
|
282
366
|
export type ExtractScreens<T extends NavigatorNode> = T extends NavigatorNode ? T["screens"] : never;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Creates a Teardown Router from a RouteTree (enhanced API)
|
|
370
|
+
*
|
|
371
|
+
* This is the new API that uses the route builder pattern with
|
|
372
|
+
* createRootLayout, defineLayout, and defineScreen.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```tsx
|
|
376
|
+
* import { createTeardownRouterFromTree, createRouteTree, createRootLayout, defineScreen } from "@teardown/navigation";
|
|
377
|
+
*
|
|
378
|
+
* const root = createRootLayout({ type: 'stack' });
|
|
379
|
+
* const home = defineScreen({ path: 'home', component: HomeScreen, getParentRoute: () => root });
|
|
380
|
+
*
|
|
381
|
+
* const routeTree = createRouteTree({
|
|
382
|
+
* root: root.addChildren([home]),
|
|
383
|
+
* });
|
|
384
|
+
*
|
|
385
|
+
* export const Router = createTeardownRouterFromTree({ routeTree });
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
export function createTeardownRouterFromTree<TRoot extends AnyRouteDefinition>(
|
|
389
|
+
options: RouteTreeRouterOptions<TRoot>
|
|
390
|
+
): ComponentType<{ children?: ReactNode }> {
|
|
391
|
+
const { routeTree, linking, theme, navigationContainerProps } = options;
|
|
392
|
+
|
|
393
|
+
function TeardownRouterFromTree(): React.JSX.Element {
|
|
394
|
+
// Initialize scoped hooks with React Navigation
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
try {
|
|
397
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
398
|
+
const { useRoute, useNavigation } = require("@react-navigation/native");
|
|
399
|
+
initializeScopedHooks(useRoute, useNavigation);
|
|
400
|
+
} catch {
|
|
401
|
+
console.warn("[@teardown/navigation] @react-navigation/native not found");
|
|
402
|
+
}
|
|
403
|
+
}, []);
|
|
404
|
+
|
|
405
|
+
// Build navigator tree from route tree
|
|
406
|
+
const navigatorNode = buildNavigatorFromRouteTree(routeTree.root);
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<NavigationContainer linking={linking} theme={theme} {...navigationContainerProps}>
|
|
410
|
+
<RenderNavigator node={navigatorNode} />
|
|
411
|
+
</NavigationContainer>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return TeardownRouterFromTree;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Builds a NavigatorNode from a RouteTree root
|
|
420
|
+
*/
|
|
421
|
+
function buildNavigatorFromRouteTree(root: AnyRouteDefinition): NavigatorNode {
|
|
422
|
+
const isLayout = "__brand" in root && root.__brand === "TeardownLayout";
|
|
423
|
+
|
|
424
|
+
if (!isLayout) {
|
|
425
|
+
// Single screen, wrap in a stack
|
|
426
|
+
return {
|
|
427
|
+
type: "stack",
|
|
428
|
+
screens: {
|
|
429
|
+
[(root as { path?: string }).path || "index"]: {
|
|
430
|
+
screen: root as unknown as ScreenEntry["screen"],
|
|
431
|
+
path: (root as { fullPath?: string }).fullPath || "/",
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const layout = root as unknown as {
|
|
438
|
+
type: "stack" | "tabs" | "drawer";
|
|
439
|
+
screenOptions?: object;
|
|
440
|
+
initialRouteName?: string;
|
|
441
|
+
children: AnyRouteDefinition[];
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const screens: Record<string, ScreenEntry> = {};
|
|
445
|
+
const navigators: Record<string, NavigatorNode> = {};
|
|
446
|
+
|
|
447
|
+
for (const child of layout.children || []) {
|
|
448
|
+
const childBrand = "__brand" in child ? child.__brand : undefined;
|
|
449
|
+
const childPath = (child as { path?: string }).path || "index";
|
|
450
|
+
const childFullPath = (child as { fullPath?: string }).fullPath || "/";
|
|
451
|
+
|
|
452
|
+
if (childBrand === "TeardownLayout") {
|
|
453
|
+
// Nested navigator
|
|
454
|
+
navigators[childPath] = buildNavigatorFromRouteTree(child);
|
|
455
|
+
} else {
|
|
456
|
+
// Screen
|
|
457
|
+
screens[childPath] = {
|
|
458
|
+
screen: child as unknown as ScreenEntry["screen"],
|
|
459
|
+
path: childFullPath,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
type: layout.type,
|
|
466
|
+
screens,
|
|
467
|
+
navigators: Object.keys(navigators).length > 0 ? navigators : undefined,
|
|
468
|
+
screenOptions: layout.screenOptions as Record<string, unknown> | undefined,
|
|
469
|
+
initialRouteName: layout.initialRouteName,
|
|
470
|
+
};
|
|
471
|
+
}
|
package/src/router/index.ts
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* Router module exports
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export type {
|
|
6
|
-
|
|
5
|
+
export type {
|
|
6
|
+
ExtractRoutePaths,
|
|
7
|
+
ExtractScreens,
|
|
8
|
+
RouteTreeRouterOptions,
|
|
9
|
+
TeardownRouterOptions,
|
|
10
|
+
} from "./create-router";
|
|
11
|
+
export { createTeardownRouter, createTeardownRouterFromTree } from "./create-router";
|
|
7
12
|
// Legacy type aliases for backwards compatibility
|
|
8
13
|
export type {
|
|
9
14
|
DrawerScreenOptions,
|
package/src/types/index.ts
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
* Type utilities and definitions for @teardown/navigation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
// Route builder types
|
|
6
|
+
export type {
|
|
7
|
+
AccumulateParams,
|
|
8
|
+
AnyRouteDefinition,
|
|
9
|
+
ComputeFullPath,
|
|
10
|
+
ExtractRouteParams,
|
|
11
|
+
ExtractRoutePaths,
|
|
12
|
+
MergeRouteParams,
|
|
13
|
+
ParamsForPath,
|
|
14
|
+
RouteDefinitionBase,
|
|
15
|
+
StripRouteGroups,
|
|
16
|
+
} from "./route-builder-types";
|
|
5
17
|
// Route type definitions
|
|
6
18
|
export type {
|
|
7
19
|
NavigationState,
|
|
@@ -22,7 +34,9 @@ export type {
|
|
|
22
34
|
InferParams,
|
|
23
35
|
IsCatchAllSegment,
|
|
24
36
|
IsDynamicSegment,
|
|
37
|
+
IsEmptyParams,
|
|
25
38
|
IsOptionalSegment,
|
|
26
39
|
NavigateArgs,
|
|
27
40
|
Simplify,
|
|
41
|
+
ToReactNavigationPath,
|
|
28
42
|
} from "./type-utils";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route builder type utilities for @teardown/navigation
|
|
3
|
+
* Enables compile-time path computation and param accumulation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { InferParams, Simplify } from "./type-utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compute full path from parent chain
|
|
10
|
+
* Handles route groups by stripping (groupName) segments
|
|
11
|
+
*/
|
|
12
|
+
export type ComputeFullPath<TPath extends string, TParent> = TParent extends {
|
|
13
|
+
fullPath: infer P extends string;
|
|
14
|
+
}
|
|
15
|
+
? P extends "/"
|
|
16
|
+
? `/${StripRouteGroups<TPath>}`
|
|
17
|
+
: StripRouteGroups<TPath> extends ""
|
|
18
|
+
? P
|
|
19
|
+
: `${P}/${StripRouteGroups<TPath>}`
|
|
20
|
+
: StripRouteGroups<TPath> extends ""
|
|
21
|
+
? "/"
|
|
22
|
+
: `/${StripRouteGroups<TPath>}`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Strip route group segments from path
|
|
26
|
+
* (auth)/login -> login
|
|
27
|
+
* (auth) -> ""
|
|
28
|
+
*/
|
|
29
|
+
export type StripRouteGroups<TPath extends string> = TPath extends `(${string})/${infer Rest}`
|
|
30
|
+
? StripRouteGroups<Rest>
|
|
31
|
+
: TPath extends `(${string})`
|
|
32
|
+
? ""
|
|
33
|
+
: TPath;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Accumulate params from parent chain + this route
|
|
37
|
+
*/
|
|
38
|
+
export type AccumulateParams<TPath extends string, TParent> = Simplify<
|
|
39
|
+
InferParams<TPath> & (TParent extends { allParams: infer P } ? P : {})
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Base interface for all route definitions (screens and layouts)
|
|
44
|
+
*/
|
|
45
|
+
export interface RouteDefinitionBase<
|
|
46
|
+
TPath extends string = string,
|
|
47
|
+
TFullPath extends string = string,
|
|
48
|
+
TParams = unknown,
|
|
49
|
+
TAllParams = unknown,
|
|
50
|
+
TContext = unknown,
|
|
51
|
+
> {
|
|
52
|
+
/** This route segment path */
|
|
53
|
+
readonly path: TPath;
|
|
54
|
+
/** Full path from root */
|
|
55
|
+
readonly fullPath: TFullPath;
|
|
56
|
+
/** Params from this route only */
|
|
57
|
+
readonly params: TParams;
|
|
58
|
+
/** Accumulated params from parent chain */
|
|
59
|
+
readonly allParams: TAllParams;
|
|
60
|
+
/** Loader context type */
|
|
61
|
+
readonly context: TContext;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Type for any route definition (screen or layout)
|
|
66
|
+
*/
|
|
67
|
+
export type AnyRouteDefinition = RouteDefinitionBase<string, string, unknown, unknown, unknown>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extract all route paths from a route tree
|
|
71
|
+
*/
|
|
72
|
+
export type ExtractRoutePaths<T> = T extends { fullPath: infer P extends string }
|
|
73
|
+
?
|
|
74
|
+
| P
|
|
75
|
+
| (T extends { children: infer C }
|
|
76
|
+
? C extends readonly (infer Child)[]
|
|
77
|
+
? ExtractRoutePaths<Child>
|
|
78
|
+
: never
|
|
79
|
+
: never)
|
|
80
|
+
: never;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract route params map from a route tree
|
|
84
|
+
*/
|
|
85
|
+
export type ExtractRouteParams<T> = T extends { fullPath: infer P extends string; allParams: infer Params }
|
|
86
|
+
? { [K in P]: Params } & (T extends { children: infer C }
|
|
87
|
+
? C extends readonly (infer Child)[]
|
|
88
|
+
? ExtractRouteParams<Child>
|
|
89
|
+
: {}
|
|
90
|
+
: {})
|
|
91
|
+
: {};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Merge two route param maps
|
|
95
|
+
*/
|
|
96
|
+
export type MergeRouteParams<A, B> = Simplify<A & B>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get params for a specific path from a route tree
|
|
100
|
+
*/
|
|
101
|
+
export type ParamsForPath<TTree, TPath extends string> = TPath extends keyof ExtractRouteParams<TTree>
|
|
102
|
+
? ExtractRouteParams<TTree>[TPath]
|
|
103
|
+
: never;
|
package/src/types/type-utils.ts
CHANGED
|
@@ -11,19 +11,46 @@ export type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Extracts parameter from a single path segment
|
|
14
|
+
* Supports both [param] and :param syntax
|
|
14
15
|
*
|
|
15
|
-
* - [userId] -> { userId: string }
|
|
16
|
-
* - [...slug] -> { slug: string[] }
|
|
17
|
-
* - [section]? -> { section?: string }
|
|
16
|
+
* - [userId] or :userId -> { userId: string }
|
|
17
|
+
* - [...slug] or *slug -> { slug: string[] }
|
|
18
|
+
* - [section]? or :section? -> { section?: string }
|
|
18
19
|
* - about -> Record<string, never>
|
|
19
20
|
*/
|
|
20
|
-
export type ExtractParam<Segment extends string> =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
? { [K in
|
|
24
|
-
: Segment extends
|
|
25
|
-
? { [K in
|
|
26
|
-
:
|
|
21
|
+
export type ExtractParam<Segment extends string> =
|
|
22
|
+
// Catch-all: [...slug] or *slug
|
|
23
|
+
Segment extends `[...${infer Rest}]`
|
|
24
|
+
? { [K in Rest]: string[] }
|
|
25
|
+
: Segment extends `*${infer Rest}`
|
|
26
|
+
? { [K in Rest]: string[] }
|
|
27
|
+
: // Optional: [param]? or :param?
|
|
28
|
+
Segment extends `[${infer Param}]?`
|
|
29
|
+
? { [K in Param]?: string }
|
|
30
|
+
: Segment extends `:${infer Param}?`
|
|
31
|
+
? { [K in Param]?: string }
|
|
32
|
+
: // Required: [param] or :param
|
|
33
|
+
Segment extends `[${infer Param}]`
|
|
34
|
+
? { [K in Param]: string }
|
|
35
|
+
: Segment extends `:${infer Param}`
|
|
36
|
+
? { [K in Param]: string }
|
|
37
|
+
: // Static segment
|
|
38
|
+
Record<string, never>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if params object is empty (no required params)
|
|
42
|
+
*/
|
|
43
|
+
export type IsEmptyParams<T> = keyof T extends never ? true : false;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Convert [param] syntax to :param syntax for React Navigation
|
|
47
|
+
*/
|
|
48
|
+
export type ToReactNavigationPath<TPath extends string> =
|
|
49
|
+
TPath extends `${infer Before}[...${infer Param}]${infer After}`
|
|
50
|
+
? ToReactNavigationPath<`${Before}*${Param}${After}`>
|
|
51
|
+
: TPath extends `${infer Before}[${infer Param}]${infer After}`
|
|
52
|
+
? ToReactNavigationPath<`${Before}:${Param}${After}`>
|
|
53
|
+
: TPath;
|
|
27
54
|
|
|
28
55
|
/**
|
|
29
56
|
* Recursively extracts all params from a path
|