expo-router 1.2.1 → 1.2.2
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/_root.tsx +4 -3
- package/metro-require.d.ts +52 -0
- package/package.json +5 -3
- package/src/NavigationContainer.tsx +1 -1
- package/src/fork/getPathFromState.ts +8 -11
- package/src/link/stateOperations.ts +3 -2
- package/src/link/useLinkToPath.ts +1 -2
- package/src/link/useLinkToPathProps.tsx +15 -9
- package/src/link/useLinkingContext.ts +16 -5
- package/src/link/useRouter.ts +2 -5
- package/src/static/renderStaticContent.tsx +8 -6
package/_root.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
/// <reference path="metro-require.d.ts" />
|
|
2
|
+
|
|
1
3
|
import "@expo/metro-runtime";
|
|
2
|
-
import React from "react";
|
|
3
4
|
|
|
4
5
|
import { ExpoRoot } from "expo-router";
|
|
6
|
+
import React from "react";
|
|
5
7
|
|
|
6
8
|
import { getNavigationConfig } from "./src/getLinkingConfig";
|
|
7
9
|
import { getRoutes } from "./src/getRoutes";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
const ctx = require.context(process.env.EXPO_ROUTER_APP_ROOT);
|
|
11
|
+
const ctx = require.context(process.env.EXPO_ROUTER_APP_ROOT!);
|
|
11
12
|
|
|
12
13
|
// Must be exported or Fast Refresh won't update the context >:[
|
|
13
14
|
export default function ExpoRouterRoot() {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Based on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/webpack-env/index.d.ts
|
|
2
|
+
// Adds support for the runtime `require.context` method.
|
|
3
|
+
// https://github.com/facebook/metro/pull/822/
|
|
4
|
+
|
|
5
|
+
declare var module: NodeModule;
|
|
6
|
+
|
|
7
|
+
declare namespace __MetroModuleApi {
|
|
8
|
+
interface RequireContext {
|
|
9
|
+
/** Return the keys that can be resolved. */
|
|
10
|
+
keys(): string[];
|
|
11
|
+
(id: string): any;
|
|
12
|
+
<T>(id: string): T;
|
|
13
|
+
/** **Unimplemented:** Return the module identifier for a user request. */
|
|
14
|
+
resolve(id: string): string;
|
|
15
|
+
/** **Unimplemented:** Readable identifier for the context module. */
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface RequireFunction {
|
|
20
|
+
/**
|
|
21
|
+
* Returns the exports from a dependency. The call is sync. No request to the server is fired. The compiler ensures that the dependency is available.
|
|
22
|
+
*/
|
|
23
|
+
(path: string): any;
|
|
24
|
+
<T>(path: string): T;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* **Experimental:** Import all modules in a given directory. This module dynamically updates when the files in a directory are added or removed.
|
|
28
|
+
*
|
|
29
|
+
* **Enabling:** This feature can be enabled by setting the `transformer.unstable_allowRequireContext` property to `true` in your Metro configuration.
|
|
30
|
+
*
|
|
31
|
+
* @param path File path pointing to the directory to require.
|
|
32
|
+
* @param recursive Should search for files recursively. Optional, default `true` when `require.context` is used.
|
|
33
|
+
* @param filter Filename filter pattern for use in `require.context`. Optional, default `.*` (any file) when `require.context` is used.
|
|
34
|
+
* @param mode Mode for resolving dynamic dependencies. Defaults to `sync`.
|
|
35
|
+
*/
|
|
36
|
+
context(
|
|
37
|
+
path: string,
|
|
38
|
+
recursive?: boolean,
|
|
39
|
+
filter?: RegExp,
|
|
40
|
+
mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once"
|
|
41
|
+
): RequireContext;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Declare process variable
|
|
47
|
+
*/
|
|
48
|
+
declare namespace NodeJS {
|
|
49
|
+
interface Require extends __MetroModuleApi.RequireFunction {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare var require: NodeRequire;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-router",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"main": "src/index.tsx",
|
|
5
5
|
"types": "src/index.tsx",
|
|
6
6
|
"files": [
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"drawer.ts",
|
|
14
14
|
"stack.ts",
|
|
15
15
|
"tabs.ts",
|
|
16
|
-
"head.ts"
|
|
16
|
+
"head.ts",
|
|
17
|
+
"metro-require.d.ts"
|
|
17
18
|
],
|
|
18
19
|
"repository": {
|
|
19
20
|
"url": "https://github.com/expo/router.git",
|
|
@@ -72,12 +73,13 @@
|
|
|
72
73
|
},
|
|
73
74
|
"dependencies": {
|
|
74
75
|
"@bacons/react-views": "^1.1.3",
|
|
75
|
-
"@expo/metro-runtime": "1.0
|
|
76
|
+
"@expo/metro-runtime": "1.1.0",
|
|
76
77
|
"@radix-ui/react-slot": "^1.0.0",
|
|
77
78
|
"@react-navigation/bottom-tabs": "~6.5.7",
|
|
78
79
|
"@react-navigation/native": "~6.1.6",
|
|
79
80
|
"@react-navigation/native-stack": "~6.9.12",
|
|
80
81
|
"expo-splash-screen": "*",
|
|
82
|
+
"query-string": "7.1.3",
|
|
81
83
|
"react-helmet-async": "^1.3.0",
|
|
82
84
|
"url": "^0.11.0"
|
|
83
85
|
}
|
|
@@ -7,7 +7,7 @@ import { RootNavigationRef } from "./useRootNavigation";
|
|
|
7
7
|
import { useRootRouteNodeContext } from "./useRootRouteNodeContext";
|
|
8
8
|
import { SplashScreen } from "./views/Splash";
|
|
9
9
|
|
|
10
|
-
const navigationRef = createNavigationContainerRef();
|
|
10
|
+
const navigationRef = createNavigationContainerRef<Record<string, unknown>>();
|
|
11
11
|
|
|
12
12
|
/** Get the root navigation container ref. */
|
|
13
13
|
export function getNavigationContainerRef() {
|
|
@@ -39,6 +39,8 @@ type CustomRoute = Route<string> & {
|
|
|
39
39
|
state?: State;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const DEFAULT_SCREENS: PathConfigMap<object> = {};
|
|
43
|
+
|
|
42
44
|
const getActiveRoute = (state: State): { name: string; params?: object } => {
|
|
43
45
|
const route =
|
|
44
46
|
typeof state.index === "number"
|
|
@@ -118,22 +120,20 @@ function encodeURIComponentPreservingBrackets(str: string) {
|
|
|
118
120
|
*/
|
|
119
121
|
export default function getPathFromState<ParamList extends object>(
|
|
120
122
|
state: State,
|
|
121
|
-
// @ts-expect-error: non-standard options
|
|
122
123
|
_options?: Options<ParamList> & {
|
|
123
124
|
preserveGroups?: boolean;
|
|
124
125
|
preserveDynamicRoutes?: boolean;
|
|
125
|
-
}
|
|
126
|
+
}
|
|
126
127
|
): string {
|
|
127
128
|
return getPathDataFromState(state, _options).path;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
export function getPathDataFromState<ParamList extends object>(
|
|
131
132
|
state: State,
|
|
132
|
-
|
|
133
|
-
_options?: Options<ParamList> & {
|
|
133
|
+
_options: Options<ParamList> & {
|
|
134
134
|
preserveGroups?: boolean;
|
|
135
135
|
preserveDynamicRoutes?: boolean;
|
|
136
|
-
} = {}
|
|
136
|
+
} = { screens: DEFAULT_SCREENS }
|
|
137
137
|
) {
|
|
138
138
|
if (state == null) {
|
|
139
139
|
throw Error(
|
|
@@ -143,13 +143,10 @@ export function getPathDataFromState<ParamList extends object>(
|
|
|
143
143
|
|
|
144
144
|
const { preserveGroups, preserveDynamicRoutes, ...options } = _options;
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
validatePathConfig(options);
|
|
148
|
-
}
|
|
146
|
+
validatePathConfig(options);
|
|
149
147
|
|
|
150
|
-
const screens = options?.screens;
|
|
151
148
|
// Expo Router disallows usage without a linking config.
|
|
152
|
-
if (
|
|
149
|
+
if (Object.is(options.screens, DEFAULT_SCREENS)) {
|
|
153
150
|
throw Error(
|
|
154
151
|
"You must pass a 'screens' object to 'getPathFromState' to generate a path."
|
|
155
152
|
);
|
|
@@ -158,7 +155,7 @@ export function getPathDataFromState<ParamList extends object>(
|
|
|
158
155
|
return getPathFromResolvedState(
|
|
159
156
|
state,
|
|
160
157
|
// Create a normalized configs object which will be easier to use
|
|
161
|
-
createNormalizedConfigs(screens),
|
|
158
|
+
createNormalizedConfigs(options.screens),
|
|
162
159
|
{ preserveGroups, preserveDynamicRoutes }
|
|
163
160
|
);
|
|
164
161
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InitialState } from "@react-navigation/native";
|
|
1
|
+
import { InitialState, NavigationState } from "@react-navigation/native";
|
|
2
2
|
|
|
3
3
|
import { ResultState } from "../fork/getStateFromPath";
|
|
4
4
|
|
|
@@ -95,7 +95,8 @@ export function getQualifiedStateForTopOfTargetState(
|
|
|
95
95
|
return currentRoot;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
type SubState = {
|
|
98
|
+
type SubState = NavigationState & {
|
|
99
|
+
key?: string;
|
|
99
100
|
type: string;
|
|
100
101
|
routes?: { name: string; state?: SubState }[];
|
|
101
102
|
index?: number;
|
|
@@ -54,7 +54,6 @@ export function useLinkToPath() {
|
|
|
54
54
|
if (href.startsWith(".")) {
|
|
55
55
|
let base = linking.getPathFromState?.(navigation.getRootState(), {
|
|
56
56
|
...linking.config,
|
|
57
|
-
// @ts-expect-error: non-standard option
|
|
58
57
|
preserveGroups: true,
|
|
59
58
|
});
|
|
60
59
|
|
|
@@ -123,8 +122,8 @@ export function useLinkToPath() {
|
|
|
123
122
|
isAbsoluteInitialRoute(action)
|
|
124
123
|
) {
|
|
125
124
|
const earliest = getEarliestMismatchedRoute(
|
|
126
|
-
// @ts-expect-error
|
|
127
125
|
rootState,
|
|
126
|
+
// @ts-expect-error
|
|
128
127
|
action.payload
|
|
129
128
|
);
|
|
130
129
|
if (earliest) {
|
|
@@ -5,20 +5,26 @@ import { useLinkToPath } from "./useLinkToPath";
|
|
|
5
5
|
import { stripGroupSegmentsFromPath } from "../matchers";
|
|
6
6
|
|
|
7
7
|
function eventShouldPreventDefault(
|
|
8
|
-
e
|
|
8
|
+
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
|
9
9
|
): boolean {
|
|
10
|
+
if (e?.defaultPrevented) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
!
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// Only check MouseEvents
|
|
16
|
+
"button" in e &&
|
|
17
|
+
// ignore clicks with modifier keys
|
|
18
|
+
!e.metaKey &&
|
|
19
|
+
!e.altKey &&
|
|
20
|
+
!e.ctrlKey &&
|
|
21
|
+
!e.shiftKey &&
|
|
22
|
+
(e.button == null || e.button === 0) && // Only accept left clicks
|
|
23
|
+
[undefined, null, "", "self"].includes(e.currentTarget.target) // let browser handle "target=_blank" etc.
|
|
19
24
|
) {
|
|
20
25
|
return true;
|
|
21
26
|
}
|
|
27
|
+
|
|
22
28
|
return false;
|
|
23
29
|
}
|
|
24
30
|
|
|
@@ -5,20 +5,31 @@ import {
|
|
|
5
5
|
} from "@react-navigation/native";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import getPathFromState from "../fork/getPathFromState";
|
|
9
|
+
|
|
10
|
+
export type RouterLinkingContext = Required<
|
|
9
11
|
Omit<LinkingOptions<ParamListBase>, "filter" | "enabled">
|
|
10
|
-
> {
|
|
12
|
+
> & {
|
|
13
|
+
getPathFromState: typeof getPathFromState;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function useLinkingContext(): RouterLinkingContext {
|
|
11
17
|
const linking = React.useContext(LinkingContext);
|
|
12
18
|
|
|
13
19
|
const { options } = linking;
|
|
14
20
|
|
|
21
|
+
assertLinkingOptions(options);
|
|
22
|
+
|
|
23
|
+
return options;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function assertLinkingOptions(
|
|
27
|
+
options: LinkingOptions<ParamListBase> | undefined
|
|
28
|
+
): asserts options is RouterLinkingContext {
|
|
15
29
|
if (!options?.config) {
|
|
16
30
|
// This should never happen in Expo Router.
|
|
17
31
|
throw new Error(
|
|
18
32
|
"Couldn't find a linking config. Is your component inside a navigator?"
|
|
19
33
|
);
|
|
20
34
|
}
|
|
21
|
-
|
|
22
|
-
// @ts-expect-error: non-standard option
|
|
23
|
-
return options;
|
|
24
35
|
}
|
package/src/link/useRouter.ts
CHANGED
|
@@ -47,11 +47,8 @@ export function useRouter(): Router {
|
|
|
47
47
|
push,
|
|
48
48
|
back,
|
|
49
49
|
replace,
|
|
50
|
-
setParams: (params) => {
|
|
51
|
-
root?.current?.setParams(
|
|
52
|
-
// @ts-expect-error
|
|
53
|
-
params
|
|
54
|
-
);
|
|
50
|
+
setParams: (params = {}) => {
|
|
51
|
+
root?.current?.setParams(params);
|
|
55
52
|
},
|
|
56
53
|
// TODO(EvanBacon): add `reload`
|
|
57
54
|
// TODO(EvanBacon): add `canGoBack` but maybe more like a `hasContext`
|
|
@@ -8,7 +8,6 @@ import { ServerContainer, ServerContainerRef } from "@react-navigation/native";
|
|
|
8
8
|
import App, { getManifest } from "expo-router/_root";
|
|
9
9
|
import React from "react";
|
|
10
10
|
import ReactDOMServer from "react-dom/server";
|
|
11
|
-
// @ts-expect-error
|
|
12
11
|
import { AppRegistry } from "react-native-web";
|
|
13
12
|
|
|
14
13
|
import Head from "../head/Head";
|
|
@@ -31,12 +30,15 @@ export function getStaticContent(location: URL): string {
|
|
|
31
30
|
// TODO: Use RNW view after they fix hydration for React 18
|
|
32
31
|
// https://github.com/necolas/react-native-web/blob/e8098fd029102d7801c32c1ede792bce01808c00/packages/react-native-web/src/exports/render/index.js#L10
|
|
33
32
|
// Otherwise this wraps the app with two extra divs
|
|
34
|
-
children:
|
|
35
|
-
// Inject the root tag
|
|
36
|
-
|
|
33
|
+
children:
|
|
34
|
+
// Inject the root tag using createElement to prevent any transforms like the ones in `@expo/html-elements`.
|
|
35
|
+
React.createElement(
|
|
36
|
+
"div",
|
|
37
|
+
{
|
|
38
|
+
id: "root",
|
|
39
|
+
},
|
|
37
40
|
<App />
|
|
38
|
-
|
|
39
|
-
),
|
|
41
|
+
),
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
const html = ReactDOMServer.renderToString(
|