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 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
- // @ts-expect-error: Not sure
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.1",
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.1",
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
- // @ts-expect-error: non-standard options
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
- if (_options) {
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 (!screens) {
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?: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
8
+ e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
9
9
  ): boolean {
10
+ if (e?.defaultPrevented) {
11
+ return false;
12
+ }
13
+
10
14
  if (
11
- !!e &&
12
- !e.defaultPrevented && // onPress prevented default
13
- // @ts-expect-error: these properties exist on web, but not in React Native
14
- !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
15
- // @ts-expect-error: these properties exist on web, but not in React Native
16
- (e.button == null || e.button === 0) && // ignore everything but left clicks
17
- // @ts-expect-error: these properties exist on web, but not in React Native
18
- [undefined, null, "", "self"].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc.
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
- export function useLinkingContext(): Required<
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
  }
@@ -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
- <div id="root">
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
- </div>
39
- ),
41
+ ),
40
42
  });
41
43
 
42
44
  const html = ReactDOMServer.renderToString(