@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/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Alternatively, you can install it individually:
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install @alepha/core @alepha/react
|
|
17
17
|
```
|
|
18
|
+
|
|
18
19
|
## Module
|
|
19
20
|
|
|
20
21
|
Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
|
|
@@ -30,3 +31,9 @@ type safety and schema validation for route parameters and data.
|
|
|
30
31
|
#### $page()
|
|
31
32
|
|
|
32
33
|
Main descriptor for defining a React route in the application.
|
|
34
|
+
|
|
35
|
+
### Hooks
|
|
36
|
+
|
|
37
|
+
#### useStore()
|
|
38
|
+
|
|
39
|
+
Hook to access and mutate the Alepha state.
|
package/dist/index.browser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND,
|
|
2
|
-
import { AlephaServer } from "@alepha/server";
|
|
1
|
+
import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
|
|
2
|
+
import { AlephaServer, HttpClient } from "@alepha/server";
|
|
3
3
|
import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
|
|
4
4
|
import { RouterProvider } from "@alepha/router";
|
|
5
5
|
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
@@ -14,6 +14,12 @@ const $page = (options) => {
|
|
|
14
14
|
return createDescriptor(PageDescriptor, options);
|
|
15
15
|
};
|
|
16
16
|
var PageDescriptor = class extends Descriptor {
|
|
17
|
+
onInit() {
|
|
18
|
+
if (this.options.static) this.options.cache ??= {
|
|
19
|
+
provider: "memory",
|
|
20
|
+
ttl: [1, "week"]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
17
23
|
get name() {
|
|
18
24
|
return this.options.name ?? this.config.propertyKey;
|
|
19
25
|
}
|
|
@@ -22,14 +28,14 @@ var PageDescriptor = class extends Descriptor {
|
|
|
22
28
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
23
29
|
*/
|
|
24
30
|
async render(options) {
|
|
25
|
-
throw new
|
|
31
|
+
throw new Error("render method is not implemented in this environment");
|
|
26
32
|
}
|
|
27
33
|
};
|
|
28
34
|
$page[KIND] = PageDescriptor;
|
|
29
35
|
|
|
30
36
|
//#endregion
|
|
31
37
|
//#region src/components/NotFound.tsx
|
|
32
|
-
function NotFoundPage() {
|
|
38
|
+
function NotFoundPage(props) {
|
|
33
39
|
return /* @__PURE__ */ jsx("div", {
|
|
34
40
|
style: {
|
|
35
41
|
height: "100vh",
|
|
@@ -39,7 +45,8 @@ function NotFoundPage() {
|
|
|
39
45
|
alignItems: "center",
|
|
40
46
|
textAlign: "center",
|
|
41
47
|
fontFamily: "sans-serif",
|
|
42
|
-
padding: "1rem"
|
|
48
|
+
padding: "1rem",
|
|
49
|
+
...props.style
|
|
43
50
|
},
|
|
44
51
|
children: /* @__PURE__ */ jsx("h1", {
|
|
45
52
|
style: {
|
|
@@ -100,7 +107,7 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
100
107
|
heading: {
|
|
101
108
|
fontSize: "20px",
|
|
102
109
|
fontWeight: "bold",
|
|
103
|
-
marginBottom: "
|
|
110
|
+
marginBottom: "10px"
|
|
104
111
|
},
|
|
105
112
|
name: {
|
|
106
113
|
fontSize: "16px",
|
|
@@ -227,20 +234,31 @@ const RouterContext = createContext(void 0);
|
|
|
227
234
|
//#region src/contexts/RouterLayerContext.ts
|
|
228
235
|
const RouterLayerContext = createContext(void 0);
|
|
229
236
|
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/contexts/AlephaContext.ts
|
|
239
|
+
const AlephaContext = createContext(void 0);
|
|
240
|
+
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/hooks/useAlepha.ts
|
|
243
|
+
const useAlepha = () => {
|
|
244
|
+
const alepha = useContext(AlephaContext);
|
|
245
|
+
if (!alepha) throw new Error("useAlepha must be used within an AlephaContext.Provider");
|
|
246
|
+
return alepha;
|
|
247
|
+
};
|
|
248
|
+
|
|
230
249
|
//#endregion
|
|
231
250
|
//#region src/hooks/useRouterEvents.ts
|
|
232
251
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
233
|
-
const
|
|
234
|
-
if (!ctx) throw new Error("useRouter must be used within a RouterProvider");
|
|
252
|
+
const alepha = useAlepha();
|
|
235
253
|
useEffect(() => {
|
|
236
|
-
if (!
|
|
254
|
+
if (!alepha.isBrowser()) return;
|
|
237
255
|
const subs = [];
|
|
238
256
|
const onBegin = opts.onBegin;
|
|
239
257
|
const onEnd = opts.onEnd;
|
|
240
258
|
const onError = opts.onError;
|
|
241
|
-
if (onBegin) subs.push(
|
|
242
|
-
if (onEnd) subs.push(
|
|
243
|
-
if (onError) subs.push(
|
|
259
|
+
if (onBegin) subs.push(alepha.on("react:transition:begin", { callback: onBegin }));
|
|
260
|
+
if (onEnd) subs.push(alepha.on("react:transition:end", { callback: onEnd }));
|
|
261
|
+
if (onError) subs.push(alepha.on("react:transition:error", { callback: onError }));
|
|
244
262
|
return () => {
|
|
245
263
|
for (const sub of subs) sub();
|
|
246
264
|
};
|
|
@@ -306,21 +324,24 @@ const NestedView = (props) => {
|
|
|
306
324
|
const layer = useContext(RouterLayerContext);
|
|
307
325
|
const index = layer?.index ?? 0;
|
|
308
326
|
const [view, setView] = useState(app?.state.layers[index]?.element);
|
|
309
|
-
useRouterEvents({ onEnd: ({ state }) => {
|
|
327
|
+
useRouterEvents({ onEnd: ({ state, context }) => {
|
|
328
|
+
if (app) app.context = context;
|
|
310
329
|
if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
|
|
311
330
|
} }, [app]);
|
|
312
331
|
if (!app) throw new Error("NestedView must be used within a RouterContext.");
|
|
313
332
|
const element = view ?? props.children ?? null;
|
|
314
333
|
return /* @__PURE__ */ jsx(ErrorBoundary_default, {
|
|
315
|
-
fallback:
|
|
334
|
+
fallback: (error) => {
|
|
335
|
+
return app.context.onError?.(error, app.context);
|
|
336
|
+
},
|
|
316
337
|
children: element
|
|
317
338
|
});
|
|
318
339
|
};
|
|
319
340
|
var NestedView_default = NestedView;
|
|
320
341
|
|
|
321
342
|
//#endregion
|
|
322
|
-
//#region src/errors/
|
|
323
|
-
var
|
|
343
|
+
//#region src/errors/Redirection.ts
|
|
344
|
+
var Redirection = class extends Error {
|
|
324
345
|
page;
|
|
325
346
|
constructor(page) {
|
|
326
347
|
super("Redirection");
|
|
@@ -343,7 +364,7 @@ var PageDescriptorProvider = class {
|
|
|
343
364
|
for (const page of this.pages) if (page.name === name) return page;
|
|
344
365
|
throw new Error(`Page ${name} not found`);
|
|
345
366
|
}
|
|
346
|
-
|
|
367
|
+
pathname(name, options = {}) {
|
|
347
368
|
const page = this.page(name);
|
|
348
369
|
if (!page) throw new Error(`Page ${name} not found`);
|
|
349
370
|
let url = page.path ?? "";
|
|
@@ -353,14 +374,20 @@ var PageDescriptorProvider = class {
|
|
|
353
374
|
parent = parent.parent;
|
|
354
375
|
}
|
|
355
376
|
url = this.compile(url, options.params ?? {});
|
|
356
|
-
|
|
377
|
+
if (options.query) {
|
|
378
|
+
const query = new URLSearchParams(options.query);
|
|
379
|
+
if (query.toString()) url += `?${query.toString()}`;
|
|
380
|
+
}
|
|
381
|
+
return url.replace(/\/\/+/g, "/") || "/";
|
|
382
|
+
}
|
|
383
|
+
url(name, options = {}) {
|
|
384
|
+
return new URL(this.pathname(name, options), options.base ?? `http://localhost`);
|
|
357
385
|
}
|
|
358
386
|
root(state, context) {
|
|
359
|
-
const root = createElement(RouterContext.Provider, { value: {
|
|
360
|
-
alepha: this.alepha,
|
|
387
|
+
const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(RouterContext.Provider, { value: {
|
|
361
388
|
state,
|
|
362
389
|
context
|
|
363
|
-
} }, createElement(NestedView_default, {}, state.layers[0]?.element));
|
|
390
|
+
} }, createElement(NestedView_default, {}, state.layers[0]?.element)));
|
|
364
391
|
if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
|
|
365
392
|
return root;
|
|
366
393
|
}
|
|
@@ -429,12 +456,10 @@ var PageDescriptorProvider = class {
|
|
|
429
456
|
...props
|
|
430
457
|
};
|
|
431
458
|
} catch (e) {
|
|
432
|
-
if (e instanceof
|
|
433
|
-
layers: [],
|
|
434
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
459
|
+
if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
|
|
435
460
|
pathname,
|
|
436
461
|
search
|
|
437
|
-
};
|
|
462
|
+
});
|
|
438
463
|
this.log.error(e);
|
|
439
464
|
it.error = e;
|
|
440
465
|
break;
|
|
@@ -450,9 +475,21 @@ var PageDescriptorProvider = class {
|
|
|
450
475
|
acc += it.route.path ? this.compile(it.route.path, params) : "";
|
|
451
476
|
const path = acc.replace(/\/+/, "/");
|
|
452
477
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
453
|
-
if (localErrorHandler)
|
|
454
|
-
|
|
455
|
-
|
|
478
|
+
if (localErrorHandler) {
|
|
479
|
+
const onErrorParent = request.onError;
|
|
480
|
+
request.onError = (error, context$1) => {
|
|
481
|
+
const result = localErrorHandler(error, context$1);
|
|
482
|
+
if (result === void 0) return onErrorParent(error, context$1);
|
|
483
|
+
return result;
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (it.error) try {
|
|
487
|
+
let element$1 = await request.onError(it.error, request);
|
|
488
|
+
if (element$1 === void 0) throw it.error;
|
|
489
|
+
if (element$1 instanceof Redirection) return this.createRedirectionLayer(element$1.page, {
|
|
490
|
+
pathname,
|
|
491
|
+
search
|
|
492
|
+
});
|
|
456
493
|
if (element$1 === null) element$1 = this.renderError(it.error);
|
|
457
494
|
layers.push({
|
|
458
495
|
props,
|
|
@@ -466,6 +503,12 @@ var PageDescriptorProvider = class {
|
|
|
466
503
|
route: it.route
|
|
467
504
|
});
|
|
468
505
|
break;
|
|
506
|
+
} catch (e) {
|
|
507
|
+
if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
|
|
508
|
+
pathname,
|
|
509
|
+
search
|
|
510
|
+
});
|
|
511
|
+
throw e;
|
|
469
512
|
}
|
|
470
513
|
const element = await this.createElement(it.route, {
|
|
471
514
|
...props,
|
|
@@ -489,6 +532,14 @@ var PageDescriptorProvider = class {
|
|
|
489
532
|
search
|
|
490
533
|
};
|
|
491
534
|
}
|
|
535
|
+
createRedirectionLayer(href, context) {
|
|
536
|
+
return {
|
|
537
|
+
layers: [],
|
|
538
|
+
redirect: typeof href === "string" ? href : this.href(href),
|
|
539
|
+
pathname: context.pathname,
|
|
540
|
+
search: context.search
|
|
541
|
+
};
|
|
542
|
+
}
|
|
492
543
|
getErrorHandler(route) {
|
|
493
544
|
if (route.errorHandler) return route.errorHandler;
|
|
494
545
|
let parent = route.parent;
|
|
@@ -544,6 +595,7 @@ var PageDescriptorProvider = class {
|
|
|
544
595
|
let hasNotFoundHandler = false;
|
|
545
596
|
const pages = this.alepha.descriptors($page);
|
|
546
597
|
const hasParent = (it) => {
|
|
598
|
+
if (it.options.parent) return true;
|
|
547
599
|
for (const page of pages) {
|
|
548
600
|
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
549
601
|
if (children.includes(it)) return true;
|
|
@@ -559,7 +611,7 @@ var PageDescriptorProvider = class {
|
|
|
559
611
|
name: "notFound",
|
|
560
612
|
cache: true,
|
|
561
613
|
component: NotFoundPage,
|
|
562
|
-
|
|
614
|
+
onServerResponse: ({ reply }) => {
|
|
563
615
|
reply.status = 404;
|
|
564
616
|
}
|
|
565
617
|
});
|
|
@@ -567,6 +619,12 @@ var PageDescriptorProvider = class {
|
|
|
567
619
|
});
|
|
568
620
|
map(pages, target) {
|
|
569
621
|
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
622
|
+
const getChildrenFromParent = (it) => {
|
|
623
|
+
const children$1 = [];
|
|
624
|
+
for (const page of pages) if (page.options.parent === it) children$1.push(page);
|
|
625
|
+
return children$1;
|
|
626
|
+
};
|
|
627
|
+
children.push(...getChildrenFromParent(target));
|
|
570
628
|
return {
|
|
571
629
|
...target.options,
|
|
572
630
|
name: target.name,
|
|
@@ -688,6 +746,10 @@ var BrowserRouterProvider = class extends RouterProvider {
|
|
|
688
746
|
options.state.pathname = state.pathname;
|
|
689
747
|
options.state.search = state.search;
|
|
690
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?.();
|
|
752
|
+
}
|
|
691
753
|
await this.alepha.emit("react:transition:end", {
|
|
692
754
|
state: options.state,
|
|
693
755
|
context
|
|
@@ -762,15 +824,11 @@ var ReactBrowserProvider = class {
|
|
|
762
824
|
}
|
|
763
825
|
async go(url, options = {}) {
|
|
764
826
|
const result = await this.render({ url });
|
|
765
|
-
if (result.context.url.pathname !== url) {
|
|
766
|
-
this.pushState(result.context.url.pathname);
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
if (options.replace) {
|
|
770
|
-
this.pushState(url);
|
|
827
|
+
if (result.context.url.pathname + result.context.url.search !== url) {
|
|
828
|
+
this.pushState(result.context.url.pathname + result.context.url.search);
|
|
771
829
|
return;
|
|
772
830
|
}
|
|
773
|
-
this.pushState(url);
|
|
831
|
+
this.pushState(url, options.replace);
|
|
774
832
|
}
|
|
775
833
|
async render(options = {}) {
|
|
776
834
|
const previous = options.previous ?? this.state.layers;
|
|
@@ -799,7 +857,13 @@ var ReactBrowserProvider = class {
|
|
|
799
857
|
handler: async () => {
|
|
800
858
|
const hydration = this.getHydrationState();
|
|
801
859
|
const previous = hydration?.layers ?? [];
|
|
802
|
-
if (hydration
|
|
860
|
+
if (hydration) {
|
|
861
|
+
for (const [key, value] of Object.entries(hydration)) if (key !== "layers" && key !== "links") this.alepha.state(key, value);
|
|
862
|
+
}
|
|
863
|
+
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink({
|
|
864
|
+
...link,
|
|
865
|
+
prefix: hydration.links.prefix
|
|
866
|
+
});
|
|
803
867
|
const { context } = await this.render({ previous });
|
|
804
868
|
await this.alepha.emit("react:browser:render", {
|
|
805
869
|
state: this.state,
|
|
@@ -857,13 +921,23 @@ var ReactBrowserRenderer = class {
|
|
|
857
921
|
//#endregion
|
|
858
922
|
//#region src/hooks/RouterHookApi.ts
|
|
859
923
|
var RouterHookApi = class {
|
|
860
|
-
constructor(pages, context, state, layer, browser) {
|
|
924
|
+
constructor(pages, context, state, layer, pageApi, browser) {
|
|
861
925
|
this.pages = pages;
|
|
862
926
|
this.context = context;
|
|
863
927
|
this.state = state;
|
|
864
928
|
this.layer = layer;
|
|
929
|
+
this.pageApi = pageApi;
|
|
865
930
|
this.browser = browser;
|
|
866
931
|
}
|
|
932
|
+
path(name, config = {}) {
|
|
933
|
+
return this.pageApi.pathname(name, {
|
|
934
|
+
params: {
|
|
935
|
+
...this.context.params,
|
|
936
|
+
...config.params
|
|
937
|
+
},
|
|
938
|
+
query: config.query
|
|
939
|
+
});
|
|
940
|
+
}
|
|
867
941
|
getURL() {
|
|
868
942
|
if (!this.browser) return this.context.url;
|
|
869
943
|
return new URL(this.location.href);
|
|
@@ -905,23 +979,23 @@ var RouterHookApi = class {
|
|
|
905
979
|
}
|
|
906
980
|
async go(path, options) {
|
|
907
981
|
for (const page of this.pages) if (page.name === path) {
|
|
908
|
-
|
|
909
|
-
|
|
982
|
+
await this.browser?.go(this.path(path, options), options);
|
|
983
|
+
return;
|
|
910
984
|
}
|
|
911
|
-
await this.browser?.go(
|
|
985
|
+
await this.browser?.go(path, options);
|
|
912
986
|
}
|
|
913
987
|
anchor(path, options = {}) {
|
|
988
|
+
let href = path;
|
|
914
989
|
for (const page of this.pages) if (page.name === path) {
|
|
915
|
-
|
|
990
|
+
href = this.path(path, options);
|
|
916
991
|
break;
|
|
917
992
|
}
|
|
918
|
-
const href = this.createHref(path, this.layer, options);
|
|
919
993
|
return {
|
|
920
994
|
href,
|
|
921
995
|
onClick: (ev) => {
|
|
922
996
|
ev.stopPropagation();
|
|
923
997
|
ev.preventDefault();
|
|
924
|
-
this.go(
|
|
998
|
+
this.go(href, options).catch(console.error);
|
|
925
999
|
}
|
|
926
1000
|
};
|
|
927
1001
|
}
|
|
@@ -943,33 +1017,25 @@ var RouterHookApi = class {
|
|
|
943
1017
|
//#endregion
|
|
944
1018
|
//#region src/hooks/useRouter.ts
|
|
945
1019
|
const useRouter = () => {
|
|
1020
|
+
const alepha = useAlepha();
|
|
946
1021
|
const ctx = useContext(RouterContext);
|
|
947
1022
|
const layer = useContext(RouterLayerContext);
|
|
948
1023
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
949
1024
|
const pages = useMemo(() => {
|
|
950
|
-
return
|
|
1025
|
+
return alepha.inject(PageDescriptorProvider).getPages();
|
|
951
1026
|
}, []);
|
|
952
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer,
|
|
1027
|
+
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.inject(PageDescriptorProvider), alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
953
1028
|
};
|
|
954
1029
|
|
|
955
1030
|
//#endregion
|
|
956
1031
|
//#region src/components/Link.tsx
|
|
957
1032
|
const Link = (props) => {
|
|
958
|
-
React.useContext(RouterContext);
|
|
959
1033
|
const router = useRouter();
|
|
960
|
-
const
|
|
961
|
-
if (!to) return null;
|
|
962
|
-
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
963
|
-
if (can && !can()) return null;
|
|
964
|
-
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
965
|
-
const anchorProps = {
|
|
966
|
-
...props,
|
|
967
|
-
to: void 0
|
|
968
|
-
};
|
|
1034
|
+
const { to,...anchorProps } = props;
|
|
969
1035
|
return /* @__PURE__ */ jsx("a", {
|
|
970
1036
|
...router.anchor(to),
|
|
971
1037
|
...anchorProps,
|
|
972
|
-
children: props.children
|
|
1038
|
+
children: props.children
|
|
973
1039
|
});
|
|
974
1040
|
};
|
|
975
1041
|
var Link_default = Link;
|
|
@@ -981,22 +1047,21 @@ const useActive = (path) => {
|
|
|
981
1047
|
const ctx = useContext(RouterContext);
|
|
982
1048
|
const layer = useContext(RouterLayerContext);
|
|
983
1049
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
984
|
-
let name;
|
|
985
|
-
if (typeof path === "object" && path.options.name) name = path.options.name;
|
|
986
1050
|
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
987
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
1051
|
+
const href = useMemo(() => router.createHref(path ?? "", layer), [path, layer]);
|
|
988
1052
|
const [isPending, setPending] = useState(false);
|
|
989
|
-
const isActive = current === href;
|
|
990
|
-
useRouterEvents({ onEnd: ({ state }) =>
|
|
1053
|
+
const isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1054
|
+
useRouterEvents({ onEnd: ({ state }) => {
|
|
1055
|
+
path && setCurrent(state.pathname);
|
|
1056
|
+
} }, [path]);
|
|
991
1057
|
return {
|
|
992
|
-
name,
|
|
993
1058
|
isPending,
|
|
994
1059
|
isActive,
|
|
995
1060
|
anchorProps: {
|
|
996
1061
|
href,
|
|
997
1062
|
onClick: (ev) => {
|
|
998
|
-
ev
|
|
999
|
-
ev
|
|
1063
|
+
ev?.stopPropagation();
|
|
1064
|
+
ev?.preventDefault();
|
|
1000
1065
|
if (isActive) return;
|
|
1001
1066
|
if (isPending) return;
|
|
1002
1067
|
setPending(true);
|
|
@@ -1009,45 +1074,62 @@ const useActive = (path) => {
|
|
|
1009
1074
|
};
|
|
1010
1075
|
|
|
1011
1076
|
//#endregion
|
|
1012
|
-
//#region src/hooks/
|
|
1013
|
-
const
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
return routerContext.alepha;
|
|
1077
|
+
//#region src/hooks/useInject.ts
|
|
1078
|
+
const useInject = (service) => {
|
|
1079
|
+
const alepha = useAlepha();
|
|
1080
|
+
return useMemo(() => alepha.inject(service), []);
|
|
1017
1081
|
};
|
|
1018
1082
|
|
|
1019
1083
|
//#endregion
|
|
1020
|
-
//#region src/hooks/
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1084
|
+
//#region src/hooks/useStore.ts
|
|
1085
|
+
/**
|
|
1086
|
+
* Hook to access and mutate the Alepha state.
|
|
1087
|
+
*/
|
|
1088
|
+
const useStore = (key, defaultValue) => {
|
|
1089
|
+
const alepha = useAlepha();
|
|
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
|
+
}];
|
|
1025
1107
|
};
|
|
1026
1108
|
|
|
1027
1109
|
//#endregion
|
|
1028
1110
|
//#region src/hooks/useClient.ts
|
|
1029
1111
|
const useClient = (_scope) => {
|
|
1112
|
+
useStore("user");
|
|
1030
1113
|
return useInject(LinkProvider).client();
|
|
1031
1114
|
};
|
|
1032
1115
|
|
|
1033
1116
|
//#endregion
|
|
1034
1117
|
//#region src/hooks/useQueryParams.ts
|
|
1035
1118
|
const useQueryParams = (schema, options = {}) => {
|
|
1036
|
-
const
|
|
1037
|
-
if (!ctx) throw new Error("useQueryParams must be used within a RouterProvider");
|
|
1119
|
+
const alepha = useAlepha();
|
|
1038
1120
|
const key = options.key ?? "q";
|
|
1039
1121
|
const router = useRouter();
|
|
1040
1122
|
const querystring = router.query[key];
|
|
1041
|
-
const [queryParams, setQueryParams] = useState(decode(
|
|
1123
|
+
const [queryParams, setQueryParams] = useState(decode(alepha, schema, router.query[key]));
|
|
1042
1124
|
useEffect(() => {
|
|
1043
|
-
setQueryParams(decode(
|
|
1125
|
+
setQueryParams(decode(alepha, schema, querystring));
|
|
1044
1126
|
}, [querystring]);
|
|
1045
1127
|
return [queryParams, (queryParams$1) => {
|
|
1046
1128
|
setQueryParams(queryParams$1);
|
|
1047
1129
|
router.setQueryParams((data) => {
|
|
1048
1130
|
return {
|
|
1049
1131
|
...data,
|
|
1050
|
-
[key]: encode(
|
|
1132
|
+
[key]: encode(alepha, schema, queryParams$1)
|
|
1051
1133
|
};
|
|
1052
1134
|
});
|
|
1053
1135
|
}];
|
|
@@ -1066,14 +1148,50 @@ const decode = (alepha, schema, data) => {
|
|
|
1066
1148
|
//#endregion
|
|
1067
1149
|
//#region src/hooks/useRouterState.ts
|
|
1068
1150
|
const useRouterState = () => {
|
|
1069
|
-
const
|
|
1151
|
+
const router = useContext(RouterContext);
|
|
1070
1152
|
const layer = useContext(RouterLayerContext);
|
|
1071
|
-
if (!
|
|
1072
|
-
const [state, setState] = useState(
|
|
1153
|
+
if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
|
|
1154
|
+
const [state, setState] = useState(router.state);
|
|
1073
1155
|
useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
|
|
1074
1156
|
return state;
|
|
1075
1157
|
};
|
|
1076
1158
|
|
|
1159
|
+
//#endregion
|
|
1160
|
+
//#region src/hooks/useSchema.ts
|
|
1161
|
+
const useSchema = (action) => {
|
|
1162
|
+
const name = action.name;
|
|
1163
|
+
const alepha = useAlepha();
|
|
1164
|
+
const httpClient = useInject(HttpClient);
|
|
1165
|
+
const linkProvider = useInject(LinkProvider);
|
|
1166
|
+
const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
|
|
1167
|
+
useEffect(() => {
|
|
1168
|
+
if (!schema.loading) return;
|
|
1169
|
+
const opts = { cache: true };
|
|
1170
|
+
httpClient.fetch(`${linkProvider.URL_LINKS}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
|
|
1171
|
+
}, [name]);
|
|
1172
|
+
return schema;
|
|
1173
|
+
};
|
|
1174
|
+
/**
|
|
1175
|
+
* Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
|
|
1176
|
+
*/
|
|
1177
|
+
const ssrSchemaLoading = (alepha, name) => {
|
|
1178
|
+
if (!alepha.isBrowser()) {
|
|
1179
|
+
const links = alepha.context.get("links")?.links ?? [];
|
|
1180
|
+
const can = links.find((it) => it.name === name);
|
|
1181
|
+
if (can) {
|
|
1182
|
+
const schema$1 = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
|
|
1183
|
+
if (schema$1) {
|
|
1184
|
+
can.schema = schema$1;
|
|
1185
|
+
return schema$1;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return { loading: true };
|
|
1189
|
+
}
|
|
1190
|
+
const schema = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
|
|
1191
|
+
if (schema) return schema;
|
|
1192
|
+
return { loading: true };
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1077
1195
|
//#endregion
|
|
1078
1196
|
//#region src/index.browser.ts
|
|
1079
1197
|
const AlephaReact = $module({
|
|
@@ -1089,5 +1207,5 @@ const AlephaReact = $module({
|
|
|
1089
1207
|
});
|
|
1090
1208
|
|
|
1091
1209
|
//#endregion
|
|
1092
|
-
export { $page, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider,
|
|
1210
|
+
export { $page, AlephaContext, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, Redirection, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
|
|
1093
1211
|
//# sourceMappingURL=index.browser.js.map
|