@alepha/react 0.7.0 → 0.7.2
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 +59 -22
- package/dist/index.browser.js +42 -6
- package/dist/index.cjs +152 -85
- package/dist/index.d.ts +317 -206
- package/dist/index.js +130 -64
- package/dist/{useActive-DjpZBEuB.cjs → useRouterState-C2uo0jXu.cjs} +319 -140
- package/dist/{useActive-BX41CqY8.js → useRouterState-D5__ZcUV.js} +321 -143
- 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/components/NotFound.tsx +30 -0
- package/src/descriptors/$page.ts +141 -30
- package/src/errors/RedirectionError.ts +4 -1
- package/src/hooks/RouterHookApi.ts +42 -24
- 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 +4 -2
- package/src/index.shared.ts +11 -5
- package/src/index.ts +3 -4
- package/src/providers/BrowserRouterProvider.ts +4 -3
- package/src/providers/PageDescriptorProvider.ts +91 -20
- package/src/providers/ReactBrowserProvider.ts +6 -59
- package/src/providers/ReactBrowserRenderer.ts +72 -0
- package/src/providers/ReactServerProvider.ts +200 -81
- 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
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { __descriptor, OPTIONS, NotImplementedError, KIND, $logger, $inject, Alepha, $hook
|
|
3
|
-
import React, {
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { __descriptor, OPTIONS, NotImplementedError, KIND, t, $logger, $inject, Alepha, $hook } from '@alepha/core';
|
|
3
|
+
import React, { useState, useEffect, createContext, useContext, createElement, StrictMode, useMemo } from 'react';
|
|
4
4
|
import { HttpClient } from '@alepha/server';
|
|
5
|
-
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
6
5
|
import { RouterProvider } from '@alepha/router';
|
|
7
6
|
|
|
8
7
|
const KEY = "PAGE";
|
|
@@ -26,27 +25,161 @@ const $page = (options) => {
|
|
|
26
25
|
[OPTIONS]: options,
|
|
27
26
|
render: () => {
|
|
28
27
|
throw new NotImplementedError(KEY);
|
|
29
|
-
},
|
|
30
|
-
go: () => {
|
|
31
|
-
throw new NotImplementedError(KEY);
|
|
32
|
-
},
|
|
33
|
-
createAnchorProps: () => {
|
|
34
|
-
throw new NotImplementedError(KEY);
|
|
35
|
-
},
|
|
36
|
-
can: () => {
|
|
37
|
-
if (options.can) {
|
|
38
|
-
return options.can();
|
|
39
|
-
}
|
|
40
|
-
return true;
|
|
41
28
|
}
|
|
42
29
|
};
|
|
43
30
|
};
|
|
44
31
|
$page[KIND] = KEY;
|
|
45
32
|
|
|
33
|
+
const ClientOnly = (props) => {
|
|
34
|
+
const [mounted, setMounted] = useState(false);
|
|
35
|
+
useEffect(() => setMounted(true), []);
|
|
36
|
+
if (props.disabled) {
|
|
37
|
+
return props.children;
|
|
38
|
+
}
|
|
39
|
+
return mounted ? props.children : props.fallback;
|
|
40
|
+
};
|
|
41
|
+
|
|
46
42
|
const RouterContext = createContext(
|
|
47
43
|
void 0
|
|
48
44
|
);
|
|
49
45
|
|
|
46
|
+
const useAlepha = () => {
|
|
47
|
+
const routerContext = useContext(RouterContext);
|
|
48
|
+
if (!routerContext) {
|
|
49
|
+
throw new Error("useAlepha must be used within a RouterProvider");
|
|
50
|
+
}
|
|
51
|
+
return routerContext.alepha;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const ErrorViewer = ({ error }) => {
|
|
55
|
+
const [expanded, setExpanded] = useState(false);
|
|
56
|
+
const isProduction = useAlepha().isProduction();
|
|
57
|
+
if (isProduction) {
|
|
58
|
+
return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
|
|
59
|
+
}
|
|
60
|
+
const stackLines = error.stack?.split("\n") ?? [];
|
|
61
|
+
const previewLines = stackLines.slice(0, 5);
|
|
62
|
+
const hiddenLineCount = stackLines.length - previewLines.length;
|
|
63
|
+
const copyToClipboard = (text) => {
|
|
64
|
+
navigator.clipboard.writeText(text).catch((err) => {
|
|
65
|
+
console.error("Clipboard error:", err);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const styles = {
|
|
69
|
+
container: {
|
|
70
|
+
padding: "24px",
|
|
71
|
+
backgroundColor: "#FEF2F2",
|
|
72
|
+
color: "#7F1D1D",
|
|
73
|
+
border: "1px solid #FECACA",
|
|
74
|
+
borderRadius: "16px",
|
|
75
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
76
|
+
fontFamily: "monospace",
|
|
77
|
+
maxWidth: "768px",
|
|
78
|
+
margin: "40px auto"
|
|
79
|
+
},
|
|
80
|
+
heading: {
|
|
81
|
+
fontSize: "20px",
|
|
82
|
+
fontWeight: "bold",
|
|
83
|
+
marginBottom: "4px"
|
|
84
|
+
},
|
|
85
|
+
name: {
|
|
86
|
+
fontSize: "16px",
|
|
87
|
+
fontWeight: 600
|
|
88
|
+
},
|
|
89
|
+
message: {
|
|
90
|
+
fontSize: "14px",
|
|
91
|
+
marginBottom: "16px"
|
|
92
|
+
},
|
|
93
|
+
sectionHeader: {
|
|
94
|
+
display: "flex",
|
|
95
|
+
justifyContent: "space-between",
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
fontSize: "12px",
|
|
98
|
+
marginBottom: "4px",
|
|
99
|
+
color: "#991B1B"
|
|
100
|
+
},
|
|
101
|
+
copyButton: {
|
|
102
|
+
fontSize: "12px",
|
|
103
|
+
color: "#DC2626",
|
|
104
|
+
background: "none",
|
|
105
|
+
border: "none",
|
|
106
|
+
cursor: "pointer",
|
|
107
|
+
textDecoration: "underline"
|
|
108
|
+
},
|
|
109
|
+
stackContainer: {
|
|
110
|
+
backgroundColor: "#FEE2E2",
|
|
111
|
+
padding: "12px",
|
|
112
|
+
borderRadius: "8px",
|
|
113
|
+
fontSize: "13px",
|
|
114
|
+
lineHeight: "1.4",
|
|
115
|
+
overflowX: "auto",
|
|
116
|
+
whiteSpace: "pre-wrap"
|
|
117
|
+
},
|
|
118
|
+
expandLine: {
|
|
119
|
+
color: "#F87171",
|
|
120
|
+
cursor: "pointer",
|
|
121
|
+
marginTop: "8px"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
|
|
125
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
126
|
+
/* @__PURE__ */ jsx("div", { style: styles.heading, children: "\u{1F525} Error" }),
|
|
127
|
+
/* @__PURE__ */ jsx("div", { style: styles.name, children: error.name }),
|
|
128
|
+
/* @__PURE__ */ jsx("div", { style: styles.message, children: error.message })
|
|
129
|
+
] }),
|
|
130
|
+
stackLines.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { style: styles.sectionHeader, children: [
|
|
132
|
+
/* @__PURE__ */ jsx("span", { children: "Stack trace" }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
onClick: () => copyToClipboard(error.stack),
|
|
137
|
+
style: styles.copyButton,
|
|
138
|
+
children: "Copy all"
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxs("pre", { style: styles.stackContainer, children: [
|
|
143
|
+
(expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ jsx("div", { children: line }, i)),
|
|
144
|
+
!expanded && hiddenLineCount > 0 && /* @__PURE__ */ jsxs("div", { style: styles.expandLine, onClick: () => setExpanded(true), children: [
|
|
145
|
+
"+ ",
|
|
146
|
+
hiddenLineCount,
|
|
147
|
+
" more lines..."
|
|
148
|
+
] })
|
|
149
|
+
] })
|
|
150
|
+
] })
|
|
151
|
+
] });
|
|
152
|
+
};
|
|
153
|
+
const ErrorViewerProduction = () => {
|
|
154
|
+
const styles = {
|
|
155
|
+
container: {
|
|
156
|
+
padding: "24px",
|
|
157
|
+
backgroundColor: "#FEF2F2",
|
|
158
|
+
color: "#7F1D1D",
|
|
159
|
+
border: "1px solid #FECACA",
|
|
160
|
+
borderRadius: "16px",
|
|
161
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
162
|
+
fontFamily: "monospace",
|
|
163
|
+
maxWidth: "768px",
|
|
164
|
+
margin: "40px auto",
|
|
165
|
+
textAlign: "center"
|
|
166
|
+
},
|
|
167
|
+
heading: {
|
|
168
|
+
fontSize: "20px",
|
|
169
|
+
fontWeight: "bold",
|
|
170
|
+
marginBottom: "8px"
|
|
171
|
+
},
|
|
172
|
+
message: {
|
|
173
|
+
fontSize: "14px",
|
|
174
|
+
opacity: 0.85
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
|
|
178
|
+
/* @__PURE__ */ jsx("div", { style: styles.heading, children: "\u{1F6A8} An error occurred" }),
|
|
179
|
+
/* @__PURE__ */ jsx("div", { style: styles.message, children: "Something went wrong. Please try again later." })
|
|
180
|
+
] });
|
|
181
|
+
};
|
|
182
|
+
|
|
50
183
|
const RouterLayerContext = createContext(void 0);
|
|
51
184
|
|
|
52
185
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
@@ -143,15 +276,53 @@ const NestedView = (props) => {
|
|
|
143
276
|
return /* @__PURE__ */ jsx(ErrorBoundary, { fallback: app.context.onError, children: element });
|
|
144
277
|
};
|
|
145
278
|
|
|
279
|
+
function NotFoundPage() {
|
|
280
|
+
return /* @__PURE__ */ jsxs(
|
|
281
|
+
"div",
|
|
282
|
+
{
|
|
283
|
+
style: {
|
|
284
|
+
height: "100vh",
|
|
285
|
+
display: "flex",
|
|
286
|
+
flexDirection: "column",
|
|
287
|
+
justifyContent: "center",
|
|
288
|
+
alignItems: "center",
|
|
289
|
+
textAlign: "center",
|
|
290
|
+
fontFamily: "sans-serif",
|
|
291
|
+
padding: "1rem"
|
|
292
|
+
},
|
|
293
|
+
children: [
|
|
294
|
+
/* @__PURE__ */ jsx("h1", { style: { fontSize: "1rem", marginBottom: "0.5rem" }, children: "This page does not exist" }),
|
|
295
|
+
/* @__PURE__ */ jsx(
|
|
296
|
+
"a",
|
|
297
|
+
{
|
|
298
|
+
href: "/",
|
|
299
|
+
style: {
|
|
300
|
+
fontSize: "0.7rem",
|
|
301
|
+
color: "#007bff",
|
|
302
|
+
textDecoration: "none"
|
|
303
|
+
},
|
|
304
|
+
children: "\u2190 Back to home"
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
146
312
|
class RedirectionError extends Error {
|
|
313
|
+
page;
|
|
147
314
|
constructor(page) {
|
|
148
315
|
super("Redirection");
|
|
149
316
|
this.page = page;
|
|
150
317
|
}
|
|
151
318
|
}
|
|
152
319
|
|
|
320
|
+
const envSchema = t.object({
|
|
321
|
+
REACT_STRICT_MODE: t.boolean({ default: true })
|
|
322
|
+
});
|
|
153
323
|
class PageDescriptorProvider {
|
|
154
324
|
log = $logger();
|
|
325
|
+
env = $inject(envSchema);
|
|
155
326
|
alepha = $inject(Alepha);
|
|
156
327
|
pages = [];
|
|
157
328
|
getPages() {
|
|
@@ -165,8 +336,25 @@ class PageDescriptorProvider {
|
|
|
165
336
|
}
|
|
166
337
|
throw new Error(`Page ${name} not found`);
|
|
167
338
|
}
|
|
339
|
+
url(name, options = {}) {
|
|
340
|
+
const page = this.page(name);
|
|
341
|
+
if (!page) {
|
|
342
|
+
throw new Error(`Page ${name} not found`);
|
|
343
|
+
}
|
|
344
|
+
let url = page.path ?? "";
|
|
345
|
+
let parent = page.parent;
|
|
346
|
+
while (parent) {
|
|
347
|
+
url = `${parent.path ?? ""}/${url}`;
|
|
348
|
+
parent = parent.parent;
|
|
349
|
+
}
|
|
350
|
+
url = this.compile(url, options.params ?? {});
|
|
351
|
+
return new URL(
|
|
352
|
+
url.replace(/\/\/+/g, "/") || "/",
|
|
353
|
+
options.base ?? `http://localhost`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
168
356
|
root(state, context) {
|
|
169
|
-
|
|
357
|
+
const root = createElement(
|
|
170
358
|
RouterContext.Provider,
|
|
171
359
|
{
|
|
172
360
|
value: {
|
|
@@ -177,13 +365,17 @@ class PageDescriptorProvider {
|
|
|
177
365
|
},
|
|
178
366
|
createElement(NestedView, {}, state.layers[0]?.element)
|
|
179
367
|
);
|
|
368
|
+
if (this.env.REACT_STRICT_MODE) {
|
|
369
|
+
return createElement(StrictMode, {}, root);
|
|
370
|
+
}
|
|
371
|
+
return root;
|
|
180
372
|
}
|
|
181
373
|
async createLayers(route, request) {
|
|
182
374
|
const { pathname, search } = request.url;
|
|
183
375
|
const layers = [];
|
|
184
376
|
let context = {};
|
|
185
377
|
const stack = [{ route }];
|
|
186
|
-
|
|
378
|
+
request.onError = (error) => this.renderError(error);
|
|
187
379
|
let parent = route.parent;
|
|
188
380
|
while (parent) {
|
|
189
381
|
stack.unshift({ route: parent });
|
|
@@ -283,23 +475,26 @@ class PageDescriptorProvider {
|
|
|
283
475
|
const path = acc.replace(/\/+/, "/");
|
|
284
476
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
285
477
|
if (localErrorHandler) {
|
|
286
|
-
onError = localErrorHandler;
|
|
478
|
+
request.onError = localErrorHandler;
|
|
287
479
|
}
|
|
288
480
|
if (it.error) {
|
|
289
|
-
|
|
481
|
+
let element2 = await request.onError(it.error);
|
|
482
|
+
if (element2 === null) {
|
|
483
|
+
element2 = this.renderError(it.error);
|
|
484
|
+
}
|
|
290
485
|
layers.push({
|
|
291
486
|
props,
|
|
292
487
|
error: it.error,
|
|
293
488
|
name: it.route.name,
|
|
294
489
|
part: it.route.path,
|
|
295
490
|
config: it.config,
|
|
296
|
-
element: this.renderView(i + 1, path,
|
|
491
|
+
element: this.renderView(i + 1, path, element2, it.route),
|
|
297
492
|
index: i + 1,
|
|
298
493
|
path
|
|
299
494
|
});
|
|
300
495
|
break;
|
|
301
496
|
}
|
|
302
|
-
const
|
|
497
|
+
const element = await this.createElement(it.route, {
|
|
303
498
|
...props,
|
|
304
499
|
...context
|
|
305
500
|
});
|
|
@@ -308,7 +503,7 @@ class PageDescriptorProvider {
|
|
|
308
503
|
props,
|
|
309
504
|
part: it.route.path,
|
|
310
505
|
config: it.config,
|
|
311
|
-
element: this.renderView(i + 1, path,
|
|
506
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
312
507
|
index: i + 1,
|
|
313
508
|
path
|
|
314
509
|
});
|
|
@@ -364,8 +559,8 @@ class PageDescriptorProvider {
|
|
|
364
559
|
ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
|
|
365
560
|
}
|
|
366
561
|
}
|
|
367
|
-
renderError(
|
|
368
|
-
return createElement(
|
|
562
|
+
renderError(error) {
|
|
563
|
+
return createElement(ErrorViewer, { error });
|
|
369
564
|
}
|
|
370
565
|
renderEmptyView() {
|
|
371
566
|
return createElement(NestedView, {});
|
|
@@ -390,7 +585,13 @@ class PageDescriptorProvider {
|
|
|
390
585
|
}
|
|
391
586
|
return path;
|
|
392
587
|
}
|
|
393
|
-
renderView(index, path, view
|
|
588
|
+
renderView(index, path, view, page) {
|
|
589
|
+
view ??= this.renderEmptyView();
|
|
590
|
+
const element = page.client ? createElement(
|
|
591
|
+
ClientOnly,
|
|
592
|
+
typeof page.client === "object" ? page.client : {},
|
|
593
|
+
view
|
|
594
|
+
) : view;
|
|
394
595
|
return createElement(
|
|
395
596
|
RouterLayerContext.Provider,
|
|
396
597
|
{
|
|
@@ -399,29 +600,41 @@ class PageDescriptorProvider {
|
|
|
399
600
|
path
|
|
400
601
|
}
|
|
401
602
|
},
|
|
402
|
-
|
|
603
|
+
element
|
|
403
604
|
);
|
|
404
605
|
}
|
|
405
606
|
configure = $hook({
|
|
406
607
|
name: "configure",
|
|
407
608
|
handler: () => {
|
|
609
|
+
let hasNotFoundHandler = false;
|
|
408
610
|
const pages = this.alepha.getDescriptorValues($page);
|
|
409
611
|
for (const { value, key } of pages) {
|
|
410
612
|
value[OPTIONS].name ??= key;
|
|
613
|
+
}
|
|
614
|
+
for (const { value } of pages) {
|
|
411
615
|
if (value[OPTIONS].parent) {
|
|
412
616
|
continue;
|
|
413
617
|
}
|
|
618
|
+
if (value[OPTIONS].path === "/*") {
|
|
619
|
+
hasNotFoundHandler = true;
|
|
620
|
+
}
|
|
414
621
|
this.add(this.map(pages, value));
|
|
415
622
|
}
|
|
623
|
+
if (!hasNotFoundHandler && pages.length > 0) {
|
|
624
|
+
this.add({
|
|
625
|
+
path: "/*",
|
|
626
|
+
name: "notFound",
|
|
627
|
+
cache: true,
|
|
628
|
+
component: NotFoundPage,
|
|
629
|
+
afterHandler: ({ reply }) => {
|
|
630
|
+
reply.status = 404;
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
416
634
|
}
|
|
417
635
|
});
|
|
418
636
|
map(pages, target) {
|
|
419
637
|
const children = target[OPTIONS].children ?? [];
|
|
420
|
-
for (const it of pages) {
|
|
421
|
-
if (it.value[OPTIONS].parent === target) {
|
|
422
|
-
children.push(it.value);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
638
|
return {
|
|
426
639
|
...target[OPTIONS],
|
|
427
640
|
parent: void 0,
|
|
@@ -570,7 +783,7 @@ class BrowserRouterProvider extends RouterProvider {
|
|
|
570
783
|
if (state.layers.length === 0) {
|
|
571
784
|
state.layers.push({
|
|
572
785
|
name: "not-found",
|
|
573
|
-
element:
|
|
786
|
+
element: createElement(NotFoundPage),
|
|
574
787
|
index: 0,
|
|
575
788
|
path: "/"
|
|
576
789
|
});
|
|
@@ -611,16 +824,12 @@ class BrowserRouterProvider extends RouterProvider {
|
|
|
611
824
|
}
|
|
612
825
|
}
|
|
613
826
|
|
|
614
|
-
const envSchema = t.object({
|
|
615
|
-
REACT_ROOT_ID: t.string({ default: "root" })
|
|
616
|
-
});
|
|
617
827
|
class ReactBrowserProvider {
|
|
618
828
|
log = $logger();
|
|
619
829
|
client = $inject(HttpClient);
|
|
620
830
|
alepha = $inject(Alepha);
|
|
621
831
|
router = $inject(BrowserRouterProvider);
|
|
622
832
|
headProvider = $inject(BrowserHeadProvider);
|
|
623
|
-
env = $inject(envSchema);
|
|
624
833
|
root;
|
|
625
834
|
transitioning;
|
|
626
835
|
state = {
|
|
@@ -658,11 +867,6 @@ class ReactBrowserProvider {
|
|
|
658
867
|
}
|
|
659
868
|
await this.render({ previous });
|
|
660
869
|
}
|
|
661
|
-
/**
|
|
662
|
-
*
|
|
663
|
-
* @param url
|
|
664
|
-
* @param options
|
|
665
|
-
*/
|
|
666
870
|
async go(url, options = {}) {
|
|
667
871
|
const result = await this.render({
|
|
668
872
|
url
|
|
@@ -696,8 +900,6 @@ class ReactBrowserProvider {
|
|
|
696
900
|
}
|
|
697
901
|
/**
|
|
698
902
|
* Get embedded layers from the server.
|
|
699
|
-
*
|
|
700
|
-
* @protected
|
|
701
903
|
*/
|
|
702
904
|
getHydrationState() {
|
|
703
905
|
try {
|
|
@@ -708,32 +910,14 @@ class ReactBrowserProvider {
|
|
|
708
910
|
console.error(error);
|
|
709
911
|
}
|
|
710
912
|
}
|
|
711
|
-
/**
|
|
712
|
-
*
|
|
713
|
-
* @protected
|
|
714
|
-
*/
|
|
715
|
-
getRootElement() {
|
|
716
|
-
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
717
|
-
if (root) {
|
|
718
|
-
return root;
|
|
719
|
-
}
|
|
720
|
-
const div = this.document.createElement("div");
|
|
721
|
-
div.id = this.env.REACT_ROOT_ID;
|
|
722
|
-
this.document.body.prepend(div);
|
|
723
|
-
return div;
|
|
724
|
-
}
|
|
725
913
|
// -------------------------------------------------------------------------------------------------------------------
|
|
726
|
-
/**
|
|
727
|
-
*
|
|
728
|
-
* @protected
|
|
729
|
-
*/
|
|
730
914
|
ready = $hook({
|
|
731
915
|
name: "ready",
|
|
732
916
|
handler: async () => {
|
|
733
917
|
const hydration = this.getHydrationState();
|
|
734
918
|
const previous = hydration?.layers ?? [];
|
|
735
919
|
if (hydration?.links) {
|
|
736
|
-
for (const link of hydration.links) {
|
|
920
|
+
for (const link of hydration.links.links) {
|
|
737
921
|
this.client.pushLink(link);
|
|
738
922
|
}
|
|
739
923
|
}
|
|
@@ -746,15 +930,6 @@ class ReactBrowserProvider {
|
|
|
746
930
|
context,
|
|
747
931
|
hydration
|
|
748
932
|
});
|
|
749
|
-
const element = this.router.root(this.state, context);
|
|
750
|
-
if (previous.length > 0) {
|
|
751
|
-
this.root = hydrateRoot(this.getRootElement(), element);
|
|
752
|
-
this.log.info("Hydrated root element");
|
|
753
|
-
} else {
|
|
754
|
-
this.root ??= createRoot(this.getRootElement());
|
|
755
|
-
this.root.render(element);
|
|
756
|
-
this.log.info("Created root element");
|
|
757
|
-
}
|
|
758
933
|
window.addEventListener("popstate", () => {
|
|
759
934
|
this.render();
|
|
760
935
|
});
|
|
@@ -769,26 +944,18 @@ class ReactBrowserProvider {
|
|
|
769
944
|
}
|
|
770
945
|
|
|
771
946
|
class RouterHookApi {
|
|
772
|
-
constructor(state, layer, browser) {
|
|
947
|
+
constructor(pages, state, layer, browser) {
|
|
948
|
+
this.pages = pages;
|
|
773
949
|
this.state = state;
|
|
774
950
|
this.layer = layer;
|
|
775
951
|
this.browser = browser;
|
|
776
952
|
}
|
|
777
|
-
/**
|
|
778
|
-
*
|
|
779
|
-
*/
|
|
780
953
|
get current() {
|
|
781
954
|
return this.state;
|
|
782
955
|
}
|
|
783
|
-
/**
|
|
784
|
-
*
|
|
785
|
-
*/
|
|
786
956
|
get pathname() {
|
|
787
957
|
return this.state.pathname;
|
|
788
958
|
}
|
|
789
|
-
/**
|
|
790
|
-
*
|
|
791
|
-
*/
|
|
792
959
|
get query() {
|
|
793
960
|
const query = {};
|
|
794
961
|
for (const [key, value] of new URLSearchParams(
|
|
@@ -798,22 +965,12 @@ class RouterHookApi {
|
|
|
798
965
|
}
|
|
799
966
|
return query;
|
|
800
967
|
}
|
|
801
|
-
/**
|
|
802
|
-
*
|
|
803
|
-
*/
|
|
804
968
|
async back() {
|
|
805
969
|
this.browser?.history.back();
|
|
806
970
|
}
|
|
807
|
-
/**
|
|
808
|
-
*
|
|
809
|
-
*/
|
|
810
971
|
async forward() {
|
|
811
972
|
this.browser?.history.forward();
|
|
812
973
|
}
|
|
813
|
-
/**
|
|
814
|
-
*
|
|
815
|
-
* @param props
|
|
816
|
-
*/
|
|
817
974
|
async invalidate(props) {
|
|
818
975
|
await this.browser?.invalidate(props);
|
|
819
976
|
}
|
|
@@ -823,23 +980,40 @@ class RouterHookApi {
|
|
|
823
980
|
* @param pathname
|
|
824
981
|
* @param layer
|
|
825
982
|
*/
|
|
826
|
-
createHref(pathname, layer = this.layer) {
|
|
983
|
+
createHref(pathname, layer = this.layer, options = {}) {
|
|
827
984
|
if (typeof pathname === "object") {
|
|
828
985
|
pathname = pathname.options.path ?? "";
|
|
829
986
|
}
|
|
987
|
+
if (options.params) {
|
|
988
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
989
|
+
pathname = pathname.replace(`:${key}`, String(value));
|
|
990
|
+
}
|
|
991
|
+
}
|
|
830
992
|
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
831
993
|
}
|
|
832
994
|
async go(path, options) {
|
|
833
|
-
|
|
995
|
+
for (const page of this.pages) {
|
|
996
|
+
if (page.name === path) {
|
|
997
|
+
path = page.path ?? "";
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
await this.browser?.go(this.createHref(path, this.layer, options), options);
|
|
834
1002
|
}
|
|
835
|
-
anchor(path) {
|
|
836
|
-
const
|
|
1003
|
+
anchor(path, options = {}) {
|
|
1004
|
+
for (const page of this.pages) {
|
|
1005
|
+
if (page.name === path) {
|
|
1006
|
+
path = page.path ?? "";
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const href = this.createHref(path, this.layer, options);
|
|
837
1011
|
return {
|
|
838
1012
|
href,
|
|
839
1013
|
onClick: (ev) => {
|
|
840
1014
|
ev.stopPropagation();
|
|
841
1015
|
ev.preventDefault();
|
|
842
|
-
this.go(path).catch(console.error);
|
|
1016
|
+
this.go(path, options).catch(console.error);
|
|
843
1017
|
}
|
|
844
1018
|
};
|
|
845
1019
|
}
|
|
@@ -867,8 +1041,12 @@ const useRouter = () => {
|
|
|
867
1041
|
if (!ctx || !layer) {
|
|
868
1042
|
throw new Error("useRouter must be used within a RouterProvider");
|
|
869
1043
|
}
|
|
1044
|
+
const pages = useMemo(() => {
|
|
1045
|
+
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
1046
|
+
}, []);
|
|
870
1047
|
return useMemo(
|
|
871
1048
|
() => new RouterHookApi(
|
|
1049
|
+
pages,
|
|
872
1050
|
ctx.state,
|
|
873
1051
|
layer,
|
|
874
1052
|
ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0
|
|
@@ -879,6 +1057,7 @@ const useRouter = () => {
|
|
|
879
1057
|
|
|
880
1058
|
const Link = (props) => {
|
|
881
1059
|
React.useContext(RouterContext);
|
|
1060
|
+
const router = useRouter();
|
|
882
1061
|
const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
|
|
883
1062
|
if (!to) {
|
|
884
1063
|
return null;
|
|
@@ -888,8 +1067,49 @@ const Link = (props) => {
|
|
|
888
1067
|
return null;
|
|
889
1068
|
}
|
|
890
1069
|
const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
|
|
1070
|
+
const anchorProps = {
|
|
1071
|
+
...props,
|
|
1072
|
+
to: void 0
|
|
1073
|
+
};
|
|
1074
|
+
return /* @__PURE__ */ jsx("a", { ...router.anchor(to), ...anchorProps, children: props.children ?? name });
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
const useActive = (path) => {
|
|
891
1078
|
const router = useRouter();
|
|
892
|
-
|
|
1079
|
+
const ctx = useContext(RouterContext);
|
|
1080
|
+
const layer = useContext(RouterLayerContext);
|
|
1081
|
+
if (!ctx || !layer) {
|
|
1082
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
1083
|
+
}
|
|
1084
|
+
let name;
|
|
1085
|
+
if (typeof path === "object" && path.options.name) {
|
|
1086
|
+
name = path.options.name;
|
|
1087
|
+
}
|
|
1088
|
+
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
1089
|
+
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
1090
|
+
const [isPending, setPending] = useState(false);
|
|
1091
|
+
const isActive = current === href;
|
|
1092
|
+
useRouterEvents({
|
|
1093
|
+
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
1094
|
+
});
|
|
1095
|
+
return {
|
|
1096
|
+
name,
|
|
1097
|
+
isPending,
|
|
1098
|
+
isActive,
|
|
1099
|
+
anchorProps: {
|
|
1100
|
+
href,
|
|
1101
|
+
onClick: (ev) => {
|
|
1102
|
+
ev.stopPropagation();
|
|
1103
|
+
ev.preventDefault();
|
|
1104
|
+
if (isActive) return;
|
|
1105
|
+
if (isPending) return;
|
|
1106
|
+
setPending(true);
|
|
1107
|
+
router.go(href).then(() => {
|
|
1108
|
+
setPending(false);
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
893
1113
|
};
|
|
894
1114
|
|
|
895
1115
|
const useInject = (clazz) => {
|
|
@@ -900,10 +1120,7 @@ const useInject = (clazz) => {
|
|
|
900
1120
|
return useMemo(() => ctx.alepha.get(clazz), []);
|
|
901
1121
|
};
|
|
902
1122
|
|
|
903
|
-
const useClient = () => {
|
|
904
|
-
return useInject(HttpClient);
|
|
905
|
-
};
|
|
906
|
-
const useApi = () => {
|
|
1123
|
+
const useClient = (_scope) => {
|
|
907
1124
|
return useInject(HttpClient).of();
|
|
908
1125
|
};
|
|
909
1126
|
|
|
@@ -937,7 +1154,7 @@ const encode = (alepha, schema, data) => {
|
|
|
937
1154
|
const decode = (alepha, schema, data) => {
|
|
938
1155
|
try {
|
|
939
1156
|
return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
|
|
940
|
-
} catch (
|
|
1157
|
+
} catch (_error) {
|
|
941
1158
|
return {};
|
|
942
1159
|
}
|
|
943
1160
|
};
|
|
@@ -955,43 +1172,4 @@ const useRouterState = () => {
|
|
|
955
1172
|
return state;
|
|
956
1173
|
};
|
|
957
1174
|
|
|
958
|
-
|
|
959
|
-
const router = useRouter();
|
|
960
|
-
const ctx = useContext(RouterContext);
|
|
961
|
-
const layer = useContext(RouterLayerContext);
|
|
962
|
-
if (!ctx || !layer) {
|
|
963
|
-
throw new Error("useRouter must be used within a RouterProvider");
|
|
964
|
-
}
|
|
965
|
-
let name;
|
|
966
|
-
if (typeof path === "object" && path.options.name) {
|
|
967
|
-
name = path.options.name;
|
|
968
|
-
}
|
|
969
|
-
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
970
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
971
|
-
const [isPending, setPending] = useState(false);
|
|
972
|
-
const isActive = current === href;
|
|
973
|
-
useRouterEvents({
|
|
974
|
-
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
975
|
-
});
|
|
976
|
-
return {
|
|
977
|
-
name,
|
|
978
|
-
isPending,
|
|
979
|
-
isActive,
|
|
980
|
-
anchorProps: {
|
|
981
|
-
href,
|
|
982
|
-
onClick: (ev) => {
|
|
983
|
-
ev.stopPropagation();
|
|
984
|
-
ev.preventDefault();
|
|
985
|
-
if (isActive) return;
|
|
986
|
-
if (isPending) return;
|
|
987
|
-
setPending(true);
|
|
988
|
-
router.go(href).then(() => {
|
|
989
|
-
setPending(false);
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
export { $page as $, BrowserRouterProvider as B, ErrorBoundary as E, Link as L, NestedView as N, PageDescriptorProvider as P, RouterContext as R, RouterLayerContext as a, RouterHookApi as b, useClient as c, useApi as d, useQueryParams as e, useRouter as f, useRouterEvents as g, useRouterState as h, useActive as i, RedirectionError as j, isPageRoute as k, ReactBrowserProvider as l, useInject as u };
|
|
997
|
-
//# sourceMappingURL=useActive-BX41CqY8.js.map
|
|
1175
|
+
export { $page as $, BrowserRouterProvider as B, ClientOnly as C, ErrorBoundary as E, Link as L, NestedView as N, PageDescriptorProvider as P, RedirectionError as R, RouterContext as a, RouterLayerContext as b, RouterHookApi as c, useAlepha as d, useClient as e, useInject as f, useQueryParams as g, useRouter as h, useRouterEvents as i, useRouterState as j, isPageRoute as k, ReactBrowserProvider as l, useActive as u };
|