@alepha/react 0.6.10 → 0.7.1
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 +1 -1
- package/dist/index.browser.cjs +21 -20
- package/dist/index.browser.js +2 -3
- package/dist/index.cjs +168 -82
- package/dist/index.d.ts +415 -232
- package/dist/index.js +146 -62
- package/dist/{useActive-4QlZKGbw.cjs → useRouterState-AdK-XeM2.cjs} +358 -170
- package/dist/{useActive-ClUsghB5.js → useRouterState-qoMq7Y9J.js} +358 -172
- package/package.json +11 -10
- package/src/components/ClientOnly.tsx +35 -0
- package/src/components/ErrorBoundary.tsx +72 -0
- package/src/components/ErrorViewer.tsx +161 -0
- package/src/components/Link.tsx +10 -4
- package/src/components/NestedView.tsx +28 -4
- package/src/descriptors/$page.ts +143 -38
- package/src/errors/RedirectionError.ts +4 -1
- package/src/hooks/RouterHookApi.ts +58 -35
- package/src/hooks/useAlepha.ts +12 -0
- package/src/hooks/useClient.ts +8 -6
- package/src/hooks/useInject.ts +3 -9
- package/src/hooks/useQueryParams.ts +4 -7
- package/src/hooks/useRouter.ts +6 -0
- package/src/index.browser.ts +1 -1
- package/src/index.shared.ts +11 -4
- package/src/index.ts +7 -4
- package/src/providers/BrowserRouterProvider.ts +27 -33
- package/src/providers/PageDescriptorProvider.ts +90 -40
- package/src/providers/ReactBrowserProvider.ts +21 -27
- package/src/providers/ReactServerProvider.ts +215 -77
- package/dist/index.browser.cjs.map +0 -1
- package/dist/index.browser.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/useActive-4QlZKGbw.cjs.map +0 -1
- package/dist/useActive-ClUsghB5.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { PageDescriptor } from "../descriptors/$page.ts";
|
|
1
2
|
import type {
|
|
2
3
|
AnchorProps,
|
|
4
|
+
PageRoute,
|
|
3
5
|
RouterState,
|
|
4
6
|
} from "../providers/PageDescriptorProvider.ts";
|
|
5
7
|
import type {
|
|
@@ -9,6 +11,7 @@ import type {
|
|
|
9
11
|
|
|
10
12
|
export class RouterHookApi {
|
|
11
13
|
constructor(
|
|
14
|
+
private readonly pages: PageRoute[],
|
|
12
15
|
private readonly state: RouterState,
|
|
13
16
|
private readonly layer: {
|
|
14
17
|
path: string;
|
|
@@ -73,41 +76,69 @@ export class RouterHookApi {
|
|
|
73
76
|
* @param pathname
|
|
74
77
|
* @param layer
|
|
75
78
|
*/
|
|
76
|
-
public createHref(
|
|
79
|
+
public createHref(
|
|
80
|
+
pathname: HrefLike,
|
|
81
|
+
layer: { path: string } = this.layer,
|
|
82
|
+
options: { params?: Record<string, any> } = {},
|
|
83
|
+
) {
|
|
77
84
|
if (typeof pathname === "object") {
|
|
78
85
|
pathname = pathname.options.path ?? "";
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
if (options.params) {
|
|
89
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
90
|
+
pathname = pathname.replace(`:${key}`, String(value));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
return pathname.startsWith("/")
|
|
82
95
|
? pathname
|
|
83
96
|
: `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
84
97
|
}
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
public async go(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
public async go(path: string, options?: RouterGoOptions): Promise<void>;
|
|
100
|
+
public async go<T extends object>(
|
|
101
|
+
path: keyof VirtualRouter<T>,
|
|
102
|
+
options?: RouterGoOptions,
|
|
103
|
+
): Promise<void>;
|
|
104
|
+
public async go(path: string, options?: RouterGoOptions): Promise<void> {
|
|
105
|
+
for (const page of this.pages) {
|
|
106
|
+
if (page.name === path) {
|
|
107
|
+
path = page.path ?? "";
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await this.browser?.go(this.createHref(path, this.layer, options), options);
|
|
96
113
|
}
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
public
|
|
103
|
-
|
|
115
|
+
public anchor(
|
|
116
|
+
path: string,
|
|
117
|
+
options?: { params?: Record<string, any> },
|
|
118
|
+
): AnchorProps;
|
|
119
|
+
public anchor<T extends object>(
|
|
120
|
+
path: keyof VirtualRouter<T>,
|
|
121
|
+
options?: { params?: Record<string, any> },
|
|
122
|
+
): AnchorProps;
|
|
123
|
+
public anchor(
|
|
124
|
+
path: string,
|
|
125
|
+
options: { params?: Record<string, any> } = {},
|
|
126
|
+
): AnchorProps {
|
|
127
|
+
for (const page of this.pages) {
|
|
128
|
+
if (page.name === path) {
|
|
129
|
+
path = page.path ?? "";
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const href = this.createHref(path, this.layer, options);
|
|
104
135
|
return {
|
|
105
136
|
href,
|
|
106
137
|
onClick: (ev: any) => {
|
|
107
138
|
ev.stopPropagation();
|
|
108
139
|
ev.preventDefault();
|
|
109
140
|
|
|
110
|
-
this.go(path).catch(console.error);
|
|
141
|
+
this.go(path, options).catch(console.error);
|
|
111
142
|
},
|
|
112
143
|
};
|
|
113
144
|
}
|
|
@@ -119,30 +150,18 @@ export class RouterHookApi {
|
|
|
119
150
|
* @param options
|
|
120
151
|
*/
|
|
121
152
|
public setQueryParams(
|
|
122
|
-
record:
|
|
153
|
+
record:
|
|
154
|
+
| Record<string, any>
|
|
155
|
+
| ((queryParams: Record<string, any>) => Record<string, any>),
|
|
123
156
|
options: {
|
|
124
|
-
/**
|
|
125
|
-
* If true, this will merge current query params with the new ones.
|
|
126
|
-
*/
|
|
127
|
-
merge?: boolean;
|
|
128
|
-
|
|
129
157
|
/**
|
|
130
158
|
* If true, this will add a new entry to the history stack.
|
|
131
159
|
*/
|
|
132
160
|
push?: boolean;
|
|
133
161
|
} = {},
|
|
134
162
|
) {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
? {
|
|
138
|
-
...this.query,
|
|
139
|
-
...record,
|
|
140
|
-
}
|
|
141
|
-
: {
|
|
142
|
-
...record,
|
|
143
|
-
},
|
|
144
|
-
).toString();
|
|
145
|
-
|
|
163
|
+
const func = typeof record === "function" ? record : () => record;
|
|
164
|
+
const search = new URLSearchParams(func(this.query)).toString();
|
|
146
165
|
const state = search ? `${this.pathname}?${search}` : this.pathname;
|
|
147
166
|
|
|
148
167
|
if (options.push) {
|
|
@@ -154,3 +173,7 @@ export class RouterHookApi {
|
|
|
154
173
|
}
|
|
155
174
|
|
|
156
175
|
export type HrefLike = string | { options: { path?: string; name?: string } };
|
|
176
|
+
|
|
177
|
+
export type VirtualRouter<T> = {
|
|
178
|
+
[K in keyof T as T[K] extends PageDescriptor ? K : never]: T[K];
|
|
179
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Alepha } from "@alepha/core";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { RouterContext } from "../contexts/RouterContext.ts";
|
|
4
|
+
|
|
5
|
+
export const useAlepha = (): Alepha => {
|
|
6
|
+
const routerContext = useContext(RouterContext);
|
|
7
|
+
if (!routerContext) {
|
|
8
|
+
throw new Error("useAlepha must be used within a RouterProvider");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return routerContext.alepha;
|
|
12
|
+
};
|
package/src/hooks/useClient.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ClientScope,
|
|
3
|
+
HttpClient,
|
|
4
|
+
type HttpVirtualClient,
|
|
5
|
+
} from "@alepha/server";
|
|
2
6
|
import { useInject } from "./useInject.ts";
|
|
3
7
|
|
|
4
|
-
export const useClient =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const useApi = <T extends object>() => {
|
|
8
|
+
export const useClient = <T extends object>(
|
|
9
|
+
_scope?: ClientScope,
|
|
10
|
+
): HttpVirtualClient<T> => {
|
|
9
11
|
return useInject(HttpClient).of<T>();
|
|
10
12
|
};
|
package/src/hooks/useInject.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Service } from "@alepha/core";
|
|
2
2
|
import { useContext, useMemo } from "react";
|
|
3
3
|
import { RouterContext } from "../contexts/RouterContext.ts";
|
|
4
4
|
|
|
5
|
-
export const useInject = <T extends object>(clazz:
|
|
5
|
+
export const useInject = <T extends object>(clazz: Service<T>): T => {
|
|
6
6
|
const ctx = useContext(RouterContext);
|
|
7
7
|
if (!ctx) {
|
|
8
8
|
throw new Error("useRouter must be used within a <RouterProvider>");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
return useMemo(
|
|
12
|
-
() =>
|
|
13
|
-
ctx.alepha.get(clazz, {
|
|
14
|
-
skipRegistration: true,
|
|
15
|
-
}),
|
|
16
|
-
[],
|
|
17
|
-
);
|
|
11
|
+
return useMemo(() => ctx.alepha.get(clazz), []);
|
|
18
12
|
};
|
|
@@ -34,12 +34,9 @@ export const useQueryParams = <T extends TObject>(
|
|
|
34
34
|
queryParams,
|
|
35
35
|
(queryParams: Static<T>) => {
|
|
36
36
|
setQueryParams(queryParams);
|
|
37
|
-
router.setQueryParams(
|
|
38
|
-
{ [key]: encode(ctx.alepha, schema, queryParams) }
|
|
39
|
-
|
|
40
|
-
merge: true,
|
|
41
|
-
},
|
|
42
|
-
);
|
|
37
|
+
router.setQueryParams((data) => {
|
|
38
|
+
return { ...data, [key]: encode(ctx.alepha, schema, queryParams) };
|
|
39
|
+
});
|
|
43
40
|
},
|
|
44
41
|
];
|
|
45
42
|
};
|
|
@@ -53,7 +50,7 @@ const encode = (alepha: Alepha, schema: TObject, data: any) => {
|
|
|
53
50
|
const decode = (alepha: Alepha, schema: TObject, data: any) => {
|
|
54
51
|
try {
|
|
55
52
|
return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
|
|
56
|
-
} catch (
|
|
53
|
+
} catch (_error) {
|
|
57
54
|
return {};
|
|
58
55
|
}
|
|
59
56
|
};
|
package/src/hooks/useRouter.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useContext, useMemo } from "react";
|
|
2
2
|
import { RouterContext } from "../contexts/RouterContext.ts";
|
|
3
3
|
import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
|
|
4
|
+
import { PageDescriptorProvider } from "../providers/PageDescriptorProvider.ts";
|
|
4
5
|
import { ReactBrowserProvider } from "../providers/ReactBrowserProvider.ts";
|
|
5
6
|
import { RouterHookApi } from "./RouterHookApi.ts";
|
|
6
7
|
|
|
@@ -11,9 +12,14 @@ export const useRouter = (): RouterHookApi => {
|
|
|
11
12
|
throw new Error("useRouter must be used within a RouterProvider");
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
const pages = useMemo(() => {
|
|
16
|
+
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
14
19
|
return useMemo(
|
|
15
20
|
() =>
|
|
16
21
|
new RouterHookApi(
|
|
22
|
+
pages,
|
|
17
23
|
ctx.state,
|
|
18
24
|
layer,
|
|
19
25
|
ctx.alepha.isBrowser()
|
package/src/index.browser.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $inject, Alepha
|
|
1
|
+
import { __bind, $inject, Alepha } from "@alepha/core";
|
|
2
2
|
import { $page } from "./descriptors/$page.ts";
|
|
3
3
|
import { BrowserRouterProvider } from "./providers/BrowserRouterProvider.ts";
|
|
4
4
|
import { PageDescriptorProvider } from "./providers/PageDescriptorProvider.ts";
|
package/src/index.shared.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
export { default as
|
|
1
|
+
export { default as ClientOnly } from "./components/ClientOnly.tsx";
|
|
2
|
+
export { default as ErrorBoundary } from "./components/ErrorBoundary.tsx";
|
|
3
|
+
export * from "./components/ErrorViewer.tsx";
|
|
2
4
|
export { default as Link } from "./components/Link.tsx";
|
|
5
|
+
export { default as NestedView } from "./components/NestedView.tsx";
|
|
3
6
|
|
|
4
7
|
export * from "./contexts/RouterContext.ts";
|
|
5
8
|
export * from "./contexts/RouterLayerContext.ts";
|
|
9
|
+
|
|
6
10
|
export * from "./descriptors/$page.ts";
|
|
11
|
+
|
|
12
|
+
export * from "./errors/RedirectionError.ts";
|
|
13
|
+
|
|
7
14
|
export * from "./hooks/RouterHookApi.ts";
|
|
8
|
-
export * from "./hooks/
|
|
15
|
+
export * from "./hooks/useActive.ts";
|
|
16
|
+
export * from "./hooks/useAlepha.ts";
|
|
9
17
|
export * from "./hooks/useClient.ts";
|
|
18
|
+
export * from "./hooks/useInject.ts";
|
|
10
19
|
export * from "./hooks/useQueryParams.ts";
|
|
11
20
|
export * from "./hooks/useRouter.ts";
|
|
12
21
|
export * from "./hooks/useRouterEvents.ts";
|
|
13
22
|
export * from "./hooks/useRouterState.ts";
|
|
14
|
-
export * from "./hooks/useActive.ts";
|
|
15
|
-
export * from "./errors/RedirectionError.ts";
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $inject, Alepha
|
|
1
|
+
import { __bind, $inject, Alepha } from "@alepha/core";
|
|
2
2
|
import {
|
|
3
3
|
ServerLinksProvider,
|
|
4
4
|
ServerModule,
|
|
@@ -13,17 +13,18 @@ import {
|
|
|
13
13
|
} from "./providers/PageDescriptorProvider.ts";
|
|
14
14
|
import type { ReactHydrationState } from "./providers/ReactBrowserProvider.ts";
|
|
15
15
|
import { ReactServerProvider } from "./providers/ReactServerProvider.ts";
|
|
16
|
-
export { default as NestedView } from "./components/NestedView.tsx";
|
|
17
16
|
|
|
17
|
+
export { default as NestedView } from "./components/NestedView.tsx";
|
|
18
|
+
export * from "./errors/RedirectionError.ts";
|
|
18
19
|
export * from "./index.shared.ts";
|
|
19
20
|
export * from "./providers/PageDescriptorProvider.ts";
|
|
20
21
|
export * from "./providers/ReactBrowserProvider.ts";
|
|
21
22
|
export * from "./providers/ReactServerProvider.ts";
|
|
22
|
-
export * from "./errors/RedirectionError.ts";
|
|
23
23
|
|
|
24
24
|
declare module "@alepha/core" {
|
|
25
25
|
interface Hooks {
|
|
26
26
|
"react:browser:render": {
|
|
27
|
+
state: RouterState;
|
|
27
28
|
context: PageReactContext;
|
|
28
29
|
hydration?: ReactHydrationState;
|
|
29
30
|
};
|
|
@@ -31,9 +32,9 @@ declare module "@alepha/core" {
|
|
|
31
32
|
request: ServerRequest;
|
|
32
33
|
pageRequest: PageRequest;
|
|
33
34
|
};
|
|
34
|
-
|
|
35
35
|
"react:transition:begin": {
|
|
36
36
|
state: RouterState;
|
|
37
|
+
context: PageReactContext;
|
|
37
38
|
};
|
|
38
39
|
"react:transition:success": {
|
|
39
40
|
state: RouterState;
|
|
@@ -41,9 +42,11 @@ declare module "@alepha/core" {
|
|
|
41
42
|
"react:transition:error": {
|
|
42
43
|
error: Error;
|
|
43
44
|
state: RouterState;
|
|
45
|
+
context: PageReactContext;
|
|
44
46
|
};
|
|
45
47
|
"react:transition:end": {
|
|
46
48
|
state: RouterState;
|
|
49
|
+
context: PageReactContext;
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -2,14 +2,15 @@ import { $hook, $inject, $logger, Alepha } from "@alepha/core";
|
|
|
2
2
|
import { type Route, RouterProvider } from "@alepha/router";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
4
|
import {
|
|
5
|
+
isPageRoute,
|
|
5
6
|
PageDescriptorProvider,
|
|
6
7
|
type PageReactContext,
|
|
8
|
+
type PageRequest,
|
|
7
9
|
type PageRoute,
|
|
8
10
|
type PageRouteEntry,
|
|
9
11
|
type RouterRenderResult,
|
|
10
12
|
type RouterState,
|
|
11
13
|
type TransitionOptions,
|
|
12
|
-
isPageRoute,
|
|
13
14
|
} from "./PageDescriptorProvider.ts";
|
|
14
15
|
|
|
15
16
|
export interface BrowserRoute extends Route {
|
|
@@ -49,10 +50,18 @@ export class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
49
50
|
pathname,
|
|
50
51
|
search,
|
|
51
52
|
layers: [],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const context: PageRequest = {
|
|
56
|
+
url,
|
|
57
|
+
query: {},
|
|
58
|
+
params: {},
|
|
52
59
|
head: {},
|
|
60
|
+
onError: () => null,
|
|
61
|
+
...(options.context ?? {}),
|
|
53
62
|
};
|
|
54
63
|
|
|
55
|
-
await this.alepha.emit("react:transition:begin", { state });
|
|
64
|
+
await this.alepha.emit("react:transition:begin", { state, context });
|
|
56
65
|
|
|
57
66
|
try {
|
|
58
67
|
const previous = options.previous;
|
|
@@ -65,31 +74,25 @@ export class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
context.query = query;
|
|
78
|
+
context.params = params ?? {};
|
|
79
|
+
context.previous = previous;
|
|
80
|
+
|
|
68
81
|
if (isPageRoute(route)) {
|
|
69
82
|
const result = await this.pageDescriptorProvider.createLayers(
|
|
70
83
|
route.page,
|
|
71
|
-
|
|
72
|
-
url,
|
|
73
|
-
params: params ?? {},
|
|
74
|
-
query,
|
|
75
|
-
previous,
|
|
76
|
-
...state,
|
|
77
|
-
head: state.head,
|
|
78
|
-
...(options.context ?? {}),
|
|
79
|
-
},
|
|
84
|
+
context,
|
|
80
85
|
);
|
|
81
86
|
|
|
82
87
|
if (result.redirect) {
|
|
83
88
|
return {
|
|
84
|
-
element: null,
|
|
85
|
-
layers: [],
|
|
86
89
|
redirect: result.redirect,
|
|
87
|
-
|
|
90
|
+
state,
|
|
91
|
+
context,
|
|
88
92
|
};
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
state.layers = result.layers;
|
|
92
|
-
state.head = result.head;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
if (state.layers.length === 0) {
|
|
@@ -116,37 +119,28 @@ export class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
116
119
|
await this.alepha.emit("react:transition:error", {
|
|
117
120
|
error: e as Error,
|
|
118
121
|
state,
|
|
122
|
+
context,
|
|
119
123
|
});
|
|
120
124
|
}
|
|
121
125
|
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
element: this.root(state, options.context),
|
|
128
|
-
layers: state.layers,
|
|
129
|
-
head: state.head,
|
|
130
|
-
};
|
|
126
|
+
if (options.state) {
|
|
127
|
+
options.state.layers = state.layers;
|
|
128
|
+
options.state.pathname = state.pathname;
|
|
129
|
+
options.state.search = state.search;
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
options.state.layers = state.layers;
|
|
134
|
-
options.state.pathname = state.pathname;
|
|
135
|
-
options.state.search = state.search;
|
|
136
|
-
options.state.head = state.head;
|
|
137
|
-
|
|
138
132
|
await this.alepha.emit("react:transition:end", {
|
|
139
133
|
state: options.state,
|
|
134
|
+
context,
|
|
140
135
|
});
|
|
141
136
|
|
|
142
137
|
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
head: state.head,
|
|
138
|
+
context,
|
|
139
|
+
state,
|
|
146
140
|
};
|
|
147
141
|
}
|
|
148
142
|
|
|
149
|
-
public root(state: RouterState, context: PageReactContext
|
|
143
|
+
public root(state: RouterState, context: PageReactContext): ReactNode {
|
|
150
144
|
return this.pageDescriptorProvider.root(state, context);
|
|
151
145
|
}
|
|
152
146
|
}
|