@alepha/react 0.9.1 → 0.9.3
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 +7 -0
- package/dist/index.browser.js +202 -84
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +228 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -165
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +261 -164
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +226 -93
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/components/ErrorViewer.tsx +1 -1
- package/src/components/Link.tsx +4 -24
- package/src/components/NestedView.tsx +11 -2
- package/src/components/NotFound.tsx +4 -1
- package/src/contexts/AlephaContext.ts +4 -0
- package/src/contexts/RouterContext.ts +0 -2
- package/src/descriptors/$page.ts +71 -9
- package/src/errors/{RedirectionError.ts → Redirection.ts} +1 -1
- package/src/hooks/RouterHookApi.ts +35 -11
- package/src/hooks/useActive.ts +22 -15
- package/src/hooks/useAlepha.ts +5 -5
- package/src/hooks/useClient.ts +2 -0
- package/src/hooks/useInject.ts +5 -8
- package/src/hooks/useQueryParams.ts +6 -9
- package/src/hooks/useRouter.ts +6 -5
- package/src/hooks/useRouterEvents.ts +12 -12
- package/src/hooks/useRouterState.ts +6 -4
- package/src/hooks/useSchema.ts +93 -0
- package/src/hooks/useStore.ts +47 -0
- package/src/index.shared.ts +5 -2
- package/src/providers/BrowserRouterProvider.ts +9 -0
- package/src/providers/PageDescriptorProvider.ts +123 -39
- package/src/providers/ReactBrowserProvider.ts +17 -11
- package/src/providers/ReactServerProvider.ts +47 -10
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND,
|
|
2
|
-
import { AlephaServer, ServerRouterProvider, ServerTimingProvider, apiLinksResponseSchema } from "@alepha/server";
|
|
1
|
+
import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
|
|
2
|
+
import { AlephaServer, HttpClient, ServerRouterProvider, ServerTimingProvider, apiLinksResponseSchema } from "@alepha/server";
|
|
3
3
|
import { AlephaServerCache } from "@alepha/server-cache";
|
|
4
4
|
import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "@alepha/server-links";
|
|
5
5
|
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
@@ -18,6 +18,12 @@ const $page = (options) => {
|
|
|
18
18
|
return createDescriptor(PageDescriptor, options);
|
|
19
19
|
};
|
|
20
20
|
var PageDescriptor = class extends Descriptor {
|
|
21
|
+
onInit() {
|
|
22
|
+
if (this.options.static) this.options.cache ??= {
|
|
23
|
+
provider: "memory",
|
|
24
|
+
ttl: [1, "week"]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
21
27
|
get name() {
|
|
22
28
|
return this.options.name ?? this.config.propertyKey;
|
|
23
29
|
}
|
|
@@ -26,7 +32,7 @@ var PageDescriptor = class extends Descriptor {
|
|
|
26
32
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
27
33
|
*/
|
|
28
34
|
async render(options) {
|
|
29
|
-
throw new
|
|
35
|
+
throw new Error("render method is not implemented in this environment");
|
|
30
36
|
}
|
|
31
37
|
};
|
|
32
38
|
$page[KIND] = PageDescriptor;
|
|
@@ -80,7 +86,7 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
80
86
|
heading: {
|
|
81
87
|
fontSize: "20px",
|
|
82
88
|
fontWeight: "bold",
|
|
83
|
-
marginBottom: "
|
|
89
|
+
marginBottom: "10px"
|
|
84
90
|
},
|
|
85
91
|
name: {
|
|
86
92
|
fontSize: "16px",
|
|
@@ -207,20 +213,31 @@ const RouterContext = createContext(void 0);
|
|
|
207
213
|
//#region src/contexts/RouterLayerContext.ts
|
|
208
214
|
const RouterLayerContext = createContext(void 0);
|
|
209
215
|
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/contexts/AlephaContext.ts
|
|
218
|
+
const AlephaContext = createContext(void 0);
|
|
219
|
+
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/hooks/useAlepha.ts
|
|
222
|
+
const useAlepha = () => {
|
|
223
|
+
const alepha = useContext(AlephaContext);
|
|
224
|
+
if (!alepha) throw new Error("useAlepha must be used within an AlephaContext.Provider");
|
|
225
|
+
return alepha;
|
|
226
|
+
};
|
|
227
|
+
|
|
210
228
|
//#endregion
|
|
211
229
|
//#region src/hooks/useRouterEvents.ts
|
|
212
230
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
213
|
-
const
|
|
214
|
-
if (!ctx) throw new Error("useRouter must be used within a RouterProvider");
|
|
231
|
+
const alepha = useAlepha();
|
|
215
232
|
useEffect(() => {
|
|
216
|
-
if (!
|
|
233
|
+
if (!alepha.isBrowser()) return;
|
|
217
234
|
const subs = [];
|
|
218
235
|
const onBegin = opts.onBegin;
|
|
219
236
|
const onEnd = opts.onEnd;
|
|
220
237
|
const onError = opts.onError;
|
|
221
|
-
if (onBegin) subs.push(
|
|
222
|
-
if (onEnd) subs.push(
|
|
223
|
-
if (onError) subs.push(
|
|
238
|
+
if (onBegin) subs.push(alepha.on("react:transition:begin", { callback: onBegin }));
|
|
239
|
+
if (onEnd) subs.push(alepha.on("react:transition:end", { callback: onEnd }));
|
|
240
|
+
if (onError) subs.push(alepha.on("react:transition:error", { callback: onError }));
|
|
224
241
|
return () => {
|
|
225
242
|
for (const sub of subs) sub();
|
|
226
243
|
};
|
|
@@ -286,13 +303,16 @@ const NestedView = (props) => {
|
|
|
286
303
|
const layer = useContext(RouterLayerContext);
|
|
287
304
|
const index = layer?.index ?? 0;
|
|
288
305
|
const [view, setView] = useState(app?.state.layers[index]?.element);
|
|
289
|
-
useRouterEvents({ onEnd: ({ state }) => {
|
|
306
|
+
useRouterEvents({ onEnd: ({ state, context }) => {
|
|
307
|
+
if (app) app.context = context;
|
|
290
308
|
if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
|
|
291
309
|
} }, [app]);
|
|
292
310
|
if (!app) throw new Error("NestedView must be used within a RouterContext.");
|
|
293
311
|
const element = view ?? props.children ?? null;
|
|
294
312
|
return /* @__PURE__ */ jsx(ErrorBoundary_default, {
|
|
295
|
-
fallback:
|
|
313
|
+
fallback: (error) => {
|
|
314
|
+
return app.context.onError?.(error, app.context);
|
|
315
|
+
},
|
|
296
316
|
children: element
|
|
297
317
|
});
|
|
298
318
|
};
|
|
@@ -300,7 +320,7 @@ var NestedView_default = NestedView;
|
|
|
300
320
|
|
|
301
321
|
//#endregion
|
|
302
322
|
//#region src/components/NotFound.tsx
|
|
303
|
-
function NotFoundPage() {
|
|
323
|
+
function NotFoundPage(props) {
|
|
304
324
|
return /* @__PURE__ */ jsx("div", {
|
|
305
325
|
style: {
|
|
306
326
|
height: "100vh",
|
|
@@ -310,7 +330,8 @@ function NotFoundPage() {
|
|
|
310
330
|
alignItems: "center",
|
|
311
331
|
textAlign: "center",
|
|
312
332
|
fontFamily: "sans-serif",
|
|
313
|
-
padding: "1rem"
|
|
333
|
+
padding: "1rem",
|
|
334
|
+
...props.style
|
|
314
335
|
},
|
|
315
336
|
children: /* @__PURE__ */ jsx("h1", {
|
|
316
337
|
style: {
|
|
@@ -323,8 +344,8 @@ function NotFoundPage() {
|
|
|
323
344
|
}
|
|
324
345
|
|
|
325
346
|
//#endregion
|
|
326
|
-
//#region src/errors/
|
|
327
|
-
var
|
|
347
|
+
//#region src/errors/Redirection.ts
|
|
348
|
+
var Redirection = class extends Error {
|
|
328
349
|
page;
|
|
329
350
|
constructor(page) {
|
|
330
351
|
super("Redirection");
|
|
@@ -347,7 +368,7 @@ var PageDescriptorProvider = class {
|
|
|
347
368
|
for (const page of this.pages) if (page.name === name) return page;
|
|
348
369
|
throw new Error(`Page ${name} not found`);
|
|
349
370
|
}
|
|
350
|
-
|
|
371
|
+
pathname(name, options = {}) {
|
|
351
372
|
const page = this.page(name);
|
|
352
373
|
if (!page) throw new Error(`Page ${name} not found`);
|
|
353
374
|
let url = page.path ?? "";
|
|
@@ -357,14 +378,20 @@ var PageDescriptorProvider = class {
|
|
|
357
378
|
parent = parent.parent;
|
|
358
379
|
}
|
|
359
380
|
url = this.compile(url, options.params ?? {});
|
|
360
|
-
|
|
381
|
+
if (options.query) {
|
|
382
|
+
const query = new URLSearchParams(options.query);
|
|
383
|
+
if (query.toString()) url += `?${query.toString()}`;
|
|
384
|
+
}
|
|
385
|
+
return url.replace(/\/\/+/g, "/") || "/";
|
|
386
|
+
}
|
|
387
|
+
url(name, options = {}) {
|
|
388
|
+
return new URL(this.pathname(name, options), options.base ?? `http://localhost`);
|
|
361
389
|
}
|
|
362
390
|
root(state, context) {
|
|
363
|
-
const root = createElement(RouterContext.Provider, { value: {
|
|
364
|
-
alepha: this.alepha,
|
|
391
|
+
const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(RouterContext.Provider, { value: {
|
|
365
392
|
state,
|
|
366
393
|
context
|
|
367
|
-
} }, createElement(NestedView_default, {}, state.layers[0]?.element));
|
|
394
|
+
} }, createElement(NestedView_default, {}, state.layers[0]?.element)));
|
|
368
395
|
if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
|
|
369
396
|
return root;
|
|
370
397
|
}
|
|
@@ -433,12 +460,10 @@ var PageDescriptorProvider = class {
|
|
|
433
460
|
...props
|
|
434
461
|
};
|
|
435
462
|
} catch (e) {
|
|
436
|
-
if (e instanceof
|
|
437
|
-
layers: [],
|
|
438
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
463
|
+
if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
|
|
439
464
|
pathname,
|
|
440
465
|
search
|
|
441
|
-
};
|
|
466
|
+
});
|
|
442
467
|
this.log.error(e);
|
|
443
468
|
it.error = e;
|
|
444
469
|
break;
|
|
@@ -454,9 +479,21 @@ var PageDescriptorProvider = class {
|
|
|
454
479
|
acc += it.route.path ? this.compile(it.route.path, params) : "";
|
|
455
480
|
const path = acc.replace(/\/+/, "/");
|
|
456
481
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
457
|
-
if (localErrorHandler)
|
|
458
|
-
|
|
459
|
-
|
|
482
|
+
if (localErrorHandler) {
|
|
483
|
+
const onErrorParent = request.onError;
|
|
484
|
+
request.onError = (error, context$1) => {
|
|
485
|
+
const result = localErrorHandler(error, context$1);
|
|
486
|
+
if (result === void 0) return onErrorParent(error, context$1);
|
|
487
|
+
return result;
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (it.error) try {
|
|
491
|
+
let element$1 = await request.onError(it.error, request);
|
|
492
|
+
if (element$1 === void 0) throw it.error;
|
|
493
|
+
if (element$1 instanceof Redirection) return this.createRedirectionLayer(element$1.page, {
|
|
494
|
+
pathname,
|
|
495
|
+
search
|
|
496
|
+
});
|
|
460
497
|
if (element$1 === null) element$1 = this.renderError(it.error);
|
|
461
498
|
layers.push({
|
|
462
499
|
props,
|
|
@@ -470,6 +507,12 @@ var PageDescriptorProvider = class {
|
|
|
470
507
|
route: it.route
|
|
471
508
|
});
|
|
472
509
|
break;
|
|
510
|
+
} catch (e) {
|
|
511
|
+
if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
|
|
512
|
+
pathname,
|
|
513
|
+
search
|
|
514
|
+
});
|
|
515
|
+
throw e;
|
|
473
516
|
}
|
|
474
517
|
const element = await this.createElement(it.route, {
|
|
475
518
|
...props,
|
|
@@ -493,6 +536,14 @@ var PageDescriptorProvider = class {
|
|
|
493
536
|
search
|
|
494
537
|
};
|
|
495
538
|
}
|
|
539
|
+
createRedirectionLayer(href, context) {
|
|
540
|
+
return {
|
|
541
|
+
layers: [],
|
|
542
|
+
redirect: typeof href === "string" ? href : this.href(href),
|
|
543
|
+
pathname: context.pathname,
|
|
544
|
+
search: context.search
|
|
545
|
+
};
|
|
546
|
+
}
|
|
496
547
|
getErrorHandler(route) {
|
|
497
548
|
if (route.errorHandler) return route.errorHandler;
|
|
498
549
|
let parent = route.parent;
|
|
@@ -548,6 +599,7 @@ var PageDescriptorProvider = class {
|
|
|
548
599
|
let hasNotFoundHandler = false;
|
|
549
600
|
const pages = this.alepha.descriptors($page);
|
|
550
601
|
const hasParent = (it) => {
|
|
602
|
+
if (it.options.parent) return true;
|
|
551
603
|
for (const page of pages) {
|
|
552
604
|
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
553
605
|
if (children.includes(it)) return true;
|
|
@@ -563,7 +615,7 @@ var PageDescriptorProvider = class {
|
|
|
563
615
|
name: "notFound",
|
|
564
616
|
cache: true,
|
|
565
617
|
component: NotFoundPage,
|
|
566
|
-
|
|
618
|
+
onServerResponse: ({ reply }) => {
|
|
567
619
|
reply.status = 404;
|
|
568
620
|
}
|
|
569
621
|
});
|
|
@@ -571,6 +623,12 @@ var PageDescriptorProvider = class {
|
|
|
571
623
|
});
|
|
572
624
|
map(pages, target) {
|
|
573
625
|
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
626
|
+
const getChildrenFromParent = (it) => {
|
|
627
|
+
const children$1 = [];
|
|
628
|
+
for (const page of pages) if (page.options.parent === it) children$1.push(page);
|
|
629
|
+
return children$1;
|
|
630
|
+
};
|
|
631
|
+
children.push(...getChildrenFromParent(target));
|
|
574
632
|
return {
|
|
575
633
|
...target.options,
|
|
576
634
|
name: target.name,
|
|
@@ -692,6 +750,10 @@ var BrowserRouterProvider = class extends RouterProvider {
|
|
|
692
750
|
options.state.pathname = state.pathname;
|
|
693
751
|
options.state.search = state.search;
|
|
694
752
|
}
|
|
753
|
+
if (options.previous) for (let i = 0; i < options.previous.length; i++) {
|
|
754
|
+
const layer = options.previous[i];
|
|
755
|
+
if (state.layers[i]?.name !== layer.name) this.pageDescriptorProvider.page(layer.name)?.onLeave?.();
|
|
756
|
+
}
|
|
695
757
|
await this.alepha.emit("react:transition:end", {
|
|
696
758
|
state: options.state,
|
|
697
759
|
context
|
|
@@ -766,15 +828,11 @@ var ReactBrowserProvider = class {
|
|
|
766
828
|
}
|
|
767
829
|
async go(url, options = {}) {
|
|
768
830
|
const result = await this.render({ url });
|
|
769
|
-
if (result.context.url.pathname !== url) {
|
|
770
|
-
this.pushState(result.context.url.pathname);
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
if (options.replace) {
|
|
774
|
-
this.pushState(url);
|
|
831
|
+
if (result.context.url.pathname + result.context.url.search !== url) {
|
|
832
|
+
this.pushState(result.context.url.pathname + result.context.url.search);
|
|
775
833
|
return;
|
|
776
834
|
}
|
|
777
|
-
this.pushState(url);
|
|
835
|
+
this.pushState(url, options.replace);
|
|
778
836
|
}
|
|
779
837
|
async render(options = {}) {
|
|
780
838
|
const previous = options.previous ?? this.state.layers;
|
|
@@ -803,7 +861,13 @@ var ReactBrowserProvider = class {
|
|
|
803
861
|
handler: async () => {
|
|
804
862
|
const hydration = this.getHydrationState();
|
|
805
863
|
const previous = hydration?.layers ?? [];
|
|
806
|
-
if (hydration
|
|
864
|
+
if (hydration) {
|
|
865
|
+
for (const [key, value] of Object.entries(hydration)) if (key !== "layers" && key !== "links") this.alepha.state(key, value);
|
|
866
|
+
}
|
|
867
|
+
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink({
|
|
868
|
+
...link,
|
|
869
|
+
prefix: hydration.links.prefix
|
|
870
|
+
});
|
|
807
871
|
const { context } = await this.render({ previous });
|
|
808
872
|
await this.alepha.emit("react:browser:render", {
|
|
809
873
|
state: this.state,
|
|
@@ -931,6 +995,7 @@ var ReactServerProvider = class {
|
|
|
931
995
|
html: renderToString(this.pageDescriptorProvider.root(state, context))
|
|
932
996
|
};
|
|
933
997
|
const html = this.renderToHtml(this.template ?? "", state, context, options.hydration);
|
|
998
|
+
if (html instanceof Redirection) throw new Error("Redirection is not supported in this context");
|
|
934
999
|
const result = {
|
|
935
1000
|
context,
|
|
936
1001
|
state,
|
|
@@ -945,6 +1010,7 @@ var ReactServerProvider = class {
|
|
|
945
1010
|
const { url, reply, query, params } = serverRequest;
|
|
946
1011
|
const template = await templateLoader();
|
|
947
1012
|
if (!template) throw new Error("Template not found");
|
|
1013
|
+
this.log.trace("Rendering page", { name: page.name });
|
|
948
1014
|
const context = {
|
|
949
1015
|
url,
|
|
950
1016
|
params,
|
|
@@ -970,6 +1036,10 @@ var ReactServerProvider = class {
|
|
|
970
1036
|
}
|
|
971
1037
|
target = target.parent;
|
|
972
1038
|
}
|
|
1039
|
+
await this.alepha.emit("react:transition:begin", {
|
|
1040
|
+
request: serverRequest,
|
|
1041
|
+
context
|
|
1042
|
+
});
|
|
973
1043
|
await this.alepha.emit("react:server:render:begin", {
|
|
974
1044
|
request: serverRequest,
|
|
975
1045
|
context
|
|
@@ -984,14 +1054,20 @@ var ReactServerProvider = class {
|
|
|
984
1054
|
reply.headers.expires = "0";
|
|
985
1055
|
if (page.cache && serverRequest.user) delete context.links;
|
|
986
1056
|
const html = this.renderToHtml(template, state, context);
|
|
987
|
-
|
|
1057
|
+
if (html instanceof Redirection) {
|
|
1058
|
+
reply.redirect(typeof html.page === "string" ? html.page : this.pageDescriptorProvider.href(html.page));
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const event = {
|
|
988
1062
|
request: serverRequest,
|
|
989
1063
|
context,
|
|
990
1064
|
state,
|
|
991
1065
|
html
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
|
|
1066
|
+
};
|
|
1067
|
+
await this.alepha.emit("react:server:render:end", event);
|
|
1068
|
+
page.onServerResponse?.(serverRequest);
|
|
1069
|
+
this.log.trace("Page rendered", { name: page.name });
|
|
1070
|
+
return event.html;
|
|
995
1071
|
};
|
|
996
1072
|
}
|
|
997
1073
|
renderToHtml(template, state, context, hydration = true) {
|
|
@@ -1002,20 +1078,23 @@ var ReactServerProvider = class {
|
|
|
1002
1078
|
app = renderToString(element);
|
|
1003
1079
|
} catch (error) {
|
|
1004
1080
|
this.log.error("Error during SSR", error);
|
|
1005
|
-
|
|
1081
|
+
const element$1 = context.onError(error, context);
|
|
1082
|
+
if (element$1 instanceof Redirection) return element$1;
|
|
1083
|
+
app = renderToString(element$1);
|
|
1006
1084
|
}
|
|
1007
1085
|
this.serverTimingProvider.endTiming("renderToString");
|
|
1008
1086
|
const response = { html: template };
|
|
1009
1087
|
if (hydration) {
|
|
1088
|
+
const { request, context: context$1,...rest } = this.alepha.context.als?.getStore() ?? {};
|
|
1010
1089
|
const hydrationData = {
|
|
1011
|
-
|
|
1090
|
+
...rest,
|
|
1012
1091
|
layers: state.layers.map((it) => ({
|
|
1013
1092
|
...it,
|
|
1014
1093
|
error: it.error ? {
|
|
1015
1094
|
...it.error,
|
|
1016
1095
|
name: it.error.name,
|
|
1017
1096
|
message: it.error.message,
|
|
1018
|
-
stack: it.error.stack
|
|
1097
|
+
stack: !this.alepha.isProduction() ? it.error.stack : void 0
|
|
1019
1098
|
} : void 0,
|
|
1020
1099
|
index: void 0,
|
|
1021
1100
|
path: void 0,
|
|
@@ -1035,24 +1114,34 @@ var ReactServerProvider = class {
|
|
|
1035
1114
|
else {
|
|
1036
1115
|
const bodyOpenTag = /<body([^>]*)>/i;
|
|
1037
1116
|
if (bodyOpenTag.test(response.html)) response.html = response.html.replace(bodyOpenTag, (match) => {
|
|
1038
|
-
return `${match}
|
|
1117
|
+
return `${match}<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
|
|
1039
1118
|
});
|
|
1040
1119
|
}
|
|
1041
1120
|
const bodyCloseTagRegex = /<\/body>/i;
|
|
1042
|
-
if (bodyCloseTagRegex.test(response.html)) response.html = response.html.replace(bodyCloseTagRegex, `${script}
|
|
1121
|
+
if (bodyCloseTagRegex.test(response.html)) response.html = response.html.replace(bodyCloseTagRegex, `${script}</body>`);
|
|
1043
1122
|
}
|
|
1044
1123
|
};
|
|
1045
1124
|
|
|
1046
1125
|
//#endregion
|
|
1047
1126
|
//#region src/hooks/RouterHookApi.ts
|
|
1048
1127
|
var RouterHookApi = class {
|
|
1049
|
-
constructor(pages, context, state, layer, browser) {
|
|
1128
|
+
constructor(pages, context, state, layer, pageApi, browser) {
|
|
1050
1129
|
this.pages = pages;
|
|
1051
1130
|
this.context = context;
|
|
1052
1131
|
this.state = state;
|
|
1053
1132
|
this.layer = layer;
|
|
1133
|
+
this.pageApi = pageApi;
|
|
1054
1134
|
this.browser = browser;
|
|
1055
1135
|
}
|
|
1136
|
+
path(name, config = {}) {
|
|
1137
|
+
return this.pageApi.pathname(name, {
|
|
1138
|
+
params: {
|
|
1139
|
+
...this.context.params,
|
|
1140
|
+
...config.params
|
|
1141
|
+
},
|
|
1142
|
+
query: config.query
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1056
1145
|
getURL() {
|
|
1057
1146
|
if (!this.browser) return this.context.url;
|
|
1058
1147
|
return new URL(this.location.href);
|
|
@@ -1094,23 +1183,23 @@ var RouterHookApi = class {
|
|
|
1094
1183
|
}
|
|
1095
1184
|
async go(path, options) {
|
|
1096
1185
|
for (const page of this.pages) if (page.name === path) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1186
|
+
await this.browser?.go(this.path(path, options), options);
|
|
1187
|
+
return;
|
|
1099
1188
|
}
|
|
1100
|
-
await this.browser?.go(
|
|
1189
|
+
await this.browser?.go(path, options);
|
|
1101
1190
|
}
|
|
1102
1191
|
anchor(path, options = {}) {
|
|
1192
|
+
let href = path;
|
|
1103
1193
|
for (const page of this.pages) if (page.name === path) {
|
|
1104
|
-
|
|
1194
|
+
href = this.path(path, options);
|
|
1105
1195
|
break;
|
|
1106
1196
|
}
|
|
1107
|
-
const href = this.createHref(path, this.layer, options);
|
|
1108
1197
|
return {
|
|
1109
1198
|
href,
|
|
1110
1199
|
onClick: (ev) => {
|
|
1111
1200
|
ev.stopPropagation();
|
|
1112
1201
|
ev.preventDefault();
|
|
1113
|
-
this.go(
|
|
1202
|
+
this.go(href, options).catch(console.error);
|
|
1114
1203
|
}
|
|
1115
1204
|
};
|
|
1116
1205
|
}
|
|
@@ -1132,33 +1221,25 @@ var RouterHookApi = class {
|
|
|
1132
1221
|
//#endregion
|
|
1133
1222
|
//#region src/hooks/useRouter.ts
|
|
1134
1223
|
const useRouter = () => {
|
|
1224
|
+
const alepha = useAlepha();
|
|
1135
1225
|
const ctx = useContext(RouterContext);
|
|
1136
1226
|
const layer = useContext(RouterLayerContext);
|
|
1137
1227
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1138
1228
|
const pages = useMemo(() => {
|
|
1139
|
-
return
|
|
1229
|
+
return alepha.inject(PageDescriptorProvider).getPages();
|
|
1140
1230
|
}, []);
|
|
1141
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer,
|
|
1231
|
+
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.inject(PageDescriptorProvider), alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1142
1232
|
};
|
|
1143
1233
|
|
|
1144
1234
|
//#endregion
|
|
1145
1235
|
//#region src/components/Link.tsx
|
|
1146
1236
|
const Link = (props) => {
|
|
1147
|
-
React.useContext(RouterContext);
|
|
1148
1237
|
const router = useRouter();
|
|
1149
|
-
const
|
|
1150
|
-
if (!to) return null;
|
|
1151
|
-
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
1152
|
-
if (can && !can()) return null;
|
|
1153
|
-
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
1154
|
-
const anchorProps = {
|
|
1155
|
-
...props,
|
|
1156
|
-
to: void 0
|
|
1157
|
-
};
|
|
1238
|
+
const { to,...anchorProps } = props;
|
|
1158
1239
|
return /* @__PURE__ */ jsx("a", {
|
|
1159
1240
|
...router.anchor(to),
|
|
1160
1241
|
...anchorProps,
|
|
1161
|
-
children: props.children
|
|
1242
|
+
children: props.children
|
|
1162
1243
|
});
|
|
1163
1244
|
};
|
|
1164
1245
|
var Link_default = Link;
|
|
@@ -1170,22 +1251,21 @@ const useActive = (path) => {
|
|
|
1170
1251
|
const ctx = useContext(RouterContext);
|
|
1171
1252
|
const layer = useContext(RouterLayerContext);
|
|
1172
1253
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1173
|
-
let name;
|
|
1174
|
-
if (typeof path === "object" && path.options.name) name = path.options.name;
|
|
1175
1254
|
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
1176
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
1255
|
+
const href = useMemo(() => router.createHref(path ?? "", layer), [path, layer]);
|
|
1177
1256
|
const [isPending, setPending] = useState(false);
|
|
1178
|
-
const isActive = current === href;
|
|
1179
|
-
useRouterEvents({ onEnd: ({ state }) =>
|
|
1257
|
+
const isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1258
|
+
useRouterEvents({ onEnd: ({ state }) => {
|
|
1259
|
+
path && setCurrent(state.pathname);
|
|
1260
|
+
} }, [path]);
|
|
1180
1261
|
return {
|
|
1181
|
-
name,
|
|
1182
1262
|
isPending,
|
|
1183
1263
|
isActive,
|
|
1184
1264
|
anchorProps: {
|
|
1185
1265
|
href,
|
|
1186
1266
|
onClick: (ev) => {
|
|
1187
|
-
ev
|
|
1188
|
-
ev
|
|
1267
|
+
ev?.stopPropagation();
|
|
1268
|
+
ev?.preventDefault();
|
|
1189
1269
|
if (isActive) return;
|
|
1190
1270
|
if (isPending) return;
|
|
1191
1271
|
setPending(true);
|
|
@@ -1198,45 +1278,62 @@ const useActive = (path) => {
|
|
|
1198
1278
|
};
|
|
1199
1279
|
|
|
1200
1280
|
//#endregion
|
|
1201
|
-
//#region src/hooks/
|
|
1202
|
-
const
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
return routerContext.alepha;
|
|
1281
|
+
//#region src/hooks/useInject.ts
|
|
1282
|
+
const useInject = (service) => {
|
|
1283
|
+
const alepha = useAlepha();
|
|
1284
|
+
return useMemo(() => alepha.inject(service), []);
|
|
1206
1285
|
};
|
|
1207
1286
|
|
|
1208
1287
|
//#endregion
|
|
1209
|
-
//#region src/hooks/
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1288
|
+
//#region src/hooks/useStore.ts
|
|
1289
|
+
/**
|
|
1290
|
+
* Hook to access and mutate the Alepha state.
|
|
1291
|
+
*/
|
|
1292
|
+
const useStore = (key, defaultValue) => {
|
|
1293
|
+
const alepha = useAlepha();
|
|
1294
|
+
useMemo(() => {
|
|
1295
|
+
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
1296
|
+
}, [defaultValue]);
|
|
1297
|
+
const [state, setState] = useState(alepha.state(key));
|
|
1298
|
+
useEffect(() => {
|
|
1299
|
+
if (!alepha.isBrowser()) return;
|
|
1300
|
+
return alepha.on("state:mutate", (ev) => {
|
|
1301
|
+
if (ev.key === key) setState(ev.value);
|
|
1302
|
+
});
|
|
1303
|
+
}, []);
|
|
1304
|
+
if (!alepha.isBrowser()) {
|
|
1305
|
+
const value = alepha.context.get(key);
|
|
1306
|
+
if (value !== null) return [value, (_) => {}];
|
|
1307
|
+
}
|
|
1308
|
+
return [state, (value) => {
|
|
1309
|
+
alepha.state(key, value);
|
|
1310
|
+
}];
|
|
1214
1311
|
};
|
|
1215
1312
|
|
|
1216
1313
|
//#endregion
|
|
1217
1314
|
//#region src/hooks/useClient.ts
|
|
1218
1315
|
const useClient = (_scope) => {
|
|
1316
|
+
useStore("user");
|
|
1219
1317
|
return useInject(LinkProvider).client();
|
|
1220
1318
|
};
|
|
1221
1319
|
|
|
1222
1320
|
//#endregion
|
|
1223
1321
|
//#region src/hooks/useQueryParams.ts
|
|
1224
1322
|
const useQueryParams = (schema, options = {}) => {
|
|
1225
|
-
const
|
|
1226
|
-
if (!ctx) throw new Error("useQueryParams must be used within a RouterProvider");
|
|
1323
|
+
const alepha = useAlepha();
|
|
1227
1324
|
const key = options.key ?? "q";
|
|
1228
1325
|
const router = useRouter();
|
|
1229
1326
|
const querystring = router.query[key];
|
|
1230
|
-
const [queryParams, setQueryParams] = useState(decode(
|
|
1327
|
+
const [queryParams, setQueryParams] = useState(decode(alepha, schema, router.query[key]));
|
|
1231
1328
|
useEffect(() => {
|
|
1232
|
-
setQueryParams(decode(
|
|
1329
|
+
setQueryParams(decode(alepha, schema, querystring));
|
|
1233
1330
|
}, [querystring]);
|
|
1234
1331
|
return [queryParams, (queryParams$1) => {
|
|
1235
1332
|
setQueryParams(queryParams$1);
|
|
1236
1333
|
router.setQueryParams((data) => {
|
|
1237
1334
|
return {
|
|
1238
1335
|
...data,
|
|
1239
|
-
[key]: encode(
|
|
1336
|
+
[key]: encode(alepha, schema, queryParams$1)
|
|
1240
1337
|
};
|
|
1241
1338
|
});
|
|
1242
1339
|
}];
|
|
@@ -1255,14 +1352,50 @@ const decode = (alepha, schema, data) => {
|
|
|
1255
1352
|
//#endregion
|
|
1256
1353
|
//#region src/hooks/useRouterState.ts
|
|
1257
1354
|
const useRouterState = () => {
|
|
1258
|
-
const
|
|
1355
|
+
const router = useContext(RouterContext);
|
|
1259
1356
|
const layer = useContext(RouterLayerContext);
|
|
1260
|
-
if (!
|
|
1261
|
-
const [state, setState] = useState(
|
|
1357
|
+
if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
|
|
1358
|
+
const [state, setState] = useState(router.state);
|
|
1262
1359
|
useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
|
|
1263
1360
|
return state;
|
|
1264
1361
|
};
|
|
1265
1362
|
|
|
1363
|
+
//#endregion
|
|
1364
|
+
//#region src/hooks/useSchema.ts
|
|
1365
|
+
const useSchema = (action) => {
|
|
1366
|
+
const name = action.name;
|
|
1367
|
+
const alepha = useAlepha();
|
|
1368
|
+
const httpClient = useInject(HttpClient);
|
|
1369
|
+
const linkProvider = useInject(LinkProvider);
|
|
1370
|
+
const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
|
|
1371
|
+
useEffect(() => {
|
|
1372
|
+
if (!schema.loading) return;
|
|
1373
|
+
const opts = { cache: true };
|
|
1374
|
+
httpClient.fetch(`${linkProvider.URL_LINKS}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
|
|
1375
|
+
}, [name]);
|
|
1376
|
+
return schema;
|
|
1377
|
+
};
|
|
1378
|
+
/**
|
|
1379
|
+
* Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
|
|
1380
|
+
*/
|
|
1381
|
+
const ssrSchemaLoading = (alepha, name) => {
|
|
1382
|
+
if (!alepha.isBrowser()) {
|
|
1383
|
+
const links = alepha.context.get("links")?.links ?? [];
|
|
1384
|
+
const can = links.find((it) => it.name === name);
|
|
1385
|
+
if (can) {
|
|
1386
|
+
const schema$1 = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
|
|
1387
|
+
if (schema$1) {
|
|
1388
|
+
can.schema = schema$1;
|
|
1389
|
+
return schema$1;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return { loading: true };
|
|
1393
|
+
}
|
|
1394
|
+
const schema = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
|
|
1395
|
+
if (schema) return schema;
|
|
1396
|
+
return { loading: true };
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1266
1399
|
//#endregion
|
|
1267
1400
|
//#region src/index.ts
|
|
1268
1401
|
/**
|
|
@@ -1287,5 +1420,5 @@ const AlephaReact = $module({
|
|
|
1287
1420
|
});
|
|
1288
1421
|
|
|
1289
1422
|
//#endregion
|
|
1290
|
-
export { $page, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider,
|
|
1423
|
+
export { $page, AlephaContext, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, Redirection, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
|
|
1291
1424
|
//# sourceMappingURL=index.js.map
|