expo-router 0.0.35 → 0.0.37
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/build/Route.d.ts +6 -5
- package/build/Route.d.ts.map +1 -1
- package/build/Route.js +13 -0
- package/build/Route.js.map +1 -1
- package/build/fork/getPathFromState.d.ts +4 -1
- package/build/fork/getPathFromState.d.ts.map +1 -1
- package/build/fork/getPathFromState.js +272 -143
- package/build/fork/getPathFromState.js.map +1 -1
- package/build/fork/getStateFromPath.d.ts.map +1 -1
- package/build/fork/getStateFromPath.js +260 -238
- package/build/fork/getStateFromPath.js.map +1 -1
- package/build/getLinkingConfig.d.ts +1 -0
- package/build/getLinkingConfig.d.ts.map +1 -1
- package/build/getLinkingConfig.js +21 -8
- package/build/getLinkingConfig.js.map +1 -1
- package/build/getRoutes.d.ts +1 -1
- package/build/getRoutes.d.ts.map +1 -1
- package/build/getRoutes.js +76 -25
- package/build/getRoutes.js.map +1 -1
- package/build/layouts/withLayoutContext.d.ts +3 -1
- package/build/layouts/withLayoutContext.d.ts.map +1 -1
- package/build/layouts/withLayoutContext.js +7 -5
- package/build/layouts/withLayoutContext.js.map +1 -1
- package/build/link/Link.js +3 -1
- package/build/link/Link.js.map +1 -1
- package/build/link/href.d.ts.map +1 -1
- package/build/link/href.js +3 -10
- package/build/link/href.js.map +1 -1
- package/build/link/path.d.ts +2 -0
- package/build/link/path.d.ts.map +1 -0
- package/build/link/path.js +143 -0
- package/build/link/path.js.map +1 -0
- package/build/link/useHref.d.ts.map +1 -1
- package/build/link/useHref.js +15 -31
- package/build/link/useHref.js.map +1 -1
- package/build/link/useLinkToPath.d.ts.map +1 -1
- package/build/link/useLinkToPath.js +22 -9
- package/build/link/useLinkToPath.js.map +1 -1
- package/build/link/useLinkToPathProps.d.ts.map +1 -1
- package/build/link/useLinkToPathProps.js +2 -1
- package/build/link/useLinkToPathProps.js.map +1 -1
- package/build/link/useLinkingContext.d.ts +3 -0
- package/build/link/useLinkingContext.d.ts.map +1 -0
- package/build/link/useLinkingContext.js +13 -0
- package/build/link/useLinkingContext.js.map +1 -0
- package/build/matchers.d.ts +1 -0
- package/build/matchers.d.ts.map +1 -1
- package/build/matchers.js +11 -0
- package/build/matchers.js.map +1 -1
- package/build/useScreens.d.ts.map +1 -1
- package/build/useScreens.js +14 -9
- package/build/useScreens.js.map +1 -1
- package/build/views/Layout.js +1 -1
- package/build/views/Layout.js.map +1 -1
- package/build/views/Sitemap.js +35 -28
- package/build/views/Sitemap.js.map +1 -1
- package/build/views/Unmatched.d.ts.map +1 -1
- package/build/views/Unmatched.js +6 -4
- package/build/views/Unmatched.js.map +1 -1
- package/package.json +2 -2
package/build/Route.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
2
|
/** The list of input keys will become optional, everything else will remain the same. */
|
|
3
3
|
export declare type PickPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
4
4
|
export declare type RouteNode = {
|
|
5
|
+
/** Load a route into memory. Returns the exports from a route. */
|
|
6
|
+
loadRoute: () => any;
|
|
7
|
+
/** Loaded initial route name. */
|
|
8
|
+
initialRouteName?: string;
|
|
5
9
|
/** nested routes */
|
|
6
10
|
children: RouteNode[];
|
|
7
|
-
/** Lazily get the React component */
|
|
8
|
-
getComponent: () => React.ComponentType<any>;
|
|
9
11
|
/** Is the route a dynamic path */
|
|
10
12
|
dynamic: null | {
|
|
11
13
|
name: string;
|
|
12
14
|
deep: boolean;
|
|
13
15
|
};
|
|
14
|
-
/** All static exports from the file. */
|
|
15
|
-
getExtras: () => Record<string, any>;
|
|
16
16
|
/** `index`, `error-boundary`, etc. */
|
|
17
17
|
route: string;
|
|
18
18
|
/** require.context key, used for matching children. */
|
|
@@ -31,5 +31,6 @@ export declare function Route({ children, node, }: {
|
|
|
31
31
|
node: RouteNode;
|
|
32
32
|
}): JSX.Element;
|
|
33
33
|
export declare function useRootRoute(): RouteNode | null;
|
|
34
|
+
export declare function sortRoutesWithInitial(initialRouteName?: string): (a: RouteNode, b: RouteNode) => number;
|
|
34
35
|
export declare function sortRoutes(a: RouteNode, b: RouteNode): number;
|
|
35
36
|
//# sourceMappingURL=Route.d.ts.map
|
package/build/Route.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Route.d.ts","sourceRoot":"","sources":["../src/Route.tsx"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"Route.d.ts","sourceRoot":"","sources":["../src/Route.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,SAAS,EAAc,MAAM,OAAO,CAAC;AAKrD,yFAAyF;AACzF,oBAAY,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAEtB,oBAAY,SAAS,GAAG;IACtB,kEAAkE;IAClE,SAAS,EAAE,MAAM,GAAG,CAAC;IAErB,iCAAiC;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB;IACpB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,kCAAkC;IAClC,OAAO,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAWF,+DAA+D;AAC/D,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAE/C;AAED,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED,iEAAiE;AACjE,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EACR,IAAI,GACL,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;CACjB,eAkBA;AAED,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAE/C;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,CAAC,EAAE,MAAM,OAClD,SAAS,KAAK,SAAS,KAAG,MAAM,CAW5C;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,CA4B7D"}
|
package/build/Route.js
CHANGED
|
@@ -35,6 +35,19 @@ export function Route({ children, node, }) {
|
|
|
35
35
|
export function useRootRoute() {
|
|
36
36
|
return useContext(RootRouteNodeContext);
|
|
37
37
|
}
|
|
38
|
+
export function sortRoutesWithInitial(initialRouteName) {
|
|
39
|
+
return (a, b) => {
|
|
40
|
+
if (initialRouteName) {
|
|
41
|
+
if (a.route === initialRouteName) {
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
44
|
+
if (b.route === initialRouteName) {
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return sortRoutes(a, b);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
38
51
|
export function sortRoutes(a, b) {
|
|
39
52
|
if (a.dynamic && !b.dynamic) {
|
|
40
53
|
return 1;
|
package/build/Route.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Route.js","sourceRoot":"","sources":["../src/Route.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAa,UAAU,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Route.js","sourceRoot":"","sources":["../src/Route.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { ReactNode, useContext } from \"react\";\n\nimport { RootRouteNodeContext } from \"./context\";\nimport { getNameFromFilePath, matchFragmentName } from \"./matchers\";\n\n/** The list of input keys will become optional, everything else will remain the same. */\nexport type PickPartial<T, K extends keyof T> = Omit<T, K> &\n Partial<Pick<T, K>>;\n\nexport type RouteNode = {\n /** Load a route into memory. Returns the exports from a route. */\n loadRoute: () => any;\n\n /** Loaded initial route name. */\n initialRouteName?: string;\n /** nested routes */\n children: RouteNode[];\n /** Is the route a dynamic path */\n dynamic: null | { name: string; deep: boolean };\n /** `index`, `error-boundary`, etc. */\n route: string;\n /** require.context key, used for matching children. */\n contextKey: string;\n /** Added in-memory */\n generated?: boolean;\n\n /** Internal screens like the directory or the auto 404 should be marked as internal. */\n internal?: boolean;\n};\n\nconst CurrentRoutePathContext = React.createContext<string | null>(null);\n\nconst CurrentRouteContext = React.createContext<RouteNode | null>(null);\n\nif (process.env.NODE_ENV !== \"production\") {\n CurrentRoutePathContext.displayName = \"RoutePath\";\n CurrentRouteContext.displayName = \"Route\";\n}\n\n/** Return the RouteNode at the current contextual boundary. */\nexport function useRouteNode(): RouteNode | null {\n return useContext(CurrentRouteContext);\n}\n\nexport function useContextKey(): string {\n const filename = useContext(CurrentRoutePathContext);\n if (filename == null) {\n throw new Error(\"No filename found. This is likely a bug in expo-router.\");\n }\n return filename;\n}\n\n/** Provides the matching routes and filename to the children. */\nexport function Route({\n children,\n node,\n}: {\n children: ReactNode;\n node: RouteNode;\n}) {\n const normalName = React.useMemo(() => {\n // The root path is `` (empty string) so always prepend `/` to ensure\n // there is some value.\n const normal = \"/\" + getNameFromFilePath(node.contextKey);\n if (!normal.endsWith(\"_layout\")) {\n return normal;\n }\n return normal.replace(/\\/?_layout$/, \"\");\n }, [node.contextKey]);\n\n return (\n <CurrentRoutePathContext.Provider value={normalName}>\n <CurrentRouteContext.Provider value={node}>\n {children}\n </CurrentRouteContext.Provider>\n </CurrentRoutePathContext.Provider>\n );\n}\n\nexport function useRootRoute(): RouteNode | null {\n return useContext(RootRouteNodeContext);\n}\n\nexport function sortRoutesWithInitial(initialRouteName?: string) {\n return (a: RouteNode, b: RouteNode): number => {\n if (initialRouteName) {\n if (a.route === initialRouteName) {\n return -1;\n }\n if (b.route === initialRouteName) {\n return 1;\n }\n }\n return sortRoutes(a, b);\n };\n}\nexport function sortRoutes(a: RouteNode, b: RouteNode): number {\n if (a.dynamic && !b.dynamic) {\n return 1;\n }\n if (!a.dynamic && b.dynamic) {\n return -1;\n }\n if (a.dynamic && b.dynamic) {\n if (a.dynamic.deep && !b.dynamic.deep) {\n return 1;\n }\n if (!a.dynamic.deep && b.dynamic.deep) {\n return -1;\n }\n return 0;\n }\n\n const aIndex = a.route === \"index\" || matchFragmentName(a.route) != null;\n const bIndex = b.route === \"index\" || matchFragmentName(b.route) != null;\n\n if (aIndex && !bIndex) {\n return -1;\n }\n if (!aIndex && bIndex) {\n return 1;\n }\n\n return a.route.length - b.route.length;\n}\n"]}
|
|
@@ -34,6 +34,9 @@ export declare type State = NavigationState | Omit<PartialState<NavigationState>
|
|
|
34
34
|
* @param options Extra options to fine-tune how to serialize the path.
|
|
35
35
|
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
|
36
36
|
*/
|
|
37
|
-
export default function getPathFromState<ParamList extends object>(state: State,
|
|
37
|
+
export default function getPathFromState<ParamList extends object>(state: State, _options?: Options<ParamList> & {
|
|
38
|
+
preserveFragments?: boolean;
|
|
39
|
+
preserveDynamicRoutes?: boolean;
|
|
40
|
+
}): string;
|
|
38
41
|
export {};
|
|
39
42
|
//# sourceMappingURL=getPathFromState.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPathFromState.d.ts","sourceRoot":"","sources":["../../src/fork/getPathFromState.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEd,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEb,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"getPathFromState.d.ts","sourceRoot":"","sources":["../../src/fork/getPathFromState.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEd,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEb,MAAM,2BAA2B,CAAC;AASnC,aAAK,OAAO,CAAC,SAAS,SAAS,MAAM,IAAI;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC,CAAC;AAEF,oBAAY,KAAK,GACb,eAAe,GACf,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;AAgEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,SAAS,SAAS,MAAM,EAC/D,KAAK,EAAE,KAAK,EAEZ,QAAQ,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAC5B,GACL,MAAM,CA2BR"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validatePathConfig, } from "@react-navigation/core";
|
|
2
2
|
import * as queryString from "query-string";
|
|
3
|
-
import { matchDeepDynamicRouteName, matchFragmentName } from "../matchers";
|
|
3
|
+
import { matchDeepDynamicRouteName, matchDynamicName, matchFragmentName, } from "../matchers";
|
|
4
4
|
const getActiveRoute = (state) => {
|
|
5
5
|
const route = typeof state.index === "number"
|
|
6
6
|
? state.routes[state.index]
|
|
@@ -30,6 +30,15 @@ function createFakeState(params) {
|
|
|
30
30
|
],
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
function segmentMatchesConvention(segment) {
|
|
34
|
+
return (segment === "index" ||
|
|
35
|
+
matchDynamicName(segment) != null ||
|
|
36
|
+
matchFragmentName(segment) != null ||
|
|
37
|
+
matchDeepDynamicRouteName(segment) != null);
|
|
38
|
+
}
|
|
39
|
+
function encodeURIComponentPreservingBrackets(str) {
|
|
40
|
+
return encodeURIComponent(str).replace(/%5B/g, "[").replace(/%5D/g, "]");
|
|
41
|
+
}
|
|
33
42
|
/**
|
|
34
43
|
* Utility to serialize a navigation state object to a path string.
|
|
35
44
|
*
|
|
@@ -59,175 +68,294 @@ function createFakeState(params) {
|
|
|
59
68
|
* @param options Extra options to fine-tune how to serialize the path.
|
|
60
69
|
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
|
61
70
|
*/
|
|
62
|
-
export default function getPathFromState(state,
|
|
71
|
+
export default function getPathFromState(state,
|
|
72
|
+
// @ts-expect-error: non-standard options
|
|
73
|
+
_options = {}) {
|
|
63
74
|
if (state == null) {
|
|
64
75
|
throw Error("Got 'undefined' for the navigation state. You must pass a valid state object.");
|
|
65
76
|
}
|
|
66
|
-
|
|
77
|
+
const { preserveFragments, preserveDynamicRoutes, ...options } = _options;
|
|
78
|
+
if (_options) {
|
|
67
79
|
validatePathConfig(options);
|
|
68
80
|
}
|
|
81
|
+
const screens = options?.screens;
|
|
82
|
+
// Expo Router disallows usage without a linking config.
|
|
83
|
+
if (!screens) {
|
|
84
|
+
throw Error("You must pass a 'screens' object to 'getPathFromState' to generate a path.");
|
|
85
|
+
}
|
|
86
|
+
return getPathFromResolvedState(state,
|
|
69
87
|
// Create a normalized configs object which will be easier to use
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
createNormalizedConfigs(screens), { preserveFragments, preserveDynamicRoutes });
|
|
89
|
+
}
|
|
90
|
+
function processParamsWithUserSettings(configItem, params) {
|
|
91
|
+
const stringify = configItem?.stringify;
|
|
92
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [
|
|
93
|
+
key,
|
|
94
|
+
// TODO: Strip nullish values here.
|
|
95
|
+
stringify?.[key] ? stringify[key](value) : String(value),
|
|
96
|
+
]));
|
|
97
|
+
}
|
|
98
|
+
function deepEqual(a, b) {
|
|
99
|
+
if (a === b) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
103
|
+
if (a.length !== b.length) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
for (let i = 0; i < a.length; i++) {
|
|
107
|
+
if (!deepEqual(a[i], b[i])) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
114
|
+
const keysA = Object.keys(a);
|
|
115
|
+
const keysB = Object.keys(b);
|
|
116
|
+
if (keysA.length !== keysB.length) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
for (const key of keysA) {
|
|
120
|
+
if (!deepEqual(a[key], b[key])) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
function walkConfigItems(route, focusedRoute, configs) {
|
|
129
|
+
// NOTE(EvanBacon): Fill in current route using state that was passed as params.
|
|
130
|
+
if (!route.state && isInvalidParams(route.params)) {
|
|
131
|
+
route.state = createFakeState(route.params);
|
|
132
|
+
}
|
|
133
|
+
let pattern = null;
|
|
134
|
+
let focusedParams;
|
|
135
|
+
const collectedParams = {};
|
|
136
|
+
while (route.name in configs) {
|
|
137
|
+
const configItem = configs[route.name];
|
|
138
|
+
const inputPattern = configItem.pattern;
|
|
139
|
+
if (inputPattern == null) {
|
|
140
|
+
// This should never happen in Expo Router.
|
|
141
|
+
throw new Error("Unexpected: No pattern found for route " + route.name);
|
|
142
|
+
}
|
|
143
|
+
pattern = inputPattern;
|
|
144
|
+
if (route.params) {
|
|
145
|
+
const params = processParamsWithUserSettings(configItem, route.params);
|
|
146
|
+
// TODO: Does this need to be a null check?
|
|
147
|
+
if (pattern) {
|
|
148
|
+
Object.assign(collectedParams, params);
|
|
149
|
+
}
|
|
150
|
+
if (deepEqual(focusedRoute, route)) {
|
|
151
|
+
// If this is the focused route, keep the params for later use
|
|
152
|
+
// We save it here since it's been stringified already
|
|
153
|
+
focusedParams = getParamsWithConventionsCollapsed({
|
|
154
|
+
params,
|
|
155
|
+
pattern,
|
|
156
|
+
routeName: route.name,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
80
160
|
if (!route.state && isInvalidParams(route.params)) {
|
|
81
161
|
route.state = createFakeState(route.params);
|
|
82
162
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
nestedRouteNames.push(route.name);
|
|
94
|
-
if (route.params) {
|
|
95
|
-
const stringify = currentOptions[route.name]?.stringify;
|
|
96
|
-
const currentParams = Object.fromEntries(Object.entries(route.params).map(([key, value]) => [
|
|
97
|
-
key,
|
|
98
|
-
stringify?.[key] ? stringify[key](value) : String(value),
|
|
99
|
-
]));
|
|
100
|
-
if (pattern) {
|
|
101
|
-
Object.assign(allParams, currentParams);
|
|
102
|
-
}
|
|
103
|
-
if (focusedRoute === route) {
|
|
163
|
+
// If there is no `screens` property or no nested state, we return pattern
|
|
164
|
+
if (!configItem.screens || route.state === undefined) {
|
|
165
|
+
if (configItem.initialRouteName &&
|
|
166
|
+
configItem.screens &&
|
|
167
|
+
configItem.initialRouteName in configItem.screens &&
|
|
168
|
+
configItem.screens[configItem.initialRouteName]?.pattern) {
|
|
169
|
+
const initialRouteConfig = configItem.screens[configItem.initialRouteName];
|
|
170
|
+
// NOTE(EvanBacon): Big hack to support initial route changes in tab bars.
|
|
171
|
+
pattern = initialRouteConfig.pattern;
|
|
172
|
+
if (focusedParams) {
|
|
104
173
|
// If this is the focused route, keep the params for later use
|
|
105
174
|
// We save it here since it's been stringified already
|
|
106
|
-
focusedParams = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// eslint-disable-next-line no-loop-func
|
|
111
|
-
.forEach((p) => {
|
|
112
|
-
let name;
|
|
113
|
-
if (p === "*") {
|
|
114
|
-
// NOTE(EvanBacon): Drop the param name matching the wildcard route name -- this is specific to Expo Router.
|
|
115
|
-
name = matchDeepDynamicRouteName(route.name) ?? route.name;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
name = getParamName(p);
|
|
119
|
-
}
|
|
120
|
-
// Remove the params present in the pattern since we'll only use the rest for query string
|
|
121
|
-
if (focusedParams) {
|
|
122
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
123
|
-
delete focusedParams[name];
|
|
124
|
-
}
|
|
175
|
+
focusedParams = getParamsWithConventionsCollapsed({
|
|
176
|
+
params: focusedParams,
|
|
177
|
+
pattern,
|
|
178
|
+
routeName: route.name,
|
|
125
179
|
});
|
|
126
180
|
}
|
|
127
181
|
}
|
|
128
|
-
|
|
129
|
-
if (!currentOptions[route.name].screens || route.state === undefined) {
|
|
130
|
-
hasNext = false;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
index =
|
|
134
|
-
typeof route.state.index === "number"
|
|
135
|
-
? route.state.index
|
|
136
|
-
: route.state.routes.length - 1;
|
|
137
|
-
const nextRoute = route.state.routes[index];
|
|
138
|
-
const nestedConfig = currentOptions[route.name].screens;
|
|
139
|
-
// if there is config for next route name, we go deeper
|
|
140
|
-
if (nestedConfig && nextRoute.name in nestedConfig) {
|
|
141
|
-
route = nextRoute;
|
|
142
|
-
currentOptions = nestedConfig;
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// If not, there is no sense in going deeper in config
|
|
146
|
-
hasNext = false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
182
|
+
break;
|
|
149
183
|
}
|
|
150
|
-
|
|
151
|
-
|
|
184
|
+
const index = route.state.index ?? route.state.routes.length - 1;
|
|
185
|
+
const nextRoute = route.state.routes[index];
|
|
186
|
+
const nestedScreens = configItem.screens;
|
|
187
|
+
// if there is config for next route name, we go deeper
|
|
188
|
+
if (nestedScreens && nextRoute.name in nestedScreens) {
|
|
189
|
+
route = nextRoute;
|
|
190
|
+
configs = nestedScreens;
|
|
152
191
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
else {
|
|
193
|
+
// If not, there is no sense in going deeper in config
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (pattern && !focusedParams && focusedRoute.params) {
|
|
198
|
+
// If this is the focused route, keep the params for later use
|
|
199
|
+
// We save it here since it's been stringified already
|
|
200
|
+
focusedParams = getParamsWithConventionsCollapsed({
|
|
201
|
+
params: focusedRoute.params,
|
|
202
|
+
pattern,
|
|
203
|
+
routeName: route.name,
|
|
204
|
+
});
|
|
205
|
+
Object.assign(focusedParams, collectedParams);
|
|
206
|
+
}
|
|
207
|
+
if (pattern == null) {
|
|
208
|
+
throw new Error(`No pattern found for route "${route.name}". Options are: ${Object.keys(configs).join(", ")}.`);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
pattern,
|
|
212
|
+
nextRoute: route,
|
|
213
|
+
focusedParams,
|
|
214
|
+
params: collectedParams,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function getPathFromResolvedState(state, configs, { preserveFragments, preserveDynamicRoutes, }) {
|
|
218
|
+
let path = "";
|
|
219
|
+
let current = state;
|
|
220
|
+
const allParams = {};
|
|
221
|
+
while (current) {
|
|
222
|
+
path += "/";
|
|
223
|
+
const route = current.routes[current.index ?? 0];
|
|
224
|
+
// NOTE(EvanBacon): Fill in current route using state that was passed as params.
|
|
225
|
+
// if (isInvalidParams(route.params)) {
|
|
226
|
+
if (!route.state && isInvalidParams(route.params)) {
|
|
227
|
+
route.state = createFakeState(route.params);
|
|
228
|
+
}
|
|
229
|
+
const { pattern, params, nextRoute, focusedParams } = walkConfigItems(route, getActiveRoute(current), { ...configs });
|
|
230
|
+
Object.assign(allParams, params);
|
|
231
|
+
path += getPathWithConventionsCollapsed({
|
|
232
|
+
pattern,
|
|
233
|
+
routePath: nextRoute.path,
|
|
234
|
+
params: allParams,
|
|
235
|
+
initialRouteName: configs[nextRoute.name]?.initialRouteName,
|
|
236
|
+
preserveFragments,
|
|
237
|
+
preserveDynamicRoutes,
|
|
238
|
+
});
|
|
239
|
+
if (nextRoute.state) {
|
|
240
|
+
// Continue looping with the next state if available.
|
|
241
|
+
current = nextRoute.state;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Finished crawling state.
|
|
245
|
+
// Check for query params before exiting.
|
|
246
|
+
if (focusedParams) {
|
|
247
|
+
for (const param in focusedParams) {
|
|
248
|
+
// TODO: This is not good. We shouldn't squat strings named "undefined".
|
|
249
|
+
if (focusedParams[param] === "undefined") {
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
251
|
+
delete focusedParams[param];
|
|
165
252
|
}
|
|
166
|
-
// remove existing segments from route.path and return it
|
|
167
|
-
// this is used for nested wildcard routes. Without this, the path would add
|
|
168
|
-
// all nested segments to the beginning of the wildcard route.
|
|
169
|
-
const path = route.path
|
|
170
|
-
?.split("/")
|
|
171
|
-
.slice(i + 1)
|
|
172
|
-
.join("/");
|
|
173
|
-
return path ?? "";
|
|
174
253
|
}
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
if (value == null) {
|
|
179
|
-
// Optional params without value assigned in route.params should be ignored
|
|
180
|
-
return "";
|
|
181
|
-
}
|
|
182
|
-
return value;
|
|
254
|
+
const query = queryString.stringify(focusedParams, { sort: false });
|
|
255
|
+
if (query) {
|
|
256
|
+
path += `?${query}`;
|
|
183
257
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.join("/");
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
// TODO: Probably fragment routes shouldn't get this far
|
|
190
|
-
path += encodeURIComponent(matchFragmentName(route.name)
|
|
191
|
-
? ""
|
|
192
|
-
: route.name === "index"
|
|
193
|
-
? ""
|
|
194
|
-
: route.name);
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
195
260
|
}
|
|
196
|
-
|
|
197
|
-
|
|
261
|
+
}
|
|
262
|
+
return basicSanitizePath(path);
|
|
263
|
+
}
|
|
264
|
+
function getPathWithConventionsCollapsed({ pattern, routePath, params, preserveFragments, preserveDynamicRoutes, initialRouteName, }) {
|
|
265
|
+
const segments = pattern.split("/");
|
|
266
|
+
return segments
|
|
267
|
+
.map((p, i) => {
|
|
268
|
+
const name = getParamName(p);
|
|
269
|
+
// We don't know what to show for wildcard patterns
|
|
270
|
+
// Showing the route name seems ok, though whatever we show here will be incorrect
|
|
271
|
+
// Since the page doesn't actually exist
|
|
272
|
+
if (p === "*") {
|
|
273
|
+
if (i === 0) {
|
|
274
|
+
// This can occur when a wildcard matches all routes and the given path was `/`.
|
|
275
|
+
return routePath;
|
|
276
|
+
}
|
|
277
|
+
// remove existing segments from route.path and return it
|
|
278
|
+
// this is used for nested wildcard routes. Without this, the path would add
|
|
279
|
+
// all nested segments to the beginning of the wildcard route.
|
|
280
|
+
return routePath
|
|
281
|
+
?.split("/")
|
|
282
|
+
.slice(i + 1)
|
|
283
|
+
.join("/");
|
|
198
284
|
}
|
|
199
|
-
|
|
200
|
-
|
|
285
|
+
// If the path has a pattern for a param, put the param in the path
|
|
286
|
+
if (p.startsWith(":")) {
|
|
287
|
+
if (preserveDynamicRoutes) {
|
|
288
|
+
return `[${name}]`;
|
|
289
|
+
}
|
|
290
|
+
// Optional params without value assigned in route.params should be ignored
|
|
291
|
+
return params[name];
|
|
201
292
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
293
|
+
if (!preserveFragments && matchFragmentName(p) != null) {
|
|
294
|
+
// When the last part is a fragment it could be a shared URL
|
|
295
|
+
// if the route has an initialRouteName defined, then we should
|
|
296
|
+
// use that as the component path as we can assume it will be shown.
|
|
297
|
+
if (segments.length - 1 === i) {
|
|
298
|
+
if (initialRouteName) {
|
|
299
|
+
// Return an empty string if the init route is ambiguous.
|
|
300
|
+
if (segmentMatchesConvention(initialRouteName)) {
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
return encodeURIComponentPreservingBrackets(initialRouteName);
|
|
207
304
|
}
|
|
208
305
|
}
|
|
209
|
-
|
|
210
|
-
if (query) {
|
|
211
|
-
path += `?${query}`;
|
|
212
|
-
}
|
|
306
|
+
return "";
|
|
213
307
|
}
|
|
214
|
-
|
|
308
|
+
// Preserve dynamic syntax for rehydration
|
|
309
|
+
return encodeURIComponentPreservingBrackets(p);
|
|
310
|
+
})
|
|
311
|
+
.map((v) => v ?? "")
|
|
312
|
+
.join("/");
|
|
313
|
+
}
|
|
314
|
+
/** Given a set of query params and a pattern with possible conventions, collapse the conventions and return the remaining params. */
|
|
315
|
+
function getParamsWithConventionsCollapsed({ pattern, routeName, params, }) {
|
|
316
|
+
const processedParams = { ...params };
|
|
317
|
+
// Remove the params present in the pattern since we'll only use the rest for query string
|
|
318
|
+
const segments = pattern.split("/");
|
|
319
|
+
// Dynamic Routes
|
|
320
|
+
segments
|
|
321
|
+
.filter((segment) => segment.startsWith(":"))
|
|
322
|
+
.forEach((segment) => {
|
|
323
|
+
const name = getParamName(segment);
|
|
324
|
+
delete processedParams[name];
|
|
325
|
+
});
|
|
326
|
+
// Deep Dynamic Routes
|
|
327
|
+
if (segments.some((segment) => segment === "*")) {
|
|
328
|
+
// NOTE(EvanBacon): Drop the param name matching the wildcard route name -- this is specific to Expo Router.
|
|
329
|
+
const name = matchDeepDynamicRouteName(routeName) ?? routeName;
|
|
330
|
+
delete processedParams[name];
|
|
215
331
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
332
|
+
return processedParams;
|
|
333
|
+
}
|
|
334
|
+
// Remove multiple as well as trailing slashes
|
|
335
|
+
function basicSanitizePath(path) {
|
|
336
|
+
// Remove duplicate slashes like `foo//bar` -> `foo/bar`
|
|
337
|
+
const simplifiedPath = path.replace(/\/+/g, "/");
|
|
338
|
+
if (simplifiedPath.length <= 1) {
|
|
339
|
+
return simplifiedPath;
|
|
340
|
+
}
|
|
341
|
+
// Remove trailing slash like `foo/bar/` -> `foo/bar`
|
|
342
|
+
return simplifiedPath.replace(/\/$/, "");
|
|
220
343
|
}
|
|
221
344
|
// TODO: Make StackRouter not do this...
|
|
222
345
|
// Detect if the params came from StackRouter using `params` to pass around internal state.
|
|
223
346
|
function isInvalidParams(params) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"params" in params &&
|
|
347
|
+
if (!params) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
if ("params" in params &&
|
|
229
351
|
typeof params.params === "object" &&
|
|
230
|
-
!!params.params)
|
|
352
|
+
!!params.params) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return ("initial" in params &&
|
|
356
|
+
typeof params.initial === "boolean" &&
|
|
357
|
+
// "path" in params &&
|
|
358
|
+
"screen" in params);
|
|
231
359
|
}
|
|
232
360
|
const getParamName = (pattern) => pattern.replace(/^:/, "").replace(/\?$/, "");
|
|
233
361
|
const joinPaths = (...paths) => []
|
|
@@ -256,10 +384,11 @@ const createConfigItem = (config, parentPattern) => {
|
|
|
256
384
|
pattern: pattern?.split("/").filter(Boolean).join("/"),
|
|
257
385
|
stringify: config.stringify,
|
|
258
386
|
screens,
|
|
387
|
+
initialRouteName: config.initialRouteName,
|
|
259
388
|
};
|
|
260
389
|
};
|
|
261
|
-
const createNormalizedConfigs = (options, pattern) => Object.fromEntries(Object.entries(options).map(([name, c]) =>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
390
|
+
const createNormalizedConfigs = (options, pattern) => Object.fromEntries(Object.entries(options).map(([name, c]) => [
|
|
391
|
+
name,
|
|
392
|
+
createConfigItem(c, pattern),
|
|
393
|
+
]));
|
|
265
394
|
//# sourceMappingURL=getPathFromState.js.map
|