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