@alepha/react 0.9.2 → 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.
Files changed (40) hide show
  1. package/README.md +46 -0
  2. package/dist/index.browser.js +378 -325
  3. package/dist/index.browser.js.map +1 -1
  4. package/dist/index.cjs +570 -458
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +305 -213
  7. package/dist/index.d.cts.map +1 -1
  8. package/dist/index.d.ts +304 -212
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +567 -460
  11. package/dist/index.js.map +1 -1
  12. package/package.json +16 -13
  13. package/src/components/ErrorViewer.tsx +1 -1
  14. package/src/components/Link.tsx +4 -24
  15. package/src/components/NestedView.tsx +20 -9
  16. package/src/components/NotFound.tsx +5 -2
  17. package/src/descriptors/$page.ts +86 -12
  18. package/src/errors/Redirection.ts +13 -0
  19. package/src/hooks/useActive.ts +28 -30
  20. package/src/hooks/useAlepha.ts +16 -2
  21. package/src/hooks/useClient.ts +7 -2
  22. package/src/hooks/useInject.ts +4 -1
  23. package/src/hooks/useQueryParams.ts +9 -6
  24. package/src/hooks/useRouter.ts +18 -30
  25. package/src/hooks/useRouterEvents.ts +7 -4
  26. package/src/hooks/useRouterState.ts +8 -20
  27. package/src/hooks/useSchema.ts +10 -15
  28. package/src/hooks/useStore.ts +9 -8
  29. package/src/index.browser.ts +11 -11
  30. package/src/index.shared.ts +4 -5
  31. package/src/index.ts +21 -30
  32. package/src/providers/ReactBrowserProvider.ts +155 -65
  33. package/src/providers/ReactBrowserRouterProvider.ts +132 -0
  34. package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +164 -112
  35. package/src/providers/ReactServerProvider.ts +100 -68
  36. package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +75 -61
  37. package/src/contexts/RouterContext.ts +0 -14
  38. package/src/errors/RedirectionError.ts +0 -10
  39. package/src/providers/BrowserRouterProvider.ts +0 -146
  40. package/src/providers/ReactBrowserRenderer.ts +0 -93
