@alepha/react 0.6.3 → 0.6.5
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/dist/index.browser.cjs +2 -1
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +3 -2
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +34 -24
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -0
- package/dist/{useActive-dAmCT31a.js → useActive-BzjLwZjs.js} +103 -78
- package/dist/useActive-BzjLwZjs.js.map +1 -0
- package/dist/{useActive-BVqdq757.cjs → useActive-Ce3Xvs5V.cjs} +102 -77
- package/dist/useActive-Ce3Xvs5V.cjs.map +1 -0
- package/package.json +46 -38
- package/src/components/Link.tsx +37 -0
- package/src/components/NestedView.tsx +38 -0
- package/src/contexts/RouterContext.ts +16 -0
- package/src/contexts/RouterLayerContext.ts +10 -0
- package/src/descriptors/$page.ts +142 -0
- package/src/errors/RedirectionError.ts +7 -0
- package/src/hooks/RouterHookApi.ts +156 -0
- package/src/hooks/useActive.ts +57 -0
- package/src/hooks/useClient.ts +6 -0
- package/src/hooks/useInject.ts +14 -0
- package/src/hooks/useQueryParams.ts +59 -0
- package/src/hooks/useRouter.ts +25 -0
- package/src/hooks/useRouterEvents.ts +58 -0
- package/src/hooks/useRouterState.ts +21 -0
- package/src/index.browser.ts +21 -0
- package/src/index.shared.ts +15 -0
- package/src/index.ts +63 -0
- package/src/providers/BrowserHeadProvider.ts +43 -0
- package/src/providers/BrowserRouterProvider.ts +152 -0
- package/src/providers/PageDescriptorProvider.ts +522 -0
- package/src/providers/ReactBrowserProvider.ts +232 -0
- package/src/providers/ReactServerProvider.ts +286 -0
- package/src/providers/ServerHeadProvider.ts +91 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { $inject, Alepha, __bind } from "@alepha/core";
|
|
2
|
+
import {
|
|
3
|
+
ServerLinksProvider,
|
|
4
|
+
ServerModule,
|
|
5
|
+
type ServerRequest,
|
|
6
|
+
} from "@alepha/server";
|
|
7
|
+
import { $page } from "./descriptors/$page.ts";
|
|
8
|
+
import {
|
|
9
|
+
PageDescriptorProvider,
|
|
10
|
+
type PageReactContext,
|
|
11
|
+
type PageRequest,
|
|
12
|
+
type RouterState,
|
|
13
|
+
} from "./providers/PageDescriptorProvider.ts";
|
|
14
|
+
import type { ReactHydrationState } from "./providers/ReactBrowserProvider.ts";
|
|
15
|
+
import { ReactServerProvider } from "./providers/ReactServerProvider.ts";
|
|
16
|
+
export { default as NestedView } from "./components/NestedView.tsx";
|
|
17
|
+
|
|
18
|
+
export * from "./index.shared.ts";
|
|
19
|
+
export * from "./providers/PageDescriptorProvider.ts";
|
|
20
|
+
export * from "./providers/ReactBrowserProvider.ts";
|
|
21
|
+
export * from "./providers/ReactServerProvider.ts";
|
|
22
|
+
export * from "./errors/RedirectionError.ts";
|
|
23
|
+
|
|
24
|
+
declare module "@alepha/core" {
|
|
25
|
+
interface Hooks {
|
|
26
|
+
"react:browser:render": {
|
|
27
|
+
context: PageReactContext;
|
|
28
|
+
hydration?: ReactHydrationState;
|
|
29
|
+
};
|
|
30
|
+
"react:server:render": {
|
|
31
|
+
request: ServerRequest;
|
|
32
|
+
pageRequest: PageRequest;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
"react:transition:begin": {
|
|
36
|
+
state: RouterState;
|
|
37
|
+
};
|
|
38
|
+
"react:transition:success": {
|
|
39
|
+
state: RouterState;
|
|
40
|
+
};
|
|
41
|
+
"react:transition:error": {
|
|
42
|
+
error: Error;
|
|
43
|
+
state: RouterState;
|
|
44
|
+
};
|
|
45
|
+
"react:transition:end": {
|
|
46
|
+
state: RouterState;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class ReactModule {
|
|
52
|
+
protected readonly alepha = $inject(Alepha);
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
this.alepha //
|
|
56
|
+
.with(ServerModule)
|
|
57
|
+
.with(ServerLinksProvider)
|
|
58
|
+
.with(PageDescriptorProvider)
|
|
59
|
+
.with(ReactServerProvider);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
__bind($page, ReactModule);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Head } from "./ServerHeadProvider.ts";
|
|
2
|
+
|
|
3
|
+
export class BrowserHeadProvider {
|
|
4
|
+
renderHead(document: Document, head: Head): void {
|
|
5
|
+
if (head.title) {
|
|
6
|
+
document.title = head.title;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (head.bodyAttributes) {
|
|
10
|
+
for (const [key, value] of Object.entries(head.bodyAttributes)) {
|
|
11
|
+
if (value) {
|
|
12
|
+
document.body.setAttribute(key, value);
|
|
13
|
+
} else {
|
|
14
|
+
document.body.removeAttribute(key);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (head.htmlAttributes) {
|
|
20
|
+
for (const [key, value] of Object.entries(head.htmlAttributes)) {
|
|
21
|
+
if (value) {
|
|
22
|
+
document.documentElement.setAttribute(key, value);
|
|
23
|
+
} else {
|
|
24
|
+
document.documentElement.removeAttribute(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (head.meta) {
|
|
30
|
+
for (const [key, value] of Object.entries(head.meta)) {
|
|
31
|
+
const meta = document.querySelector(`meta[name="${key}"]`);
|
|
32
|
+
if (meta) {
|
|
33
|
+
meta.setAttribute("content", value.content);
|
|
34
|
+
} else {
|
|
35
|
+
const newMeta = document.createElement("meta");
|
|
36
|
+
newMeta.setAttribute("name", key);
|
|
37
|
+
newMeta.setAttribute("content", value.content);
|
|
38
|
+
document.head.appendChild(newMeta);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { $hook, $inject, $logger, Alepha } from "@alepha/core";
|
|
2
|
+
import { type Route, RouterProvider } from "@alepha/router";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import {
|
|
5
|
+
PageDescriptorProvider,
|
|
6
|
+
type PageReactContext,
|
|
7
|
+
type PageRoute,
|
|
8
|
+
type PageRouteEntry,
|
|
9
|
+
type RouterRenderResult,
|
|
10
|
+
type RouterState,
|
|
11
|
+
type TransitionOptions,
|
|
12
|
+
isPageRoute,
|
|
13
|
+
} from "./PageDescriptorProvider.ts";
|
|
14
|
+
|
|
15
|
+
export interface BrowserRoute extends Route {
|
|
16
|
+
page: PageRoute;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
20
|
+
protected readonly log = $logger();
|
|
21
|
+
protected readonly alepha = $inject(Alepha);
|
|
22
|
+
protected readonly pageDescriptorProvider = $inject(PageDescriptorProvider);
|
|
23
|
+
|
|
24
|
+
public add(entry: PageRouteEntry) {
|
|
25
|
+
this.pageDescriptorProvider.add(entry);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected readonly configure = $hook({
|
|
29
|
+
name: "configure",
|
|
30
|
+
handler: async () => {
|
|
31
|
+
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
32
|
+
// mount only if a view is provided
|
|
33
|
+
if (page.component || page.lazy) {
|
|
34
|
+
this.push({
|
|
35
|
+
path: page.match,
|
|
36
|
+
page,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
public async transition(
|
|
44
|
+
url: URL,
|
|
45
|
+
options: TransitionOptions = {},
|
|
46
|
+
): Promise<RouterRenderResult> {
|
|
47
|
+
const { pathname, search } = url;
|
|
48
|
+
const state: RouterState = {
|
|
49
|
+
pathname,
|
|
50
|
+
search,
|
|
51
|
+
layers: [],
|
|
52
|
+
head: {},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await this.alepha.emit("react:transition:begin", { state });
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const previous = options.previous;
|
|
59
|
+
const { route, params } = this.match(pathname);
|
|
60
|
+
|
|
61
|
+
const query: Record<string, string> = {};
|
|
62
|
+
if (search) {
|
|
63
|
+
for (const [key, value] of new URLSearchParams(search).entries()) {
|
|
64
|
+
query[key] = String(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isPageRoute(route)) {
|
|
69
|
+
const result = await this.pageDescriptorProvider.createLayers(
|
|
70
|
+
route.page,
|
|
71
|
+
{
|
|
72
|
+
url,
|
|
73
|
+
params: params ?? {},
|
|
74
|
+
query,
|
|
75
|
+
previous,
|
|
76
|
+
...state,
|
|
77
|
+
head: state.head,
|
|
78
|
+
...(options.context ?? {}),
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (result.redirect) {
|
|
83
|
+
return {
|
|
84
|
+
element: null,
|
|
85
|
+
layers: [],
|
|
86
|
+
redirect: result.redirect,
|
|
87
|
+
head: state.head,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
state.layers = result.layers;
|
|
92
|
+
state.head = result.head;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (state.layers.length === 0) {
|
|
96
|
+
state.layers.push({
|
|
97
|
+
name: "not-found",
|
|
98
|
+
element: "Not Found",
|
|
99
|
+
index: 0,
|
|
100
|
+
path: "/",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await this.alepha.emit("react:transition:success", { state });
|
|
105
|
+
} catch (e) {
|
|
106
|
+
this.log.error(e);
|
|
107
|
+
state.layers = [
|
|
108
|
+
{
|
|
109
|
+
name: "error",
|
|
110
|
+
element: this.pageDescriptorProvider.renderError(e as Error),
|
|
111
|
+
index: 0,
|
|
112
|
+
path: "/",
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
await this.alepha.emit("react:transition:error", {
|
|
117
|
+
error: e as Error,
|
|
118
|
+
state,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!options.state) {
|
|
123
|
+
await this.alepha.emit("react:transition:end", {
|
|
124
|
+
state,
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
element: this.root(state, options.context),
|
|
128
|
+
layers: state.layers,
|
|
129
|
+
head: state.head,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
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
|
+
await this.alepha.emit("react:transition:end", {
|
|
139
|
+
state: options.state,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
element: this.root(state, options.context),
|
|
144
|
+
layers: options.state.layers,
|
|
145
|
+
head: state.head,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public root(state: RouterState, context: PageReactContext = {}): ReactNode {
|
|
150
|
+
return this.pageDescriptorProvider.root(state, context);
|
|
151
|
+
}
|
|
152
|
+
}
|