@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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/index.browser.cjs +21 -20
  3. package/dist/index.browser.js +2 -3
  4. package/dist/index.cjs +168 -82
  5. package/dist/index.d.ts +415 -232
  6. package/dist/index.js +146 -62
  7. package/dist/{useActive-4QlZKGbw.cjs → useRouterState-AdK-XeM2.cjs} +358 -170
  8. package/dist/{useActive-ClUsghB5.js → useRouterState-qoMq7Y9J.js} +358 -172
  9. package/package.json +11 -10
  10. package/src/components/ClientOnly.tsx +35 -0
  11. package/src/components/ErrorBoundary.tsx +72 -0
  12. package/src/components/ErrorViewer.tsx +161 -0
  13. package/src/components/Link.tsx +10 -4
  14. package/src/components/NestedView.tsx +28 -4
  15. package/src/descriptors/$page.ts +143 -38
  16. package/src/errors/RedirectionError.ts +4 -1
  17. package/src/hooks/RouterHookApi.ts +58 -35
  18. package/src/hooks/useAlepha.ts +12 -0
  19. package/src/hooks/useClient.ts +8 -6
  20. package/src/hooks/useInject.ts +3 -9
  21. package/src/hooks/useQueryParams.ts +4 -7
  22. package/src/hooks/useRouter.ts +6 -0
  23. package/src/index.browser.ts +1 -1
  24. package/src/index.shared.ts +11 -4
  25. package/src/index.ts +7 -4
  26. package/src/providers/BrowserRouterProvider.ts +27 -33
  27. package/src/providers/PageDescriptorProvider.ts +90 -40
  28. package/src/providers/ReactBrowserProvider.ts +21 -27
  29. package/src/providers/ReactServerProvider.ts +215 -77
  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-4QlZKGbw.cjs.map +0 -1
  35. 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(pathname: HrefLike, layer: { path: string } = this.layer) {
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
- * @param path
89
- * @param options
90
- */
91
- public async go(
92
- path: HrefLike,
93
- options: RouterGoOptions = {},
94
- ): Promise<void> {
95
- return await this.browser?.go(this.createHref(path, this.layer), options);
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
- * @param path
101
- */
102
- public createAnchorProps(path: string): AnchorProps {
103
- const href = this.createHref(path, this.layer);
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: Record<string, any>,
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 search = new URLSearchParams(
136
- options.merge
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
+ };
@@ -1,10 +1,12 @@
1
- import { HttpClient } from "@alepha/server";
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 = (): HttpClient => {
5
- return useInject(HttpClient);
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
  };
@@ -1,18 +1,12 @@
1
- import type { Class } from "@alepha/core";
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: Class<T>): T => {
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 (error) {
53
+ } catch (_error) {
57
54
  return {};
58
55
  }
59
56
  };
@@ -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()
@@ -1,4 +1,4 @@
1
- import { $inject, Alepha, __bind } from "@alepha/core";
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";
@@ -1,15 +1,22 @@
1
- export { default as NestedView } from "./components/NestedView.tsx";
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/useInject.ts";
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, __bind } from "@alepha/core";
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
- head: state.head,
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 (!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
- };
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
- element: this.root(state, options.context),
144
- layers: options.state.layers,
145
- head: state.head,
138
+ context,
139
+ state,
146
140
  };
147
141
  }
148
142
 
149
- public root(state: RouterState, context: PageReactContext = {}): ReactNode {
143
+ public root(state: RouterState, context: PageReactContext): ReactNode {
150
144
  return this.pageDescriptorProvider.root(state, context);
151
145
  }
152
146
  }