@alepha/react 0.9.3 → 0.9.4
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 +46 -0
- package/dist/index.browser.js +315 -320
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +496 -457
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -258
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +274 -256
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +494 -460
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
- package/src/components/NestedView.tsx +15 -13
- package/src/components/NotFound.tsx +1 -1
- package/src/descriptors/$page.ts +16 -4
- package/src/errors/Redirection.ts +8 -5
- package/src/hooks/useActive.ts +25 -34
- package/src/hooks/useAlepha.ts +16 -2
- package/src/hooks/useClient.ts +7 -4
- package/src/hooks/useInject.ts +4 -1
- package/src/hooks/useQueryParams.ts +9 -6
- package/src/hooks/useRouter.ts +18 -31
- package/src/hooks/useRouterEvents.ts +7 -7
- package/src/hooks/useRouterState.ts +8 -20
- package/src/hooks/useSchema.ts +10 -15
- package/src/hooks/useStore.ts +0 -7
- package/src/index.browser.ts +11 -11
- package/src/index.shared.ts +2 -3
- package/src/index.ts +21 -30
- package/src/providers/ReactBrowserProvider.ts +149 -65
- package/src/providers/ReactBrowserRouterProvider.ts +132 -0
- package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +84 -112
- package/src/providers/ReactServerProvider.ts +69 -74
- package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +44 -54
- package/src/contexts/RouterContext.ts +0 -14
- package/src/providers/BrowserRouterProvider.ts +0 -155
- package/src/providers/ReactBrowserRenderer.ts +0 -93
package/src/index.ts
CHANGED
|
@@ -3,65 +3,55 @@ import { AlephaServer, type ServerRequest } from "@alepha/server";
|
|
|
3
3
|
import { AlephaServerCache } from "@alepha/server-cache";
|
|
4
4
|
import { AlephaServerLinks } from "@alepha/server-links";
|
|
5
5
|
import { $page } from "./descriptors/$page.ts";
|
|
6
|
+
import type { ReactHydrationState } from "./providers/ReactBrowserProvider.ts";
|
|
6
7
|
import {
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
type RouterState,
|
|
11
|
-
} from "./providers/PageDescriptorProvider.ts";
|
|
12
|
-
import {
|
|
13
|
-
ReactBrowserProvider,
|
|
14
|
-
type ReactHydrationState,
|
|
15
|
-
} from "./providers/ReactBrowserProvider.ts";
|
|
8
|
+
ReactPageProvider,
|
|
9
|
+
type ReactRouterState,
|
|
10
|
+
} from "./providers/ReactPageProvider.ts";
|
|
16
11
|
import { ReactServerProvider } from "./providers/ReactServerProvider.ts";
|
|
12
|
+
import { ReactRouter } from "./services/ReactRouter.ts";
|
|
17
13
|
|
|
18
14
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
19
15
|
|
|
20
16
|
export * from "./index.shared.ts";
|
|
21
|
-
export * from "./providers/PageDescriptorProvider.ts";
|
|
22
17
|
export * from "./providers/ReactBrowserProvider.ts";
|
|
18
|
+
export * from "./providers/ReactPageProvider.ts";
|
|
23
19
|
export * from "./providers/ReactServerProvider.ts";
|
|
24
20
|
|
|
25
21
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
26
22
|
|
|
27
23
|
declare module "@alepha/core" {
|
|
24
|
+
interface State {
|
|
25
|
+
"react.router.state"?: ReactRouterState;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
28
|
interface Hooks {
|
|
29
|
-
"react:router:createLayers": {
|
|
30
|
-
request: ServerRequest;
|
|
31
|
-
context: PageRequest;
|
|
32
|
-
layers: PageRequest[];
|
|
33
|
-
};
|
|
34
29
|
"react:server:render:begin": {
|
|
35
30
|
request?: ServerRequest;
|
|
36
|
-
|
|
31
|
+
state: ReactRouterState;
|
|
37
32
|
};
|
|
38
33
|
"react:server:render:end": {
|
|
39
34
|
request?: ServerRequest;
|
|
40
|
-
|
|
41
|
-
state: RouterState;
|
|
35
|
+
state: ReactRouterState;
|
|
42
36
|
html: string;
|
|
43
37
|
};
|
|
38
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
44
39
|
"react:browser:render": {
|
|
45
|
-
state:
|
|
46
|
-
context: PageReactContext;
|
|
40
|
+
state: ReactRouterState;
|
|
47
41
|
hydration?: ReactHydrationState;
|
|
48
42
|
};
|
|
49
43
|
"react:transition:begin": {
|
|
50
|
-
state:
|
|
51
|
-
context: PageReactContext;
|
|
44
|
+
state: ReactRouterState;
|
|
52
45
|
};
|
|
53
46
|
"react:transition:success": {
|
|
54
|
-
state:
|
|
55
|
-
context: PageReactContext;
|
|
47
|
+
state: ReactRouterState;
|
|
56
48
|
};
|
|
57
49
|
"react:transition:error": {
|
|
50
|
+
state: ReactRouterState;
|
|
58
51
|
error: Error;
|
|
59
|
-
state: RouterState;
|
|
60
|
-
context: PageReactContext;
|
|
61
52
|
};
|
|
62
53
|
"react:transition:end": {
|
|
63
|
-
state:
|
|
64
|
-
context: PageReactContext;
|
|
54
|
+
state: ReactRouterState;
|
|
65
55
|
};
|
|
66
56
|
}
|
|
67
57
|
}
|
|
@@ -81,12 +71,13 @@ declare module "@alepha/core" {
|
|
|
81
71
|
export const AlephaReact = $module({
|
|
82
72
|
name: "alepha.react",
|
|
83
73
|
descriptors: [$page],
|
|
84
|
-
services: [ReactServerProvider,
|
|
74
|
+
services: [ReactServerProvider, ReactPageProvider, ReactRouter],
|
|
85
75
|
register: (alepha) =>
|
|
86
76
|
alepha
|
|
87
77
|
.with(AlephaServer)
|
|
88
78
|
.with(AlephaServerCache)
|
|
89
79
|
.with(AlephaServerLinks)
|
|
90
80
|
.with(ReactServerProvider)
|
|
91
|
-
.with(
|
|
81
|
+
.with(ReactPageProvider)
|
|
82
|
+
.with(ReactRouter),
|
|
92
83
|
});
|
|
@@ -1,74 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
$env,
|
|
3
|
+
$hook,
|
|
4
|
+
$inject,
|
|
5
|
+
Alepha,
|
|
6
|
+
type State,
|
|
7
|
+
type Static,
|
|
8
|
+
t,
|
|
9
|
+
} from "@alepha/core";
|
|
10
|
+
import { DateTimeProvider } from "@alepha/datetime";
|
|
11
|
+
import { $logger } from "@alepha/logger";
|
|
3
12
|
import { LinkProvider } from "@alepha/server-links";
|
|
4
|
-
import type
|
|
5
|
-
import {
|
|
13
|
+
import { createRoot, hydrateRoot, type Root } from "react-dom/client";
|
|
14
|
+
import { ReactBrowserRouterProvider } from "./ReactBrowserRouterProvider.ts";
|
|
6
15
|
import type {
|
|
7
16
|
PreviousLayerData,
|
|
8
|
-
|
|
9
|
-
RouterState,
|
|
17
|
+
ReactRouterState,
|
|
10
18
|
TransitionOptions,
|
|
11
|
-
} from "./
|
|
19
|
+
} from "./ReactPageProvider.ts";
|
|
20
|
+
|
|
21
|
+
const envSchema = t.object({
|
|
22
|
+
REACT_ROOT_ID: t.string({ default: "root" }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
declare module "@alepha/core" {
|
|
26
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ReactBrowserRendererOptions {
|
|
30
|
+
scrollRestoration?: "top" | "manual";
|
|
31
|
+
}
|
|
12
32
|
|
|
13
33
|
export class ReactBrowserProvider {
|
|
34
|
+
protected readonly env = $env(envSchema);
|
|
14
35
|
protected readonly log = $logger();
|
|
15
36
|
protected readonly client = $inject(LinkProvider);
|
|
16
37
|
protected readonly alepha = $inject(Alepha);
|
|
17
|
-
protected readonly router = $inject(
|
|
18
|
-
protected
|
|
38
|
+
protected readonly router = $inject(ReactBrowserRouterProvider);
|
|
39
|
+
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
40
|
+
protected root?: Root;
|
|
41
|
+
|
|
42
|
+
public options: ReactBrowserRendererOptions = {
|
|
43
|
+
scrollRestoration: "top",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
protected getRootElement() {
|
|
47
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
48
|
+
if (root) {
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const div = this.document.createElement("div");
|
|
53
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
54
|
+
|
|
55
|
+
this.document.body.prepend(div);
|
|
56
|
+
|
|
57
|
+
return div;
|
|
58
|
+
}
|
|
19
59
|
|
|
20
60
|
public transitioning?: {
|
|
21
61
|
to: string;
|
|
62
|
+
from?: string;
|
|
22
63
|
};
|
|
23
64
|
|
|
24
|
-
public state:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
search: "",
|
|
28
|
-
};
|
|
65
|
+
public get state(): ReactRouterState {
|
|
66
|
+
return this.alepha.state("react.router.state")!;
|
|
67
|
+
}
|
|
29
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Accessor for Document DOM API.
|
|
71
|
+
*/
|
|
30
72
|
public get document() {
|
|
31
73
|
return window.document;
|
|
32
74
|
}
|
|
33
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Accessor for History DOM API.
|
|
78
|
+
*/
|
|
34
79
|
public get history() {
|
|
35
80
|
return window.history;
|
|
36
81
|
}
|
|
37
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Accessor for Location DOM API.
|
|
85
|
+
*/
|
|
38
86
|
public get location() {
|
|
39
87
|
return window.location;
|
|
40
88
|
}
|
|
41
89
|
|
|
42
|
-
public get
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
url = url.replace(import.meta.env?.BASE_URL, "");
|
|
47
|
-
if (!url.startsWith("/")) {
|
|
48
|
-
url = `/${url}`;
|
|
49
|
-
}
|
|
90
|
+
public get base() {
|
|
91
|
+
const base = import.meta.env?.BASE_URL;
|
|
92
|
+
if (!base || base === "/") {
|
|
93
|
+
return "";
|
|
50
94
|
}
|
|
51
95
|
|
|
52
|
-
return
|
|
96
|
+
return base;
|
|
53
97
|
}
|
|
54
98
|
|
|
55
|
-
public
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
|
|
99
|
+
public get url(): string {
|
|
100
|
+
const url = this.location.pathname + this.location.search;
|
|
101
|
+
if (this.base) {
|
|
102
|
+
return url.replace(this.base, "");
|
|
60
103
|
}
|
|
104
|
+
return url;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public pushState(path: string, replace?: boolean) {
|
|
108
|
+
const url = this.base + path;
|
|
61
109
|
|
|
62
110
|
if (replace) {
|
|
63
|
-
this.history.replaceState({}, "",
|
|
111
|
+
this.history.replaceState({}, "", url);
|
|
64
112
|
} else {
|
|
65
|
-
this.history.pushState({}, "",
|
|
113
|
+
this.history.pushState({}, "", url);
|
|
66
114
|
}
|
|
67
115
|
}
|
|
68
116
|
|
|
69
117
|
public async invalidate(props?: Record<string, any>) {
|
|
70
118
|
const previous: PreviousLayerData[] = [];
|
|
71
119
|
|
|
120
|
+
this.log.trace("Invalidating layers");
|
|
121
|
+
|
|
72
122
|
if (props) {
|
|
73
123
|
const [key] = Object.keys(props);
|
|
74
124
|
const value = props[key];
|
|
@@ -92,13 +142,19 @@ export class ReactBrowserProvider {
|
|
|
92
142
|
}
|
|
93
143
|
|
|
94
144
|
public async go(url: string, options: RouterGoOptions = {}): Promise<void> {
|
|
95
|
-
|
|
145
|
+
this.log.trace(`Going to ${url}`, {
|
|
146
|
+
url,
|
|
147
|
+
options,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await this.render({
|
|
96
151
|
url,
|
|
152
|
+
previous: options.force ? [] : this.state.layers,
|
|
97
153
|
});
|
|
98
154
|
|
|
99
155
|
// when redirecting in browser
|
|
100
|
-
if (
|
|
101
|
-
this.pushState(
|
|
156
|
+
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
157
|
+
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
102
158
|
return;
|
|
103
159
|
}
|
|
104
160
|
|
|
@@ -107,27 +163,36 @@ export class ReactBrowserProvider {
|
|
|
107
163
|
|
|
108
164
|
protected async render(
|
|
109
165
|
options: { url?: string; previous?: PreviousLayerData[] } = {},
|
|
110
|
-
): Promise<
|
|
166
|
+
): Promise<void> {
|
|
111
167
|
const previous = options.previous ?? this.state.layers;
|
|
112
168
|
const url = options.url ?? this.url;
|
|
169
|
+
const start = this.dateTimeProvider.now();
|
|
113
170
|
|
|
114
|
-
this.transitioning = {
|
|
171
|
+
this.transitioning = {
|
|
172
|
+
to: url,
|
|
173
|
+
from: this.state?.url.pathname,
|
|
174
|
+
};
|
|
115
175
|
|
|
116
|
-
|
|
176
|
+
this.log.debug("Transitioning...", {
|
|
177
|
+
to: url,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const redirect = await this.router.transition(
|
|
117
181
|
new URL(`http://localhost${url}`),
|
|
118
|
-
|
|
119
|
-
previous,
|
|
120
|
-
state: this.state,
|
|
121
|
-
},
|
|
182
|
+
previous,
|
|
122
183
|
);
|
|
123
184
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
185
|
+
if (redirect) {
|
|
186
|
+
this.log.info("Redirecting to", {
|
|
187
|
+
redirect,
|
|
188
|
+
});
|
|
189
|
+
return await this.render({ url: redirect });
|
|
126
190
|
}
|
|
127
191
|
|
|
128
|
-
|
|
192
|
+
const ms = this.dateTimeProvider.now().diff(start);
|
|
193
|
+
this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
|
|
129
194
|
|
|
130
|
-
|
|
195
|
+
this.transitioning = undefined;
|
|
131
196
|
}
|
|
132
197
|
|
|
133
198
|
/**
|
|
@@ -145,6 +210,19 @@ export class ReactBrowserProvider {
|
|
|
145
210
|
|
|
146
211
|
// -------------------------------------------------------------------------------------------------------------------
|
|
147
212
|
|
|
213
|
+
protected readonly onTransitionEnd = $hook({
|
|
214
|
+
on: "react:transition:end",
|
|
215
|
+
handler: () => {
|
|
216
|
+
if (
|
|
217
|
+
this.options.scrollRestoration === "top" &&
|
|
218
|
+
typeof window !== "undefined"
|
|
219
|
+
) {
|
|
220
|
+
this.log.trace("Restoring scroll position to top");
|
|
221
|
+
window.scrollTo(0, 0);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
148
226
|
public readonly ready = $hook({
|
|
149
227
|
on: "ready",
|
|
150
228
|
handler: async () => {
|
|
@@ -152,37 +230,37 @@ export class ReactBrowserProvider {
|
|
|
152
230
|
const previous = hydration?.layers ?? [];
|
|
153
231
|
|
|
154
232
|
if (hydration) {
|
|
233
|
+
// low budget, but works for now
|
|
155
234
|
for (const [key, value] of Object.entries(hydration)) {
|
|
156
|
-
if (key !== "layers"
|
|
235
|
+
if (key !== "layers") {
|
|
157
236
|
this.alepha.state(key as keyof State, value);
|
|
158
237
|
}
|
|
159
238
|
}
|
|
160
239
|
}
|
|
161
240
|
|
|
162
|
-
|
|
163
|
-
for (const link of hydration.links.links) {
|
|
164
|
-
this.client.pushLink({
|
|
165
|
-
...link,
|
|
166
|
-
prefix: hydration.links.prefix,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const { context } = await this.render({ previous });
|
|
241
|
+
await this.render({ previous });
|
|
172
242
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
243
|
+
const element = this.router.root(this.state);
|
|
244
|
+
if (hydration?.layers) {
|
|
245
|
+
this.root = hydrateRoot(this.getRootElement(), element);
|
|
246
|
+
this.log.info("Hydrated root element");
|
|
247
|
+
} else {
|
|
248
|
+
this.root ??= createRoot(this.getRootElement());
|
|
249
|
+
this.root.render(element);
|
|
250
|
+
this.log.info("Created root element");
|
|
251
|
+
}
|
|
178
252
|
|
|
179
253
|
window.addEventListener("popstate", () => {
|
|
180
|
-
// when you update silently
|
|
254
|
+
// when you update silently queryParams or hash, skip rendering
|
|
181
255
|
// if you want to force a rendering, use #go()
|
|
182
|
-
if (this.state.pathname === this.
|
|
256
|
+
if (this.base + this.state.url.pathname === this.location.pathname) {
|
|
183
257
|
return;
|
|
184
258
|
}
|
|
185
259
|
|
|
260
|
+
this.log.debug("Popstate event triggered - rendering new state", {
|
|
261
|
+
url: this.location.pathname + this.location.search,
|
|
262
|
+
});
|
|
263
|
+
|
|
186
264
|
this.render();
|
|
187
265
|
});
|
|
188
266
|
},
|
|
@@ -196,9 +274,15 @@ export interface RouterGoOptions {
|
|
|
196
274
|
match?: TransitionOptions;
|
|
197
275
|
params?: Record<string, string>;
|
|
198
276
|
query?: Record<string, string>;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Recreate the whole page, ignoring the current state.
|
|
280
|
+
*/
|
|
281
|
+
force?: boolean;
|
|
199
282
|
}
|
|
200
283
|
|
|
201
|
-
export
|
|
284
|
+
export type ReactHydrationState = {
|
|
202
285
|
layers?: Array<PreviousLayerData>;
|
|
203
|
-
|
|
204
|
-
|
|
286
|
+
} & {
|
|
287
|
+
[key: string]: any;
|
|
288
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { $hook, $inject, Alepha } from "@alepha/core";
|
|
2
|
+
import { $logger } from "@alepha/logger";
|
|
3
|
+
import { type Route, RouterProvider } from "@alepha/router";
|
|
4
|
+
import { createElement, type ReactNode } from "react";
|
|
5
|
+
import NotFoundPage from "../components/NotFound.tsx";
|
|
6
|
+
import {
|
|
7
|
+
isPageRoute,
|
|
8
|
+
type PageRoute,
|
|
9
|
+
type PageRouteEntry,
|
|
10
|
+
type PreviousLayerData,
|
|
11
|
+
ReactPageProvider,
|
|
12
|
+
type ReactRouterState,
|
|
13
|
+
} from "./ReactPageProvider.ts";
|
|
14
|
+
|
|
15
|
+
export interface BrowserRoute extends Route {
|
|
16
|
+
page: PageRoute;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
20
|
+
protected readonly log = $logger();
|
|
21
|
+
protected readonly alepha = $inject(Alepha);
|
|
22
|
+
protected readonly pageApi = $inject(ReactPageProvider);
|
|
23
|
+
|
|
24
|
+
public add(entry: PageRouteEntry) {
|
|
25
|
+
this.pageApi.add(entry);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected readonly configure = $hook({
|
|
29
|
+
on: "configure",
|
|
30
|
+
handler: async () => {
|
|
31
|
+
for (const page of this.pageApi.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
|
+
previous: PreviousLayerData[] = [],
|
|
46
|
+
): Promise<string | void> {
|
|
47
|
+
const { pathname, search } = url;
|
|
48
|
+
|
|
49
|
+
const entry: Partial<ReactRouterState> = {
|
|
50
|
+
url,
|
|
51
|
+
query: {},
|
|
52
|
+
params: {},
|
|
53
|
+
layers: [],
|
|
54
|
+
onError: () => null,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const state = entry as ReactRouterState;
|
|
58
|
+
|
|
59
|
+
await this.alepha.emit("react:transition:begin", { state });
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const { route, params } = this.match(pathname);
|
|
63
|
+
|
|
64
|
+
const query: Record<string, string> = {};
|
|
65
|
+
if (search) {
|
|
66
|
+
for (const [key, value] of new URLSearchParams(search).entries()) {
|
|
67
|
+
query[key] = String(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
state.query = query;
|
|
72
|
+
state.params = params ?? {};
|
|
73
|
+
|
|
74
|
+
if (isPageRoute(route)) {
|
|
75
|
+
const { redirect } = await this.pageApi.createLayers(
|
|
76
|
+
route.page,
|
|
77
|
+
state,
|
|
78
|
+
previous,
|
|
79
|
+
);
|
|
80
|
+
if (redirect) {
|
|
81
|
+
return redirect;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (state.layers.length === 0) {
|
|
86
|
+
state.layers.push({
|
|
87
|
+
name: "not-found",
|
|
88
|
+
element: createElement(NotFoundPage),
|
|
89
|
+
index: 0,
|
|
90
|
+
path: "/",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await this.alepha.emit("react:transition:success", { state });
|
|
95
|
+
} catch (e) {
|
|
96
|
+
this.log.error("Transition has failed", e);
|
|
97
|
+
state.layers = [
|
|
98
|
+
{
|
|
99
|
+
name: "error",
|
|
100
|
+
element: this.pageApi.renderError(e as Error),
|
|
101
|
+
index: 0,
|
|
102
|
+
path: "/",
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
await this.alepha.emit("react:transition:error", {
|
|
107
|
+
error: e as Error,
|
|
108
|
+
state,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// [feature]: local hook for leaving a page
|
|
113
|
+
if (previous) {
|
|
114
|
+
for (let i = 0; i < previous.length; i++) {
|
|
115
|
+
const layer = previous[i];
|
|
116
|
+
if (state.layers[i]?.name !== layer.name) {
|
|
117
|
+
this.pageApi.page(layer.name)?.onLeave?.();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await this.alepha.emit("react:transition:end", {
|
|
123
|
+
state,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
this.alepha.state("react.router.state", state);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public root(state: ReactRouterState): ReactNode {
|
|
130
|
+
return this.pageApi.root(state);
|
|
131
|
+
}
|
|
132
|
+
}
|