@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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/index.browser.cjs +21 -21
  3. package/dist/index.browser.js +2 -3
  4. package/dist/index.cjs +151 -83
  5. package/dist/index.d.ts +360 -205
  6. package/dist/index.js +129 -62
  7. package/dist/{useActive-DjpZBEuB.cjs → useRouterState-AdK-XeM2.cjs} +270 -81
  8. package/dist/{useActive-BX41CqY8.js → useRouterState-qoMq7Y9J.js} +272 -84
  9. package/package.json +11 -10
  10. package/src/components/ClientOnly.tsx +35 -0
  11. package/src/components/ErrorBoundary.tsx +1 -1
  12. package/src/components/ErrorViewer.tsx +161 -0
  13. package/src/components/Link.tsx +9 -3
  14. package/src/components/NestedView.tsx +18 -3
  15. package/src/descriptors/$page.ts +139 -30
  16. package/src/errors/RedirectionError.ts +4 -1
  17. package/src/hooks/RouterHookApi.ts +42 -5
  18. package/src/hooks/useAlepha.ts +12 -0
  19. package/src/hooks/useClient.ts +8 -6
  20. package/src/hooks/useInject.ts +2 -2
  21. package/src/hooks/useQueryParams.ts +1 -1
  22. package/src/hooks/useRouter.ts +6 -0
  23. package/src/index.browser.ts +1 -1
  24. package/src/index.shared.ts +11 -5
  25. package/src/index.ts +3 -4
  26. package/src/providers/BrowserRouterProvider.ts +1 -1
  27. package/src/providers/PageDescriptorProvider.ts +72 -21
  28. package/src/providers/ReactBrowserProvider.ts +5 -8
  29. package/src/providers/ReactServerProvider.ts +197 -80
  30. package/dist/index.browser.cjs.map +0 -1
  31. package/dist/index.browser.js.map +0 -1
  32. package/dist/index.cjs.map +0 -1
  33. package/dist/index.js.map +0 -1
  34. package/dist/useActive-BX41CqY8.js.map +0 -1
  35. package/dist/useActive-DjpZBEuB.cjs.map +0 -1
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $inject, Alepha, __bind } from "@alepha/core";
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 { $hook, $inject, $logger, Alepha, OPTIONS } from "@alepha/core";
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, createElement } from "react";
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
- return createElement(
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
- let onError = this.renderError; // error handler
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
- const element = await onError(it.error);
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 layer = await this.createElement(it.route, {
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, layer),
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(e: Error): ReactNode {
297
- return createElement("pre", { style: { overflow: "auto" } }, `${e.stack}`);
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 = this.renderEmptyView(),
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
- view,
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 { HttpClient, type HttpClientLink } from "@alepha/server";
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?: HttpClientLink[];
224
+ layers?: Array<PreviousLayerData>;
225
+ links?: ApiLinksResponse;
229
226
  }