@@ -1,146 +0,0 @@
1
- import { $hook, $inject, $logger, Alepha } from "@alepha/core";
2
- import { type Route, RouterProvider } from "@alepha/router";
3
- import { createElement, type ReactNode } from "react";
4
- import NotFoundPage from "../components/NotFound.tsx";
5
- import {
6
- isPageRoute,
7
- PageDescriptorProvider,
8
- type PageReactContext,
9
- type PageRequest,
10
- type PageRoute,
11
- type PageRouteEntry,
12
- type RouterRenderResult,
13
- type RouterState,
14
- type TransitionOptions,
15
- } from "./PageDescriptorProvider.ts";
16
-
17
- export interface BrowserRoute extends Route {
18
- page: PageRoute;
19
- }
20
-
21
- export class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
22
- protected readonly log = $logger();
23
- protected readonly alepha = $inject(Alepha);
24
- protected readonly pageDescriptorProvider = $inject(PageDescriptorProvider);
25
-
26
- public add(entry: PageRouteEntry) {
27
- this.pageDescriptorProvider.add(entry);
28
- }
29
-
30
- protected readonly configure = $hook({
31
- on: "configure",
32
- handler: async () => {
33
- for (const page of this.pageDescriptorProvider.getPages()) {
34
- // mount only if a view is provided
35
- if (page.component || page.lazy) {
36
- this.push({
37
- path: page.match,
38
- page,
39
- });
40
- }
41
- }
42
- },
43
- });
44
-
45
- public async transition(
46
- url: URL,
47
- options: TransitionOptions = {},
48
- ): Promise<RouterRenderResult> {
49
- const { pathname, search } = url;
50
- const state: RouterState = {
51
- pathname,
52
- search,
53
- layers: [],
54
- };
55
-
56
- const context = {
57
- url,
58
- query: {},
59
- params: {},
60
- onError: () => null,
61
- ...(options.context ?? {}),
62
- } as PageRequest;
63
-
64
- await this.alepha.emit("react:transition:begin", { state, context });
65
-
66
- try {
67
- const previous = options.previous;
68
- const { route, params } = this.match(pathname);
69
-
70
- const query: Record<string, string> = {};
71
- if (search) {
72
- for (const [key, value] of new URLSearchParams(search).entries()) {
73
- query[key] = String(value);
74
- }
75
- }
76
-
77
- context.query = query;
78
- context.params = params ?? {};
79
- context.previous = previous;
80
-
81
- if (isPageRoute(route)) {
82
- const result = await this.pageDescriptorProvider.createLayers(
83
- route.page,
84
- context,
85
- );
86
-
87
- if (result.redirect) {
88
- return {
89
- redirect: result.redirect,
90
- state,
91
- context,
92
- };
93
- }
94
-
95
- state.layers = result.layers;
96
- }
97
-
98
- if (state.layers.length === 0) {
99
- state.layers.push({
100
- name: "not-found",
101
- element: createElement(NotFoundPage),
102
- index: 0,
103
- path: "/",
104
- });
105
- }
106
-
107
- await this.alepha.emit("react:transition:success", { state, context });
108
- } catch (e) {
109
- this.log.error(e);
110
- state.layers = [
111
- {
112
- name: "error",
113
- element: this.pageDescriptorProvider.renderError(e as Error),
114
- index: 0,
115
- path: "/",
116
- },
117
- ];
118
-
119
- await this.alepha.emit("react:transition:error", {
120
- error: e as Error,
121
- state,
122
- context,
123
- });
124
- }
125
-
126
- if (options.state) {
127
- options.state.layers = state.layers;
128
- options.state.pathname = state.pathname;
129
- options.state.search = state.search;
130
- }
131
-
132
- await this.alepha.emit("react:transition:end", {
133
- state: options.state,
134
- context,
135
- });
136
-
137
- return {
138
- context,
139
- state,
140
- };
141
- }
142
-
143
- public root(state: RouterState, context: PageReactContext): ReactNode {
144
- return this.pageDescriptorProvider.root(state, context);
145
- }
146
- }
@@ -1,93 +0,0 @@
1
- import { $env, $hook, $inject, $logger, type Static, t } from "@alepha/core";
2
- import type { ApiLinksResponse } from "@alepha/server";
3
- import type { Root } from "react-dom/client";
4
- import { createRoot, hydrateRoot } from "react-dom/client";
5
- import { BrowserRouterProvider } from "./BrowserRouterProvider.ts";
6
- import type {
7
- PreviousLayerData,
8
- TransitionOptions,
9
- } from "./PageDescriptorProvider.ts";
10
- import { ReactBrowserProvider } from "./ReactBrowserProvider.ts";
11
-
12
- const envSchema = t.object({
13
- REACT_ROOT_ID: t.string({ default: "root" }),
14
- });
15
-
16
- declare module "@alepha/core" {
17
- interface Env extends Partial<Static<typeof envSchema>> {}
18
- }
19
-
20
- export interface ReactBrowserRendererOptions {
21
- scrollRestoration?: "top" | "manual";
22
- }
23
-
24
- // TODO: move to ReactBrowserProvider when it will be removed from server-side imports
25
- export class ReactBrowserRenderer {
26
- protected readonly browserProvider = $inject(ReactBrowserProvider);
27
- protected readonly browserRouterProvider = $inject(BrowserRouterProvider);
28
- protected readonly env = $env(envSchema);
29
- protected readonly log = $logger();
30
-
31
- protected root!: Root;
32
-
33
- public options: ReactBrowserRendererOptions = {
34
- scrollRestoration: "top",
35
- };
36
-
37
- protected getRootElement() {
38
- const root = this.browserProvider.document.getElementById(
39
- this.env.REACT_ROOT_ID,
40
- );
41
- if (root) {
42
- return root;
43
- }
44
-
45
- const div = this.browserProvider.document.createElement("div");
46
- div.id = this.env.REACT_ROOT_ID;
47
-
48
- this.browserProvider.document.body.prepend(div);
49
-
50
- return div;
51
- }
52
-
53
- public readonly ready = $hook({
54
- on: "react:browser:render",
55
- handler: async ({ state, context, hydration }) => {
56
- const element = this.browserRouterProvider.root(state, context);
57
-
58
- if (hydration?.layers) {
59
- this.root = hydrateRoot(this.getRootElement(), element);
60
- this.log.info("Hydrated root element");
61
- } else {
62
- this.root ??= createRoot(this.getRootElement());
63
- this.root.render(element);
64
- this.log.info("Created root element");
65
- }
66
- },
67
- });
68
-
69
- protected readonly onTransitionEnd = $hook({
70
- on: "react:transition:end",
71
- handler: () => {
72
- if (
73
- this.options.scrollRestoration === "top" &&
74
- typeof window !== "undefined"
75
- ) {
76
- window.scrollTo(0, 0);
77
- }
78
- },
79
- });
80
- }
81
-
82
- // ---------------------------------------------------------------------------------------------------------------------
83
-
84
- export interface RouterGoOptions {
85
- replace?: boolean;
86
- match?: TransitionOptions;
87
- params?: Record<string, string>;
88
- }
89
-
90
- export interface ReactHydrationState {
91
- layers?: Array<PreviousLayerData>;
92
- links?: ApiLinksResponse;
93
- }