@alepha/react 0.7.0 → 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 -21
- package/dist/index.browser.js +2 -3
- package/dist/index.cjs +151 -83
- package/dist/index.d.ts +360 -205
- package/dist/index.js +129 -62
- package/dist/{useActive-DjpZBEuB.cjs → useRouterState-AdK-XeM2.cjs} +270 -81
- package/dist/{useActive-BX41CqY8.js → useRouterState-qoMq7Y9J.js} +272 -84
- package/package.json +11 -10
- package/src/components/ClientOnly.tsx +35 -0
- package/src/components/ErrorBoundary.tsx +1 -1
- package/src/components/ErrorViewer.tsx +161 -0
- package/src/components/Link.tsx +9 -3
- package/src/components/NestedView.tsx +18 -3
- package/src/descriptors/$page.ts +139 -30
- package/src/errors/RedirectionError.ts +4 -1
- package/src/hooks/RouterHookApi.ts +42 -5
- package/src/hooks/useAlepha.ts +12 -0
- package/src/hooks/useClient.ts +8 -6
- package/src/hooks/useInject.ts +2 -2
- package/src/hooks/useQueryParams.ts +1 -1
- package/src/hooks/useRouter.ts +6 -0
- package/src/index.browser.ts +1 -1
- package/src/index.shared.ts +11 -5
- package/src/index.ts +3 -4
- package/src/providers/BrowserRouterProvider.ts +1 -1
- package/src/providers/PageDescriptorProvider.ts +72 -21
- package/src/providers/ReactBrowserProvider.ts +5 -8
- package/src/providers/ReactServerProvider.ts +197 -80
- 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-BX41CqY8.js.map +0 -1
- package/dist/useActive-DjpZBEuB.cjs.map +0 -1
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,13 +13,13 @@ 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 {
|
|
@@ -32,7 +32,6 @@ declare module "@alepha/core" {
|
|
|
32
32
|
request: ServerRequest;
|
|
33
33
|
pageRequest: PageRequest;
|
|
34
34
|
};
|
|
35
|
-
|
|
36
35
|
"react:transition:begin": {
|
|
37
36
|
state: RouterState;
|
|
38
37
|
context: PageReactContext;
|
|
@@ -2,6 +2,7 @@ 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,
|
|
7
8
|
type PageRequest,
|
|
@@ -10,7 +11,6 @@ import {
|
|
|
10
11
|
type RouterRenderResult,
|
|
11
12
|
type RouterState,
|
|
12
13
|
type TransitionOptions,
|
|
13
|
-
isPageRoute,
|
|
14
14
|
} from "./PageDescriptorProvider.ts";
|
|
15
15
|
|
|
16
16
|
export interface BrowserRoute extends Route {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Static } from "@alepha/core";
|
|
2
|
+
import { $hook, $inject, $logger, Alepha, OPTIONS, t } from "@alepha/core";
|
|
2
3
|
import type { ApiLinksResponse } from "@alepha/server";
|
|
3
|
-
import { type ReactNode,
|
|
4
|
+
import { createElement, type ReactNode, StrictMode } from "react";
|
|
5
|
+
import ClientOnly from "../components/ClientOnly.tsx";
|
|
6
|
+
import ErrorViewer from "../components/ErrorViewer.tsx";
|
|
4
7
|
import NestedView from "../components/NestedView.tsx";
|
|
5
8
|
import { RouterContext } from "../contexts/RouterContext.ts";
|
|
6
9
|
import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
|
|
@@ -11,8 +14,17 @@ import {
|
|
|
11
14
|
} from "../descriptors/$page.ts";
|
|
12
15
|
import { RedirectionError } from "../errors/RedirectionError.ts";
|
|
13
16
|
|
|
17
|
+
const envSchema = t.object({
|
|
18
|
+
REACT_STRICT_MODE: t.boolean({ default: true }),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
declare module "@alepha/core" {
|
|
22
|
+
export interface Env extends Partial<Static<typeof envSchema>> {}
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
export class PageDescriptorProvider {
|
|
15
26
|
protected readonly log = $logger();
|
|
27
|
+
protected readonly env = $inject(envSchema);
|
|
16
28
|
protected readonly alepha = $inject(Alepha);
|
|
17
29
|
protected readonly pages: PageRoute[] = [];
|
|
18
30
|
|
|
@@ -30,8 +42,32 @@ export class PageDescriptorProvider {
|
|
|
30
42
|
throw new Error(`Page ${name} not found`);
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
public url(
|
|
46
|
+
name: string,
|
|
47
|
+
options: { params?: Record<string, string>; base?: string } = {},
|
|
48
|
+
): URL {
|
|
49
|
+
const page = this.page(name);
|
|
50
|
+
if (!page) {
|
|
51
|
+
throw new Error(`Page ${name} not found`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let url = page.path ?? "";
|
|
55
|
+
let parent = page.parent;
|
|
56
|
+
while (parent) {
|
|
57
|
+
url = `${parent.path ?? ""}/${url}`;
|
|
58
|
+
parent = parent.parent;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
url = this.compile(url, options.params ?? {});
|
|
62
|
+
|
|
63
|
+
return new URL(
|
|
64
|
+
url.replace(/\/\/+/g, "/") || "/",
|
|
65
|
+
options.base ?? `http://localhost`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
33
69
|
public root(state: RouterState, context: PageReactContext): ReactNode {
|
|
34
|
-
|
|
70
|
+
const root = createElement(
|
|
35
71
|
RouterContext.Provider,
|
|
36
72
|
{
|
|
37
73
|
value: {
|
|
@@ -42,6 +78,12 @@ export class PageDescriptorProvider {
|
|
|
42
78
|
},
|
|
43
79
|
createElement(NestedView, {}, state.layers[0]?.element),
|
|
44
80
|
);
|
|
81
|
+
|
|
82
|
+
if (this.env.REACT_STRICT_MODE) {
|
|
83
|
+
return createElement(StrictMode, {}, root);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return root;
|
|
45
87
|
}
|
|
46
88
|
|
|
47
89
|
public async createLayers(
|
|
@@ -52,7 +94,7 @@ export class PageDescriptorProvider {
|
|
|
52
94
|
const layers: Layer[] = []; // result layers
|
|
53
95
|
let context: Record<string, any> = {}; // all props
|
|
54
96
|
const stack: Array<RouterStackItem> = [{ route }]; // stack of routes
|
|
55
|
-
|
|
97
|
+
request.onError = (error) => this.renderError(error); // error handler
|
|
56
98
|
|
|
57
99
|
let parent = route.parent;
|
|
58
100
|
while (parent) {
|
|
@@ -182,12 +224,15 @@ export class PageDescriptorProvider {
|
|
|
182
224
|
const path = acc.replace(/\/+/, "/");
|
|
183
225
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
184
226
|
if (localErrorHandler) {
|
|
185
|
-
onError = localErrorHandler;
|
|
227
|
+
request.onError = localErrorHandler;
|
|
186
228
|
}
|
|
187
229
|
|
|
188
230
|
// handler has thrown an error, render an error view
|
|
189
231
|
if (it.error) {
|
|
190
|
-
|
|
232
|
+
let element: ReactNode = await request.onError(it.error);
|
|
233
|
+
if (element === null) {
|
|
234
|
+
element = this.renderError(it.error);
|
|
235
|
+
}
|
|
191
236
|
|
|
192
237
|
layers.push({
|
|
193
238
|
props,
|
|
@@ -195,7 +240,7 @@ export class PageDescriptorProvider {
|
|
|
195
240
|
name: it.route.name,
|
|
196
241
|
part: it.route.path,
|
|
197
242
|
config: it.config,
|
|
198
|
-
element: this.renderView(i + 1, path, element),
|
|
243
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
199
244
|
index: i + 1,
|
|
200
245
|
path,
|
|
201
246
|
});
|
|
@@ -204,7 +249,7 @@ export class PageDescriptorProvider {
|
|
|
204
249
|
|
|
205
250
|
// normal use case
|
|
206
251
|
|
|
207
|
-
const
|
|
252
|
+
const element = await this.createElement(it.route, {
|
|
208
253
|
...props,
|
|
209
254
|
...context,
|
|
210
255
|
});
|
|
@@ -214,7 +259,7 @@ export class PageDescriptorProvider {
|
|
|
214
259
|
props,
|
|
215
260
|
part: it.route.path,
|
|
216
261
|
config: it.config,
|
|
217
|
-
element: this.renderView(i + 1, path,
|
|
262
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
218
263
|
index: i + 1,
|
|
219
264
|
path,
|
|
220
265
|
});
|
|
@@ -293,8 +338,8 @@ export class PageDescriptorProvider {
|
|
|
293
338
|
}
|
|
294
339
|
}
|
|
295
340
|
|
|
296
|
-
public renderError(
|
|
297
|
-
return createElement(
|
|
341
|
+
public renderError(error: Error): ReactNode {
|
|
342
|
+
return createElement(ErrorViewer, { error });
|
|
298
343
|
}
|
|
299
344
|
|
|
300
345
|
public renderEmptyView(): ReactNode {
|
|
@@ -332,8 +377,19 @@ export class PageDescriptorProvider {
|
|
|
332
377
|
protected renderView(
|
|
333
378
|
index: number,
|
|
334
379
|
path: string,
|
|
335
|
-
view: ReactNode
|
|
380
|
+
view: ReactNode | undefined,
|
|
381
|
+
page: PageRoute,
|
|
336
382
|
): ReactNode {
|
|
383
|
+
view ??= this.renderEmptyView();
|
|
384
|
+
|
|
385
|
+
const element = page.client
|
|
386
|
+
? createElement(
|
|
387
|
+
ClientOnly,
|
|
388
|
+
typeof page.client === "object" ? page.client : {},
|
|
389
|
+
view,
|
|
390
|
+
)
|
|
391
|
+
: view;
|
|
392
|
+
|
|
337
393
|
return createElement(
|
|
338
394
|
RouterLayerContext.Provider,
|
|
339
395
|
{
|
|
@@ -342,7 +398,7 @@ export class PageDescriptorProvider {
|
|
|
342
398
|
path,
|
|
343
399
|
},
|
|
344
400
|
},
|
|
345
|
-
|
|
401
|
+
element,
|
|
346
402
|
);
|
|
347
403
|
}
|
|
348
404
|
|
|
@@ -352,7 +408,8 @@ export class PageDescriptorProvider {
|
|
|
352
408
|
const pages = this.alepha.getDescriptorValues($page);
|
|
353
409
|
for (const { value, key } of pages) {
|
|
354
410
|
value[OPTIONS].name ??= key;
|
|
355
|
-
|
|
411
|
+
}
|
|
412
|
+
for (const { value } of pages) {
|
|
356
413
|
// skip children, we only want root pages
|
|
357
414
|
if (value[OPTIONS].parent) {
|
|
358
415
|
continue;
|
|
@@ -369,12 +426,6 @@ export class PageDescriptorProvider {
|
|
|
369
426
|
): PageRouteEntry {
|
|
370
427
|
const children = target[OPTIONS].children ?? [];
|
|
371
428
|
|
|
372
|
-
for (const it of pages) {
|
|
373
|
-
if (it.value[OPTIONS].parent === target) {
|
|
374
|
-
children.push(it.value);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
429
|
return {
|
|
379
430
|
...target[OPTIONS],
|
|
380
431
|
parent: undefined,
|
|
@@ -465,7 +516,7 @@ export interface Layer {
|
|
|
465
516
|
path: string;
|
|
466
517
|
}
|
|
467
518
|
|
|
468
|
-
export type PreviousLayerData = Omit<Layer, "element">;
|
|
519
|
+
export type PreviousLayerData = Omit<Layer, "element" | "index" | "path">;
|
|
469
520
|
|
|
470
521
|
export interface AnchorProps {
|
|
471
522
|
href: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $hook, $inject, $logger, Alepha, type Static, t } from "@alepha/core";
|
|
2
|
-
import {
|
|
2
|
+
import { type ApiLinksResponse, HttpClient } from "@alepha/server";
|
|
3
3
|
import type { Root } from "react-dom/client";
|
|
4
4
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
5
5
|
import { BrowserHeadProvider } from "./BrowserHeadProvider.ts";
|
|
@@ -101,10 +101,7 @@ export class ReactBrowserProvider {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
protected async render(
|
|
104
|
-
options: {
|
|
105
|
-
url?: string;
|
|
106
|
-
previous?: PreviousLayerData[];
|
|
107
|
-
} = {},
|
|
104
|
+
options: { url?: string; previous?: PreviousLayerData[] } = {},
|
|
108
105
|
): Promise<RouterRenderResult> {
|
|
109
106
|
const previous = options.previous ?? this.state.layers;
|
|
110
107
|
const url = options.url ?? this.url;
|
|
@@ -174,7 +171,7 @@ export class ReactBrowserProvider {
|
|
|
174
171
|
const previous = hydration?.layers ?? [];
|
|
175
172
|
|
|
176
173
|
if (hydration?.links) {
|
|
177
|
-
for (const link of hydration.links) {
|
|
174
|
+
for (const link of hydration.links.links) {
|
|
178
175
|
this.client.pushLink(link);
|
|
179
176
|
}
|
|
180
177
|
}
|
|
@@ -224,6 +221,6 @@ export interface RouterGoOptions {
|
|
|
224
221
|
}
|
|
225
222
|
|
|
226
223
|
export interface ReactHydrationState {
|
|
227
|
-
layers?: PreviousLayerData
|
|
228
|
-
links?:
|
|
224
|
+
layers?: Array<PreviousLayerData>;
|
|
225
|
+
links?: ApiLinksResponse;
|
|
229
226
|
}
|