@alepha/react 0.9.3 → 0.9.5
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 +64 -6
- package/dist/index.browser.js +442 -328
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +644 -482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +402 -339
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +412 -349
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +641 -484
- package/dist/index.js.map +1 -1
- package/package.json +16 -11
- package/src/components/Link.tsx +2 -5
- package/src/components/NestedView.tsx +164 -19
- package/src/components/NotFound.tsx +1 -1
- package/src/descriptors/$page.ts +100 -5
- package/src/errors/Redirection.ts +8 -5
- package/src/hooks/useActive.ts +25 -35
- package/src/hooks/useAlepha.ts +16 -2
- package/src/hooks/useClient.ts +7 -4
- package/src/hooks/useInject.ts +4 -1
- package/src/hooks/useQueryParams.ts +9 -6
- package/src/hooks/useRouter.ts +18 -31
- package/src/hooks/useRouterEvents.ts +30 -22
- package/src/hooks/useRouterState.ts +8 -20
- package/src/hooks/useSchema.ts +10 -15
- package/src/hooks/useStore.ts +0 -7
- package/src/index.browser.ts +14 -11
- package/src/index.shared.ts +2 -3
- package/src/index.ts +27 -31
- package/src/providers/ReactBrowserProvider.ts +151 -62
- package/src/providers/ReactBrowserRendererProvider.ts +22 -0
- package/src/providers/ReactBrowserRouterProvider.ts +137 -0
- package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +121 -104
- package/src/providers/ReactServerProvider.ts +90 -76
- package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +49 -62
- package/src/contexts/RouterContext.ts +0 -14
- package/src/providers/BrowserRouterProvider.ts +0 -155
- package/src/providers/ReactBrowserRenderer.ts +0 -93
package/dist/index.browser.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $
|
|
1
|
+
import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, TypeGuard, createDescriptor, t } from "@alepha/core";
|
|
2
2
|
import { AlephaServer, HttpClient } from "@alepha/server";
|
|
3
3
|
import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
|
|
4
|
+
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
|
+
import { $logger } from "@alepha/logger";
|
|
4
6
|
import { RouterProvider } from "@alepha/router";
|
|
5
|
-
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import React, { StrictMode, createContext, createElement, memo, use, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
9
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
8
10
|
|
|
9
11
|
//#region src/descriptors/$page.ts
|
|
@@ -28,7 +30,16 @@ var PageDescriptor = class extends Descriptor {
|
|
|
28
30
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
29
31
|
*/
|
|
30
32
|
async render(options) {
|
|
31
|
-
throw new
|
|
33
|
+
throw new AlephaError("render() method is not implemented in this environment");
|
|
34
|
+
}
|
|
35
|
+
async fetch(options) {
|
|
36
|
+
throw new AlephaError("fetch() method is not implemented in this environment");
|
|
37
|
+
}
|
|
38
|
+
match(url) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
pathname(config) {
|
|
42
|
+
return this.options.path || "";
|
|
32
43
|
}
|
|
33
44
|
};
|
|
34
45
|
$page[KIND] = PageDescriptor;
|
|
@@ -53,7 +64,7 @@ function NotFoundPage(props) {
|
|
|
53
64
|
fontSize: "1rem",
|
|
54
65
|
marginBottom: "0.5rem"
|
|
55
66
|
},
|
|
56
|
-
children: "This page does not exist"
|
|
67
|
+
children: "404 - This page does not exist"
|
|
57
68
|
})
|
|
58
69
|
});
|
|
59
70
|
}
|
|
@@ -76,7 +87,6 @@ const ClientOnly = (props) => {
|
|
|
76
87
|
if (props.disabled) return props.children;
|
|
77
88
|
return mounted ? props.children : props.fallback;
|
|
78
89
|
};
|
|
79
|
-
var ClientOnly_default = ClientOnly;
|
|
80
90
|
|
|
81
91
|
//#endregion
|
|
82
92
|
//#region src/components/ErrorViewer.tsx
|
|
@@ -184,7 +194,6 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
184
194
|
})] })]
|
|
185
195
|
});
|
|
186
196
|
};
|
|
187
|
-
var ErrorViewer_default = ErrorViewer;
|
|
188
197
|
const ErrorViewerProduction = () => {
|
|
189
198
|
const styles = {
|
|
190
199
|
container: {
|
|
@@ -226,45 +235,107 @@ const ErrorViewerProduction = () => {
|
|
|
226
235
|
});
|
|
227
236
|
};
|
|
228
237
|
|
|
229
|
-
//#endregion
|
|
230
|
-
//#region src/contexts/RouterContext.ts
|
|
231
|
-
const RouterContext = createContext(void 0);
|
|
232
|
-
|
|
233
238
|
//#endregion
|
|
234
239
|
//#region src/contexts/RouterLayerContext.ts
|
|
235
240
|
const RouterLayerContext = createContext(void 0);
|
|
236
241
|
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/errors/Redirection.ts
|
|
244
|
+
/**
|
|
245
|
+
* Used for Redirection during the page loading.
|
|
246
|
+
*
|
|
247
|
+
* Depends on the context, it can be thrown or just returned.
|
|
248
|
+
*/
|
|
249
|
+
var Redirection = class extends Error {
|
|
250
|
+
redirect;
|
|
251
|
+
constructor(redirect) {
|
|
252
|
+
super("Redirection");
|
|
253
|
+
this.redirect = redirect;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
237
257
|
//#endregion
|
|
238
258
|
//#region src/contexts/AlephaContext.ts
|
|
239
259
|
const AlephaContext = createContext(void 0);
|
|
240
260
|
|
|
241
261
|
//#endregion
|
|
242
262
|
//#region src/hooks/useAlepha.ts
|
|
263
|
+
/**
|
|
264
|
+
* Main Alepha hook.
|
|
265
|
+
*
|
|
266
|
+
* It provides access to the Alepha instance within a React component.
|
|
267
|
+
*
|
|
268
|
+
* With Alepha, you can access the core functionalities of the framework:
|
|
269
|
+
*
|
|
270
|
+
* - alepha.state() for state management
|
|
271
|
+
* - alepha.inject() for dependency injection
|
|
272
|
+
* - alepha.emit() for event handling
|
|
273
|
+
* etc...
|
|
274
|
+
*/
|
|
243
275
|
const useAlepha = () => {
|
|
244
276
|
const alepha = useContext(AlephaContext);
|
|
245
|
-
if (!alepha) throw new
|
|
277
|
+
if (!alepha) throw new AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
|
|
246
278
|
return alepha;
|
|
247
279
|
};
|
|
248
280
|
|
|
249
281
|
//#endregion
|
|
250
282
|
//#region src/hooks/useRouterEvents.ts
|
|
283
|
+
/**
|
|
284
|
+
* Subscribe to various router events.
|
|
285
|
+
*/
|
|
251
286
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
252
287
|
const alepha = useAlepha();
|
|
253
288
|
useEffect(() => {
|
|
254
289
|
if (!alepha.isBrowser()) return;
|
|
290
|
+
const cb = (callback) => {
|
|
291
|
+
if (typeof callback === "function") return { callback };
|
|
292
|
+
return callback;
|
|
293
|
+
};
|
|
255
294
|
const subs = [];
|
|
256
295
|
const onBegin = opts.onBegin;
|
|
257
296
|
const onEnd = opts.onEnd;
|
|
258
297
|
const onError = opts.onError;
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
if (
|
|
298
|
+
const onSuccess = opts.onSuccess;
|
|
299
|
+
if (onBegin) subs.push(alepha.on("react:transition:begin", cb(onBegin)));
|
|
300
|
+
if (onEnd) subs.push(alepha.on("react:transition:end", cb(onEnd)));
|
|
301
|
+
if (onError) subs.push(alepha.on("react:transition:error", cb(onError)));
|
|
302
|
+
if (onSuccess) subs.push(alepha.on("react:transition:success", cb(onSuccess)));
|
|
262
303
|
return () => {
|
|
263
304
|
for (const sub of subs) sub();
|
|
264
305
|
};
|
|
265
306
|
}, deps);
|
|
266
307
|
};
|
|
267
308
|
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/hooks/useStore.ts
|
|
311
|
+
/**
|
|
312
|
+
* Hook to access and mutate the Alepha state.
|
|
313
|
+
*/
|
|
314
|
+
const useStore = (key, defaultValue) => {
|
|
315
|
+
const alepha = useAlepha();
|
|
316
|
+
useMemo(() => {
|
|
317
|
+
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
318
|
+
}, [defaultValue]);
|
|
319
|
+
const [state, setState] = useState(alepha.state(key));
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
if (!alepha.isBrowser()) return;
|
|
322
|
+
return alepha.on("state:mutate", (ev) => {
|
|
323
|
+
if (ev.key === key) setState(ev.value);
|
|
324
|
+
});
|
|
325
|
+
}, []);
|
|
326
|
+
return [state, (value) => {
|
|
327
|
+
alepha.state(key, value);
|
|
328
|
+
}];
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/hooks/useRouterState.ts
|
|
333
|
+
const useRouterState = () => {
|
|
334
|
+
const [state] = useStore("react.router.state");
|
|
335
|
+
if (!state) throw new AlephaError("Missing react router state");
|
|
336
|
+
return state;
|
|
337
|
+
};
|
|
338
|
+
|
|
268
339
|
//#endregion
|
|
269
340
|
//#region src/components/ErrorBoundary.tsx
|
|
270
341
|
/**
|
|
@@ -294,7 +365,6 @@ var ErrorBoundary = class extends React.Component {
|
|
|
294
365
|
return this.props.children;
|
|
295
366
|
}
|
|
296
367
|
};
|
|
297
|
-
var ErrorBoundary_default = ErrorBoundary;
|
|
298
368
|
|
|
299
369
|
//#endregion
|
|
300
370
|
//#region src/components/NestedView.tsx
|
|
@@ -305,7 +375,7 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
305
375
|
*
|
|
306
376
|
* @example
|
|
307
377
|
* ```tsx
|
|
308
|
-
* import { NestedView } from "
|
|
378
|
+
* import { NestedView } from "alepha/react";
|
|
309
379
|
*
|
|
310
380
|
* class App {
|
|
311
381
|
* parent = $page({
|
|
@@ -320,39 +390,113 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
320
390
|
* ```
|
|
321
391
|
*/
|
|
322
392
|
const NestedView = (props) => {
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const [
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
393
|
+
const index = use(RouterLayerContext)?.index ?? 0;
|
|
394
|
+
const state = useRouterState();
|
|
395
|
+
const [view, setView] = useState(state.layers[index]?.element);
|
|
396
|
+
const [animation, setAnimation] = useState("");
|
|
397
|
+
const animationExitDuration = useRef(0);
|
|
398
|
+
const animationExitNow = useRef(0);
|
|
399
|
+
useRouterEvents({
|
|
400
|
+
onBegin: async ({ previous, state: state$1 }) => {
|
|
401
|
+
const layer = previous.layers[index];
|
|
402
|
+
if (`${state$1.url.pathname}/`.startsWith(`${layer?.path}/`)) return;
|
|
403
|
+
const animationExit = parseAnimation(layer.route?.animation, state$1, "exit");
|
|
404
|
+
if (animationExit) {
|
|
405
|
+
const duration = animationExit.duration || 200;
|
|
406
|
+
animationExitNow.current = Date.now();
|
|
407
|
+
animationExitDuration.current = duration;
|
|
408
|
+
setAnimation(animationExit.animation);
|
|
409
|
+
} else {
|
|
410
|
+
animationExitNow.current = 0;
|
|
411
|
+
animationExitDuration.current = 0;
|
|
412
|
+
setAnimation("");
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
onEnd: async ({ state: state$1 }) => {
|
|
416
|
+
const layer = state$1.layers[index];
|
|
417
|
+
if (animationExitNow.current) {
|
|
418
|
+
const duration = animationExitDuration.current;
|
|
419
|
+
const diff = Date.now() - animationExitNow.current;
|
|
420
|
+
if (diff < duration) await new Promise((resolve) => setTimeout(resolve, duration - diff));
|
|
421
|
+
}
|
|
422
|
+
if (!layer?.cache) {
|
|
423
|
+
setView(layer?.element);
|
|
424
|
+
const animationEnter = parseAnimation(layer?.route?.animation, state$1, "enter");
|
|
425
|
+
if (animationEnter) setAnimation(animationEnter.animation);
|
|
426
|
+
else setAnimation("");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}, []);
|
|
430
|
+
let element = view ?? props.children ?? null;
|
|
431
|
+
if (animation) element = /* @__PURE__ */ jsx("div", {
|
|
432
|
+
style: {
|
|
433
|
+
display: "flex",
|
|
434
|
+
flex: 1,
|
|
435
|
+
height: "100%",
|
|
436
|
+
width: "100%",
|
|
437
|
+
position: "relative",
|
|
438
|
+
overflow: "hidden"
|
|
439
|
+
},
|
|
440
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
441
|
+
style: {
|
|
442
|
+
height: "100%",
|
|
443
|
+
width: "100%",
|
|
444
|
+
display: "flex",
|
|
445
|
+
animation
|
|
446
|
+
},
|
|
447
|
+
children: element
|
|
448
|
+
})
|
|
449
|
+
});
|
|
450
|
+
if (props.errorBoundary === false) return /* @__PURE__ */ jsx(Fragment, { children: element });
|
|
451
|
+
if (props.errorBoundary) return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
452
|
+
fallback: props.errorBoundary,
|
|
453
|
+
children: element
|
|
454
|
+
});
|
|
455
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
334
456
|
fallback: (error) => {
|
|
335
|
-
|
|
457
|
+
const result = state.onError(error, state);
|
|
458
|
+
if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
|
|
459
|
+
return result;
|
|
336
460
|
},
|
|
337
461
|
children: element
|
|
338
462
|
});
|
|
339
463
|
};
|
|
340
|
-
var NestedView_default = NestedView;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
464
|
+
var NestedView_default = memo(NestedView);
|
|
465
|
+
function parseAnimation(animationLike, state, type = "enter") {
|
|
466
|
+
if (!animationLike) return void 0;
|
|
467
|
+
const DEFAULT_DURATION = 300;
|
|
468
|
+
const animation = typeof animationLike === "function" ? animationLike(state) : animationLike;
|
|
469
|
+
if (typeof animation === "string") {
|
|
470
|
+
if (type === "exit") return;
|
|
471
|
+
return {
|
|
472
|
+
duration: DEFAULT_DURATION,
|
|
473
|
+
animation: `${DEFAULT_DURATION}ms ${animation}`
|
|
474
|
+
};
|
|
349
475
|
}
|
|
350
|
-
|
|
476
|
+
if (typeof animation === "object") {
|
|
477
|
+
const anim = animation[type];
|
|
478
|
+
const duration = typeof anim === "object" ? anim.duration ?? DEFAULT_DURATION : DEFAULT_DURATION;
|
|
479
|
+
const name = typeof anim === "object" ? anim.name : anim;
|
|
480
|
+
if (type === "exit") {
|
|
481
|
+
const timing$1 = typeof anim === "object" ? anim.timing ?? "" : "";
|
|
482
|
+
return {
|
|
483
|
+
duration,
|
|
484
|
+
animation: `${duration}ms ${timing$1} ${name}`
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const timing = typeof anim === "object" ? anim.timing ?? "" : "";
|
|
488
|
+
return {
|
|
489
|
+
duration,
|
|
490
|
+
animation: `${duration}ms ${timing} ${name}`
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return void 0;
|
|
494
|
+
}
|
|
351
495
|
|
|
352
496
|
//#endregion
|
|
353
|
-
//#region src/providers/
|
|
497
|
+
//#region src/providers/ReactPageProvider.ts
|
|
354
498
|
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
355
|
-
var
|
|
499
|
+
var ReactPageProvider = class {
|
|
356
500
|
log = $logger();
|
|
357
501
|
env = $env(envSchema$1);
|
|
358
502
|
alepha = $inject(Alepha);
|
|
@@ -381,22 +525,29 @@ var PageDescriptorProvider = class {
|
|
|
381
525
|
return url.replace(/\/\/+/g, "/") || "/";
|
|
382
526
|
}
|
|
383
527
|
url(name, options = {}) {
|
|
384
|
-
return new URL(this.pathname(name, options), options.
|
|
528
|
+
return new URL(this.pathname(name, options), options.host ?? `http://localhost`);
|
|
385
529
|
}
|
|
386
|
-
root(state
|
|
387
|
-
const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(
|
|
388
|
-
state,
|
|
389
|
-
context
|
|
390
|
-
} }, createElement(NestedView_default, {}, state.layers[0]?.element)));
|
|
530
|
+
root(state) {
|
|
531
|
+
const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(NestedView_default, {}, state.layers[0]?.element));
|
|
391
532
|
if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
|
|
392
533
|
return root;
|
|
393
534
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
535
|
+
convertStringObjectToObject = (schema, value) => {
|
|
536
|
+
if (TypeGuard.IsObject(schema) && typeof value === "object") {
|
|
537
|
+
for (const key in schema.properties) if (TypeGuard.IsObject(schema.properties[key]) && typeof value[key] === "string") try {
|
|
538
|
+
value[key] = this.alepha.parse(schema.properties[key], decodeURIComponent(value[key]));
|
|
539
|
+
} catch (e) {}
|
|
540
|
+
}
|
|
541
|
+
return value;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* Create a new RouterState based on a given route and request.
|
|
545
|
+
* This method resolves the layers for the route, applying any query and params schemas defined in the route.
|
|
546
|
+
* It also handles errors and redirects.
|
|
547
|
+
*/
|
|
548
|
+
async createLayers(route, state, previous = []) {
|
|
397
549
|
let context = {};
|
|
398
550
|
const stack = [{ route }];
|
|
399
|
-
request.onError = (error) => this.renderError(error);
|
|
400
551
|
let parent = route.parent;
|
|
401
552
|
while (parent) {
|
|
402
553
|
stack.unshift({ route: parent });
|
|
@@ -408,19 +559,19 @@ var PageDescriptorProvider = class {
|
|
|
408
559
|
const route$1 = it.route;
|
|
409
560
|
const config = {};
|
|
410
561
|
try {
|
|
411
|
-
|
|
562
|
+
this.convertStringObjectToObject(route$1.schema?.query, state.query);
|
|
563
|
+
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, state.query) : {};
|
|
412
564
|
} catch (e) {
|
|
413
565
|
it.error = e;
|
|
414
566
|
break;
|
|
415
567
|
}
|
|
416
568
|
try {
|
|
417
|
-
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params,
|
|
569
|
+
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, state.params) : {};
|
|
418
570
|
} catch (e) {
|
|
419
571
|
it.error = e;
|
|
420
572
|
break;
|
|
421
573
|
}
|
|
422
574
|
it.config = { ...config };
|
|
423
|
-
const previous = request.previous;
|
|
424
575
|
if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
|
|
425
576
|
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
426
577
|
const prev = JSON.stringify({
|
|
@@ -446,7 +597,7 @@ var PageDescriptorProvider = class {
|
|
|
446
597
|
if (!route$1.resolve) continue;
|
|
447
598
|
try {
|
|
448
599
|
const props = await route$1.resolve?.({
|
|
449
|
-
...
|
|
600
|
+
...state,
|
|
450
601
|
...config,
|
|
451
602
|
...context
|
|
452
603
|
}) ?? {};
|
|
@@ -456,11 +607,8 @@ var PageDescriptorProvider = class {
|
|
|
456
607
|
...props
|
|
457
608
|
};
|
|
458
609
|
} catch (e) {
|
|
459
|
-
if (e instanceof Redirection) return
|
|
460
|
-
|
|
461
|
-
search
|
|
462
|
-
});
|
|
463
|
-
this.log.error(e);
|
|
610
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
611
|
+
this.log.error("Page resolver has failed", e);
|
|
464
612
|
it.error = e;
|
|
465
613
|
break;
|
|
466
614
|
}
|
|
@@ -476,69 +624,58 @@ var PageDescriptorProvider = class {
|
|
|
476
624
|
const path = acc.replace(/\/+/, "/");
|
|
477
625
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
478
626
|
if (localErrorHandler) {
|
|
479
|
-
const onErrorParent =
|
|
480
|
-
|
|
627
|
+
const onErrorParent = state.onError;
|
|
628
|
+
state.onError = (error, context$1) => {
|
|
481
629
|
const result = localErrorHandler(error, context$1);
|
|
482
630
|
if (result === void 0) return onErrorParent(error, context$1);
|
|
483
631
|
return result;
|
|
484
632
|
};
|
|
485
633
|
}
|
|
486
|
-
if (it.error) try {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
pathname,
|
|
491
|
-
search
|
|
634
|
+
if (!it.error) try {
|
|
635
|
+
const element = await this.createElement(it.route, {
|
|
636
|
+
...props,
|
|
637
|
+
...context
|
|
492
638
|
});
|
|
493
|
-
|
|
494
|
-
|
|
639
|
+
state.layers.push({
|
|
640
|
+
name: it.route.name,
|
|
641
|
+
props,
|
|
642
|
+
part: it.route.path,
|
|
643
|
+
config: it.config,
|
|
644
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
645
|
+
index: i + 1,
|
|
646
|
+
path,
|
|
647
|
+
route: it.route,
|
|
648
|
+
cache: it.cache
|
|
649
|
+
});
|
|
650
|
+
} catch (e) {
|
|
651
|
+
it.error = e;
|
|
652
|
+
}
|
|
653
|
+
if (it.error) try {
|
|
654
|
+
let element = await state.onError(it.error, state);
|
|
655
|
+
if (element === void 0) throw it.error;
|
|
656
|
+
if (element instanceof Redirection) return { redirect: element.redirect };
|
|
657
|
+
if (element === null) element = this.renderError(it.error);
|
|
658
|
+
state.layers.push({
|
|
495
659
|
props,
|
|
496
660
|
error: it.error,
|
|
497
661
|
name: it.route.name,
|
|
498
662
|
part: it.route.path,
|
|
499
663
|
config: it.config,
|
|
500
|
-
element: this.renderView(i + 1, path, element
|
|
664
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
501
665
|
index: i + 1,
|
|
502
666
|
path,
|
|
503
667
|
route: it.route
|
|
504
668
|
});
|
|
505
669
|
break;
|
|
506
670
|
} catch (e) {
|
|
507
|
-
if (e instanceof Redirection) return
|
|
508
|
-
pathname,
|
|
509
|
-
search
|
|
510
|
-
});
|
|
671
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
511
672
|
throw e;
|
|
512
673
|
}
|
|
513
|
-
const element = await this.createElement(it.route, {
|
|
514
|
-
...props,
|
|
515
|
-
...context
|
|
516
|
-
});
|
|
517
|
-
layers.push({
|
|
518
|
-
name: it.route.name,
|
|
519
|
-
props,
|
|
520
|
-
part: it.route.path,
|
|
521
|
-
config: it.config,
|
|
522
|
-
element: this.renderView(i + 1, path, element, it.route),
|
|
523
|
-
index: i + 1,
|
|
524
|
-
path,
|
|
525
|
-
route: it.route,
|
|
526
|
-
cache: it.cache
|
|
527
|
-
});
|
|
528
674
|
}
|
|
529
|
-
return {
|
|
530
|
-
layers,
|
|
531
|
-
pathname,
|
|
532
|
-
search
|
|
533
|
-
};
|
|
675
|
+
return { state };
|
|
534
676
|
}
|
|
535
|
-
createRedirectionLayer(
|
|
536
|
-
return {
|
|
537
|
-
layers: [],
|
|
538
|
-
redirect: typeof href === "string" ? href : this.href(href),
|
|
539
|
-
pathname: context.pathname,
|
|
540
|
-
search: context.search
|
|
541
|
-
};
|
|
677
|
+
createRedirectionLayer(redirect) {
|
|
678
|
+
return { redirect };
|
|
542
679
|
}
|
|
543
680
|
getErrorHandler(route) {
|
|
544
681
|
if (route.errorHandler) return route.errorHandler;
|
|
@@ -549,6 +686,7 @@ var PageDescriptorProvider = class {
|
|
|
549
686
|
}
|
|
550
687
|
}
|
|
551
688
|
async createElement(page, props) {
|
|
689
|
+
if (page.lazy && page.component) this.log.warn(`Page ${page.name} has both lazy and component options, lazy will be used`);
|
|
552
690
|
if (page.lazy) {
|
|
553
691
|
const component = await page.lazy();
|
|
554
692
|
return createElement(component.default, props);
|
|
@@ -557,7 +695,7 @@ var PageDescriptorProvider = class {
|
|
|
557
695
|
return void 0;
|
|
558
696
|
}
|
|
559
697
|
renderError(error) {
|
|
560
|
-
return createElement(
|
|
698
|
+
return createElement(ErrorViewer, {
|
|
561
699
|
error,
|
|
562
700
|
alepha: this.alepha
|
|
563
701
|
});
|
|
@@ -583,7 +721,7 @@ var PageDescriptorProvider = class {
|
|
|
583
721
|
}
|
|
584
722
|
renderView(index, path, view, page) {
|
|
585
723
|
view ??= this.renderEmptyView();
|
|
586
|
-
const element = page.client ? createElement(
|
|
724
|
+
const element = page.client ? createElement(ClientOnly, typeof page.client === "object" ? page.client : {}, view) : view;
|
|
587
725
|
return createElement(RouterLayerContext.Provider, { value: {
|
|
588
726
|
index,
|
|
589
727
|
path
|
|
@@ -665,57 +803,47 @@ const isPageRoute = (it) => {
|
|
|
665
803
|
};
|
|
666
804
|
|
|
667
805
|
//#endregion
|
|
668
|
-
//#region src/providers/
|
|
669
|
-
var
|
|
806
|
+
//#region src/providers/ReactBrowserRouterProvider.ts
|
|
807
|
+
var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
670
808
|
log = $logger();
|
|
671
809
|
alepha = $inject(Alepha);
|
|
672
|
-
|
|
810
|
+
pageApi = $inject(ReactPageProvider);
|
|
673
811
|
add(entry) {
|
|
674
|
-
this.
|
|
812
|
+
this.pageApi.add(entry);
|
|
675
813
|
}
|
|
676
814
|
configure = $hook({
|
|
677
815
|
on: "configure",
|
|
678
816
|
handler: async () => {
|
|
679
|
-
for (const page of this.
|
|
817
|
+
for (const page of this.pageApi.getPages()) if (page.component || page.lazy) this.push({
|
|
680
818
|
path: page.match,
|
|
681
819
|
page
|
|
682
820
|
});
|
|
683
821
|
}
|
|
684
822
|
});
|
|
685
|
-
async transition(url,
|
|
823
|
+
async transition(url, previous = [], meta = {}) {
|
|
686
824
|
const { pathname, search } = url;
|
|
687
|
-
const
|
|
688
|
-
pathname,
|
|
689
|
-
search,
|
|
690
|
-
layers: []
|
|
691
|
-
};
|
|
692
|
-
const context = {
|
|
825
|
+
const entry = {
|
|
693
826
|
url,
|
|
694
827
|
query: {},
|
|
695
828
|
params: {},
|
|
829
|
+
layers: [],
|
|
696
830
|
onError: () => null,
|
|
697
|
-
|
|
831
|
+
meta
|
|
698
832
|
};
|
|
833
|
+
const state = entry;
|
|
699
834
|
await this.alepha.emit("react:transition:begin", {
|
|
700
|
-
state,
|
|
701
|
-
|
|
835
|
+
previous: this.alepha.state("react.router.state"),
|
|
836
|
+
state
|
|
702
837
|
});
|
|
703
838
|
try {
|
|
704
|
-
const previous = options.previous;
|
|
705
839
|
const { route, params } = this.match(pathname);
|
|
706
840
|
const query = {};
|
|
707
841
|
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
context.previous = previous;
|
|
842
|
+
state.query = query;
|
|
843
|
+
state.params = params ?? {};
|
|
711
844
|
if (isPageRoute(route)) {
|
|
712
|
-
const
|
|
713
|
-
if (
|
|
714
|
-
redirect: result.redirect,
|
|
715
|
-
state,
|
|
716
|
-
context
|
|
717
|
-
};
|
|
718
|
-
state.layers = result.layers;
|
|
845
|
+
const { redirect } = await this.pageApi.createLayers(route.page, state, previous);
|
|
846
|
+
if (redirect) return redirect;
|
|
719
847
|
}
|
|
720
848
|
if (state.layers.length === 0) state.layers.push({
|
|
721
849
|
name: "not-found",
|
|
@@ -723,86 +851,91 @@ var BrowserRouterProvider = class extends RouterProvider {
|
|
|
723
851
|
index: 0,
|
|
724
852
|
path: "/"
|
|
725
853
|
});
|
|
726
|
-
await this.alepha.emit("react:transition:success", {
|
|
727
|
-
state,
|
|
728
|
-
context
|
|
729
|
-
});
|
|
854
|
+
await this.alepha.emit("react:transition:success", { state });
|
|
730
855
|
} catch (e) {
|
|
731
|
-
this.log.error(e);
|
|
856
|
+
this.log.error("Transition has failed", e);
|
|
732
857
|
state.layers = [{
|
|
733
858
|
name: "error",
|
|
734
|
-
element: this.
|
|
859
|
+
element: this.pageApi.renderError(e),
|
|
735
860
|
index: 0,
|
|
736
861
|
path: "/"
|
|
737
862
|
}];
|
|
738
863
|
await this.alepha.emit("react:transition:error", {
|
|
739
864
|
error: e,
|
|
740
|
-
state
|
|
741
|
-
context
|
|
865
|
+
state
|
|
742
866
|
});
|
|
743
867
|
}
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
options.state.search = state.search;
|
|
748
|
-
}
|
|
749
|
-
if (options.previous) for (let i = 0; i < options.previous.length; i++) {
|
|
750
|
-
const layer = options.previous[i];
|
|
751
|
-
if (state.layers[i]?.name !== layer.name) this.pageDescriptorProvider.page(layer.name)?.onLeave?.();
|
|
868
|
+
if (previous) for (let i = 0; i < previous.length; i++) {
|
|
869
|
+
const layer = previous[i];
|
|
870
|
+
if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
|
|
752
871
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
context
|
|
756
|
-
});
|
|
757
|
-
return {
|
|
758
|
-
context,
|
|
759
|
-
state
|
|
760
|
-
};
|
|
872
|
+
this.alepha.state("react.router.state", state);
|
|
873
|
+
await this.alepha.emit("react:transition:end", { state });
|
|
761
874
|
}
|
|
762
|
-
root(state
|
|
763
|
-
return this.
|
|
875
|
+
root(state) {
|
|
876
|
+
return this.pageApi.root(state);
|
|
764
877
|
}
|
|
765
878
|
};
|
|
766
879
|
|
|
767
880
|
//#endregion
|
|
768
881
|
//#region src/providers/ReactBrowserProvider.ts
|
|
882
|
+
const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
|
|
769
883
|
var ReactBrowserProvider = class {
|
|
884
|
+
env = $env(envSchema);
|
|
770
885
|
log = $logger();
|
|
771
886
|
client = $inject(LinkProvider);
|
|
772
887
|
alepha = $inject(Alepha);
|
|
773
|
-
router = $inject(
|
|
774
|
-
|
|
888
|
+
router = $inject(ReactBrowserRouterProvider);
|
|
889
|
+
dateTimeProvider = $inject(DateTimeProvider);
|
|
890
|
+
options = { scrollRestoration: "top" };
|
|
891
|
+
getRootElement() {
|
|
892
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
893
|
+
if (root) return root;
|
|
894
|
+
const div = this.document.createElement("div");
|
|
895
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
896
|
+
this.document.body.prepend(div);
|
|
897
|
+
return div;
|
|
898
|
+
}
|
|
775
899
|
transitioning;
|
|
776
|
-
state
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
900
|
+
get state() {
|
|
901
|
+
return this.alepha.state("react.router.state");
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Accessor for Document DOM API.
|
|
905
|
+
*/
|
|
781
906
|
get document() {
|
|
782
907
|
return window.document;
|
|
783
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Accessor for History DOM API.
|
|
911
|
+
*/
|
|
784
912
|
get history() {
|
|
785
913
|
return window.history;
|
|
786
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Accessor for Location DOM API.
|
|
917
|
+
*/
|
|
787
918
|
get location() {
|
|
788
919
|
return window.location;
|
|
789
920
|
}
|
|
921
|
+
get base() {
|
|
922
|
+
const base = import.meta.env?.BASE_URL;
|
|
923
|
+
if (!base || base === "/") return "";
|
|
924
|
+
return base;
|
|
925
|
+
}
|
|
790
926
|
get url() {
|
|
791
|
-
|
|
792
|
-
if (
|
|
793
|
-
url = url.replace(import.meta.env?.BASE_URL, "");
|
|
794
|
-
if (!url.startsWith("/")) url = `/${url}`;
|
|
795
|
-
}
|
|
927
|
+
const url = this.location.pathname + this.location.search;
|
|
928
|
+
if (this.base) return url.replace(this.base, "");
|
|
796
929
|
return url;
|
|
797
930
|
}
|
|
798
|
-
pushState(
|
|
799
|
-
|
|
800
|
-
if (
|
|
801
|
-
|
|
802
|
-
else this.history.pushState({}, "", path);
|
|
931
|
+
pushState(path, replace) {
|
|
932
|
+
const url = this.base + path;
|
|
933
|
+
if (replace) this.history.replaceState({}, "", url);
|
|
934
|
+
else this.history.pushState({}, "", url);
|
|
803
935
|
}
|
|
804
936
|
async invalidate(props) {
|
|
805
937
|
const previous = [];
|
|
938
|
+
this.log.trace("Invalidating layers");
|
|
806
939
|
if (props) {
|
|
807
940
|
const [key] = Object.keys(props);
|
|
808
941
|
const value = props[key];
|
|
@@ -823,9 +956,17 @@ var ReactBrowserProvider = class {
|
|
|
823
956
|
await this.render({ previous });
|
|
824
957
|
}
|
|
825
958
|
async go(url, options = {}) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
959
|
+
this.log.trace(`Going to ${url}`, {
|
|
960
|
+
url,
|
|
961
|
+
options
|
|
962
|
+
});
|
|
963
|
+
await this.render({
|
|
964
|
+
url,
|
|
965
|
+
previous: options.force ? [] : this.state.layers,
|
|
966
|
+
meta: options.meta
|
|
967
|
+
});
|
|
968
|
+
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
969
|
+
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
829
970
|
return;
|
|
830
971
|
}
|
|
831
972
|
this.pushState(url, options.replace);
|
|
@@ -833,14 +974,20 @@ var ReactBrowserProvider = class {
|
|
|
833
974
|
async render(options = {}) {
|
|
834
975
|
const previous = options.previous ?? this.state.layers;
|
|
835
976
|
const url = options.url ?? this.url;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
|
|
977
|
+
const start = this.dateTimeProvider.now();
|
|
978
|
+
this.transitioning = {
|
|
979
|
+
to: url,
|
|
980
|
+
from: this.state?.url.pathname
|
|
981
|
+
};
|
|
982
|
+
this.log.debug("Transitioning...", { to: url });
|
|
983
|
+
const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous, options.meta);
|
|
984
|
+
if (redirect) {
|
|
985
|
+
this.log.info("Redirecting to", { redirect });
|
|
986
|
+
return await this.render({ url: redirect });
|
|
987
|
+
}
|
|
988
|
+
const ms = this.dateTimeProvider.now().diff(start);
|
|
989
|
+
this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
|
|
842
990
|
this.transitioning = void 0;
|
|
843
|
-
return result;
|
|
844
991
|
}
|
|
845
992
|
/**
|
|
846
993
|
* Get embedded layers from the server.
|
|
@@ -852,26 +999,34 @@ var ReactBrowserProvider = class {
|
|
|
852
999
|
console.error(error);
|
|
853
1000
|
}
|
|
854
1001
|
}
|
|
1002
|
+
onTransitionEnd = $hook({
|
|
1003
|
+
on: "react:transition:end",
|
|
1004
|
+
handler: () => {
|
|
1005
|
+
if (this.options.scrollRestoration === "top" && typeof window !== "undefined" && !this.alepha.isTest()) {
|
|
1006
|
+
this.log.trace("Restoring scroll position to top");
|
|
1007
|
+
window.scrollTo(0, 0);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
855
1011
|
ready = $hook({
|
|
856
1012
|
on: "ready",
|
|
857
1013
|
handler: async () => {
|
|
858
1014
|
const hydration = this.getHydrationState();
|
|
859
1015
|
const previous = hydration?.layers ?? [];
|
|
860
1016
|
if (hydration) {
|
|
861
|
-
for (const [key, value] of Object.entries(hydration)) if (key !== "layers"
|
|
1017
|
+
for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state(key, value);
|
|
862
1018
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
prefix: hydration.links.prefix
|
|
866
|
-
});
|
|
867
|
-
const { context } = await this.render({ previous });
|
|
1019
|
+
await this.render({ previous });
|
|
1020
|
+
const element = this.router.root(this.state);
|
|
868
1021
|
await this.alepha.emit("react:browser:render", {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
hydration
|
|
1022
|
+
element,
|
|
1023
|
+
root: this.getRootElement(),
|
|
1024
|
+
hydration,
|
|
1025
|
+
state: this.state
|
|
872
1026
|
});
|
|
873
1027
|
window.addEventListener("popstate", () => {
|
|
874
|
-
if (this.state.pathname === this.
|
|
1028
|
+
if (this.base + this.state.url.pathname === this.location.pathname) return;
|
|
1029
|
+
this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
|
|
875
1030
|
this.render();
|
|
876
1031
|
});
|
|
877
1032
|
}
|
|
@@ -879,67 +1034,51 @@ var ReactBrowserProvider = class {
|
|
|
879
1034
|
};
|
|
880
1035
|
|
|
881
1036
|
//#endregion
|
|
882
|
-
//#region src/providers/
|
|
883
|
-
|
|
884
|
-
var ReactBrowserRenderer = class {
|
|
885
|
-
browserProvider = $inject(ReactBrowserProvider);
|
|
886
|
-
browserRouterProvider = $inject(BrowserRouterProvider);
|
|
887
|
-
env = $env(envSchema);
|
|
1037
|
+
//#region src/providers/ReactBrowserRendererProvider.ts
|
|
1038
|
+
var ReactBrowserRendererProvider = class {
|
|
888
1039
|
log = $logger();
|
|
889
1040
|
root;
|
|
890
|
-
|
|
891
|
-
getRootElement() {
|
|
892
|
-
const root = this.browserProvider.document.getElementById(this.env.REACT_ROOT_ID);
|
|
893
|
-
if (root) return root;
|
|
894
|
-
const div = this.browserProvider.document.createElement("div");
|
|
895
|
-
div.id = this.env.REACT_ROOT_ID;
|
|
896
|
-
this.browserProvider.document.body.prepend(div);
|
|
897
|
-
return div;
|
|
898
|
-
}
|
|
899
|
-
ready = $hook({
|
|
1041
|
+
onBrowserRender = $hook({
|
|
900
1042
|
on: "react:browser:render",
|
|
901
|
-
handler: async ({
|
|
902
|
-
const element = this.browserRouterProvider.root(state, context);
|
|
1043
|
+
handler: async ({ hydration, root, element }) => {
|
|
903
1044
|
if (hydration?.layers) {
|
|
904
|
-
this.root = hydrateRoot(
|
|
1045
|
+
this.root = hydrateRoot(root, element);
|
|
905
1046
|
this.log.info("Hydrated root element");
|
|
906
1047
|
} else {
|
|
907
|
-
this.root ??= createRoot(
|
|
1048
|
+
this.root ??= createRoot(root);
|
|
908
1049
|
this.root.render(element);
|
|
909
1050
|
this.log.info("Created root element");
|
|
910
1051
|
}
|
|
911
1052
|
}
|
|
912
1053
|
});
|
|
913
|
-
onTransitionEnd = $hook({
|
|
914
|
-
on: "react:transition:end",
|
|
915
|
-
handler: () => {
|
|
916
|
-
if (this.options.scrollRestoration === "top" && typeof window !== "undefined") window.scrollTo(0, 0);
|
|
917
|
-
}
|
|
918
|
-
});
|
|
919
1054
|
};
|
|
920
1055
|
|
|
921
1056
|
//#endregion
|
|
922
|
-
//#region src/
|
|
923
|
-
var
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
this.state
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
this.
|
|
1057
|
+
//#region src/services/ReactRouter.ts
|
|
1058
|
+
var ReactRouter = class {
|
|
1059
|
+
alepha = $inject(Alepha);
|
|
1060
|
+
pageApi = $inject(ReactPageProvider);
|
|
1061
|
+
get state() {
|
|
1062
|
+
return this.alepha.state("react.router.state");
|
|
1063
|
+
}
|
|
1064
|
+
get pages() {
|
|
1065
|
+
return this.pageApi.getPages();
|
|
1066
|
+
}
|
|
1067
|
+
get browser() {
|
|
1068
|
+
if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
|
|
1069
|
+
return void 0;
|
|
931
1070
|
}
|
|
932
1071
|
path(name, config = {}) {
|
|
933
1072
|
return this.pageApi.pathname(name, {
|
|
934
1073
|
params: {
|
|
935
|
-
...this.
|
|
1074
|
+
...this.state.params,
|
|
936
1075
|
...config.params
|
|
937
1076
|
},
|
|
938
1077
|
query: config.query
|
|
939
1078
|
});
|
|
940
1079
|
}
|
|
941
1080
|
getURL() {
|
|
942
|
-
if (!this.browser) return this.
|
|
1081
|
+
if (!this.browser) return this.state.url;
|
|
943
1082
|
return new URL(this.location.href);
|
|
944
1083
|
}
|
|
945
1084
|
get location() {
|
|
@@ -950,11 +1089,11 @@ var RouterHookApi = class {
|
|
|
950
1089
|
return this.state;
|
|
951
1090
|
}
|
|
952
1091
|
get pathname() {
|
|
953
|
-
return this.state.pathname;
|
|
1092
|
+
return this.state.url.pathname;
|
|
954
1093
|
}
|
|
955
1094
|
get query() {
|
|
956
1095
|
const query = {};
|
|
957
|
-
for (const [key, value] of new URLSearchParams(this.state.search).entries()) query[key] = String(value);
|
|
1096
|
+
for (const [key, value] of new URLSearchParams(this.state.url.search).entries()) query[key] = String(value);
|
|
958
1097
|
return query;
|
|
959
1098
|
}
|
|
960
1099
|
async back() {
|
|
@@ -966,17 +1105,6 @@ var RouterHookApi = class {
|
|
|
966
1105
|
async invalidate(props) {
|
|
967
1106
|
await this.browser?.invalidate(props);
|
|
968
1107
|
}
|
|
969
|
-
/**
|
|
970
|
-
* Create a valid href for the given pathname.
|
|
971
|
-
*
|
|
972
|
-
* @param pathname
|
|
973
|
-
* @param layer
|
|
974
|
-
*/
|
|
975
|
-
createHref(pathname, layer = this.layer, options = {}) {
|
|
976
|
-
if (typeof pathname === "object") pathname = pathname.options.path ?? "";
|
|
977
|
-
if (options.params) for (const [key, value] of Object.entries(options.params)) pathname = pathname.replace(`:${key}`, String(value));
|
|
978
|
-
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
979
|
-
}
|
|
980
1108
|
async go(path, options) {
|
|
981
1109
|
for (const page of this.pages) if (page.name === path) {
|
|
982
1110
|
await this.browser?.go(this.path(path, options), options);
|
|
@@ -991,7 +1119,7 @@ var RouterHookApi = class {
|
|
|
991
1119
|
break;
|
|
992
1120
|
}
|
|
993
1121
|
return {
|
|
994
|
-
href,
|
|
1122
|
+
href: this.base(href),
|
|
995
1123
|
onClick: (ev) => {
|
|
996
1124
|
ev.stopPropagation();
|
|
997
1125
|
ev.preventDefault();
|
|
@@ -999,6 +1127,11 @@ var RouterHookApi = class {
|
|
|
999
1127
|
}
|
|
1000
1128
|
};
|
|
1001
1129
|
}
|
|
1130
|
+
base(path) {
|
|
1131
|
+
const base = import.meta.env?.BASE_URL;
|
|
1132
|
+
if (!base || base === "/") return path;
|
|
1133
|
+
return base + path;
|
|
1134
|
+
}
|
|
1002
1135
|
/**
|
|
1003
1136
|
* Set query params.
|
|
1004
1137
|
*
|
|
@@ -1014,107 +1147,99 @@ var RouterHookApi = class {
|
|
|
1014
1147
|
}
|
|
1015
1148
|
};
|
|
1016
1149
|
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/hooks/useInject.ts
|
|
1152
|
+
/**
|
|
1153
|
+
* Hook to inject a service instance.
|
|
1154
|
+
* It's a wrapper of `useAlepha().inject(service)` with a memoization.
|
|
1155
|
+
*/
|
|
1156
|
+
const useInject = (service) => {
|
|
1157
|
+
const alepha = useAlepha();
|
|
1158
|
+
return useMemo(() => alepha.inject(service), []);
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1017
1161
|
//#endregion
|
|
1018
1162
|
//#region src/hooks/useRouter.ts
|
|
1163
|
+
/**
|
|
1164
|
+
* Use this hook to access the React Router instance.
|
|
1165
|
+
*
|
|
1166
|
+
* You can add a type parameter to specify the type of your application.
|
|
1167
|
+
* This will allow you to use the router in a typesafe way.
|
|
1168
|
+
*
|
|
1169
|
+
* @example
|
|
1170
|
+
* class App {
|
|
1171
|
+
* home = $page();
|
|
1172
|
+
* }
|
|
1173
|
+
*
|
|
1174
|
+
* const router = useRouter<App>();
|
|
1175
|
+
* router.go("home"); // typesafe
|
|
1176
|
+
*/
|
|
1019
1177
|
const useRouter = () => {
|
|
1020
|
-
|
|
1021
|
-
const ctx = useContext(RouterContext);
|
|
1022
|
-
const layer = useContext(RouterLayerContext);
|
|
1023
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1024
|
-
const pages = useMemo(() => {
|
|
1025
|
-
return alepha.inject(PageDescriptorProvider).getPages();
|
|
1026
|
-
}, []);
|
|
1027
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.inject(PageDescriptorProvider), alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1178
|
+
return useInject(ReactRouter);
|
|
1028
1179
|
};
|
|
1029
1180
|
|
|
1030
1181
|
//#endregion
|
|
1031
1182
|
//#region src/components/Link.tsx
|
|
1032
1183
|
const Link = (props) => {
|
|
1033
1184
|
const router = useRouter();
|
|
1034
|
-
const { to,...anchorProps } = props;
|
|
1035
1185
|
return /* @__PURE__ */ jsx("a", {
|
|
1036
|
-
...
|
|
1037
|
-
...
|
|
1186
|
+
...props,
|
|
1187
|
+
...router.anchor(props.href),
|
|
1038
1188
|
children: props.children
|
|
1039
1189
|
});
|
|
1040
1190
|
};
|
|
1041
|
-
var Link_default = Link;
|
|
1042
1191
|
|
|
1043
1192
|
//#endregion
|
|
1044
1193
|
//#region src/hooks/useActive.ts
|
|
1045
|
-
const useActive = (
|
|
1194
|
+
const useActive = (args) => {
|
|
1046
1195
|
const router = useRouter();
|
|
1047
|
-
const ctx = useContext(RouterContext);
|
|
1048
|
-
const layer = useContext(RouterLayerContext);
|
|
1049
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1050
|
-
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
1051
|
-
const href = useMemo(() => router.createHref(path ?? "", layer), [path, layer]);
|
|
1052
1196
|
const [isPending, setPending] = useState(false);
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1197
|
+
const state = useRouterState();
|
|
1198
|
+
const current = state.url.pathname;
|
|
1199
|
+
const options = typeof args === "string" ? { href: args } : {
|
|
1200
|
+
...args,
|
|
1201
|
+
href: args.href
|
|
1202
|
+
};
|
|
1203
|
+
const href = options.href;
|
|
1204
|
+
let isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1205
|
+
if (options.startWith && !isActive) isActive = current.startsWith(href);
|
|
1057
1206
|
return {
|
|
1058
1207
|
isPending,
|
|
1059
1208
|
isActive,
|
|
1060
1209
|
anchorProps: {
|
|
1061
|
-
href,
|
|
1062
|
-
onClick: (ev) => {
|
|
1210
|
+
href: router.base(href),
|
|
1211
|
+
onClick: async (ev) => {
|
|
1063
1212
|
ev?.stopPropagation();
|
|
1064
1213
|
ev?.preventDefault();
|
|
1065
1214
|
if (isActive) return;
|
|
1066
1215
|
if (isPending) return;
|
|
1067
1216
|
setPending(true);
|
|
1068
|
-
|
|
1217
|
+
try {
|
|
1218
|
+
await router.go(href);
|
|
1219
|
+
} finally {
|
|
1069
1220
|
setPending(false);
|
|
1070
|
-
}
|
|
1221
|
+
}
|
|
1071
1222
|
}
|
|
1072
1223
|
}
|
|
1073
1224
|
};
|
|
1074
1225
|
};
|
|
1075
1226
|
|
|
1076
1227
|
//#endregion
|
|
1077
|
-
//#region src/hooks/
|
|
1078
|
-
const useInject = (service) => {
|
|
1079
|
-
const alepha = useAlepha();
|
|
1080
|
-
return useMemo(() => alepha.inject(service), []);
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
//#endregion
|
|
1084
|
-
//#region src/hooks/useStore.ts
|
|
1228
|
+
//#region src/hooks/useClient.ts
|
|
1085
1229
|
/**
|
|
1086
|
-
* Hook to
|
|
1230
|
+
* Hook to get a virtual client for the specified scope.
|
|
1231
|
+
*
|
|
1232
|
+
* It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
|
|
1087
1233
|
*/
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
useMemo(() => {
|
|
1091
|
-
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
1092
|
-
}, [defaultValue]);
|
|
1093
|
-
const [state, setState] = useState(alepha.state(key));
|
|
1094
|
-
useEffect(() => {
|
|
1095
|
-
if (!alepha.isBrowser()) return;
|
|
1096
|
-
return alepha.on("state:mutate", (ev) => {
|
|
1097
|
-
if (ev.key === key) setState(ev.value);
|
|
1098
|
-
});
|
|
1099
|
-
}, []);
|
|
1100
|
-
if (!alepha.isBrowser()) {
|
|
1101
|
-
const value = alepha.context.get(key);
|
|
1102
|
-
if (value !== null) return [value, (_) => {}];
|
|
1103
|
-
}
|
|
1104
|
-
return [state, (value) => {
|
|
1105
|
-
alepha.state(key, value);
|
|
1106
|
-
}];
|
|
1107
|
-
};
|
|
1108
|
-
|
|
1109
|
-
//#endregion
|
|
1110
|
-
//#region src/hooks/useClient.ts
|
|
1111
|
-
const useClient = (_scope) => {
|
|
1112
|
-
useStore("user");
|
|
1113
|
-
return useInject(LinkProvider).client();
|
|
1234
|
+
const useClient = (scope) => {
|
|
1235
|
+
return useInject(LinkProvider).client(scope);
|
|
1114
1236
|
};
|
|
1115
1237
|
|
|
1116
1238
|
//#endregion
|
|
1117
1239
|
//#region src/hooks/useQueryParams.ts
|
|
1240
|
+
/**
|
|
1241
|
+
* Not well tested. Use with caution.
|
|
1242
|
+
*/
|
|
1118
1243
|
const useQueryParams = (schema, options = {}) => {
|
|
1119
1244
|
const alepha = useAlepha();
|
|
1120
1245
|
const key = options.key ?? "q";
|
|
@@ -1145,29 +1270,17 @@ const decode = (alepha, schema, data) => {
|
|
|
1145
1270
|
}
|
|
1146
1271
|
};
|
|
1147
1272
|
|
|
1148
|
-
//#endregion
|
|
1149
|
-
//#region src/hooks/useRouterState.ts
|
|
1150
|
-
const useRouterState = () => {
|
|
1151
|
-
const router = useContext(RouterContext);
|
|
1152
|
-
const layer = useContext(RouterLayerContext);
|
|
1153
|
-
if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
|
|
1154
|
-
const [state, setState] = useState(router.state);
|
|
1155
|
-
useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
|
|
1156
|
-
return state;
|
|
1157
|
-
};
|
|
1158
|
-
|
|
1159
1273
|
//#endregion
|
|
1160
1274
|
//#region src/hooks/useSchema.ts
|
|
1161
1275
|
const useSchema = (action) => {
|
|
1162
1276
|
const name = action.name;
|
|
1163
1277
|
const alepha = useAlepha();
|
|
1164
1278
|
const httpClient = useInject(HttpClient);
|
|
1165
|
-
const linkProvider = useInject(LinkProvider);
|
|
1166
1279
|
const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
|
|
1167
1280
|
useEffect(() => {
|
|
1168
1281
|
if (!schema.loading) return;
|
|
1169
1282
|
const opts = { cache: true };
|
|
1170
|
-
httpClient.fetch(`${
|
|
1283
|
+
httpClient.fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
|
|
1171
1284
|
}, [name]);
|
|
1172
1285
|
return schema;
|
|
1173
1286
|
};
|
|
@@ -1176,10 +1289,10 @@ const useSchema = (action) => {
|
|
|
1176
1289
|
*/
|
|
1177
1290
|
const ssrSchemaLoading = (alepha, name) => {
|
|
1178
1291
|
if (!alepha.isBrowser()) {
|
|
1179
|
-
const
|
|
1180
|
-
const can =
|
|
1292
|
+
const linkProvider = alepha.inject(LinkProvider);
|
|
1293
|
+
const can = linkProvider.getServerLinks().find((link) => link.name === name);
|
|
1181
1294
|
if (can) {
|
|
1182
|
-
const schema$1 =
|
|
1295
|
+
const schema$1 = linkProvider.links.find((it) => it.name === name)?.schema;
|
|
1183
1296
|
if (schema$1) {
|
|
1184
1297
|
can.schema = schema$1;
|
|
1185
1298
|
return schema$1;
|
|
@@ -1187,7 +1300,7 @@ const ssrSchemaLoading = (alepha, name) => {
|
|
|
1187
1300
|
}
|
|
1188
1301
|
return { loading: true };
|
|
1189
1302
|
}
|
|
1190
|
-
const schema = alepha.inject(LinkProvider).links
|
|
1303
|
+
const schema = alepha.inject(LinkProvider).links.find((it) => it.name === name)?.schema;
|
|
1191
1304
|
if (schema) return schema;
|
|
1192
1305
|
return { loading: true };
|
|
1193
1306
|
};
|
|
@@ -1198,14 +1311,15 @@ const AlephaReact = $module({
|
|
|
1198
1311
|
name: "alepha.react",
|
|
1199
1312
|
descriptors: [$page],
|
|
1200
1313
|
services: [
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1314
|
+
ReactPageProvider,
|
|
1315
|
+
ReactBrowserRouterProvider,
|
|
1316
|
+
ReactBrowserProvider,
|
|
1317
|
+
ReactRouter,
|
|
1318
|
+
ReactBrowserRendererProvider
|
|
1205
1319
|
],
|
|
1206
|
-
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(
|
|
1320
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
|
|
1207
1321
|
});
|
|
1208
1322
|
|
|
1209
1323
|
//#endregion
|
|
1210
|
-
export { $page, AlephaContext, AlephaReact,
|
|
1324
|
+
export { $page, AlephaContext, AlephaReact, ClientOnly, ErrorBoundary, ErrorViewer, Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, ReactBrowserProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactRouter, Redirection, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
|
|
1211
1325
|
//# sourceMappingURL=index.browser.js.map
|