@alepha/react 0.9.2 → 0.9.4
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 +46 -0
- package/dist/index.browser.js +378 -325
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +570 -458
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -213
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +304 -212
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +567 -460
- package/dist/index.js.map +1 -1
- package/package.json +16 -13
- package/src/components/ErrorViewer.tsx +1 -1
- package/src/components/Link.tsx +4 -24
- package/src/components/NestedView.tsx +20 -9
- package/src/components/NotFound.tsx +5 -2
- package/src/descriptors/$page.ts +86 -12
- package/src/errors/Redirection.ts +13 -0
- package/src/hooks/useActive.ts +28 -30
- package/src/hooks/useAlepha.ts +16 -2
- package/src/hooks/useClient.ts +7 -2
- package/src/hooks/useInject.ts +4 -1
- package/src/hooks/useQueryParams.ts +9 -6
- package/src/hooks/useRouter.ts +18 -30
- package/src/hooks/useRouterEvents.ts +7 -4
- package/src/hooks/useRouterState.ts +8 -20
- package/src/hooks/useSchema.ts +10 -15
- package/src/hooks/useStore.ts +9 -8
- package/src/index.browser.ts +11 -11
- package/src/index.shared.ts +4 -5
- package/src/index.ts +21 -30
- package/src/providers/ReactBrowserProvider.ts +155 -65
- package/src/providers/ReactBrowserRouterProvider.ts +132 -0
- package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +164 -112
- package/src/providers/ReactServerProvider.ts +100 -68
- package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +75 -61
- package/src/contexts/RouterContext.ts +0 -14
- package/src/errors/RedirectionError.ts +0 -10
- package/src/providers/BrowserRouterProvider.ts +0 -146
- package/src/providers/ReactBrowserRenderer.ts +0 -93
package/dist/index.browser.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $
|
|
1
|
+
import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, 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";
|
|
6
|
+
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
4
7
|
import { RouterProvider } from "@alepha/router";
|
|
5
8
|
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
6
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
8
10
|
|
|
9
11
|
//#region src/descriptors/$page.ts
|
|
10
12
|
/**
|
|
@@ -14,6 +16,12 @@ const $page = (options) => {
|
|
|
14
16
|
return createDescriptor(PageDescriptor, options);
|
|
15
17
|
};
|
|
16
18
|
var PageDescriptor = class extends Descriptor {
|
|
19
|
+
onInit() {
|
|
20
|
+
if (this.options.static) this.options.cache ??= {
|
|
21
|
+
provider: "memory",
|
|
22
|
+
ttl: [1, "week"]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
get name() {
|
|
18
26
|
return this.options.name ?? this.config.propertyKey;
|
|
19
27
|
}
|
|
@@ -22,14 +30,20 @@ var PageDescriptor = class extends Descriptor {
|
|
|
22
30
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
23
31
|
*/
|
|
24
32
|
async render(options) {
|
|
25
|
-
throw new
|
|
33
|
+
throw new Error("render method is not implemented in this environment");
|
|
34
|
+
}
|
|
35
|
+
match(url) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
pathname(config) {
|
|
39
|
+
return this.options.path || "";
|
|
26
40
|
}
|
|
27
41
|
};
|
|
28
42
|
$page[KIND] = PageDescriptor;
|
|
29
43
|
|
|
30
44
|
//#endregion
|
|
31
45
|
//#region src/components/NotFound.tsx
|
|
32
|
-
function NotFoundPage() {
|
|
46
|
+
function NotFoundPage(props) {
|
|
33
47
|
return /* @__PURE__ */ jsx("div", {
|
|
34
48
|
style: {
|
|
35
49
|
height: "100vh",
|
|
@@ -39,14 +53,15 @@ function NotFoundPage() {
|
|
|
39
53
|
alignItems: "center",
|
|
40
54
|
textAlign: "center",
|
|
41
55
|
fontFamily: "sans-serif",
|
|
42
|
-
padding: "1rem"
|
|
56
|
+
padding: "1rem",
|
|
57
|
+
...props.style
|
|
43
58
|
},
|
|
44
59
|
children: /* @__PURE__ */ jsx("h1", {
|
|
45
60
|
style: {
|
|
46
61
|
fontSize: "1rem",
|
|
47
62
|
marginBottom: "0.5rem"
|
|
48
63
|
},
|
|
49
|
-
children: "This page does not exist"
|
|
64
|
+
children: "404 - This page does not exist"
|
|
50
65
|
})
|
|
51
66
|
});
|
|
52
67
|
}
|
|
@@ -100,7 +115,7 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
100
115
|
heading: {
|
|
101
116
|
fontSize: "20px",
|
|
102
117
|
fontWeight: "bold",
|
|
103
|
-
marginBottom: "
|
|
118
|
+
marginBottom: "10px"
|
|
104
119
|
},
|
|
105
120
|
name: {
|
|
106
121
|
fontSize: "16px",
|
|
@@ -219,28 +234,54 @@ const ErrorViewerProduction = () => {
|
|
|
219
234
|
});
|
|
220
235
|
};
|
|
221
236
|
|
|
222
|
-
//#endregion
|
|
223
|
-
//#region src/contexts/RouterContext.ts
|
|
224
|
-
const RouterContext = createContext(void 0);
|
|
225
|
-
|
|
226
237
|
//#endregion
|
|
227
238
|
//#region src/contexts/RouterLayerContext.ts
|
|
228
239
|
const RouterLayerContext = createContext(void 0);
|
|
229
240
|
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/errors/Redirection.ts
|
|
243
|
+
/**
|
|
244
|
+
* Used for Redirection during the page loading.
|
|
245
|
+
*
|
|
246
|
+
* Depends on the context, it can be thrown or just returned.
|
|
247
|
+
*/
|
|
248
|
+
var Redirection = class extends Error {
|
|
249
|
+
redirect;
|
|
250
|
+
constructor(redirect) {
|
|
251
|
+
super("Redirection");
|
|
252
|
+
this.redirect = redirect;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
230
256
|
//#endregion
|
|
231
257
|
//#region src/contexts/AlephaContext.ts
|
|
232
258
|
const AlephaContext = createContext(void 0);
|
|
233
259
|
|
|
234
260
|
//#endregion
|
|
235
261
|
//#region src/hooks/useAlepha.ts
|
|
262
|
+
/**
|
|
263
|
+
* Main Alepha hook.
|
|
264
|
+
*
|
|
265
|
+
* It provides access to the Alepha instance within a React component.
|
|
266
|
+
*
|
|
267
|
+
* With Alepha, you can access the core functionalities of the framework:
|
|
268
|
+
*
|
|
269
|
+
* - alepha.state() for state management
|
|
270
|
+
* - alepha.inject() for dependency injection
|
|
271
|
+
* - alepha.emit() for event handling
|
|
272
|
+
* etc...
|
|
273
|
+
*/
|
|
236
274
|
const useAlepha = () => {
|
|
237
275
|
const alepha = useContext(AlephaContext);
|
|
238
|
-
if (!alepha) throw new
|
|
276
|
+
if (!alepha) throw new AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
|
|
239
277
|
return alepha;
|
|
240
278
|
};
|
|
241
279
|
|
|
242
280
|
//#endregion
|
|
243
281
|
//#region src/hooks/useRouterEvents.ts
|
|
282
|
+
/**
|
|
283
|
+
* Subscribe to various router events.
|
|
284
|
+
*/
|
|
244
285
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
245
286
|
const alepha = useAlepha();
|
|
246
287
|
useEffect(() => {
|
|
@@ -313,36 +354,31 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
313
354
|
* ```
|
|
314
355
|
*/
|
|
315
356
|
const NestedView = (props) => {
|
|
316
|
-
const app = useContext(RouterContext);
|
|
317
357
|
const layer = useContext(RouterLayerContext);
|
|
318
358
|
const index = layer?.index ?? 0;
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
359
|
+
const alepha = useAlepha();
|
|
360
|
+
const state = alepha.state("react.router.state");
|
|
361
|
+
if (!state) throw new Error("<NestedView/> must be used inside a RouterLayerContext.");
|
|
362
|
+
const [view, setView] = useState(state.layers[index]?.element);
|
|
363
|
+
useRouterEvents({ onEnd: ({ state: state$1 }) => {
|
|
364
|
+
if (!state$1.layers[index]?.cache) setView(state$1.layers[index]?.element);
|
|
365
|
+
} }, []);
|
|
324
366
|
const element = view ?? props.children ?? null;
|
|
325
367
|
return /* @__PURE__ */ jsx(ErrorBoundary_default, {
|
|
326
|
-
fallback:
|
|
368
|
+
fallback: (error) => {
|
|
369
|
+
const result = state.onError(error, state);
|
|
370
|
+
if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
|
|
371
|
+
return result;
|
|
372
|
+
},
|
|
327
373
|
children: element
|
|
328
374
|
});
|
|
329
375
|
};
|
|
330
376
|
var NestedView_default = NestedView;
|
|
331
377
|
|
|
332
378
|
//#endregion
|
|
333
|
-
//#region src/
|
|
334
|
-
var RedirectionError = class extends Error {
|
|
335
|
-
page;
|
|
336
|
-
constructor(page) {
|
|
337
|
-
super("Redirection");
|
|
338
|
-
this.page = page;
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
//#endregion
|
|
343
|
-
//#region src/providers/PageDescriptorProvider.ts
|
|
379
|
+
//#region src/providers/ReactPageProvider.ts
|
|
344
380
|
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
345
|
-
var
|
|
381
|
+
var ReactPageProvider = class {
|
|
346
382
|
log = $logger();
|
|
347
383
|
env = $env(envSchema$1);
|
|
348
384
|
alepha = $inject(Alepha);
|
|
@@ -354,7 +390,7 @@ var PageDescriptorProvider = class {
|
|
|
354
390
|
for (const page of this.pages) if (page.name === name) return page;
|
|
355
391
|
throw new Error(`Page ${name} not found`);
|
|
356
392
|
}
|
|
357
|
-
|
|
393
|
+
pathname(name, options = {}) {
|
|
358
394
|
const page = this.page(name);
|
|
359
395
|
if (!page) throw new Error(`Page ${name} not found`);
|
|
360
396
|
let url = page.path ?? "";
|
|
@@ -364,22 +400,28 @@ var PageDescriptorProvider = class {
|
|
|
364
400
|
parent = parent.parent;
|
|
365
401
|
}
|
|
366
402
|
url = this.compile(url, options.params ?? {});
|
|
367
|
-
|
|
403
|
+
if (options.query) {
|
|
404
|
+
const query = new URLSearchParams(options.query);
|
|
405
|
+
if (query.toString()) url += `?${query.toString()}`;
|
|
406
|
+
}
|
|
407
|
+
return url.replace(/\/\/+/g, "/") || "/";
|
|
368
408
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
409
|
+
url(name, options = {}) {
|
|
410
|
+
return new URL(this.pathname(name, options), options.host ?? `http://localhost`);
|
|
411
|
+
}
|
|
412
|
+
root(state) {
|
|
413
|
+
const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(NestedView_default, {}, state.layers[0]?.element));
|
|
374
414
|
if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
|
|
375
415
|
return root;
|
|
376
416
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Create a new RouterState based on a given route and request.
|
|
419
|
+
* This method resolves the layers for the route, applying any query and params schemas defined in the route.
|
|
420
|
+
* It also handles errors and redirects.
|
|
421
|
+
*/
|
|
422
|
+
async createLayers(route, state, previous = []) {
|
|
380
423
|
let context = {};
|
|
381
424
|
const stack = [{ route }];
|
|
382
|
-
request.onError = (error) => this.renderError(error);
|
|
383
425
|
let parent = route.parent;
|
|
384
426
|
while (parent) {
|
|
385
427
|
stack.unshift({ route: parent });
|
|
@@ -391,19 +433,18 @@ var PageDescriptorProvider = class {
|
|
|
391
433
|
const route$1 = it.route;
|
|
392
434
|
const config = {};
|
|
393
435
|
try {
|
|
394
|
-
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query,
|
|
436
|
+
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, state.query) : {};
|
|
395
437
|
} catch (e) {
|
|
396
438
|
it.error = e;
|
|
397
439
|
break;
|
|
398
440
|
}
|
|
399
441
|
try {
|
|
400
|
-
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params,
|
|
442
|
+
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, state.params) : {};
|
|
401
443
|
} catch (e) {
|
|
402
444
|
it.error = e;
|
|
403
445
|
break;
|
|
404
446
|
}
|
|
405
447
|
it.config = { ...config };
|
|
406
|
-
const previous = request.previous;
|
|
407
448
|
if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
|
|
408
449
|
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
409
450
|
const prev = JSON.stringify({
|
|
@@ -429,7 +470,7 @@ var PageDescriptorProvider = class {
|
|
|
429
470
|
if (!route$1.resolve) continue;
|
|
430
471
|
try {
|
|
431
472
|
const props = await route$1.resolve?.({
|
|
432
|
-
...
|
|
473
|
+
...state,
|
|
433
474
|
...config,
|
|
434
475
|
...context
|
|
435
476
|
}) ?? {};
|
|
@@ -439,13 +480,8 @@ var PageDescriptorProvider = class {
|
|
|
439
480
|
...props
|
|
440
481
|
};
|
|
441
482
|
} catch (e) {
|
|
442
|
-
if (e instanceof
|
|
443
|
-
|
|
444
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
445
|
-
pathname,
|
|
446
|
-
search
|
|
447
|
-
};
|
|
448
|
-
this.log.error(e);
|
|
483
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
484
|
+
this.log.error("Page resolver has failed", e);
|
|
449
485
|
it.error = e;
|
|
450
486
|
break;
|
|
451
487
|
}
|
|
@@ -460,44 +496,59 @@ var PageDescriptorProvider = class {
|
|
|
460
496
|
acc += it.route.path ? this.compile(it.route.path, params) : "";
|
|
461
497
|
const path = acc.replace(/\/+/, "/");
|
|
462
498
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
463
|
-
if (localErrorHandler)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
499
|
+
if (localErrorHandler) {
|
|
500
|
+
const onErrorParent = state.onError;
|
|
501
|
+
state.onError = (error, context$1) => {
|
|
502
|
+
const result = localErrorHandler(error, context$1);
|
|
503
|
+
if (result === void 0) return onErrorParent(error, context$1);
|
|
504
|
+
return result;
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (!it.error) try {
|
|
508
|
+
const element = await this.createElement(it.route, {
|
|
509
|
+
...props,
|
|
510
|
+
...context
|
|
511
|
+
});
|
|
512
|
+
state.layers.push({
|
|
513
|
+
name: it.route.name,
|
|
514
|
+
props,
|
|
515
|
+
part: it.route.path,
|
|
516
|
+
config: it.config,
|
|
517
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
518
|
+
index: i + 1,
|
|
519
|
+
path,
|
|
520
|
+
route: it.route,
|
|
521
|
+
cache: it.cache
|
|
522
|
+
});
|
|
523
|
+
} catch (e) {
|
|
524
|
+
it.error = e;
|
|
525
|
+
}
|
|
526
|
+
if (it.error) try {
|
|
527
|
+
let element = await state.onError(it.error, state);
|
|
528
|
+
if (element === void 0) throw it.error;
|
|
529
|
+
if (element instanceof Redirection) return { redirect: element.redirect };
|
|
530
|
+
if (element === null) element = this.renderError(it.error);
|
|
531
|
+
state.layers.push({
|
|
468
532
|
props,
|
|
469
533
|
error: it.error,
|
|
470
534
|
name: it.route.name,
|
|
471
535
|
part: it.route.path,
|
|
472
536
|
config: it.config,
|
|
473
|
-
element: this.renderView(i + 1, path, element
|
|
537
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
474
538
|
index: i + 1,
|
|
475
539
|
path,
|
|
476
540
|
route: it.route
|
|
477
541
|
});
|
|
478
542
|
break;
|
|
543
|
+
} catch (e) {
|
|
544
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
545
|
+
throw e;
|
|
479
546
|
}
|
|
480
|
-
const element = await this.createElement(it.route, {
|
|
481
|
-
...props,
|
|
482
|
-
...context
|
|
483
|
-
});
|
|
484
|
-
layers.push({
|
|
485
|
-
name: it.route.name,
|
|
486
|
-
props,
|
|
487
|
-
part: it.route.path,
|
|
488
|
-
config: it.config,
|
|
489
|
-
element: this.renderView(i + 1, path, element, it.route),
|
|
490
|
-
index: i + 1,
|
|
491
|
-
path,
|
|
492
|
-
route: it.route,
|
|
493
|
-
cache: it.cache
|
|
494
|
-
});
|
|
495
547
|
}
|
|
496
|
-
return {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
};
|
|
548
|
+
return { state };
|
|
549
|
+
}
|
|
550
|
+
createRedirectionLayer(redirect) {
|
|
551
|
+
return { redirect };
|
|
501
552
|
}
|
|
502
553
|
getErrorHandler(route) {
|
|
503
554
|
if (route.errorHandler) return route.errorHandler;
|
|
@@ -554,6 +605,7 @@ var PageDescriptorProvider = class {
|
|
|
554
605
|
let hasNotFoundHandler = false;
|
|
555
606
|
const pages = this.alepha.descriptors($page);
|
|
556
607
|
const hasParent = (it) => {
|
|
608
|
+
if (it.options.parent) return true;
|
|
557
609
|
for (const page of pages) {
|
|
558
610
|
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
559
611
|
if (children.includes(it)) return true;
|
|
@@ -569,7 +621,7 @@ var PageDescriptorProvider = class {
|
|
|
569
621
|
name: "notFound",
|
|
570
622
|
cache: true,
|
|
571
623
|
component: NotFoundPage,
|
|
572
|
-
|
|
624
|
+
onServerResponse: ({ reply }) => {
|
|
573
625
|
reply.status = 404;
|
|
574
626
|
}
|
|
575
627
|
});
|
|
@@ -577,6 +629,12 @@ var PageDescriptorProvider = class {
|
|
|
577
629
|
});
|
|
578
630
|
map(pages, target) {
|
|
579
631
|
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
632
|
+
const getChildrenFromParent = (it) => {
|
|
633
|
+
const children$1 = [];
|
|
634
|
+
for (const page of pages) if (page.options.parent === it) children$1.push(page);
|
|
635
|
+
return children$1;
|
|
636
|
+
};
|
|
637
|
+
children.push(...getChildrenFromParent(target));
|
|
580
638
|
return {
|
|
581
639
|
...target.options,
|
|
582
640
|
name: target.name,
|
|
@@ -617,57 +675,43 @@ const isPageRoute = (it) => {
|
|
|
617
675
|
};
|
|
618
676
|
|
|
619
677
|
//#endregion
|
|
620
|
-
//#region src/providers/
|
|
621
|
-
var
|
|
678
|
+
//#region src/providers/ReactBrowserRouterProvider.ts
|
|
679
|
+
var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
622
680
|
log = $logger();
|
|
623
681
|
alepha = $inject(Alepha);
|
|
624
|
-
|
|
682
|
+
pageApi = $inject(ReactPageProvider);
|
|
625
683
|
add(entry) {
|
|
626
|
-
this.
|
|
684
|
+
this.pageApi.add(entry);
|
|
627
685
|
}
|
|
628
686
|
configure = $hook({
|
|
629
687
|
on: "configure",
|
|
630
688
|
handler: async () => {
|
|
631
|
-
for (const page of this.
|
|
689
|
+
for (const page of this.pageApi.getPages()) if (page.component || page.lazy) this.push({
|
|
632
690
|
path: page.match,
|
|
633
691
|
page
|
|
634
692
|
});
|
|
635
693
|
}
|
|
636
694
|
});
|
|
637
|
-
async transition(url,
|
|
695
|
+
async transition(url, previous = []) {
|
|
638
696
|
const { pathname, search } = url;
|
|
639
|
-
const
|
|
640
|
-
pathname,
|
|
641
|
-
search,
|
|
642
|
-
layers: []
|
|
643
|
-
};
|
|
644
|
-
const context = {
|
|
697
|
+
const entry = {
|
|
645
698
|
url,
|
|
646
699
|
query: {},
|
|
647
700
|
params: {},
|
|
648
|
-
|
|
649
|
-
|
|
701
|
+
layers: [],
|
|
702
|
+
onError: () => null
|
|
650
703
|
};
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
context
|
|
654
|
-
});
|
|
704
|
+
const state = entry;
|
|
705
|
+
await this.alepha.emit("react:transition:begin", { state });
|
|
655
706
|
try {
|
|
656
|
-
const previous = options.previous;
|
|
657
707
|
const { route, params } = this.match(pathname);
|
|
658
708
|
const query = {};
|
|
659
709
|
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
context.previous = previous;
|
|
710
|
+
state.query = query;
|
|
711
|
+
state.params = params ?? {};
|
|
663
712
|
if (isPageRoute(route)) {
|
|
664
|
-
const
|
|
665
|
-
if (
|
|
666
|
-
redirect: result.redirect,
|
|
667
|
-
state,
|
|
668
|
-
context
|
|
669
|
-
};
|
|
670
|
-
state.layers = result.layers;
|
|
713
|
+
const { redirect } = await this.pageApi.createLayers(route.page, state, previous);
|
|
714
|
+
if (redirect) return redirect;
|
|
671
715
|
}
|
|
672
716
|
if (state.layers.length === 0) state.layers.push({
|
|
673
717
|
name: "not-found",
|
|
@@ -675,82 +719,92 @@ var BrowserRouterProvider = class extends RouterProvider {
|
|
|
675
719
|
index: 0,
|
|
676
720
|
path: "/"
|
|
677
721
|
});
|
|
678
|
-
await this.alepha.emit("react:transition:success", {
|
|
679
|
-
state,
|
|
680
|
-
context
|
|
681
|
-
});
|
|
722
|
+
await this.alepha.emit("react:transition:success", { state });
|
|
682
723
|
} catch (e) {
|
|
683
|
-
this.log.error(e);
|
|
724
|
+
this.log.error("Transition has failed", e);
|
|
684
725
|
state.layers = [{
|
|
685
726
|
name: "error",
|
|
686
|
-
element: this.
|
|
727
|
+
element: this.pageApi.renderError(e),
|
|
687
728
|
index: 0,
|
|
688
729
|
path: "/"
|
|
689
730
|
}];
|
|
690
731
|
await this.alepha.emit("react:transition:error", {
|
|
691
732
|
error: e,
|
|
692
|
-
state
|
|
693
|
-
context
|
|
733
|
+
state
|
|
694
734
|
});
|
|
695
735
|
}
|
|
696
|
-
if (
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
options.state.search = state.search;
|
|
736
|
+
if (previous) for (let i = 0; i < previous.length; i++) {
|
|
737
|
+
const layer = previous[i];
|
|
738
|
+
if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
|
|
700
739
|
}
|
|
701
|
-
await this.alepha.emit("react:transition:end", {
|
|
702
|
-
|
|
703
|
-
context
|
|
704
|
-
});
|
|
705
|
-
return {
|
|
706
|
-
context,
|
|
707
|
-
state
|
|
708
|
-
};
|
|
740
|
+
await this.alepha.emit("react:transition:end", { state });
|
|
741
|
+
this.alepha.state("react.router.state", state);
|
|
709
742
|
}
|
|
710
|
-
root(state
|
|
711
|
-
return this.
|
|
743
|
+
root(state) {
|
|
744
|
+
return this.pageApi.root(state);
|
|
712
745
|
}
|
|
713
746
|
};
|
|
714
747
|
|
|
715
748
|
//#endregion
|
|
716
749
|
//#region src/providers/ReactBrowserProvider.ts
|
|
750
|
+
const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
|
|
717
751
|
var ReactBrowserProvider = class {
|
|
752
|
+
env = $env(envSchema);
|
|
718
753
|
log = $logger();
|
|
719
754
|
client = $inject(LinkProvider);
|
|
720
755
|
alepha = $inject(Alepha);
|
|
721
|
-
router = $inject(
|
|
756
|
+
router = $inject(ReactBrowserRouterProvider);
|
|
757
|
+
dateTimeProvider = $inject(DateTimeProvider);
|
|
722
758
|
root;
|
|
759
|
+
options = { scrollRestoration: "top" };
|
|
760
|
+
getRootElement() {
|
|
761
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
762
|
+
if (root) return root;
|
|
763
|
+
const div = this.document.createElement("div");
|
|
764
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
765
|
+
this.document.body.prepend(div);
|
|
766
|
+
return div;
|
|
767
|
+
}
|
|
723
768
|
transitioning;
|
|
724
|
-
state
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
769
|
+
get state() {
|
|
770
|
+
return this.alepha.state("react.router.state");
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Accessor for Document DOM API.
|
|
774
|
+
*/
|
|
729
775
|
get document() {
|
|
730
776
|
return window.document;
|
|
731
777
|
}
|
|
778
|
+
/**
|
|
779
|
+
* Accessor for History DOM API.
|
|
780
|
+
*/
|
|
732
781
|
get history() {
|
|
733
782
|
return window.history;
|
|
734
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* Accessor for Location DOM API.
|
|
786
|
+
*/
|
|
735
787
|
get location() {
|
|
736
788
|
return window.location;
|
|
737
789
|
}
|
|
790
|
+
get base() {
|
|
791
|
+
const base = import.meta.env?.BASE_URL;
|
|
792
|
+
if (!base || base === "/") return "";
|
|
793
|
+
return base;
|
|
794
|
+
}
|
|
738
795
|
get url() {
|
|
739
|
-
|
|
740
|
-
if (
|
|
741
|
-
url = url.replace(import.meta.env?.BASE_URL, "");
|
|
742
|
-
if (!url.startsWith("/")) url = `/${url}`;
|
|
743
|
-
}
|
|
796
|
+
const url = this.location.pathname + this.location.search;
|
|
797
|
+
if (this.base) return url.replace(this.base, "");
|
|
744
798
|
return url;
|
|
745
799
|
}
|
|
746
|
-
pushState(
|
|
747
|
-
|
|
748
|
-
if (
|
|
749
|
-
|
|
750
|
-
else this.history.pushState({}, "", path);
|
|
800
|
+
pushState(path, replace) {
|
|
801
|
+
const url = this.base + path;
|
|
802
|
+
if (replace) this.history.replaceState({}, "", url);
|
|
803
|
+
else this.history.pushState({}, "", url);
|
|
751
804
|
}
|
|
752
805
|
async invalidate(props) {
|
|
753
806
|
const previous = [];
|
|
807
|
+
this.log.trace("Invalidating layers");
|
|
754
808
|
if (props) {
|
|
755
809
|
const [key] = Object.keys(props);
|
|
756
810
|
const value = props[key];
|
|
@@ -771,28 +825,37 @@ var ReactBrowserProvider = class {
|
|
|
771
825
|
await this.render({ previous });
|
|
772
826
|
}
|
|
773
827
|
async go(url, options = {}) {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
this.
|
|
828
|
+
this.log.trace(`Going to ${url}`, {
|
|
829
|
+
url,
|
|
830
|
+
options
|
|
831
|
+
});
|
|
832
|
+
await this.render({
|
|
833
|
+
url,
|
|
834
|
+
previous: options.force ? [] : this.state.layers
|
|
835
|
+
});
|
|
836
|
+
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
837
|
+
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
781
838
|
return;
|
|
782
839
|
}
|
|
783
|
-
this.pushState(url);
|
|
840
|
+
this.pushState(url, options.replace);
|
|
784
841
|
}
|
|
785
842
|
async render(options = {}) {
|
|
786
843
|
const previous = options.previous ?? this.state.layers;
|
|
787
844
|
const url = options.url ?? this.url;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
793
|
-
|
|
845
|
+
const start = this.dateTimeProvider.now();
|
|
846
|
+
this.transitioning = {
|
|
847
|
+
to: url,
|
|
848
|
+
from: this.state?.url.pathname
|
|
849
|
+
};
|
|
850
|
+
this.log.debug("Transitioning...", { to: url });
|
|
851
|
+
const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous);
|
|
852
|
+
if (redirect) {
|
|
853
|
+
this.log.info("Redirecting to", { redirect });
|
|
854
|
+
return await this.render({ url: redirect });
|
|
855
|
+
}
|
|
856
|
+
const ms = this.dateTimeProvider.now().diff(start);
|
|
857
|
+
this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
|
|
794
858
|
this.transitioning = void 0;
|
|
795
|
-
return result;
|
|
796
859
|
}
|
|
797
860
|
/**
|
|
798
861
|
* Get embedded layers from the server.
|
|
@@ -804,48 +867,25 @@ var ReactBrowserProvider = class {
|
|
|
804
867
|
console.error(error);
|
|
805
868
|
}
|
|
806
869
|
}
|
|
870
|
+
onTransitionEnd = $hook({
|
|
871
|
+
on: "react:transition:end",
|
|
872
|
+
handler: () => {
|
|
873
|
+
if (this.options.scrollRestoration === "top" && typeof window !== "undefined") {
|
|
874
|
+
this.log.trace("Restoring scroll position to top");
|
|
875
|
+
window.scrollTo(0, 0);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
});
|
|
807
879
|
ready = $hook({
|
|
808
880
|
on: "ready",
|
|
809
881
|
handler: async () => {
|
|
810
882
|
const hydration = this.getHydrationState();
|
|
811
883
|
const previous = hydration?.layers ?? [];
|
|
812
|
-
if (hydration
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
hydration
|
|
818
|
-
});
|
|
819
|
-
window.addEventListener("popstate", () => {
|
|
820
|
-
if (this.state.pathname === this.url) return;
|
|
821
|
-
this.render();
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
//#endregion
|
|
828
|
-
//#region src/providers/ReactBrowserRenderer.ts
|
|
829
|
-
const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
|
|
830
|
-
var ReactBrowserRenderer = class {
|
|
831
|
-
browserProvider = $inject(ReactBrowserProvider);
|
|
832
|
-
browserRouterProvider = $inject(BrowserRouterProvider);
|
|
833
|
-
env = $env(envSchema);
|
|
834
|
-
log = $logger();
|
|
835
|
-
root;
|
|
836
|
-
options = { scrollRestoration: "top" };
|
|
837
|
-
getRootElement() {
|
|
838
|
-
const root = this.browserProvider.document.getElementById(this.env.REACT_ROOT_ID);
|
|
839
|
-
if (root) return root;
|
|
840
|
-
const div = this.browserProvider.document.createElement("div");
|
|
841
|
-
div.id = this.env.REACT_ROOT_ID;
|
|
842
|
-
this.browserProvider.document.body.prepend(div);
|
|
843
|
-
return div;
|
|
844
|
-
}
|
|
845
|
-
ready = $hook({
|
|
846
|
-
on: "react:browser:render",
|
|
847
|
-
handler: async ({ state, context, hydration }) => {
|
|
848
|
-
const element = this.browserRouterProvider.root(state, context);
|
|
884
|
+
if (hydration) {
|
|
885
|
+
for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state(key, value);
|
|
886
|
+
}
|
|
887
|
+
await this.render({ previous });
|
|
888
|
+
const element = this.router.root(this.state);
|
|
849
889
|
if (hydration?.layers) {
|
|
850
890
|
this.root = hydrateRoot(this.getRootElement(), element);
|
|
851
891
|
this.log.info("Hydrated root element");
|
|
@@ -854,28 +894,41 @@ var ReactBrowserRenderer = class {
|
|
|
854
894
|
this.root.render(element);
|
|
855
895
|
this.log.info("Created root element");
|
|
856
896
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
if (this.options.scrollRestoration === "top" && typeof window !== "undefined") window.scrollTo(0, 0);
|
|
897
|
+
window.addEventListener("popstate", () => {
|
|
898
|
+
if (this.base + this.state.url.pathname === this.location.pathname) return;
|
|
899
|
+
this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
|
|
900
|
+
this.render();
|
|
901
|
+
});
|
|
863
902
|
}
|
|
864
903
|
});
|
|
865
904
|
};
|
|
866
905
|
|
|
867
906
|
//#endregion
|
|
868
|
-
//#region src/
|
|
869
|
-
var
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
this.state
|
|
874
|
-
|
|
875
|
-
|
|
907
|
+
//#region src/services/ReactRouter.ts
|
|
908
|
+
var ReactRouter = class {
|
|
909
|
+
alepha = $inject(Alepha);
|
|
910
|
+
pageApi = $inject(ReactPageProvider);
|
|
911
|
+
get state() {
|
|
912
|
+
return this.alepha.state("react.router.state");
|
|
913
|
+
}
|
|
914
|
+
get pages() {
|
|
915
|
+
return this.pageApi.getPages();
|
|
916
|
+
}
|
|
917
|
+
get browser() {
|
|
918
|
+
if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
|
|
919
|
+
return void 0;
|
|
920
|
+
}
|
|
921
|
+
path(name, config = {}) {
|
|
922
|
+
return this.pageApi.pathname(name, {
|
|
923
|
+
params: {
|
|
924
|
+
...this.state.params,
|
|
925
|
+
...config.params
|
|
926
|
+
},
|
|
927
|
+
query: config.query
|
|
928
|
+
});
|
|
876
929
|
}
|
|
877
930
|
getURL() {
|
|
878
|
-
if (!this.browser) return this.
|
|
931
|
+
if (!this.browser) return this.state.url;
|
|
879
932
|
return new URL(this.location.href);
|
|
880
933
|
}
|
|
881
934
|
get location() {
|
|
@@ -886,11 +939,11 @@ var RouterHookApi = class {
|
|
|
886
939
|
return this.state;
|
|
887
940
|
}
|
|
888
941
|
get pathname() {
|
|
889
|
-
return this.state.pathname;
|
|
942
|
+
return this.state.url.pathname;
|
|
890
943
|
}
|
|
891
944
|
get query() {
|
|
892
945
|
const query = {};
|
|
893
|
-
for (const [key, value] of new URLSearchParams(this.state.search).entries()) query[key] = String(value);
|
|
946
|
+
for (const [key, value] of new URLSearchParams(this.state.url.search).entries()) query[key] = String(value);
|
|
894
947
|
return query;
|
|
895
948
|
}
|
|
896
949
|
async back() {
|
|
@@ -902,39 +955,33 @@ var RouterHookApi = class {
|
|
|
902
955
|
async invalidate(props) {
|
|
903
956
|
await this.browser?.invalidate(props);
|
|
904
957
|
}
|
|
905
|
-
/**
|
|
906
|
-
* Create a valid href for the given pathname.
|
|
907
|
-
*
|
|
908
|
-
* @param pathname
|
|
909
|
-
* @param layer
|
|
910
|
-
*/
|
|
911
|
-
createHref(pathname, layer = this.layer, options = {}) {
|
|
912
|
-
if (typeof pathname === "object") pathname = pathname.options.path ?? "";
|
|
913
|
-
if (options.params) for (const [key, value] of Object.entries(options.params)) pathname = pathname.replace(`:${key}`, String(value));
|
|
914
|
-
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
915
|
-
}
|
|
916
958
|
async go(path, options) {
|
|
917
959
|
for (const page of this.pages) if (page.name === path) {
|
|
918
|
-
|
|
919
|
-
|
|
960
|
+
await this.browser?.go(this.path(path, options), options);
|
|
961
|
+
return;
|
|
920
962
|
}
|
|
921
|
-
await this.browser?.go(
|
|
963
|
+
await this.browser?.go(path, options);
|
|
922
964
|
}
|
|
923
965
|
anchor(path, options = {}) {
|
|
966
|
+
let href = path;
|
|
924
967
|
for (const page of this.pages) if (page.name === path) {
|
|
925
|
-
|
|
968
|
+
href = this.path(path, options);
|
|
926
969
|
break;
|
|
927
970
|
}
|
|
928
|
-
const href = this.createHref(path, this.layer, options);
|
|
929
971
|
return {
|
|
930
|
-
href,
|
|
972
|
+
href: this.base(href),
|
|
931
973
|
onClick: (ev) => {
|
|
932
974
|
ev.stopPropagation();
|
|
933
975
|
ev.preventDefault();
|
|
934
|
-
this.go(
|
|
976
|
+
this.go(href, options).catch(console.error);
|
|
935
977
|
}
|
|
936
978
|
};
|
|
937
979
|
}
|
|
980
|
+
base(path) {
|
|
981
|
+
const base = import.meta.env?.BASE_URL;
|
|
982
|
+
if (!base || base === "/") return path;
|
|
983
|
+
return base + path;
|
|
984
|
+
}
|
|
938
985
|
/**
|
|
939
986
|
* Set query params.
|
|
940
987
|
*
|
|
@@ -950,90 +997,131 @@ var RouterHookApi = class {
|
|
|
950
997
|
}
|
|
951
998
|
};
|
|
952
999
|
|
|
1000
|
+
//#endregion
|
|
1001
|
+
//#region src/hooks/useInject.ts
|
|
1002
|
+
/**
|
|
1003
|
+
* Hook to inject a service instance.
|
|
1004
|
+
* It's a wrapper of `useAlepha().inject(service)` with a memoization.
|
|
1005
|
+
*/
|
|
1006
|
+
const useInject = (service) => {
|
|
1007
|
+
const alepha = useAlepha();
|
|
1008
|
+
return useMemo(() => alepha.inject(service), []);
|
|
1009
|
+
};
|
|
1010
|
+
|
|
953
1011
|
//#endregion
|
|
954
1012
|
//#region src/hooks/useRouter.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* Use this hook to access the React Router instance.
|
|
1015
|
+
*
|
|
1016
|
+
* You can add a type parameter to specify the type of your application.
|
|
1017
|
+
* This will allow you to use the router in a typesafe way.
|
|
1018
|
+
*
|
|
1019
|
+
* @example
|
|
1020
|
+
* class App {
|
|
1021
|
+
* home = $page();
|
|
1022
|
+
* }
|
|
1023
|
+
*
|
|
1024
|
+
* const router = useRouter<App>();
|
|
1025
|
+
* router.go("home"); // typesafe
|
|
1026
|
+
*/
|
|
955
1027
|
const useRouter = () => {
|
|
956
|
-
|
|
957
|
-
const ctx = useContext(RouterContext);
|
|
958
|
-
const layer = useContext(RouterLayerContext);
|
|
959
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
960
|
-
const pages = useMemo(() => {
|
|
961
|
-
return alepha.inject(PageDescriptorProvider).getPages();
|
|
962
|
-
}, []);
|
|
963
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1028
|
+
return useInject(ReactRouter);
|
|
964
1029
|
};
|
|
965
1030
|
|
|
966
1031
|
//#endregion
|
|
967
1032
|
//#region src/components/Link.tsx
|
|
968
1033
|
const Link = (props) => {
|
|
969
|
-
React.useContext(RouterContext);
|
|
970
1034
|
const router = useRouter();
|
|
971
|
-
const
|
|
972
|
-
if (!to) return null;
|
|
973
|
-
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
974
|
-
if (can && !can()) return null;
|
|
975
|
-
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
976
|
-
const anchorProps = {
|
|
977
|
-
...props,
|
|
978
|
-
to: void 0
|
|
979
|
-
};
|
|
1035
|
+
const { to,...anchorProps } = props;
|
|
980
1036
|
return /* @__PURE__ */ jsx("a", {
|
|
981
1037
|
...router.anchor(to),
|
|
982
1038
|
...anchorProps,
|
|
983
|
-
children: props.children
|
|
1039
|
+
children: props.children
|
|
984
1040
|
});
|
|
985
1041
|
};
|
|
986
1042
|
var Link_default = Link;
|
|
987
1043
|
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region src/hooks/useStore.ts
|
|
1046
|
+
/**
|
|
1047
|
+
* Hook to access and mutate the Alepha state.
|
|
1048
|
+
*/
|
|
1049
|
+
const useStore = (key, defaultValue) => {
|
|
1050
|
+
const alepha = useAlepha();
|
|
1051
|
+
useMemo(() => {
|
|
1052
|
+
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
1053
|
+
}, [defaultValue]);
|
|
1054
|
+
const [state, setState] = useState(alepha.state(key));
|
|
1055
|
+
useEffect(() => {
|
|
1056
|
+
if (!alepha.isBrowser()) return;
|
|
1057
|
+
return alepha.on("state:mutate", (ev) => {
|
|
1058
|
+
if (ev.key === key) setState(ev.value);
|
|
1059
|
+
});
|
|
1060
|
+
}, []);
|
|
1061
|
+
return [state, (value) => {
|
|
1062
|
+
alepha.state(key, value);
|
|
1063
|
+
}];
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region src/hooks/useRouterState.ts
|
|
1068
|
+
const useRouterState = () => {
|
|
1069
|
+
const [state] = useStore("react.router.state");
|
|
1070
|
+
if (!state) throw new AlephaError("Missing react router state");
|
|
1071
|
+
return state;
|
|
1072
|
+
};
|
|
1073
|
+
|
|
988
1074
|
//#endregion
|
|
989
1075
|
//#region src/hooks/useActive.ts
|
|
990
|
-
const useActive = (
|
|
1076
|
+
const useActive = (args) => {
|
|
991
1077
|
const router = useRouter();
|
|
992
|
-
const ctx = useContext(RouterContext);
|
|
993
|
-
const layer = useContext(RouterLayerContext);
|
|
994
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
995
|
-
let name;
|
|
996
|
-
if (typeof path === "object" && path.options.name) name = path.options.name;
|
|
997
|
-
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
998
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
999
1078
|
const [isPending, setPending] = useState(false);
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1079
|
+
const state = useRouterState();
|
|
1080
|
+
const current = state.url.pathname;
|
|
1081
|
+
const options = typeof args === "string" ? { href: args } : {
|
|
1082
|
+
...args,
|
|
1083
|
+
href: args.href
|
|
1084
|
+
};
|
|
1085
|
+
const href = options.href;
|
|
1086
|
+
let isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1087
|
+
if (options.startWith && !isActive) isActive = current.startsWith(href);
|
|
1002
1088
|
return {
|
|
1003
|
-
name,
|
|
1004
1089
|
isPending,
|
|
1005
1090
|
isActive,
|
|
1006
1091
|
anchorProps: {
|
|
1007
|
-
href,
|
|
1008
|
-
onClick: (ev) => {
|
|
1009
|
-
ev
|
|
1010
|
-
ev
|
|
1092
|
+
href: router.base(href),
|
|
1093
|
+
onClick: async (ev) => {
|
|
1094
|
+
ev?.stopPropagation();
|
|
1095
|
+
ev?.preventDefault();
|
|
1011
1096
|
if (isActive) return;
|
|
1012
1097
|
if (isPending) return;
|
|
1013
1098
|
setPending(true);
|
|
1014
|
-
|
|
1099
|
+
try {
|
|
1100
|
+
await router.go(href);
|
|
1101
|
+
} finally {
|
|
1015
1102
|
setPending(false);
|
|
1016
|
-
}
|
|
1103
|
+
}
|
|
1017
1104
|
}
|
|
1018
1105
|
}
|
|
1019
1106
|
};
|
|
1020
1107
|
};
|
|
1021
1108
|
|
|
1022
|
-
//#endregion
|
|
1023
|
-
//#region src/hooks/useInject.ts
|
|
1024
|
-
const useInject = (service) => {
|
|
1025
|
-
const alepha = useAlepha();
|
|
1026
|
-
return useMemo(() => alepha.inject(service), []);
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
1109
|
//#endregion
|
|
1030
1110
|
//#region src/hooks/useClient.ts
|
|
1031
|
-
|
|
1032
|
-
|
|
1111
|
+
/**
|
|
1112
|
+
* Hook to get a virtual client for the specified scope.
|
|
1113
|
+
*
|
|
1114
|
+
* It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
|
|
1115
|
+
*/
|
|
1116
|
+
const useClient = (scope) => {
|
|
1117
|
+
return useInject(LinkProvider).client(scope);
|
|
1033
1118
|
};
|
|
1034
1119
|
|
|
1035
1120
|
//#endregion
|
|
1036
1121
|
//#region src/hooks/useQueryParams.ts
|
|
1122
|
+
/**
|
|
1123
|
+
* Not well tested. Use with caution.
|
|
1124
|
+
*/
|
|
1037
1125
|
const useQueryParams = (schema, options = {}) => {
|
|
1038
1126
|
const alepha = useAlepha();
|
|
1039
1127
|
const key = options.key ?? "q";
|
|
@@ -1064,29 +1152,17 @@ const decode = (alepha, schema, data) => {
|
|
|
1064
1152
|
}
|
|
1065
1153
|
};
|
|
1066
1154
|
|
|
1067
|
-
//#endregion
|
|
1068
|
-
//#region src/hooks/useRouterState.ts
|
|
1069
|
-
const useRouterState = () => {
|
|
1070
|
-
const router = useContext(RouterContext);
|
|
1071
|
-
const layer = useContext(RouterLayerContext);
|
|
1072
|
-
if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
|
|
1073
|
-
const [state, setState] = useState(router.state);
|
|
1074
|
-
useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
|
|
1075
|
-
return state;
|
|
1076
|
-
};
|
|
1077
|
-
|
|
1078
1155
|
//#endregion
|
|
1079
1156
|
//#region src/hooks/useSchema.ts
|
|
1080
1157
|
const useSchema = (action) => {
|
|
1081
1158
|
const name = action.name;
|
|
1082
1159
|
const alepha = useAlepha();
|
|
1083
1160
|
const httpClient = useInject(HttpClient);
|
|
1084
|
-
const linkProvider = useInject(LinkProvider);
|
|
1085
1161
|
const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
|
|
1086
1162
|
useEffect(() => {
|
|
1087
1163
|
if (!schema.loading) return;
|
|
1088
1164
|
const opts = { cache: true };
|
|
1089
|
-
httpClient.fetch(`${
|
|
1165
|
+
httpClient.fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
|
|
1090
1166
|
}, [name]);
|
|
1091
1167
|
return schema;
|
|
1092
1168
|
};
|
|
@@ -1095,10 +1171,10 @@ const useSchema = (action) => {
|
|
|
1095
1171
|
*/
|
|
1096
1172
|
const ssrSchemaLoading = (alepha, name) => {
|
|
1097
1173
|
if (!alepha.isBrowser()) {
|
|
1098
|
-
const
|
|
1099
|
-
const can =
|
|
1174
|
+
const linkProvider = alepha.inject(LinkProvider);
|
|
1175
|
+
const can = linkProvider.getServerLinks().find((link) => link.name === name);
|
|
1100
1176
|
if (can) {
|
|
1101
|
-
const schema$1 =
|
|
1177
|
+
const schema$1 = linkProvider.links.find((it) => it.name === name)?.schema;
|
|
1102
1178
|
if (schema$1) {
|
|
1103
1179
|
can.schema = schema$1;
|
|
1104
1180
|
return schema$1;
|
|
@@ -1106,48 +1182,25 @@ const ssrSchemaLoading = (alepha, name) => {
|
|
|
1106
1182
|
}
|
|
1107
1183
|
return { loading: true };
|
|
1108
1184
|
}
|
|
1109
|
-
const schema = alepha.inject(LinkProvider).links
|
|
1185
|
+
const schema = alepha.inject(LinkProvider).links.find((it) => it.name === name)?.schema;
|
|
1110
1186
|
if (schema) return schema;
|
|
1111
1187
|
return { loading: true };
|
|
1112
1188
|
};
|
|
1113
1189
|
|
|
1114
|
-
//#endregion
|
|
1115
|
-
//#region src/hooks/useStore.ts
|
|
1116
|
-
/**
|
|
1117
|
-
* Hook to access and mutate the Alepha state.
|
|
1118
|
-
*/
|
|
1119
|
-
const useStore = (key) => {
|
|
1120
|
-
const alepha = useAlepha();
|
|
1121
|
-
const [state, setState] = useState(alepha.state(key));
|
|
1122
|
-
useEffect(() => {
|
|
1123
|
-
if (!alepha.isBrowser()) return;
|
|
1124
|
-
return alepha.on("state:mutate", (ev) => {
|
|
1125
|
-
if (ev.key === key) setState(ev.value);
|
|
1126
|
-
});
|
|
1127
|
-
}, []);
|
|
1128
|
-
if (!alepha.isBrowser()) {
|
|
1129
|
-
const value = alepha.context.get(key);
|
|
1130
|
-
if (value !== null) return [value, (_) => {}];
|
|
1131
|
-
}
|
|
1132
|
-
return [state, (value) => {
|
|
1133
|
-
alepha.state(key, value);
|
|
1134
|
-
}];
|
|
1135
|
-
};
|
|
1136
|
-
|
|
1137
1190
|
//#endregion
|
|
1138
1191
|
//#region src/index.browser.ts
|
|
1139
1192
|
const AlephaReact = $module({
|
|
1140
1193
|
name: "alepha.react",
|
|
1141
1194
|
descriptors: [$page],
|
|
1142
1195
|
services: [
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1196
|
+
ReactPageProvider,
|
|
1197
|
+
ReactBrowserRouterProvider,
|
|
1198
|
+
ReactBrowserProvider,
|
|
1199
|
+
ReactRouter
|
|
1147
1200
|
],
|
|
1148
|
-
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(
|
|
1201
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactRouter)
|
|
1149
1202
|
});
|
|
1150
1203
|
|
|
1151
1204
|
//#endregion
|
|
1152
|
-
export { $page, AlephaContext, AlephaReact,
|
|
1205
|
+
export { $page, AlephaContext, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, ErrorViewer_default as ErrorViewer, Link_default as 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 };
|
|
1153
1206
|
//# sourceMappingURL=index.browser.js.map
|