@multiplatform.one/router 8.6.0 → 9.0.0
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/README.md +6 -1
- package/dist/cjs/router/binding.cjs +35 -0
- package/dist/cjs/router/binding.native.js +38 -0
- package/dist/cjs/router/binding.native.js.map +1 -0
- package/dist/cjs/router/expoRouter.cjs +47 -0
- package/dist/cjs/router/expoRouter.native.js +50 -0
- package/dist/cjs/router/expoRouter.native.js.map +1 -0
- package/dist/cjs/router/expoRouterBinding.cjs +53 -0
- package/dist/cjs/router/expoRouterBinding.native.js +64 -0
- package/dist/cjs/router/expoRouterBinding.native.js.map +1 -0
- package/dist/cjs/router/index.native.js +2 -2
- package/dist/cjs/router/index.native.js.map +1 -1
- package/dist/cjs/router/{nativeFallback.cjs → nativeRouter.cjs} +28 -9
- package/dist/cjs/router/{nativeFallback.native.js → nativeRouter.native.js} +38 -10
- package/dist/cjs/router/nativeRouter.native.js.map +1 -0
- package/dist/esm/router/binding.mjs +9 -0
- package/dist/esm/router/binding.mjs.map +1 -0
- package/dist/esm/router/binding.native.js +9 -0
- package/dist/esm/router/binding.native.js.map +1 -0
- package/dist/esm/router/expoRouter.mjs +19 -0
- package/dist/esm/router/expoRouter.mjs.map +1 -0
- package/dist/esm/router/expoRouter.native.js +19 -0
- package/dist/esm/router/expoRouter.native.js.map +1 -0
- package/dist/esm/router/expoRouterBinding.mjs +28 -0
- package/dist/esm/router/expoRouterBinding.mjs.map +1 -0
- package/dist/esm/router/expoRouterBinding.native.js +36 -0
- package/dist/esm/router/expoRouterBinding.native.js.map +1 -0
- package/dist/esm/router/index.native.js +2 -2
- package/dist/esm/router/index.native.js.map +1 -1
- package/dist/esm/router/nativeRouter.mjs +37 -0
- package/dist/esm/router/nativeRouter.mjs.map +1 -0
- package/dist/esm/router/nativeRouter.native.js +51 -0
- package/dist/esm/router/nativeRouter.native.js.map +1 -0
- package/dist/jsx/router/binding.mjs +9 -0
- package/dist/jsx/router/binding.mjs.map +1 -0
- package/dist/jsx/router/binding.native.js +38 -0
- package/dist/jsx/router/binding.native.js.map +1 -0
- package/dist/jsx/router/expoRouter.mjs +19 -0
- package/dist/jsx/router/expoRouter.mjs.map +1 -0
- package/dist/jsx/router/expoRouter.native.js +50 -0
- package/dist/jsx/router/expoRouter.native.js.map +1 -0
- package/dist/jsx/router/expoRouterBinding.mjs +28 -0
- package/dist/jsx/router/expoRouterBinding.mjs.map +1 -0
- package/dist/jsx/router/expoRouterBinding.native.js +64 -0
- package/dist/jsx/router/expoRouterBinding.native.js.map +1 -0
- package/dist/jsx/router/index.native.js +2 -2
- package/dist/jsx/router/index.native.js.map +1 -1
- package/dist/jsx/router/nativeRouter.mjs +37 -0
- package/dist/jsx/router/nativeRouter.mjs.map +1 -0
- package/dist/jsx/router/{nativeFallback.native.js → nativeRouter.native.js} +38 -10
- package/dist/jsx/router/nativeRouter.native.js.map +1 -0
- package/package.json +21 -4
- package/src/router/binding.ts +25 -0
- package/src/router/expoRouter.spec.tsx +170 -0
- package/src/router/expoRouter.tsx +38 -0
- package/src/router/expoRouterBinding.ts +46 -0
- package/src/router/index.native.tsx +5 -4
- package/src/router/nativeRouter.tsx +45 -0
- package/types/router/binding.d.ts +10 -0
- package/types/router/binding.d.ts.map +1 -0
- package/types/router/expoRouter.d.ts +6 -0
- package/types/router/expoRouter.d.ts.map +1 -0
- package/types/router/expoRouterBinding.d.ts +16 -0
- package/types/router/expoRouterBinding.d.ts.map +1 -0
- package/types/router/index.native.d.ts.map +1 -1
- package/types/router/nativeRouter.d.ts +13 -0
- package/types/router/nativeRouter.d.ts.map +1 -0
- package/dist/cjs/router/nativeFallback.native.js.map +0 -1
- package/dist/esm/router/nativeFallback.mjs +0 -19
- package/dist/esm/router/nativeFallback.mjs.map +0 -1
- package/dist/esm/router/nativeFallback.native.js +0 -24
- package/dist/esm/router/nativeFallback.native.js.map +0 -1
- package/dist/jsx/router/nativeFallback.mjs +0 -19
- package/dist/jsx/router/nativeFallback.mjs.map +0 -1
- package/dist/jsx/router/nativeFallback.native.js.map +0 -1
- package/src/router/nativeFallback.spec.tsx +0 -46
- package/src/router/nativeFallback.tsx +0 -19
- package/types/router/nativeFallback.d.ts +0 -10
- package/types/router/nativeFallback.d.ts.map +0 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { render, renderHook, screen } from "@testing-library/react";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { LinkComponent } from "../types";
|
|
7
|
+
import { bindRouter, boundRouter, type RouterBinding } from "./binding";
|
|
8
|
+
import { createExpoRouterBinding, type ExpoRouterModule } from "./expoRouterBinding";
|
|
9
|
+
import { hostRouter, nativeFallback, pickNativeRouter } from "./nativeRouter";
|
|
10
|
+
|
|
11
|
+
const expo = { push: vi.fn(), replace: vi.fn(), back: vi.fn() };
|
|
12
|
+
|
|
13
|
+
const ExpoLink = ({ href, children }: { href: string; children?: ReactNode }) => (
|
|
14
|
+
<a data-testid="expo-link" href={href}>
|
|
15
|
+
{children}
|
|
16
|
+
</a>
|
|
17
|
+
);
|
|
18
|
+
ExpoLink.resolveHref = (href: string) => `expo:${href}`;
|
|
19
|
+
const expoRouter = { push: expo.push, replace: expo.replace, back: expo.back };
|
|
20
|
+
const expoModule: ExpoRouterModule = {
|
|
21
|
+
Link: ExpoLink,
|
|
22
|
+
useLocalSearchParams: () => ({ id: "7" }),
|
|
23
|
+
usePathname: () => "/sessions/7",
|
|
24
|
+
useRouter: () => expoRouter,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function unbind() {
|
|
28
|
+
delete (globalThis as Record<symbol, unknown>)[Symbol.for("@multiplatform.one/router/binding")];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const manifest = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf8")) as {
|
|
32
|
+
exports: Record<string, unknown>;
|
|
33
|
+
dependencies?: Record<string, string>;
|
|
34
|
+
peerDependencies?: Record<string, string>;
|
|
35
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// FZ-10637, FZ-10634, FZ-1854. An Expo Router app is a supported host: it never
|
|
39
|
+
// installs `one`, and MPO's links and router hooks navigate with Expo Router.
|
|
40
|
+
describe("router packaging for a host without One", () => {
|
|
41
|
+
it("one and expo-router are optional peers, so neither is installed into an app that does not list it", () => {
|
|
42
|
+
for (const name of ["one", "expo-router", "@react-navigation/native", "react-router-dom"]) {
|
|
43
|
+
expect(manifest.dependencies ?? {}).not.toHaveProperty(name);
|
|
44
|
+
expect(manifest.peerDependencies).toHaveProperty(name);
|
|
45
|
+
expect(manifest.peerDependenciesMeta?.[name]?.optional).toBe(true);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("ships the Expo Router binding as the ./expo-router subpath", () => {
|
|
50
|
+
expect(manifest.exports["./expo-router"]).toMatchObject({
|
|
51
|
+
source: "./src/router/expoRouter.tsx",
|
|
52
|
+
"react-native": { import: "./dist/esm/router/expoRouter.native.js" },
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("loads one from inside a try block, never a static import, so Metro bundles without it", () => {
|
|
57
|
+
const source = readFileSync(join(__dirname, "index.native.tsx"), "utf8");
|
|
58
|
+
expect(source).not.toMatch(/^import[^;]*from "one"/m);
|
|
59
|
+
expect(source).toMatch(/try \{\s*return require\("one"\)/);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("the native router without One", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
unbind();
|
|
66
|
+
expo.push.mockClear();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps One's own router, untouched, in a One app", () => {
|
|
70
|
+
const one: RouterBinding = {
|
|
71
|
+
Link: (() => null) as unknown as LinkComponent,
|
|
72
|
+
useParams: () => ({ id: "1" }),
|
|
73
|
+
usePathname: () => "/one",
|
|
74
|
+
useRouter: nativeFallback.useRouter,
|
|
75
|
+
};
|
|
76
|
+
const picked = pickNativeRouter(one);
|
|
77
|
+
expect(picked).toBe(one);
|
|
78
|
+
expect(picked.Link).toBe(one.Link);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("falls back to a no-op router when the host has bound none", () => {
|
|
82
|
+
const router = pickNativeRouter(undefined);
|
|
83
|
+
expect(router).toBe(hostRouter);
|
|
84
|
+
const { result } = renderHook(() => router.useRouter());
|
|
85
|
+
expect(result.current.canGoBack()).toBe(false);
|
|
86
|
+
expect(() => result.current.push("/a" as never)).not.toThrow();
|
|
87
|
+
expect(renderHook(() => router.usePathname()).result.current).toBe("/");
|
|
88
|
+
expect(renderHook(() => router.useParams()).result.current).toEqual({});
|
|
89
|
+
const { container } = render(
|
|
90
|
+
<router.Link href={"/a" as never}>
|
|
91
|
+
<span>go</span>
|
|
92
|
+
</router.Link>,
|
|
93
|
+
);
|
|
94
|
+
expect(screen.getByText("go")).toBeTruthy();
|
|
95
|
+
expect(container.querySelector("a")).toBeNull();
|
|
96
|
+
expect(router.Link.resolveHref("/a" as never)).toBe("/a");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("navigates with whatever router the host binds", () => {
|
|
100
|
+
const push = vi.fn();
|
|
101
|
+
bindRouter({
|
|
102
|
+
...nativeFallback,
|
|
103
|
+
usePathname: () => "/bound",
|
|
104
|
+
useRouter: () => ({ ...nativeFallback.useRouter(), push }),
|
|
105
|
+
});
|
|
106
|
+
const router = pickNativeRouter(undefined);
|
|
107
|
+
expect(renderHook(() => router.usePathname()).result.current).toBe("/bound");
|
|
108
|
+
renderHook(() => router.useRouter()).result.current.push("/x" as never);
|
|
109
|
+
expect(push).toHaveBeenCalledWith("/x");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("the Expo Router binding", () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
unbind();
|
|
116
|
+
expo.push.mockClear();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("the ./expo-router module binds Expo Router when the app imports it", () => {
|
|
120
|
+
const source = readFileSync(join(__dirname, "expoRouter.tsx"), "utf8");
|
|
121
|
+
expect(source).toMatch(/try \{\s*return require\("expo-router"\)/);
|
|
122
|
+
expect(source).not.toMatch(/^import[^;]*from "expo-router"/m);
|
|
123
|
+
expect(source).toMatch(
|
|
124
|
+
/const binding = createExpoRouterBinding\(loadExpoRouter\(\)\);[\s\S]*bindRouter\(binding\);/,
|
|
125
|
+
);
|
|
126
|
+
expect(source).not.toMatch(/"one"/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("a link pushes the Expo Router route it names", () => {
|
|
130
|
+
expect(boundRouter()).toBeUndefined();
|
|
131
|
+
bindRouter(createExpoRouterBinding(expoModule));
|
|
132
|
+
const router = pickNativeRouter(undefined);
|
|
133
|
+
renderHook(() => router.useRouter()).result.current.push("/sessions/7" as never);
|
|
134
|
+
expect(expo.push).toHaveBeenCalledWith("/sessions/7");
|
|
135
|
+
render(
|
|
136
|
+
<router.Link href={"/sessions/7" as never}>
|
|
137
|
+
<span>open</span>
|
|
138
|
+
</router.Link>,
|
|
139
|
+
);
|
|
140
|
+
expect(screen.getByTestId("expo-link").getAttribute("href")).toBe("/sessions/7");
|
|
141
|
+
expect(router.Link.resolveHref("/sessions/7" as never)).toBe("expo:/sessions/7");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("reads the path and the route params from Expo Router", () => {
|
|
145
|
+
bindRouter(createExpoRouterBinding(expoModule));
|
|
146
|
+
const router = pickNativeRouter(undefined);
|
|
147
|
+
expect(renderHook(() => router.usePathname()).result.current).toBe("/sessions/7");
|
|
148
|
+
expect(renderHook(() => router.useParams()).result.current).toEqual({ id: "7" });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("fills in subscribe and onLoadState, which Expo Router does not have", () => {
|
|
152
|
+
const { useRouter } = createExpoRouterBinding(expoModule);
|
|
153
|
+
const { result, rerender } = renderHook(() => useRouter());
|
|
154
|
+
const first = result.current;
|
|
155
|
+
const onState = vi.fn();
|
|
156
|
+
const onLoad = vi.fn();
|
|
157
|
+
expect(first.subscribe(onState)).toBeTypeOf("function");
|
|
158
|
+
expect(first.onLoadState(onLoad)).toBeTypeOf("function");
|
|
159
|
+
expect(onState).toHaveBeenCalledWith("success");
|
|
160
|
+
expect(onLoad).toHaveBeenCalledWith("loaded");
|
|
161
|
+
rerender();
|
|
162
|
+
expect(result.current).toBe(first);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("gives a Link without resolveHref one that returns the href", () => {
|
|
166
|
+
const Bare = (() => null) as unknown as ExpoRouterModule["Link"];
|
|
167
|
+
const { Link } = createExpoRouterBinding({ ...expoModule, Link: Bare });
|
|
168
|
+
expect(Link.resolveHref("/a" as never)).toBe("/a");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { LinkComponent, UseParams, UsePathname, UseRouter } from "../types";
|
|
2
|
+
import { bindRouter } from "./binding";
|
|
3
|
+
import { createExpoRouterBinding, type ExpoRouterModule } from "./expoRouterBinding";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The Expo Router binding (FZ-10634). An Expo Router app imports this module
|
|
7
|
+
* once, before its first render:
|
|
8
|
+
*
|
|
9
|
+
* import "@multiplatform.one/router/expo-router";
|
|
10
|
+
*
|
|
11
|
+
* From then on MPO's links and router hooks (forms' LinkCell, useUrlState)
|
|
12
|
+
* navigate with Expo Router. The app needs no Metro alias and never bundles
|
|
13
|
+
* `one`.
|
|
14
|
+
*
|
|
15
|
+
* expo-router is an optional peer of this package, so it is loaded the way
|
|
16
|
+
* every optional peer is, from inside a try block. Importing this module in
|
|
17
|
+
* an app without expo-router is a mistake, and it says so.
|
|
18
|
+
*/
|
|
19
|
+
function loadExpoRouter(): ExpoRouterModule {
|
|
20
|
+
try {
|
|
21
|
+
return require("expo-router") as ExpoRouterModule;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"[@multiplatform.one/router] @multiplatform.one/router/expo-router binds Expo Router, " +
|
|
25
|
+
"but expo-router is not installed. Add expo-router to your app's dependencies.",
|
|
26
|
+
{ cause: error },
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const binding = createExpoRouterBinding(loadExpoRouter());
|
|
32
|
+
|
|
33
|
+
export const Link: LinkComponent = binding.Link;
|
|
34
|
+
export const useParams: UseParams = binding.useParams;
|
|
35
|
+
export const usePathname: UsePathname = binding.usePathname;
|
|
36
|
+
export const useRouter: UseRouter = binding.useRouter;
|
|
37
|
+
|
|
38
|
+
bindRouter(binding);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import type { LinkComponent, Router } from "../types";
|
|
3
|
+
import type { RouterBinding } from "./binding";
|
|
4
|
+
|
|
5
|
+
/** The part of expo-router the binding uses. */
|
|
6
|
+
export interface ExpoRouterModule {
|
|
7
|
+
Link: unknown;
|
|
8
|
+
useLocalSearchParams: () => Record<string, string | string[] | undefined>;
|
|
9
|
+
usePathname: () => string;
|
|
10
|
+
useRouter: () => unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MPO's router contract on top of Expo Router: its Link, its path, its route
|
|
15
|
+
* params as `useParams`, and its router with `subscribe` and `onLoadState`
|
|
16
|
+
* filled in. Expo Router has neither, so both report a settled router at
|
|
17
|
+
* once, as MPO's other routers without a loading phase do.
|
|
18
|
+
*/
|
|
19
|
+
export function createExpoRouterBinding(expo: ExpoRouterModule): RouterBinding {
|
|
20
|
+
const Link = expo.Link as LinkComponent;
|
|
21
|
+
if (typeof Link.resolveHref !== "function") {
|
|
22
|
+
Link.resolveHref = (href) => href.toString();
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
Link,
|
|
26
|
+
useParams: () => expo.useLocalSearchParams(),
|
|
27
|
+
usePathname: () => expo.usePathname(),
|
|
28
|
+
useRouter: () => {
|
|
29
|
+
const router = expo.useRouter() as Omit<Router, "subscribe" | "onLoadState">;
|
|
30
|
+
return useMemo<Router>(
|
|
31
|
+
() => ({
|
|
32
|
+
...router,
|
|
33
|
+
subscribe: (listener) => {
|
|
34
|
+
listener("success");
|
|
35
|
+
return () => {};
|
|
36
|
+
},
|
|
37
|
+
onLoadState: (listener) => {
|
|
38
|
+
listener("loaded");
|
|
39
|
+
return () => {};
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
[router],
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { LinkComponent, UseParams, UseRouter } from "../types";
|
|
2
|
-
import
|
|
2
|
+
import type { RouterBinding } from "./binding";
|
|
3
|
+
import { pickNativeRouter } from "./nativeRouter";
|
|
3
4
|
|
|
4
|
-
function loadOne():
|
|
5
|
+
function loadOne(): Partial<RouterBinding> | undefined {
|
|
5
6
|
try {
|
|
6
|
-
return require("one") as
|
|
7
|
+
return require("one") as Partial<RouterBinding>;
|
|
7
8
|
} catch {
|
|
8
9
|
return undefined;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const router =
|
|
13
|
+
const router = pickNativeRouter(loadOne());
|
|
13
14
|
|
|
14
15
|
export const Link: LinkComponent = router.Link;
|
|
15
16
|
export const useParams: UseParams = router.useParams;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LinkComponent } from "../types";
|
|
2
|
+
import { boundRouter, type RouterBinding } from "./binding";
|
|
3
|
+
import { useParams, usePathname, useRouter } from "./EmptyRouter";
|
|
4
|
+
|
|
5
|
+
const FallbackLink: LinkComponent = ({ children }) => <>{children}</>;
|
|
6
|
+
|
|
7
|
+
FallbackLink.resolveHref = (href) => href.toString();
|
|
8
|
+
|
|
9
|
+
/** No router at all: links render their children and navigation is a no-op. */
|
|
10
|
+
export const nativeFallback: RouterBinding = {
|
|
11
|
+
Link: FallbackLink,
|
|
12
|
+
useParams,
|
|
13
|
+
usePathname,
|
|
14
|
+
useRouter,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function host(): RouterBinding {
|
|
18
|
+
return boundRouter() ?? nativeFallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const HostLink: LinkComponent = (props) => {
|
|
22
|
+
const Bound = host().Link;
|
|
23
|
+
return <Bound {...props} />;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
HostLink.resolveHref = (href) => host().Link.resolveHref(href);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The router of an app without One. Each call reads the binding, so a host
|
|
30
|
+
* that binds its router (`import "@multiplatform.one/router/expo-router"`)
|
|
31
|
+
* before the first render is the one that navigates. Bind once, at startup:
|
|
32
|
+
* a binding that changes between renders changes the hooks a component calls.
|
|
33
|
+
*/
|
|
34
|
+
export const hostRouter: RouterBinding = {
|
|
35
|
+
Link: HostLink,
|
|
36
|
+
useParams: () => host().useParams(),
|
|
37
|
+
usePathname: () => host().usePathname(),
|
|
38
|
+
useRouter: () => host().useRouter(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** One's own router in a One app, untouched; the host's everywhere else. */
|
|
42
|
+
export function pickNativeRouter(one: Partial<RouterBinding> | undefined): RouterBinding {
|
|
43
|
+
if (one?.Link && one.useParams && one.usePathname && one.useRouter) return one as RouterBinding;
|
|
44
|
+
return hostRouter;
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LinkComponent, UseParams, UsePathname, UseRouter } from "../types";
|
|
2
|
+
export interface RouterBinding {
|
|
3
|
+
Link: LinkComponent;
|
|
4
|
+
useParams: UseParams;
|
|
5
|
+
usePathname: UsePathname;
|
|
6
|
+
useRouter: UseRouter;
|
|
7
|
+
}
|
|
8
|
+
export declare function bindRouter(binding: RouterBinding): void;
|
|
9
|
+
export declare function boundRouter(): RouterBinding | undefined;
|
|
10
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/router/binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEjF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;CACtB;AAWD,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAEvD;AAED,wBAAgB,WAAW,IAAI,aAAa,GAAG,SAAS,CAEvD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LinkComponent, UseParams, UsePathname, UseRouter } from "../types";
|
|
2
|
+
export declare const Link: LinkComponent;
|
|
3
|
+
export declare const useParams: UseParams;
|
|
4
|
+
export declare const usePathname: UsePathname;
|
|
5
|
+
export declare const useRouter: UseRouter;
|
|
6
|
+
//# sourceMappingURL=expoRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expoRouter.d.ts","sourceRoot":"","sources":["../../src/router/expoRouter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAgCjF,eAAO,MAAM,IAAI,EAAE,aAA4B,CAAC;AAChD,eAAO,MAAM,SAAS,EAAE,SAA6B,CAAC;AACtD,eAAO,MAAM,WAAW,EAAE,WAAiC,CAAC;AAC5D,eAAO,MAAM,SAAS,EAAE,SAA6B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RouterBinding } from "./binding";
|
|
2
|
+
/** The part of expo-router the binding uses. */
|
|
3
|
+
export interface ExpoRouterModule {
|
|
4
|
+
Link: unknown;
|
|
5
|
+
useLocalSearchParams: () => Record<string, string | string[] | undefined>;
|
|
6
|
+
usePathname: () => string;
|
|
7
|
+
useRouter: () => unknown;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* MPO's router contract on top of Expo Router: its Link, its path, its route
|
|
11
|
+
* params as `useParams`, and its router with `subscribe` and `onLoadState`
|
|
12
|
+
* filled in. Expo Router has neither, so both report a settled router at
|
|
13
|
+
* once, as MPO's other routers without a loading phase do.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createExpoRouterBinding(expo: ExpoRouterModule): RouterBinding;
|
|
16
|
+
//# sourceMappingURL=expoRouterBinding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expoRouterBinding.d.ts","sourceRoot":"","sources":["../../src/router/expoRouterBinding.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,oBAAoB,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IAC1E,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CA2B7E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.native.d.ts","sourceRoot":"","sources":["../../src/router/index.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.native.d.ts","sourceRoot":"","sources":["../../src/router/index.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAcpE,eAAO,MAAM,IAAI,EAAE,aAA2B,CAAC;AAC/C,eAAO,MAAM,SAAS,EAAE,SAA4B,CAAC;AACrD,eAAO,MAAM,SAAS,EAAE,SAA4B,CAAC;AAIrD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAMpC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type RouterBinding } from "./binding";
|
|
2
|
+
/** No router at all: links render their children and navigation is a no-op. */
|
|
3
|
+
export declare const nativeFallback: RouterBinding;
|
|
4
|
+
/**
|
|
5
|
+
* The router of an app without One. Each call reads the binding, so a host
|
|
6
|
+
* that binds its router (`import "@multiplatform.one/router/expo-router"`)
|
|
7
|
+
* before the first render is the one that navigates. Bind once, at startup:
|
|
8
|
+
* a binding that changes between renders changes the hooks a component calls.
|
|
9
|
+
*/
|
|
10
|
+
export declare const hostRouter: RouterBinding;
|
|
11
|
+
/** One's own router in a One app, untouched; the host's everywhere else. */
|
|
12
|
+
export declare function pickNativeRouter(one: Partial<RouterBinding> | undefined): RouterBinding;
|
|
13
|
+
//# sourceMappingURL=nativeRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeRouter.d.ts","sourceRoot":"","sources":["../../src/router/nativeRouter.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAO5D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,EAAE,aAK5B,CAAC;AAaF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,aAKxB,CAAC;AAEF,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,GAAG,aAAa,CAGvF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","nativeFallback_exports","__export","nativeFallback","pickRouter","module","exports","import_jsx_runtime","require","import_EmptyRouter","Link","Link2","param","children","jsx","Fragment"],"sources":["../../../src/router/nativeFallback.tsx"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,GAAA,IAAAC,WAAA,CAAAC,SAAA;EAAAC,KAAA;AAAA,IAAAH,GAAA;AAAA,IAAAI,sBAAA;AAAAC,QAAA,CAAAD,sBAAA;EAAAE,cAAA,EAAAA,CAAA,KAAAA,cAAA;EAAAC,UAAA,EAAAA,CAAA,KAAAA;AAAA;AACAC,MAAA,CAAAC,OAAA,GAAAV,YAAkD,CAAAK,sBAAA;AASJ,IAAAM,kBAAA,GAAAC,OAAA;AAA9C,IAAAC,kBAA+B,GAASD,OAAM;AAE9C,IAAAE,IAAK,YAAeC,MAAAC,KAAS;EAEtB,IAAM;IAAAC;EAAA,IAAAD,KAAiC;EAEvC,OAAS,eAAwD,IAAAL,kBAAA,CAAAO,GAAA,EAAAP,kBAAA,CAAAQ,QAAA;IACtEF;EACF","ignoreList":[]}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useParams, usePathname, useRouter } from "./EmptyRouter.mjs";
|
|
2
|
-
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
-
const Link = ({
|
|
4
|
-
children
|
|
5
|
-
}) => /* @__PURE__ */jsx(Fragment, {
|
|
6
|
-
children
|
|
7
|
-
});
|
|
8
|
-
Link.resolveHref = href => href.toString();
|
|
9
|
-
const nativeFallback = {
|
|
10
|
-
Link,
|
|
11
|
-
useParams,
|
|
12
|
-
usePathname,
|
|
13
|
-
useRouter
|
|
14
|
-
};
|
|
15
|
-
function pickRouter(one) {
|
|
16
|
-
return one ?? nativeFallback;
|
|
17
|
-
}
|
|
18
|
-
export { nativeFallback, pickRouter };
|
|
19
|
-
//# sourceMappingURL=nativeFallback.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["useParams","usePathname","useRouter","Fragment","jsx","Link","children","resolveHref","href","toString","nativeFallback","pickRouter","one"],"sources":["../../../src/router/nativeFallback.tsx"],"sourcesContent":[null],"mappings":"AACA,SAASA,SAAA,EAAWC,WAAA,EAAaC,SAAA,QAAiB;AASJ,SAAAC,QAAA,EAAAC,GAAA;AAA9C,MAAMC,IAAA,GAAsBA,CAAC;EAAEC;AAAS,MAAM,eAAAF,GAAA,CAAAD,QAAA;EAAGG;AAAA,CAAS;AAE1DD,IAAA,CAAKE,WAAA,GAAeC,IAAA,IAASA,IAAA,CAAKC,QAAA,CAAS;AAEpC,MAAMC,cAAA,GAA+B;EAAEL,IAAA;EAAML,SAAA;EAAWC,WAAA;EAAaC;AAAU;AAE/E,SAASS,WAAWC,GAAA,EAA6C;EACtE,OAAOA,GAAA,IAAOF,cAAA;AAChB","ignoreList":[]}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useParams, usePathname, useRouter } from "./EmptyRouter.native.js";
|
|
3
|
-
var Link = function Link2(param) {
|
|
4
|
-
var {
|
|
5
|
-
children
|
|
6
|
-
} = param;
|
|
7
|
-
return /* @__PURE__ */_jsx(_Fragment, {
|
|
8
|
-
children
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Link.resolveHref = function (href) {
|
|
12
|
-
return href.toString();
|
|
13
|
-
};
|
|
14
|
-
var nativeFallback = {
|
|
15
|
-
Link,
|
|
16
|
-
useParams,
|
|
17
|
-
usePathname,
|
|
18
|
-
useRouter
|
|
19
|
-
};
|
|
20
|
-
function pickRouter(one) {
|
|
21
|
-
return one !== null && one !== void 0 ? one : nativeFallback;
|
|
22
|
-
}
|
|
23
|
-
export { nativeFallback, pickRouter };
|
|
24
|
-
//# sourceMappingURL=nativeFallback.native.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["jsx","_jsx","Fragment","_Fragment","useParams","usePathname","useRouter","Link","Link2","param","children"],"sources":["../../../src/router/nativeFallback.tsx"],"sourcesContent":[null],"mappings":"AACA,SAASA,GAAA,IAAAC,IAAW,EAAAC,QAAA,IAAaC,SAAA,QAAiB;AASJ,SAAAC,SAAA,EAAAC,WAAA,EAAAC,SAAA;AAA9C,IAAAC,IAAM,YAAyBC,KAASA,CAAAC,KAAM;EAE9C,IAAK;IAAAC;EAAA,IAAeD,KAAA;EAEb,OAAM,eAAiCR,IAAA,CAAAE,SAAM;IAE7CO;EACL;AACF","ignoreList":[]}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useParams, usePathname, useRouter } from "./EmptyRouter.mjs";
|
|
2
|
-
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
-
const Link = ({
|
|
4
|
-
children
|
|
5
|
-
}) => /* @__PURE__ */jsx(Fragment, {
|
|
6
|
-
children
|
|
7
|
-
});
|
|
8
|
-
Link.resolveHref = href => href.toString();
|
|
9
|
-
const nativeFallback = {
|
|
10
|
-
Link,
|
|
11
|
-
useParams,
|
|
12
|
-
usePathname,
|
|
13
|
-
useRouter
|
|
14
|
-
};
|
|
15
|
-
function pickRouter(one) {
|
|
16
|
-
return one ?? nativeFallback;
|
|
17
|
-
}
|
|
18
|
-
export { nativeFallback, pickRouter };
|
|
19
|
-
//# sourceMappingURL=nativeFallback.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["useParams","usePathname","useRouter","Fragment","jsx","Link","children","resolveHref","href","toString","nativeFallback","pickRouter","one"],"sources":["../../../src/router/nativeFallback.tsx"],"sourcesContent":[null],"mappings":"AACA,SAASA,SAAA,EAAWC,WAAA,EAAaC,SAAA,QAAiB;AASJ,SAAAC,QAAA,EAAAC,GAAA;AAA9C,MAAMC,IAAA,GAAsBA,CAAC;EAAEC;AAAS,MAAM,eAAAF,GAAA,CAAAD,QAAA;EAAGG;AAAA,CAAS;AAE1DD,IAAA,CAAKE,WAAA,GAAeC,IAAA,IAASA,IAAA,CAAKC,QAAA,CAAS;AAEpC,MAAMC,cAAA,GAA+B;EAAEL,IAAA;EAAML,SAAA;EAAWC,WAAA;EAAaC;AAAU;AAE/E,SAASS,WAAWC,GAAA,EAA6C;EACtE,OAAOA,GAAA,IAAOF,cAAA;AAChB","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable"],"sources":["../../../src/router/nativeFallback.tsx"],"sourcesContent":[null],"mappings":"AACA,YAAS;;AASqC,IAAAA,SAAA,GAAAC,MAAA,CAAAC,cAAA;AAA9C,IAAAC,gBAA+B,GAAAF,MAAS,CAAAG,wBAAM;AAE9C,IAAAC,iBAAoB,GAAAJ,MAAS,CAAAK,mBAAc;AAEpC,IAAAC,YAAM,GAAAN,MAAiC,CAAAO,SAAM,CAAAC,cAAW;AAExD,IAAAC,QAAS,GAAAA,CAAAC,MAAW,EAAAC,GAA6C;EACtE,SAAOC,IAAA,IAAOD,GAAA,EAChBZ,SAAA,CAAAW,MAAA,EAAAE,IAAA;IAAAC,GAAA,EAAAF,GAAA,CAAAC,IAAA;IAAAE,UAAA;EAAA","ignoreList":[]}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { render, renderHook, screen } from "@testing-library/react";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import type { LinkComponent } from "../types";
|
|
6
|
-
import { type NativeRouter, nativeFallback, pickRouter } from "./nativeFallback";
|
|
7
|
-
|
|
8
|
-
describe("native router without One", () => {
|
|
9
|
-
it("falls back to the no-op router when one is not installed", () => {
|
|
10
|
-
const router = pickRouter(undefined);
|
|
11
|
-
expect(router).toBe(nativeFallback);
|
|
12
|
-
const { result } = renderHook(() => router.useRouter());
|
|
13
|
-
expect(result.current.canGoBack()).toBe(false);
|
|
14
|
-
expect(() => result.current.push("/a" as never)).not.toThrow();
|
|
15
|
-
expect(renderHook(() => router.usePathname()).result.current).toBe("/");
|
|
16
|
-
expect(renderHook(() => router.useParams()).result.current).toEqual({});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("renders a Link's children with no anchor", () => {
|
|
20
|
-
const { container } = render(
|
|
21
|
-
<nativeFallback.Link href={"/a" as never}>
|
|
22
|
-
<span>go</span>
|
|
23
|
-
</nativeFallback.Link>,
|
|
24
|
-
);
|
|
25
|
-
expect(screen.getByText("go")).toBeTruthy();
|
|
26
|
-
expect(container.querySelector("a")).toBeNull();
|
|
27
|
-
expect(nativeFallback.Link.resolveHref("/a" as never)).toBe("/a");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("uses One's router when one is installed", () => {
|
|
31
|
-
const Link = (() => null) as unknown as LinkComponent;
|
|
32
|
-
const one: NativeRouter = {
|
|
33
|
-
Link,
|
|
34
|
-
useParams: () => ({ id: "1" }),
|
|
35
|
-
usePathname: () => "/one",
|
|
36
|
-
useRouter: nativeFallback.useRouter,
|
|
37
|
-
};
|
|
38
|
-
expect(pickRouter(one)).toBe(one);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("loads one from inside a try block so Metro treats it as optional", () => {
|
|
42
|
-
const source = readFileSync(join(__dirname, "index.native.tsx"), "utf8");
|
|
43
|
-
expect(source).not.toMatch(/^import[^;]*from "one"/m);
|
|
44
|
-
expect(source).toMatch(/try \{\s*return require\("one"\)/);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { LinkComponent, UseParams, UsePathname, UseRouter } from "../types";
|
|
2
|
-
import { useParams, usePathname, useRouter } from "./EmptyRouter";
|
|
3
|
-
|
|
4
|
-
export interface NativeRouter {
|
|
5
|
-
Link: LinkComponent;
|
|
6
|
-
useParams: UseParams;
|
|
7
|
-
usePathname: UsePathname;
|
|
8
|
-
useRouter: UseRouter;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const Link: LinkComponent = ({ children }) => <>{children}</>;
|
|
12
|
-
|
|
13
|
-
Link.resolveHref = (href) => href.toString();
|
|
14
|
-
|
|
15
|
-
export const nativeFallback: NativeRouter = { Link, useParams, usePathname, useRouter };
|
|
16
|
-
|
|
17
|
-
export function pickRouter(one: NativeRouter | undefined): NativeRouter {
|
|
18
|
-
return one ?? nativeFallback;
|
|
19
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { LinkComponent, UseParams, UsePathname, UseRouter } from "../types";
|
|
2
|
-
export interface NativeRouter {
|
|
3
|
-
Link: LinkComponent;
|
|
4
|
-
useParams: UseParams;
|
|
5
|
-
usePathname: UsePathname;
|
|
6
|
-
useRouter: UseRouter;
|
|
7
|
-
}
|
|
8
|
-
export declare const nativeFallback: NativeRouter;
|
|
9
|
-
export declare function pickRouter(one: NativeRouter | undefined): NativeRouter;
|
|
10
|
-
//# sourceMappingURL=nativeFallback.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nativeFallback.d.ts","sourceRoot":"","sources":["../../src/router/nativeFallback.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGjF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;CACtB;AAMD,eAAO,MAAM,cAAc,EAAE,YAA0D,CAAC;AAExF,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,YAAY,CAEtE"}
|