@alepha/react 0.7.4 → 0.7.6
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/dist/index.browser.d.ts +578 -0
- package/dist/index.browser.js +1091 -49
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cjs +1333 -571
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +678 -0
- package/dist/index.d.ts +530 -570
- package/dist/index.js +1297 -557
- package/dist/index.js.map +1 -0
- package/package.json +11 -8
- package/src/components/NotFound.tsx +0 -10
- package/src/descriptors/$page.ts +5 -2
- package/src/index.browser.ts +1 -0
- package/src/index.shared.ts +0 -4
- package/src/index.ts +0 -2
- package/dist/ReactBrowserProvider-CXDElhnK.cjs +0 -960
- package/dist/ReactBrowserProvider-ufHSOTmv.js +0 -946
- package/dist/index.browser.cjs +0 -58
|
@@ -1,960 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var core = require('@alepha/core');
|
|
4
|
-
var server = require('@alepha/server');
|
|
5
|
-
var router = require('@alepha/router');
|
|
6
|
-
var React = require('react');
|
|
7
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
-
|
|
9
|
-
const KEY = "PAGE";
|
|
10
|
-
const $page = (options) => {
|
|
11
|
-
core.__descriptor(KEY);
|
|
12
|
-
if (options.children) {
|
|
13
|
-
for (const child of options.children) {
|
|
14
|
-
child[core.OPTIONS].parent = {
|
|
15
|
-
[core.OPTIONS]: options
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
if (options.parent) {
|
|
20
|
-
options.parent[core.OPTIONS].children ??= [];
|
|
21
|
-
options.parent[core.OPTIONS].children.push({
|
|
22
|
-
[core.OPTIONS]: options
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
[core.KIND]: KEY,
|
|
27
|
-
[core.OPTIONS]: options,
|
|
28
|
-
render: () => {
|
|
29
|
-
throw new core.NotImplementedError(KEY);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
$page[core.KIND] = KEY;
|
|
34
|
-
|
|
35
|
-
const ClientOnly = (props) => {
|
|
36
|
-
const [mounted, setMounted] = React.useState(false);
|
|
37
|
-
React.useEffect(() => setMounted(true), []);
|
|
38
|
-
if (props.disabled) {
|
|
39
|
-
return props.children;
|
|
40
|
-
}
|
|
41
|
-
return mounted ? props.children : props.fallback;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const RouterContext = React.createContext(
|
|
45
|
-
void 0
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const useAlepha = () => {
|
|
49
|
-
const routerContext = React.useContext(RouterContext);
|
|
50
|
-
if (!routerContext) {
|
|
51
|
-
throw new Error("useAlepha must be used within a RouterProvider");
|
|
52
|
-
}
|
|
53
|
-
return routerContext.alepha;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const ErrorViewer = ({ error }) => {
|
|
57
|
-
const [expanded, setExpanded] = React.useState(false);
|
|
58
|
-
const isProduction = useAlepha().isProduction();
|
|
59
|
-
if (isProduction) {
|
|
60
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ErrorViewerProduction, {});
|
|
61
|
-
}
|
|
62
|
-
const stackLines = error.stack?.split("\n") ?? [];
|
|
63
|
-
const previewLines = stackLines.slice(0, 5);
|
|
64
|
-
const hiddenLineCount = stackLines.length - previewLines.length;
|
|
65
|
-
const copyToClipboard = (text) => {
|
|
66
|
-
navigator.clipboard.writeText(text).catch((err) => {
|
|
67
|
-
console.error("Clipboard error:", err);
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
const styles = {
|
|
71
|
-
container: {
|
|
72
|
-
padding: "24px",
|
|
73
|
-
backgroundColor: "#FEF2F2",
|
|
74
|
-
color: "#7F1D1D",
|
|
75
|
-
border: "1px solid #FECACA",
|
|
76
|
-
borderRadius: "16px",
|
|
77
|
-
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
78
|
-
fontFamily: "monospace",
|
|
79
|
-
maxWidth: "768px",
|
|
80
|
-
margin: "40px auto"
|
|
81
|
-
},
|
|
82
|
-
heading: {
|
|
83
|
-
fontSize: "20px",
|
|
84
|
-
fontWeight: "bold",
|
|
85
|
-
marginBottom: "4px"
|
|
86
|
-
},
|
|
87
|
-
name: {
|
|
88
|
-
fontSize: "16px",
|
|
89
|
-
fontWeight: 600
|
|
90
|
-
},
|
|
91
|
-
message: {
|
|
92
|
-
fontSize: "14px",
|
|
93
|
-
marginBottom: "16px"
|
|
94
|
-
},
|
|
95
|
-
sectionHeader: {
|
|
96
|
-
display: "flex",
|
|
97
|
-
justifyContent: "space-between",
|
|
98
|
-
alignItems: "center",
|
|
99
|
-
fontSize: "12px",
|
|
100
|
-
marginBottom: "4px",
|
|
101
|
-
color: "#991B1B"
|
|
102
|
-
},
|
|
103
|
-
copyButton: {
|
|
104
|
-
fontSize: "12px",
|
|
105
|
-
color: "#DC2626",
|
|
106
|
-
background: "none",
|
|
107
|
-
border: "none",
|
|
108
|
-
cursor: "pointer",
|
|
109
|
-
textDecoration: "underline"
|
|
110
|
-
},
|
|
111
|
-
stackContainer: {
|
|
112
|
-
backgroundColor: "#FEE2E2",
|
|
113
|
-
padding: "12px",
|
|
114
|
-
borderRadius: "8px",
|
|
115
|
-
fontSize: "13px",
|
|
116
|
-
lineHeight: "1.4",
|
|
117
|
-
overflowX: "auto",
|
|
118
|
-
whiteSpace: "pre-wrap"
|
|
119
|
-
},
|
|
120
|
-
expandLine: {
|
|
121
|
-
color: "#F87171",
|
|
122
|
-
cursor: "pointer",
|
|
123
|
-
marginTop: "8px"
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.container, children: [
|
|
127
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
128
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.heading, children: "\u{1F525} Error" }),
|
|
129
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.name, children: error.name }),
|
|
130
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.message, children: error.message })
|
|
131
|
-
] }),
|
|
132
|
-
stackLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
133
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.sectionHeader, children: [
|
|
134
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Stack trace" }),
|
|
135
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
136
|
-
"button",
|
|
137
|
-
{
|
|
138
|
-
onClick: () => copyToClipboard(error.stack),
|
|
139
|
-
style: styles.copyButton,
|
|
140
|
-
children: "Copy all"
|
|
141
|
-
}
|
|
142
|
-
)
|
|
143
|
-
] }),
|
|
144
|
-
/* @__PURE__ */ jsxRuntime.jsxs("pre", { style: styles.stackContainer, children: [
|
|
145
|
-
(expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: line }, i)),
|
|
146
|
-
!expanded && hiddenLineCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.expandLine, onClick: () => setExpanded(true), children: [
|
|
147
|
-
"+ ",
|
|
148
|
-
hiddenLineCount,
|
|
149
|
-
" more lines..."
|
|
150
|
-
] })
|
|
151
|
-
] })
|
|
152
|
-
] })
|
|
153
|
-
] });
|
|
154
|
-
};
|
|
155
|
-
const ErrorViewerProduction = () => {
|
|
156
|
-
const styles = {
|
|
157
|
-
container: {
|
|
158
|
-
padding: "24px",
|
|
159
|
-
backgroundColor: "#FEF2F2",
|
|
160
|
-
color: "#7F1D1D",
|
|
161
|
-
border: "1px solid #FECACA",
|
|
162
|
-
borderRadius: "16px",
|
|
163
|
-
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
164
|
-
fontFamily: "monospace",
|
|
165
|
-
maxWidth: "768px",
|
|
166
|
-
margin: "40px auto",
|
|
167
|
-
textAlign: "center"
|
|
168
|
-
},
|
|
169
|
-
heading: {
|
|
170
|
-
fontSize: "20px",
|
|
171
|
-
fontWeight: "bold",
|
|
172
|
-
marginBottom: "8px"
|
|
173
|
-
},
|
|
174
|
-
message: {
|
|
175
|
-
fontSize: "14px",
|
|
176
|
-
opacity: 0.85
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.container, children: [
|
|
180
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.heading, children: "\u{1F6A8} An error occurred" }),
|
|
181
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.message, children: "Something went wrong. Please try again later." })
|
|
182
|
-
] });
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const RouterLayerContext = React.createContext(void 0);
|
|
186
|
-
|
|
187
|
-
const useRouterEvents = (opts = {}, deps = []) => {
|
|
188
|
-
const ctx = React.useContext(RouterContext);
|
|
189
|
-
if (!ctx) {
|
|
190
|
-
throw new Error("useRouter must be used within a RouterProvider");
|
|
191
|
-
}
|
|
192
|
-
React.useEffect(() => {
|
|
193
|
-
if (!ctx.alepha.isBrowser()) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const subs = [];
|
|
197
|
-
const onBegin = opts.onBegin;
|
|
198
|
-
const onEnd = opts.onEnd;
|
|
199
|
-
const onError = opts.onError;
|
|
200
|
-
if (onBegin) {
|
|
201
|
-
subs.push(
|
|
202
|
-
ctx.alepha.on("react:transition:begin", {
|
|
203
|
-
callback: onBegin
|
|
204
|
-
})
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
if (onEnd) {
|
|
208
|
-
subs.push(
|
|
209
|
-
ctx.alepha.on("react:transition:end", {
|
|
210
|
-
callback: onEnd
|
|
211
|
-
})
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
if (onError) {
|
|
215
|
-
subs.push(
|
|
216
|
-
ctx.alepha.on("react:transition:error", {
|
|
217
|
-
callback: onError
|
|
218
|
-
})
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
return () => {
|
|
222
|
-
for (const sub of subs) {
|
|
223
|
-
sub();
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
}, deps);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
class ErrorBoundary extends React.Component {
|
|
230
|
-
constructor(props) {
|
|
231
|
-
super(props);
|
|
232
|
-
this.state = {};
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Update state so the next render shows the fallback UI.
|
|
236
|
-
*/
|
|
237
|
-
static getDerivedStateFromError(error) {
|
|
238
|
-
return {
|
|
239
|
-
error
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Lifecycle method called when an error is caught.
|
|
244
|
-
* You can log the error or perform side effects here.
|
|
245
|
-
*/
|
|
246
|
-
componentDidCatch(error, info) {
|
|
247
|
-
if (this.props.onError) {
|
|
248
|
-
this.props.onError(error, info);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
render() {
|
|
252
|
-
if (this.state.error) {
|
|
253
|
-
return this.props.fallback(this.state.error);
|
|
254
|
-
}
|
|
255
|
-
return this.props.children;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const NestedView = (props) => {
|
|
260
|
-
const app = React.useContext(RouterContext);
|
|
261
|
-
const layer = React.useContext(RouterLayerContext);
|
|
262
|
-
const index = layer?.index ?? 0;
|
|
263
|
-
const [view, setView] = React.useState(
|
|
264
|
-
app?.state.layers[index]?.element
|
|
265
|
-
);
|
|
266
|
-
useRouterEvents(
|
|
267
|
-
{
|
|
268
|
-
onEnd: ({ state }) => {
|
|
269
|
-
setView(state.layers[index]?.element);
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
[app]
|
|
273
|
-
);
|
|
274
|
-
if (!app) {
|
|
275
|
-
throw new Error("NestedView must be used within a RouterContext.");
|
|
276
|
-
}
|
|
277
|
-
const element = view ?? props.children ?? null;
|
|
278
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { fallback: app.context.onError, children: element });
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
function NotFoundPage() {
|
|
282
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
283
|
-
"div",
|
|
284
|
-
{
|
|
285
|
-
style: {
|
|
286
|
-
height: "100vh",
|
|
287
|
-
display: "flex",
|
|
288
|
-
flexDirection: "column",
|
|
289
|
-
justifyContent: "center",
|
|
290
|
-
alignItems: "center",
|
|
291
|
-
textAlign: "center",
|
|
292
|
-
fontFamily: "sans-serif",
|
|
293
|
-
padding: "1rem"
|
|
294
|
-
},
|
|
295
|
-
children: [
|
|
296
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "1rem", marginBottom: "0.5rem" }, children: "This page does not exist" }),
|
|
297
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
298
|
-
"a",
|
|
299
|
-
{
|
|
300
|
-
href: "/",
|
|
301
|
-
style: {
|
|
302
|
-
fontSize: "0.7rem",
|
|
303
|
-
color: "#007bff",
|
|
304
|
-
textDecoration: "none"
|
|
305
|
-
},
|
|
306
|
-
children: "\u2190 Back to home"
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
class RedirectionError extends Error {
|
|
315
|
-
page;
|
|
316
|
-
constructor(page) {
|
|
317
|
-
super("Redirection");
|
|
318
|
-
this.page = page;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const envSchema = core.t.object({
|
|
323
|
-
REACT_STRICT_MODE: core.t.boolean({ default: true })
|
|
324
|
-
});
|
|
325
|
-
class PageDescriptorProvider {
|
|
326
|
-
log = core.$logger();
|
|
327
|
-
env = core.$inject(envSchema);
|
|
328
|
-
alepha = core.$inject(core.Alepha);
|
|
329
|
-
pages = [];
|
|
330
|
-
getPages() {
|
|
331
|
-
return this.pages;
|
|
332
|
-
}
|
|
333
|
-
page(name) {
|
|
334
|
-
for (const page of this.pages) {
|
|
335
|
-
if (page.name === name) {
|
|
336
|
-
return page;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
throw new Error(`Page ${name} not found`);
|
|
340
|
-
}
|
|
341
|
-
url(name, options = {}) {
|
|
342
|
-
const page = this.page(name);
|
|
343
|
-
if (!page) {
|
|
344
|
-
throw new Error(`Page ${name} not found`);
|
|
345
|
-
}
|
|
346
|
-
let url = page.path ?? "";
|
|
347
|
-
let parent = page.parent;
|
|
348
|
-
while (parent) {
|
|
349
|
-
url = `${parent.path ?? ""}/${url}`;
|
|
350
|
-
parent = parent.parent;
|
|
351
|
-
}
|
|
352
|
-
url = this.compile(url, options.params ?? {});
|
|
353
|
-
return new URL(
|
|
354
|
-
url.replace(/\/\/+/g, "/") || "/",
|
|
355
|
-
options.base ?? `http://localhost`
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
root(state, context) {
|
|
359
|
-
const root = React.createElement(
|
|
360
|
-
RouterContext.Provider,
|
|
361
|
-
{
|
|
362
|
-
value: {
|
|
363
|
-
alepha: this.alepha,
|
|
364
|
-
state,
|
|
365
|
-
context
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
React.createElement(NestedView, {}, state.layers[0]?.element)
|
|
369
|
-
);
|
|
370
|
-
if (this.env.REACT_STRICT_MODE) {
|
|
371
|
-
return React.createElement(React.StrictMode, {}, root);
|
|
372
|
-
}
|
|
373
|
-
return root;
|
|
374
|
-
}
|
|
375
|
-
async createLayers(route, request) {
|
|
376
|
-
const { pathname, search } = request.url;
|
|
377
|
-
const layers = [];
|
|
378
|
-
let context = {};
|
|
379
|
-
const stack = [{ route }];
|
|
380
|
-
request.onError = (error) => this.renderError(error);
|
|
381
|
-
let parent = route.parent;
|
|
382
|
-
while (parent) {
|
|
383
|
-
stack.unshift({ route: parent });
|
|
384
|
-
parent = parent.parent;
|
|
385
|
-
}
|
|
386
|
-
let forceRefresh = false;
|
|
387
|
-
for (let i = 0; i < stack.length; i++) {
|
|
388
|
-
const it = stack[i];
|
|
389
|
-
const route2 = it.route;
|
|
390
|
-
const config = {};
|
|
391
|
-
try {
|
|
392
|
-
config.query = route2.schema?.query ? this.alepha.parse(route2.schema.query, request.query) : request.query;
|
|
393
|
-
} catch (e) {
|
|
394
|
-
it.error = e;
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
config.params = route2.schema?.params ? this.alepha.parse(route2.schema.params, request.params) : request.params;
|
|
399
|
-
} catch (e) {
|
|
400
|
-
it.error = e;
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
it.config = {
|
|
404
|
-
...config
|
|
405
|
-
};
|
|
406
|
-
if (!route2.resolve) {
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
const previous = request.previous;
|
|
410
|
-
if (previous?.[i] && !forceRefresh && previous[i].name === route2.name) {
|
|
411
|
-
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
412
|
-
const prev = JSON.stringify({
|
|
413
|
-
part: url(previous[i].part),
|
|
414
|
-
params: previous[i].config?.params ?? {}
|
|
415
|
-
});
|
|
416
|
-
const curr = JSON.stringify({
|
|
417
|
-
part: url(route2.path),
|
|
418
|
-
params: config.params ?? {}
|
|
419
|
-
});
|
|
420
|
-
if (prev === curr) {
|
|
421
|
-
it.props = previous[i].props;
|
|
422
|
-
it.error = previous[i].error;
|
|
423
|
-
context = {
|
|
424
|
-
...context,
|
|
425
|
-
...it.props
|
|
426
|
-
};
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
forceRefresh = true;
|
|
430
|
-
}
|
|
431
|
-
try {
|
|
432
|
-
const props = await route2.resolve?.({
|
|
433
|
-
...request,
|
|
434
|
-
// request
|
|
435
|
-
...config,
|
|
436
|
-
// params, query
|
|
437
|
-
...context
|
|
438
|
-
// previous props
|
|
439
|
-
}) ?? {};
|
|
440
|
-
it.props = {
|
|
441
|
-
...props
|
|
442
|
-
};
|
|
443
|
-
context = {
|
|
444
|
-
...context,
|
|
445
|
-
...props
|
|
446
|
-
};
|
|
447
|
-
} catch (e) {
|
|
448
|
-
if (e instanceof RedirectionError) {
|
|
449
|
-
return {
|
|
450
|
-
layers: [],
|
|
451
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
452
|
-
pathname,
|
|
453
|
-
search
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
this.log.error(e);
|
|
457
|
-
it.error = e;
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
let acc = "";
|
|
462
|
-
for (let i = 0; i < stack.length; i++) {
|
|
463
|
-
const it = stack[i];
|
|
464
|
-
const props = it.props ?? {};
|
|
465
|
-
const params = { ...it.config?.params };
|
|
466
|
-
for (const key of Object.keys(params)) {
|
|
467
|
-
params[key] = String(params[key]);
|
|
468
|
-
}
|
|
469
|
-
if (it.route.head && !it.error) {
|
|
470
|
-
this.fillHead(it.route, request, {
|
|
471
|
-
...props,
|
|
472
|
-
...context
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
acc += "/";
|
|
476
|
-
acc += it.route.path ? this.compile(it.route.path, params) : "";
|
|
477
|
-
const path = acc.replace(/\/+/, "/");
|
|
478
|
-
const localErrorHandler = this.getErrorHandler(it.route);
|
|
479
|
-
if (localErrorHandler) {
|
|
480
|
-
request.onError = localErrorHandler;
|
|
481
|
-
}
|
|
482
|
-
if (it.error) {
|
|
483
|
-
let element2 = await request.onError(it.error);
|
|
484
|
-
if (element2 === null) {
|
|
485
|
-
element2 = this.renderError(it.error);
|
|
486
|
-
}
|
|
487
|
-
layers.push({
|
|
488
|
-
props,
|
|
489
|
-
error: it.error,
|
|
490
|
-
name: it.route.name,
|
|
491
|
-
part: it.route.path,
|
|
492
|
-
config: it.config,
|
|
493
|
-
element: this.renderView(i + 1, path, element2, it.route),
|
|
494
|
-
index: i + 1,
|
|
495
|
-
path
|
|
496
|
-
});
|
|
497
|
-
break;
|
|
498
|
-
}
|
|
499
|
-
const element = await this.createElement(it.route, {
|
|
500
|
-
...props,
|
|
501
|
-
...context
|
|
502
|
-
});
|
|
503
|
-
layers.push({
|
|
504
|
-
name: it.route.name,
|
|
505
|
-
props,
|
|
506
|
-
part: it.route.path,
|
|
507
|
-
config: it.config,
|
|
508
|
-
element: this.renderView(i + 1, path, element, it.route),
|
|
509
|
-
index: i + 1,
|
|
510
|
-
path
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
return { layers, pathname, search };
|
|
514
|
-
}
|
|
515
|
-
getErrorHandler(route) {
|
|
516
|
-
if (route.errorHandler) return route.errorHandler;
|
|
517
|
-
let parent = route.parent;
|
|
518
|
-
while (parent) {
|
|
519
|
-
if (parent.errorHandler) return parent.errorHandler;
|
|
520
|
-
parent = parent.parent;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
async createElement(page, props) {
|
|
524
|
-
if (page.lazy) {
|
|
525
|
-
const component = await page.lazy();
|
|
526
|
-
return React.createElement(component.default, props);
|
|
527
|
-
}
|
|
528
|
-
if (page.component) {
|
|
529
|
-
return React.createElement(page.component, props);
|
|
530
|
-
}
|
|
531
|
-
return void 0;
|
|
532
|
-
}
|
|
533
|
-
fillHead(page, ctx, props) {
|
|
534
|
-
if (!page.head) {
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
ctx.head ??= {};
|
|
538
|
-
const head = typeof page.head === "function" ? page.head(props, ctx.head) : page.head;
|
|
539
|
-
if (head.title) {
|
|
540
|
-
ctx.head ??= {};
|
|
541
|
-
if (ctx.head.titleSeparator) {
|
|
542
|
-
ctx.head.title = `${head.title}${ctx.head.titleSeparator}${ctx.head.title}`;
|
|
543
|
-
} else {
|
|
544
|
-
ctx.head.title = head.title;
|
|
545
|
-
}
|
|
546
|
-
ctx.head.titleSeparator = head.titleSeparator;
|
|
547
|
-
}
|
|
548
|
-
if (head.htmlAttributes) {
|
|
549
|
-
ctx.head.htmlAttributes = {
|
|
550
|
-
...ctx.head.htmlAttributes,
|
|
551
|
-
...head.htmlAttributes
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
if (head.bodyAttributes) {
|
|
555
|
-
ctx.head.bodyAttributes = {
|
|
556
|
-
...ctx.head.bodyAttributes,
|
|
557
|
-
...head.bodyAttributes
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
if (head.meta) {
|
|
561
|
-
ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
renderError(error) {
|
|
565
|
-
return React.createElement(ErrorViewer, { error });
|
|
566
|
-
}
|
|
567
|
-
renderEmptyView() {
|
|
568
|
-
return React.createElement(NestedView, {});
|
|
569
|
-
}
|
|
570
|
-
href(page, params = {}) {
|
|
571
|
-
const found = this.pages.find((it) => it.name === page.options.name);
|
|
572
|
-
if (!found) {
|
|
573
|
-
throw new Error(`Page ${page.options.name} not found`);
|
|
574
|
-
}
|
|
575
|
-
let url = found.path ?? "";
|
|
576
|
-
let parent = found.parent;
|
|
577
|
-
while (parent) {
|
|
578
|
-
url = `${parent.path ?? ""}/${url}`;
|
|
579
|
-
parent = parent.parent;
|
|
580
|
-
}
|
|
581
|
-
url = this.compile(url, params);
|
|
582
|
-
return url.replace(/\/\/+/g, "/") || "/";
|
|
583
|
-
}
|
|
584
|
-
compile(path, params = {}) {
|
|
585
|
-
for (const [key, value] of Object.entries(params)) {
|
|
586
|
-
path = path.replace(`:${key}`, value);
|
|
587
|
-
}
|
|
588
|
-
return path;
|
|
589
|
-
}
|
|
590
|
-
renderView(index, path, view, page) {
|
|
591
|
-
view ??= this.renderEmptyView();
|
|
592
|
-
const element = page.client ? React.createElement(
|
|
593
|
-
ClientOnly,
|
|
594
|
-
typeof page.client === "object" ? page.client : {},
|
|
595
|
-
view
|
|
596
|
-
) : view;
|
|
597
|
-
return React.createElement(
|
|
598
|
-
RouterLayerContext.Provider,
|
|
599
|
-
{
|
|
600
|
-
value: {
|
|
601
|
-
index,
|
|
602
|
-
path
|
|
603
|
-
}
|
|
604
|
-
},
|
|
605
|
-
element
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
configure = core.$hook({
|
|
609
|
-
name: "configure",
|
|
610
|
-
handler: () => {
|
|
611
|
-
let hasNotFoundHandler = false;
|
|
612
|
-
const pages = this.alepha.getDescriptorValues($page);
|
|
613
|
-
for (const { value, key } of pages) {
|
|
614
|
-
value[core.OPTIONS].name ??= key;
|
|
615
|
-
}
|
|
616
|
-
for (const { value } of pages) {
|
|
617
|
-
if (value[core.OPTIONS].parent) {
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
if (value[core.OPTIONS].path === "/*") {
|
|
621
|
-
hasNotFoundHandler = true;
|
|
622
|
-
}
|
|
623
|
-
this.add(this.map(pages, value));
|
|
624
|
-
}
|
|
625
|
-
if (!hasNotFoundHandler && pages.length > 0) {
|
|
626
|
-
this.add({
|
|
627
|
-
path: "/*",
|
|
628
|
-
name: "notFound",
|
|
629
|
-
cache: true,
|
|
630
|
-
component: NotFoundPage,
|
|
631
|
-
afterHandler: ({ reply }) => {
|
|
632
|
-
reply.status = 404;
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
});
|
|
638
|
-
map(pages, target) {
|
|
639
|
-
const children = target[core.OPTIONS].children ?? [];
|
|
640
|
-
return {
|
|
641
|
-
...target[core.OPTIONS],
|
|
642
|
-
parent: void 0,
|
|
643
|
-
children: children.map((it) => this.map(pages, it))
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
add(entry) {
|
|
647
|
-
if (this.alepha.isReady()) {
|
|
648
|
-
throw new Error("Router is already initialized");
|
|
649
|
-
}
|
|
650
|
-
entry.name ??= this.nextId();
|
|
651
|
-
const page = entry;
|
|
652
|
-
page.match = this.createMatch(page);
|
|
653
|
-
this.pages.push(page);
|
|
654
|
-
if (page.children) {
|
|
655
|
-
for (const child of page.children) {
|
|
656
|
-
child.parent = page;
|
|
657
|
-
this.add(child);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
createMatch(page) {
|
|
662
|
-
let url = page.path ?? "/";
|
|
663
|
-
let target = page.parent;
|
|
664
|
-
while (target) {
|
|
665
|
-
url = `${target.path ?? ""}/${url}`;
|
|
666
|
-
target = target.parent;
|
|
667
|
-
}
|
|
668
|
-
let path = url.replace(/\/\/+/g, "/");
|
|
669
|
-
if (path.endsWith("/") && path !== "/") {
|
|
670
|
-
path = path.slice(0, -1);
|
|
671
|
-
}
|
|
672
|
-
return path;
|
|
673
|
-
}
|
|
674
|
-
_next = 0;
|
|
675
|
-
nextId() {
|
|
676
|
-
this._next += 1;
|
|
677
|
-
return `P${this._next}`;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
const isPageRoute = (it) => {
|
|
681
|
-
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
class BrowserHeadProvider {
|
|
685
|
-
renderHead(document, head) {
|
|
686
|
-
if (head.title) {
|
|
687
|
-
document.title = head.title;
|
|
688
|
-
}
|
|
689
|
-
if (head.bodyAttributes) {
|
|
690
|
-
for (const [key, value] of Object.entries(head.bodyAttributes)) {
|
|
691
|
-
if (value) {
|
|
692
|
-
document.body.setAttribute(key, value);
|
|
693
|
-
} else {
|
|
694
|
-
document.body.removeAttribute(key);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
if (head.htmlAttributes) {
|
|
699
|
-
for (const [key, value] of Object.entries(head.htmlAttributes)) {
|
|
700
|
-
if (value) {
|
|
701
|
-
document.documentElement.setAttribute(key, value);
|
|
702
|
-
} else {
|
|
703
|
-
document.documentElement.removeAttribute(key);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
if (head.meta) {
|
|
708
|
-
for (const [key, value] of Object.entries(head.meta)) {
|
|
709
|
-
const meta = document.querySelector(`meta[name="${key}"]`);
|
|
710
|
-
if (meta) {
|
|
711
|
-
meta.setAttribute("content", value.content);
|
|
712
|
-
} else {
|
|
713
|
-
const newMeta = document.createElement("meta");
|
|
714
|
-
newMeta.setAttribute("name", key);
|
|
715
|
-
newMeta.setAttribute("content", value.content);
|
|
716
|
-
document.head.appendChild(newMeta);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
class BrowserRouterProvider extends router.RouterProvider {
|
|
724
|
-
log = core.$logger();
|
|
725
|
-
alepha = core.$inject(core.Alepha);
|
|
726
|
-
pageDescriptorProvider = core.$inject(PageDescriptorProvider);
|
|
727
|
-
add(entry) {
|
|
728
|
-
this.pageDescriptorProvider.add(entry);
|
|
729
|
-
}
|
|
730
|
-
configure = core.$hook({
|
|
731
|
-
name: "configure",
|
|
732
|
-
handler: async () => {
|
|
733
|
-
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
734
|
-
if (page.component || page.lazy) {
|
|
735
|
-
this.push({
|
|
736
|
-
path: page.match,
|
|
737
|
-
page
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
async transition(url, options = {}) {
|
|
744
|
-
const { pathname, search } = url;
|
|
745
|
-
const state = {
|
|
746
|
-
pathname,
|
|
747
|
-
search,
|
|
748
|
-
layers: []
|
|
749
|
-
};
|
|
750
|
-
const context = {
|
|
751
|
-
url,
|
|
752
|
-
query: {},
|
|
753
|
-
params: {},
|
|
754
|
-
head: {},
|
|
755
|
-
onError: () => null,
|
|
756
|
-
...options.context ?? {}
|
|
757
|
-
};
|
|
758
|
-
await this.alepha.emit("react:transition:begin", { state, context });
|
|
759
|
-
try {
|
|
760
|
-
const previous = options.previous;
|
|
761
|
-
const { route, params } = this.match(pathname);
|
|
762
|
-
const query = {};
|
|
763
|
-
if (search) {
|
|
764
|
-
for (const [key, value] of new URLSearchParams(search).entries()) {
|
|
765
|
-
query[key] = String(value);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
context.query = query;
|
|
769
|
-
context.params = params ?? {};
|
|
770
|
-
context.previous = previous;
|
|
771
|
-
if (isPageRoute(route)) {
|
|
772
|
-
const result = await this.pageDescriptorProvider.createLayers(
|
|
773
|
-
route.page,
|
|
774
|
-
context
|
|
775
|
-
);
|
|
776
|
-
if (result.redirect) {
|
|
777
|
-
return {
|
|
778
|
-
redirect: result.redirect,
|
|
779
|
-
state,
|
|
780
|
-
context
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
state.layers = result.layers;
|
|
784
|
-
}
|
|
785
|
-
if (state.layers.length === 0) {
|
|
786
|
-
state.layers.push({
|
|
787
|
-
name: "not-found",
|
|
788
|
-
element: React.createElement(NotFoundPage),
|
|
789
|
-
index: 0,
|
|
790
|
-
path: "/"
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
await this.alepha.emit("react:transition:success", { state });
|
|
794
|
-
} catch (e) {
|
|
795
|
-
this.log.error(e);
|
|
796
|
-
state.layers = [
|
|
797
|
-
{
|
|
798
|
-
name: "error",
|
|
799
|
-
element: this.pageDescriptorProvider.renderError(e),
|
|
800
|
-
index: 0,
|
|
801
|
-
path: "/"
|
|
802
|
-
}
|
|
803
|
-
];
|
|
804
|
-
await this.alepha.emit("react:transition:error", {
|
|
805
|
-
error: e,
|
|
806
|
-
state,
|
|
807
|
-
context
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
if (options.state) {
|
|
811
|
-
options.state.layers = state.layers;
|
|
812
|
-
options.state.pathname = state.pathname;
|
|
813
|
-
options.state.search = state.search;
|
|
814
|
-
}
|
|
815
|
-
await this.alepha.emit("react:transition:end", {
|
|
816
|
-
state: options.state,
|
|
817
|
-
context
|
|
818
|
-
});
|
|
819
|
-
return {
|
|
820
|
-
context,
|
|
821
|
-
state
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
root(state, context) {
|
|
825
|
-
return this.pageDescriptorProvider.root(state, context);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
class ReactBrowserProvider {
|
|
830
|
-
log = core.$logger();
|
|
831
|
-
client = core.$inject(server.HttpClient);
|
|
832
|
-
alepha = core.$inject(core.Alepha);
|
|
833
|
-
router = core.$inject(BrowserRouterProvider);
|
|
834
|
-
headProvider = core.$inject(BrowserHeadProvider);
|
|
835
|
-
root;
|
|
836
|
-
transitioning;
|
|
837
|
-
state = {
|
|
838
|
-
layers: [],
|
|
839
|
-
pathname: "",
|
|
840
|
-
search: ""
|
|
841
|
-
};
|
|
842
|
-
get document() {
|
|
843
|
-
return window.document;
|
|
844
|
-
}
|
|
845
|
-
get history() {
|
|
846
|
-
return window.history;
|
|
847
|
-
}
|
|
848
|
-
get url() {
|
|
849
|
-
return window.location.pathname + window.location.search;
|
|
850
|
-
}
|
|
851
|
-
async invalidate(props) {
|
|
852
|
-
const previous = [];
|
|
853
|
-
if (props) {
|
|
854
|
-
const [key] = Object.keys(props);
|
|
855
|
-
const value = props[key];
|
|
856
|
-
for (const layer of this.state.layers) {
|
|
857
|
-
if (layer.props?.[key]) {
|
|
858
|
-
previous.push({
|
|
859
|
-
...layer,
|
|
860
|
-
props: {
|
|
861
|
-
...layer.props,
|
|
862
|
-
[key]: value
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
break;
|
|
866
|
-
}
|
|
867
|
-
previous.push(layer);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
await this.render({ previous });
|
|
871
|
-
}
|
|
872
|
-
async go(url, options = {}) {
|
|
873
|
-
const result = await this.render({
|
|
874
|
-
url
|
|
875
|
-
});
|
|
876
|
-
if (result.context.url.pathname !== url) {
|
|
877
|
-
this.history.replaceState({}, "", result.context.url.pathname);
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
if (options.replace) {
|
|
881
|
-
this.history.replaceState({}, "", url);
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
this.history.pushState({}, "", url);
|
|
885
|
-
}
|
|
886
|
-
async render(options = {}) {
|
|
887
|
-
const previous = options.previous ?? this.state.layers;
|
|
888
|
-
const url = options.url ?? this.url;
|
|
889
|
-
this.transitioning = { to: url };
|
|
890
|
-
const result = await this.router.transition(
|
|
891
|
-
new URL(`http://localhost${url}`),
|
|
892
|
-
{
|
|
893
|
-
previous,
|
|
894
|
-
state: this.state
|
|
895
|
-
}
|
|
896
|
-
);
|
|
897
|
-
if (result.redirect) {
|
|
898
|
-
return await this.render({ url: result.redirect });
|
|
899
|
-
}
|
|
900
|
-
this.transitioning = void 0;
|
|
901
|
-
return result;
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
|
-
* Get embedded layers from the server.
|
|
905
|
-
*/
|
|
906
|
-
getHydrationState() {
|
|
907
|
-
try {
|
|
908
|
-
if ("__ssr" in window && typeof window.__ssr === "object") {
|
|
909
|
-
return window.__ssr;
|
|
910
|
-
}
|
|
911
|
-
} catch (error) {
|
|
912
|
-
console.error(error);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
916
|
-
ready = core.$hook({
|
|
917
|
-
name: "ready",
|
|
918
|
-
handler: async () => {
|
|
919
|
-
const hydration = this.getHydrationState();
|
|
920
|
-
const previous = hydration?.layers ?? [];
|
|
921
|
-
if (hydration?.links) {
|
|
922
|
-
for (const link of hydration.links.links) {
|
|
923
|
-
this.client.pushLink(link);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
const { context } = await this.render({ previous });
|
|
927
|
-
if (context.head) {
|
|
928
|
-
this.headProvider.renderHead(this.document, context.head);
|
|
929
|
-
}
|
|
930
|
-
await this.alepha.emit("react:browser:render", {
|
|
931
|
-
state: this.state,
|
|
932
|
-
context,
|
|
933
|
-
hydration
|
|
934
|
-
});
|
|
935
|
-
window.addEventListener("popstate", () => {
|
|
936
|
-
this.render();
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
|
-
onTransitionEnd = core.$hook({
|
|
941
|
-
name: "react:transition:end",
|
|
942
|
-
handler: async ({ context }) => {
|
|
943
|
-
this.headProvider.renderHead(this.document, context.head);
|
|
944
|
-
}
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
exports.$page = $page;
|
|
949
|
-
exports.BrowserRouterProvider = BrowserRouterProvider;
|
|
950
|
-
exports.ClientOnly = ClientOnly;
|
|
951
|
-
exports.ErrorBoundary = ErrorBoundary;
|
|
952
|
-
exports.NestedView = NestedView;
|
|
953
|
-
exports.PageDescriptorProvider = PageDescriptorProvider;
|
|
954
|
-
exports.ReactBrowserProvider = ReactBrowserProvider;
|
|
955
|
-
exports.RedirectionError = RedirectionError;
|
|
956
|
-
exports.RouterContext = RouterContext;
|
|
957
|
-
exports.RouterLayerContext = RouterLayerContext;
|
|
958
|
-
exports.isPageRoute = isPageRoute;
|
|
959
|
-
exports.useAlepha = useAlepha;
|
|
960
|
-
exports.useRouterEvents = useRouterEvents;
|