@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.
- package/README.md +1 -1
- package/dist/index.browser.cjs +21 -20
- package/dist/index.browser.js +2 -3
- package/dist/index.cjs +168 -82
- package/dist/index.d.ts +415 -232
- package/dist/index.js +146 -62
- package/dist/{useActive-4QlZKGbw.cjs → useRouterState-AdK-XeM2.cjs} +358 -170
- package/dist/{useActive-ClUsghB5.js → useRouterState-qoMq7Y9J.js} +358 -172
- package/package.json +11 -10
- package/src/components/ClientOnly.tsx +35 -0
- package/src/components/ErrorBoundary.tsx +72 -0
- package/src/components/ErrorViewer.tsx +161 -0
- package/src/components/Link.tsx +10 -4
- package/src/components/NestedView.tsx +28 -4
- package/src/descriptors/$page.ts +143 -38
- package/src/errors/RedirectionError.ts +4 -1
- package/src/hooks/RouterHookApi.ts +58 -35
- package/src/hooks/useAlepha.ts +12 -0
- package/src/hooks/useClient.ts +8 -6
- package/src/hooks/useInject.ts +3 -9
- package/src/hooks/useQueryParams.ts +4 -7
- package/src/hooks/useRouter.ts +6 -0
- package/src/index.browser.ts +1 -1
- package/src/index.shared.ts +11 -4
- package/src/index.ts +7 -4
- package/src/providers/BrowserRouterProvider.ts +27 -33
- package/src/providers/PageDescriptorProvider.ts +90 -40
- package/src/providers/ReactBrowserProvider.ts +21 -27
- package/src/providers/ReactServerProvider.ts +215 -77
- 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-4QlZKGbw.cjs.map +0 -1
- package/dist/useActive-ClUsghB5.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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
5
|
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
6
6
|
import { RouterProvider } from '@alepha/router';
|
|
@@ -26,27 +26,161 @@ const $page = (options) => {
|
|
|
26
26
|
[OPTIONS]: options,
|
|
27
27
|
render: () => {
|
|
28
28
|
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
29
|
}
|
|
42
30
|
};
|
|
43
31
|
};
|
|
44
32
|
$page[KIND] = KEY;
|
|
45
33
|
|
|
34
|
+
const ClientOnly = (props) => {
|
|
35
|
+
const [mounted, setMounted] = useState(false);
|
|
36
|
+
useEffect(() => setMounted(true), []);
|
|
37
|
+
if (props.disabled) {
|
|
38
|
+
return props.children;
|
|
39
|
+
}
|
|
40
|
+
return mounted ? props.children : props.fallback;
|
|
41
|
+
};
|
|
42
|
+
|
|
46
43
|
const RouterContext = createContext(
|
|
47
44
|
void 0
|
|
48
45
|
);
|
|
49
46
|
|
|
47
|
+
const useAlepha = () => {
|
|
48
|
+
const routerContext = useContext(RouterContext);
|
|
49
|
+
if (!routerContext) {
|
|
50
|
+
throw new Error("useAlepha must be used within a RouterProvider");
|
|
51
|
+
}
|
|
52
|
+
return routerContext.alepha;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ErrorViewer = ({ error }) => {
|
|
56
|
+
const [expanded, setExpanded] = useState(false);
|
|
57
|
+
const isProduction = useAlepha().isProduction();
|
|
58
|
+
if (isProduction) {
|
|
59
|
+
return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
|
|
60
|
+
}
|
|
61
|
+
const stackLines = error.stack?.split("\n") ?? [];
|
|
62
|
+
const previewLines = stackLines.slice(0, 5);
|
|
63
|
+
const hiddenLineCount = stackLines.length - previewLines.length;
|
|
64
|
+
const copyToClipboard = (text) => {
|
|
65
|
+
navigator.clipboard.writeText(text).catch((err) => {
|
|
66
|
+
console.error("Clipboard error:", err);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const styles = {
|
|
70
|
+
container: {
|
|
71
|
+
padding: "24px",
|
|
72
|
+
backgroundColor: "#FEF2F2",
|
|
73
|
+
color: "#7F1D1D",
|
|
74
|
+
border: "1px solid #FECACA",
|
|
75
|
+
borderRadius: "16px",
|
|
76
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
77
|
+
fontFamily: "monospace",
|
|
78
|
+
maxWidth: "768px",
|
|
79
|
+
margin: "40px auto"
|
|
80
|
+
},
|
|
81
|
+
heading: {
|
|
82
|
+
fontSize: "20px",
|
|
83
|
+
fontWeight: "bold",
|
|
84
|
+
marginBottom: "4px"
|
|
85
|
+
},
|
|
86
|
+
name: {
|
|
87
|
+
fontSize: "16px",
|
|
88
|
+
fontWeight: 600
|
|
89
|
+
},
|
|
90
|
+
message: {
|
|
91
|
+
fontSize: "14px",
|
|
92
|
+
marginBottom: "16px"
|
|
93
|
+
},
|
|
94
|
+
sectionHeader: {
|
|
95
|
+
display: "flex",
|
|
96
|
+
justifyContent: "space-between",
|
|
97
|
+
alignItems: "center",
|
|
98
|
+
fontSize: "12px",
|
|
99
|
+
marginBottom: "4px",
|
|
100
|
+
color: "#991B1B"
|
|
101
|
+
},
|
|
102
|
+
copyButton: {
|
|
103
|
+
fontSize: "12px",
|
|
104
|
+
color: "#DC2626",
|
|
105
|
+
background: "none",
|
|
106
|
+
border: "none",
|
|
107
|
+
cursor: "pointer",
|
|
108
|
+
textDecoration: "underline"
|
|
109
|
+
},
|
|
110
|
+
stackContainer: {
|
|
111
|
+
backgroundColor: "#FEE2E2",
|
|
112
|
+
padding: "12px",
|
|
113
|
+
borderRadius: "8px",
|
|
114
|
+
fontSize: "13px",
|
|
115
|
+
lineHeight: "1.4",
|
|
116
|
+
overflowX: "auto",
|
|
117
|
+
whiteSpace: "pre-wrap"
|
|
118
|
+
},
|
|
119
|
+
expandLine: {
|
|
120
|
+
color: "#F87171",
|
|
121
|
+
cursor: "pointer",
|
|
122
|
+
marginTop: "8px"
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
|
|
126
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
127
|
+
/* @__PURE__ */ jsx("div", { style: styles.heading, children: "\u{1F525} Error" }),
|
|
128
|
+
/* @__PURE__ */ jsx("div", { style: styles.name, children: error.name }),
|
|
129
|
+
/* @__PURE__ */ jsx("div", { style: styles.message, children: error.message })
|
|
130
|
+
] }),
|
|
131
|
+
stackLines.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { style: styles.sectionHeader, children: [
|
|
133
|
+
/* @__PURE__ */ jsx("span", { children: "Stack trace" }),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
onClick: () => copyToClipboard(error.stack),
|
|
138
|
+
style: styles.copyButton,
|
|
139
|
+
children: "Copy all"
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsxs("pre", { style: styles.stackContainer, children: [
|
|
144
|
+
(expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ jsx("div", { children: line }, i)),
|
|
145
|
+
!expanded && hiddenLineCount > 0 && /* @__PURE__ */ jsxs("div", { style: styles.expandLine, onClick: () => setExpanded(true), children: [
|
|
146
|
+
"+ ",
|
|
147
|
+
hiddenLineCount,
|
|
148
|
+
" more lines..."
|
|
149
|
+
] })
|
|
150
|
+
] })
|
|
151
|
+
] })
|
|
152
|
+
] });
|
|
153
|
+
};
|
|
154
|
+
const ErrorViewerProduction = () => {
|
|
155
|
+
const styles = {
|
|
156
|
+
container: {
|
|
157
|
+
padding: "24px",
|
|
158
|
+
backgroundColor: "#FEF2F2",
|
|
159
|
+
color: "#7F1D1D",
|
|
160
|
+
border: "1px solid #FECACA",
|
|
161
|
+
borderRadius: "16px",
|
|
162
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
163
|
+
fontFamily: "monospace",
|
|
164
|
+
maxWidth: "768px",
|
|
165
|
+
margin: "40px auto",
|
|
166
|
+
textAlign: "center"
|
|
167
|
+
},
|
|
168
|
+
heading: {
|
|
169
|
+
fontSize: "20px",
|
|
170
|
+
fontWeight: "bold",
|
|
171
|
+
marginBottom: "8px"
|
|
172
|
+
},
|
|
173
|
+
message: {
|
|
174
|
+
fontSize: "14px",
|
|
175
|
+
opacity: 0.85
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
|
|
179
|
+
/* @__PURE__ */ jsx("div", { style: styles.heading, children: "\u{1F6A8} An error occurred" }),
|
|
180
|
+
/* @__PURE__ */ jsx("div", { style: styles.message, children: "Something went wrong. Please try again later." })
|
|
181
|
+
] });
|
|
182
|
+
};
|
|
183
|
+
|
|
50
184
|
const RouterLayerContext = createContext(void 0);
|
|
51
185
|
|
|
52
186
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
@@ -91,6 +225,36 @@ const useRouterEvents = (opts = {}, deps = []) => {
|
|
|
91
225
|
}, deps);
|
|
92
226
|
};
|
|
93
227
|
|
|
228
|
+
class ErrorBoundary extends React.Component {
|
|
229
|
+
constructor(props) {
|
|
230
|
+
super(props);
|
|
231
|
+
this.state = {};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Update state so the next render shows the fallback UI.
|
|
235
|
+
*/
|
|
236
|
+
static getDerivedStateFromError(error) {
|
|
237
|
+
return {
|
|
238
|
+
error
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Lifecycle method called when an error is caught.
|
|
243
|
+
* You can log the error or perform side effects here.
|
|
244
|
+
*/
|
|
245
|
+
componentDidCatch(error, info) {
|
|
246
|
+
if (this.props.onError) {
|
|
247
|
+
this.props.onError(error, info);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
render() {
|
|
251
|
+
if (this.state.error) {
|
|
252
|
+
return this.props.fallback(this.state.error);
|
|
253
|
+
}
|
|
254
|
+
return this.props.children;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
94
258
|
const NestedView = (props) => {
|
|
95
259
|
const app = useContext(RouterContext);
|
|
96
260
|
const layer = useContext(RouterLayerContext);
|
|
@@ -106,18 +270,27 @@ const NestedView = (props) => {
|
|
|
106
270
|
},
|
|
107
271
|
[app]
|
|
108
272
|
);
|
|
109
|
-
|
|
273
|
+
if (!app) {
|
|
274
|
+
throw new Error("NestedView must be used within a RouterContext.");
|
|
275
|
+
}
|
|
276
|
+
const element = view ?? props.children ?? null;
|
|
277
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, { fallback: app.context.onError, children: element });
|
|
110
278
|
};
|
|
111
279
|
|
|
112
280
|
class RedirectionError extends Error {
|
|
281
|
+
page;
|
|
113
282
|
constructor(page) {
|
|
114
283
|
super("Redirection");
|
|
115
284
|
this.page = page;
|
|
116
285
|
}
|
|
117
286
|
}
|
|
118
287
|
|
|
288
|
+
const envSchema$1 = t.object({
|
|
289
|
+
REACT_STRICT_MODE: t.boolean({ default: true })
|
|
290
|
+
});
|
|
119
291
|
class PageDescriptorProvider {
|
|
120
292
|
log = $logger();
|
|
293
|
+
env = $inject(envSchema$1);
|
|
121
294
|
alepha = $inject(Alepha);
|
|
122
295
|
pages = [];
|
|
123
296
|
getPages() {
|
|
@@ -131,8 +304,25 @@ class PageDescriptorProvider {
|
|
|
131
304
|
}
|
|
132
305
|
throw new Error(`Page ${name} not found`);
|
|
133
306
|
}
|
|
134
|
-
|
|
135
|
-
|
|
307
|
+
url(name, options = {}) {
|
|
308
|
+
const page = this.page(name);
|
|
309
|
+
if (!page) {
|
|
310
|
+
throw new Error(`Page ${name} not found`);
|
|
311
|
+
}
|
|
312
|
+
let url = page.path ?? "";
|
|
313
|
+
let parent = page.parent;
|
|
314
|
+
while (parent) {
|
|
315
|
+
url = `${parent.path ?? ""}/${url}`;
|
|
316
|
+
parent = parent.parent;
|
|
317
|
+
}
|
|
318
|
+
url = this.compile(url, options.params ?? {});
|
|
319
|
+
return new URL(
|
|
320
|
+
url.replace(/\/\/+/g, "/") || "/",
|
|
321
|
+
options.base ?? `http://localhost`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
root(state, context) {
|
|
325
|
+
const root = createElement(
|
|
136
326
|
RouterContext.Provider,
|
|
137
327
|
{
|
|
138
328
|
value: {
|
|
@@ -143,12 +333,17 @@ class PageDescriptorProvider {
|
|
|
143
333
|
},
|
|
144
334
|
createElement(NestedView, {}, state.layers[0]?.element)
|
|
145
335
|
);
|
|
336
|
+
if (this.env.REACT_STRICT_MODE) {
|
|
337
|
+
return createElement(StrictMode, {}, root);
|
|
338
|
+
}
|
|
339
|
+
return root;
|
|
146
340
|
}
|
|
147
341
|
async createLayers(route, request) {
|
|
148
342
|
const { pathname, search } = request.url;
|
|
149
343
|
const layers = [];
|
|
150
344
|
let context = {};
|
|
151
345
|
const stack = [{ route }];
|
|
346
|
+
request.onError = (error) => this.renderError(error);
|
|
152
347
|
let parent = route.parent;
|
|
153
348
|
while (parent) {
|
|
154
349
|
stack.unshift({ route: parent });
|
|
@@ -220,7 +415,6 @@ class PageDescriptorProvider {
|
|
|
220
415
|
return {
|
|
221
416
|
layers: [],
|
|
222
417
|
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
223
|
-
head: request.head,
|
|
224
418
|
pathname,
|
|
225
419
|
search
|
|
226
420
|
};
|
|
@@ -247,26 +441,28 @@ class PageDescriptorProvider {
|
|
|
247
441
|
acc += "/";
|
|
248
442
|
acc += it.route.path ? this.compile(it.route.path, params) : "";
|
|
249
443
|
const path = acc.replace(/\/+/, "/");
|
|
444
|
+
const localErrorHandler = this.getErrorHandler(it.route);
|
|
445
|
+
if (localErrorHandler) {
|
|
446
|
+
request.onError = localErrorHandler;
|
|
447
|
+
}
|
|
250
448
|
if (it.error) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
url: ""
|
|
256
|
-
}) : this.renderError(it.error));
|
|
449
|
+
let element2 = await request.onError(it.error);
|
|
450
|
+
if (element2 === null) {
|
|
451
|
+
element2 = this.renderError(it.error);
|
|
452
|
+
}
|
|
257
453
|
layers.push({
|
|
258
454
|
props,
|
|
259
455
|
error: it.error,
|
|
260
456
|
name: it.route.name,
|
|
261
457
|
part: it.route.path,
|
|
262
458
|
config: it.config,
|
|
263
|
-
element: this.renderView(i + 1, path,
|
|
459
|
+
element: this.renderView(i + 1, path, element2, it.route),
|
|
264
460
|
index: i + 1,
|
|
265
461
|
path
|
|
266
462
|
});
|
|
267
463
|
break;
|
|
268
464
|
}
|
|
269
|
-
const
|
|
465
|
+
const element = await this.createElement(it.route, {
|
|
270
466
|
...props,
|
|
271
467
|
...context
|
|
272
468
|
});
|
|
@@ -275,12 +471,12 @@ class PageDescriptorProvider {
|
|
|
275
471
|
props,
|
|
276
472
|
part: it.route.path,
|
|
277
473
|
config: it.config,
|
|
278
|
-
element: this.renderView(i + 1, path,
|
|
474
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
279
475
|
index: i + 1,
|
|
280
476
|
path
|
|
281
477
|
});
|
|
282
478
|
}
|
|
283
|
-
return { layers,
|
|
479
|
+
return { layers, pathname, search };
|
|
284
480
|
}
|
|
285
481
|
getErrorHandler(route) {
|
|
286
482
|
if (route.errorHandler) return route.errorHandler;
|
|
@@ -331,8 +527,8 @@ class PageDescriptorProvider {
|
|
|
331
527
|
ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
|
|
332
528
|
}
|
|
333
529
|
}
|
|
334
|
-
renderError(
|
|
335
|
-
return createElement(
|
|
530
|
+
renderError(error) {
|
|
531
|
+
return createElement(ErrorViewer, { error });
|
|
336
532
|
}
|
|
337
533
|
renderEmptyView() {
|
|
338
534
|
return createElement(NestedView, {});
|
|
@@ -357,7 +553,13 @@ class PageDescriptorProvider {
|
|
|
357
553
|
}
|
|
358
554
|
return path;
|
|
359
555
|
}
|
|
360
|
-
renderView(index, path, view
|
|
556
|
+
renderView(index, path, view, page) {
|
|
557
|
+
view ??= this.renderEmptyView();
|
|
558
|
+
const element = page.client ? createElement(
|
|
559
|
+
ClientOnly,
|
|
560
|
+
typeof page.client === "object" ? page.client : {},
|
|
561
|
+
view
|
|
562
|
+
) : view;
|
|
361
563
|
return createElement(
|
|
362
564
|
RouterLayerContext.Provider,
|
|
363
565
|
{
|
|
@@ -366,7 +568,7 @@ class PageDescriptorProvider {
|
|
|
366
568
|
path
|
|
367
569
|
}
|
|
368
570
|
},
|
|
369
|
-
|
|
571
|
+
element
|
|
370
572
|
);
|
|
371
573
|
}
|
|
372
574
|
configure = $hook({
|
|
@@ -375,6 +577,8 @@ class PageDescriptorProvider {
|
|
|
375
577
|
const pages = this.alepha.getDescriptorValues($page);
|
|
376
578
|
for (const { value, key } of pages) {
|
|
377
579
|
value[OPTIONS].name ??= key;
|
|
580
|
+
}
|
|
581
|
+
for (const { value } of pages) {
|
|
378
582
|
if (value[OPTIONS].parent) {
|
|
379
583
|
continue;
|
|
380
584
|
}
|
|
@@ -384,11 +588,6 @@ class PageDescriptorProvider {
|
|
|
384
588
|
});
|
|
385
589
|
map(pages, target) {
|
|
386
590
|
const children = target[OPTIONS].children ?? [];
|
|
387
|
-
for (const it of pages) {
|
|
388
|
-
if (it.value[OPTIONS].parent === target) {
|
|
389
|
-
children.push(it.value);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
591
|
return {
|
|
393
592
|
...target[OPTIONS],
|
|
394
593
|
parent: void 0,
|
|
@@ -497,10 +696,17 @@ class BrowserRouterProvider extends RouterProvider {
|
|
|
497
696
|
const state = {
|
|
498
697
|
pathname,
|
|
499
698
|
search,
|
|
500
|
-
layers: []
|
|
501
|
-
|
|
699
|
+
layers: []
|
|
700
|
+
};
|
|
701
|
+
const context = {
|
|
702
|
+
url,
|
|
703
|
+
query: {},
|
|
704
|
+
params: {},
|
|
705
|
+
head: {},
|
|
706
|
+
onError: () => null,
|
|
707
|
+
...options.context ?? {}
|
|
502
708
|
};
|
|
503
|
-
await this.alepha.emit("react:transition:begin", { state });
|
|
709
|
+
await this.alepha.emit("react:transition:begin", { state, context });
|
|
504
710
|
try {
|
|
505
711
|
const previous = options.previous;
|
|
506
712
|
const { route, params } = this.match(pathname);
|
|
@@ -510,29 +716,22 @@ class BrowserRouterProvider extends RouterProvider {
|
|
|
510
716
|
query[key] = String(value);
|
|
511
717
|
}
|
|
512
718
|
}
|
|
719
|
+
context.query = query;
|
|
720
|
+
context.params = params ?? {};
|
|
721
|
+
context.previous = previous;
|
|
513
722
|
if (isPageRoute(route)) {
|
|
514
723
|
const result = await this.pageDescriptorProvider.createLayers(
|
|
515
724
|
route.page,
|
|
516
|
-
|
|
517
|
-
url,
|
|
518
|
-
params: params ?? {},
|
|
519
|
-
query,
|
|
520
|
-
previous,
|
|
521
|
-
...state,
|
|
522
|
-
head: state.head,
|
|
523
|
-
...options.context ?? {}
|
|
524
|
-
}
|
|
725
|
+
context
|
|
525
726
|
);
|
|
526
727
|
if (result.redirect) {
|
|
527
728
|
return {
|
|
528
|
-
element: null,
|
|
529
|
-
layers: [],
|
|
530
729
|
redirect: result.redirect,
|
|
531
|
-
|
|
730
|
+
state,
|
|
731
|
+
context
|
|
532
732
|
};
|
|
533
733
|
}
|
|
534
734
|
state.layers = result.layers;
|
|
535
|
-
state.head = result.head;
|
|
536
735
|
}
|
|
537
736
|
if (state.layers.length === 0) {
|
|
538
737
|
state.layers.push({
|
|
@@ -555,33 +754,25 @@ class BrowserRouterProvider extends RouterProvider {
|
|
|
555
754
|
];
|
|
556
755
|
await this.alepha.emit("react:transition:error", {
|
|
557
756
|
error: e,
|
|
558
|
-
state
|
|
757
|
+
state,
|
|
758
|
+
context
|
|
559
759
|
});
|
|
560
760
|
}
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return {
|
|
566
|
-
element: this.root(state, options.context),
|
|
567
|
-
layers: state.layers,
|
|
568
|
-
head: state.head
|
|
569
|
-
};
|
|
761
|
+
if (options.state) {
|
|
762
|
+
options.state.layers = state.layers;
|
|
763
|
+
options.state.pathname = state.pathname;
|
|
764
|
+
options.state.search = state.search;
|
|
570
765
|
}
|
|
571
|
-
options.state.layers = state.layers;
|
|
572
|
-
options.state.pathname = state.pathname;
|
|
573
|
-
options.state.search = state.search;
|
|
574
|
-
options.state.head = state.head;
|
|
575
766
|
await this.alepha.emit("react:transition:end", {
|
|
576
|
-
state: options.state
|
|
767
|
+
state: options.state,
|
|
768
|
+
context
|
|
577
769
|
});
|
|
578
770
|
return {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
head: state.head
|
|
771
|
+
context,
|
|
772
|
+
state
|
|
582
773
|
};
|
|
583
774
|
}
|
|
584
|
-
root(state, context
|
|
775
|
+
root(state, context) {
|
|
585
776
|
return this.pageDescriptorProvider.root(state, context);
|
|
586
777
|
}
|
|
587
778
|
}
|
|
@@ -601,8 +792,7 @@ class ReactBrowserProvider {
|
|
|
601
792
|
state = {
|
|
602
793
|
layers: [],
|
|
603
794
|
pathname: "",
|
|
604
|
-
search: ""
|
|
605
|
-
head: {}
|
|
795
|
+
search: ""
|
|
606
796
|
};
|
|
607
797
|
get document() {
|
|
608
798
|
return window.document;
|
|
@@ -643,8 +833,8 @@ class ReactBrowserProvider {
|
|
|
643
833
|
const result = await this.render({
|
|
644
834
|
url
|
|
645
835
|
});
|
|
646
|
-
if (result.url !== url) {
|
|
647
|
-
this.history.replaceState({}, "", result.url);
|
|
836
|
+
if (result.context.url.pathname !== url) {
|
|
837
|
+
this.history.replaceState({}, "", result.context.url.pathname);
|
|
648
838
|
return;
|
|
649
839
|
}
|
|
650
840
|
if (options.replace) {
|
|
@@ -668,7 +858,7 @@ class ReactBrowserProvider {
|
|
|
668
858
|
return await this.render({ url: result.redirect });
|
|
669
859
|
}
|
|
670
860
|
this.transitioning = void 0;
|
|
671
|
-
return
|
|
861
|
+
return result;
|
|
672
862
|
}
|
|
673
863
|
/**
|
|
674
864
|
* Get embedded layers from the server.
|
|
@@ -709,14 +899,16 @@ class ReactBrowserProvider {
|
|
|
709
899
|
const hydration = this.getHydrationState();
|
|
710
900
|
const previous = hydration?.layers ?? [];
|
|
711
901
|
if (hydration?.links) {
|
|
712
|
-
|
|
902
|
+
for (const link of hydration.links.links) {
|
|
903
|
+
this.client.pushLink(link);
|
|
904
|
+
}
|
|
713
905
|
}
|
|
714
|
-
const {
|
|
715
|
-
if (head) {
|
|
716
|
-
this.headProvider.renderHead(this.document, head);
|
|
906
|
+
const { context } = await this.render({ previous });
|
|
907
|
+
if (context.head) {
|
|
908
|
+
this.headProvider.renderHead(this.document, context.head);
|
|
717
909
|
}
|
|
718
|
-
const context = {};
|
|
719
910
|
await this.alepha.emit("react:browser:render", {
|
|
911
|
+
state: this.state,
|
|
720
912
|
context,
|
|
721
913
|
hydration
|
|
722
914
|
});
|
|
@@ -732,23 +924,19 @@ class ReactBrowserProvider {
|
|
|
732
924
|
window.addEventListener("popstate", () => {
|
|
733
925
|
this.render();
|
|
734
926
|
});
|
|
735
|
-
this.alepha.on("react:transition:end", {
|
|
736
|
-
callback: ({ state }) => {
|
|
737
|
-
this.headProvider.renderHead(this.document, state.head);
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
927
|
}
|
|
741
928
|
});
|
|
742
929
|
onTransitionEnd = $hook({
|
|
743
930
|
name: "react:transition:end",
|
|
744
|
-
handler: async ({
|
|
745
|
-
this.headProvider.renderHead(this.document,
|
|
931
|
+
handler: async ({ context }) => {
|
|
932
|
+
this.headProvider.renderHead(this.document, context.head);
|
|
746
933
|
}
|
|
747
934
|
});
|
|
748
935
|
}
|
|
749
936
|
|
|
750
937
|
class RouterHookApi {
|
|
751
|
-
constructor(state, layer, browser) {
|
|
938
|
+
constructor(pages, state, layer, browser) {
|
|
939
|
+
this.pages = pages;
|
|
752
940
|
this.state = state;
|
|
753
941
|
this.layer = layer;
|
|
754
942
|
this.browser = browser;
|
|
@@ -802,32 +990,40 @@ class RouterHookApi {
|
|
|
802
990
|
* @param pathname
|
|
803
991
|
* @param layer
|
|
804
992
|
*/
|
|
805
|
-
createHref(pathname, layer = this.layer) {
|
|
993
|
+
createHref(pathname, layer = this.layer, options = {}) {
|
|
806
994
|
if (typeof pathname === "object") {
|
|
807
995
|
pathname = pathname.options.path ?? "";
|
|
808
996
|
}
|
|
997
|
+
if (options.params) {
|
|
998
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
999
|
+
pathname = pathname.replace(`:${key}`, String(value));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
809
1002
|
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
810
1003
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1004
|
+
async go(path, options) {
|
|
1005
|
+
for (const page of this.pages) {
|
|
1006
|
+
if (page.name === path) {
|
|
1007
|
+
path = page.path ?? "";
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
await this.browser?.go(this.createHref(path, this.layer, options), options);
|
|
818
1012
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1013
|
+
anchor(path, options = {}) {
|
|
1014
|
+
for (const page of this.pages) {
|
|
1015
|
+
if (page.name === path) {
|
|
1016
|
+
path = page.path ?? "";
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const href = this.createHref(path, this.layer, options);
|
|
825
1021
|
return {
|
|
826
1022
|
href,
|
|
827
1023
|
onClick: (ev) => {
|
|
828
1024
|
ev.stopPropagation();
|
|
829
1025
|
ev.preventDefault();
|
|
830
|
-
this.go(path).catch(console.error);
|
|
1026
|
+
this.go(path, options).catch(console.error);
|
|
831
1027
|
}
|
|
832
1028
|
};
|
|
833
1029
|
}
|
|
@@ -838,14 +1034,8 @@ class RouterHookApi {
|
|
|
838
1034
|
* @param options
|
|
839
1035
|
*/
|
|
840
1036
|
setQueryParams(record, options = {}) {
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
...this.query,
|
|
844
|
-
...record
|
|
845
|
-
} : {
|
|
846
|
-
...record
|
|
847
|
-
}
|
|
848
|
-
).toString();
|
|
1037
|
+
const func = typeof record === "function" ? record : () => record;
|
|
1038
|
+
const search = new URLSearchParams(func(this.query)).toString();
|
|
849
1039
|
const state = search ? `${this.pathname}?${search}` : this.pathname;
|
|
850
1040
|
if (options.push) {
|
|
851
1041
|
window.history.pushState({}, "", state);
|
|
@@ -861,8 +1051,12 @@ const useRouter = () => {
|
|
|
861
1051
|
if (!ctx || !layer) {
|
|
862
1052
|
throw new Error("useRouter must be used within a RouterProvider");
|
|
863
1053
|
}
|
|
1054
|
+
const pages = useMemo(() => {
|
|
1055
|
+
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
1056
|
+
}, []);
|
|
864
1057
|
return useMemo(
|
|
865
1058
|
() => new RouterHookApi(
|
|
1059
|
+
pages,
|
|
866
1060
|
ctx.state,
|
|
867
1061
|
layer,
|
|
868
1062
|
ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0
|
|
@@ -873,6 +1067,7 @@ const useRouter = () => {
|
|
|
873
1067
|
|
|
874
1068
|
const Link = (props) => {
|
|
875
1069
|
React.useContext(RouterContext);
|
|
1070
|
+
const router = useRouter();
|
|
876
1071
|
const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
|
|
877
1072
|
if (!to) {
|
|
878
1073
|
return null;
|
|
@@ -882,8 +1077,49 @@ const Link = (props) => {
|
|
|
882
1077
|
return null;
|
|
883
1078
|
}
|
|
884
1079
|
const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
|
|
1080
|
+
const anchorProps = {
|
|
1081
|
+
...props,
|
|
1082
|
+
to: void 0
|
|
1083
|
+
};
|
|
1084
|
+
return /* @__PURE__ */ jsx("a", { ...router.anchor(to), ...anchorProps, children: props.children ?? name });
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const useActive = (path) => {
|
|
885
1088
|
const router = useRouter();
|
|
886
|
-
|
|
1089
|
+
const ctx = useContext(RouterContext);
|
|
1090
|
+
const layer = useContext(RouterLayerContext);
|
|
1091
|
+
if (!ctx || !layer) {
|
|
1092
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
1093
|
+
}
|
|
1094
|
+
let name;
|
|
1095
|
+
if (typeof path === "object" && path.options.name) {
|
|
1096
|
+
name = path.options.name;
|
|
1097
|
+
}
|
|
1098
|
+
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
1099
|
+
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
1100
|
+
const [isPending, setPending] = useState(false);
|
|
1101
|
+
const isActive = current === href;
|
|
1102
|
+
useRouterEvents({
|
|
1103
|
+
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
1104
|
+
});
|
|
1105
|
+
return {
|
|
1106
|
+
name,
|
|
1107
|
+
isPending,
|
|
1108
|
+
isActive,
|
|
1109
|
+
anchorProps: {
|
|
1110
|
+
href,
|
|
1111
|
+
onClick: (ev) => {
|
|
1112
|
+
ev.stopPropagation();
|
|
1113
|
+
ev.preventDefault();
|
|
1114
|
+
if (isActive) return;
|
|
1115
|
+
if (isPending) return;
|
|
1116
|
+
setPending(true);
|
|
1117
|
+
router.go(href).then(() => {
|
|
1118
|
+
setPending(false);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
887
1123
|
};
|
|
888
1124
|
|
|
889
1125
|
const useInject = (clazz) => {
|
|
@@ -891,18 +1127,10 @@ const useInject = (clazz) => {
|
|
|
891
1127
|
if (!ctx) {
|
|
892
1128
|
throw new Error("useRouter must be used within a <RouterProvider>");
|
|
893
1129
|
}
|
|
894
|
-
return useMemo(
|
|
895
|
-
() => ctx.alepha.get(clazz, {
|
|
896
|
-
skipRegistration: true
|
|
897
|
-
}),
|
|
898
|
-
[]
|
|
899
|
-
);
|
|
1130
|
+
return useMemo(() => ctx.alepha.get(clazz), []);
|
|
900
1131
|
};
|
|
901
1132
|
|
|
902
|
-
const useClient = () => {
|
|
903
|
-
return useInject(HttpClient);
|
|
904
|
-
};
|
|
905
|
-
const useApi = () => {
|
|
1133
|
+
const useClient = (_scope) => {
|
|
906
1134
|
return useInject(HttpClient).of();
|
|
907
1135
|
};
|
|
908
1136
|
|
|
@@ -924,12 +1152,9 @@ const useQueryParams = (schema, options = {}) => {
|
|
|
924
1152
|
queryParams,
|
|
925
1153
|
(queryParams2) => {
|
|
926
1154
|
setQueryParams(queryParams2);
|
|
927
|
-
router.setQueryParams(
|
|
928
|
-
{ [key]: encode(ctx.alepha, schema, queryParams2) }
|
|
929
|
-
|
|
930
|
-
merge: true
|
|
931
|
-
}
|
|
932
|
-
);
|
|
1155
|
+
router.setQueryParams((data) => {
|
|
1156
|
+
return { ...data, [key]: encode(ctx.alepha, schema, queryParams2) };
|
|
1157
|
+
});
|
|
933
1158
|
}
|
|
934
1159
|
];
|
|
935
1160
|
};
|
|
@@ -939,7 +1164,7 @@ const encode = (alepha, schema, data) => {
|
|
|
939
1164
|
const decode = (alepha, schema, data) => {
|
|
940
1165
|
try {
|
|
941
1166
|
return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
|
|
942
|
-
} catch (
|
|
1167
|
+
} catch (_error) {
|
|
943
1168
|
return {};
|
|
944
1169
|
}
|
|
945
1170
|
};
|
|
@@ -957,43 +1182,4 @@ const useRouterState = () => {
|
|
|
957
1182
|
return state;
|
|
958
1183
|
};
|
|
959
1184
|
|
|
960
|
-
|
|
961
|
-
const router = useRouter();
|
|
962
|
-
const ctx = useContext(RouterContext);
|
|
963
|
-
const layer = useContext(RouterLayerContext);
|
|
964
|
-
if (!ctx || !layer) {
|
|
965
|
-
throw new Error("useRouter must be used within a RouterProvider");
|
|
966
|
-
}
|
|
967
|
-
let name;
|
|
968
|
-
if (typeof path === "object" && path.options.name) {
|
|
969
|
-
name = path.options.name;
|
|
970
|
-
}
|
|
971
|
-
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
972
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
973
|
-
const [isPending, setPending] = useState(false);
|
|
974
|
-
const isActive = current === href;
|
|
975
|
-
useRouterEvents({
|
|
976
|
-
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
977
|
-
});
|
|
978
|
-
return {
|
|
979
|
-
name,
|
|
980
|
-
isPending,
|
|
981
|
-
isActive,
|
|
982
|
-
anchorProps: {
|
|
983
|
-
href,
|
|
984
|
-
onClick: (ev) => {
|
|
985
|
-
ev.stopPropagation();
|
|
986
|
-
ev.preventDefault();
|
|
987
|
-
if (isActive) return;
|
|
988
|
-
if (isPending) return;
|
|
989
|
-
setPending(true);
|
|
990
|
-
router.go(href).then(() => {
|
|
991
|
-
setPending(false);
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
};
|
|
997
|
-
|
|
998
|
-
export { $page as $, BrowserRouterProvider as B, 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 };
|
|
999
|
-
//# sourceMappingURL=useActive-ClUsghB5.js.map
|
|
1185
|
+
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 };
|