@alepha/react 0.14.0 → 0.14.2
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 +1 -1
- package/dist/auth/index.browser.js +1488 -4
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +1827 -4
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +54 -937
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +132 -2010
- package/dist/core/index.js.map +1 -1
- package/dist/form/index.d.ts.map +1 -1
- package/dist/form/index.js +6 -1
- package/dist/form/index.js.map +1 -1
- package/dist/head/index.browser.js +191 -17
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.d.ts +652 -31
- package/dist/head/index.d.ts.map +1 -1
- package/dist/head/index.js +209 -18
- package/dist/head/index.js.map +1 -1
- package/dist/{core → router}/index.browser.js +126 -516
- package/dist/router/index.browser.js.map +1 -0
- package/dist/router/index.d.ts +1334 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +1939 -0
- package/dist/router/index.js.map +1 -0
- package/package.json +12 -6
- package/src/auth/index.ts +1 -1
- package/src/auth/services/ReactAuth.ts +1 -1
- package/src/core/components/ClientOnly.tsx +14 -0
- package/src/core/components/ErrorBoundary.tsx +3 -2
- package/src/core/contexts/AlephaContext.ts +3 -0
- package/src/core/contexts/AlephaProvider.tsx +2 -1
- package/src/core/index.ts +13 -102
- package/src/form/services/FormModel.ts +5 -0
- package/src/head/helpers/SeoExpander.ts +141 -0
- package/src/head/index.browser.ts +1 -0
- package/src/head/index.ts +17 -7
- package/src/head/interfaces/Head.ts +69 -27
- package/src/head/providers/BrowserHeadProvider.ts +45 -12
- package/src/head/providers/HeadProvider.ts +32 -8
- package/src/head/providers/ServerHeadProvider.ts +34 -2
- package/src/{core → router}/components/ErrorViewer.tsx +2 -0
- package/src/router/components/Link.tsx +21 -0
- package/src/{core → router}/components/NestedView.tsx +3 -5
- package/src/router/components/NotFound.tsx +30 -0
- package/src/router/errors/Redirection.ts +28 -0
- package/src/{core → router}/hooks/useActive.ts +6 -2
- package/src/{core → router}/hooks/useQueryParams.ts +2 -2
- package/src/{core → router}/hooks/useRouter.ts +1 -1
- package/src/{core → router}/hooks/useRouterState.ts +1 -1
- package/src/{core → router}/index.browser.ts +14 -12
- package/src/{core/index.shared-router.ts → router/index.shared.ts} +6 -3
- package/src/router/index.ts +125 -0
- package/src/{core → router}/primitives/$page.ts +1 -1
- package/src/{core → router}/providers/ReactBrowserProvider.ts +3 -13
- package/src/{core → router}/providers/ReactBrowserRendererProvider.ts +3 -0
- package/src/{core → router}/providers/ReactBrowserRouterProvider.ts +3 -0
- package/src/{core → router}/providers/ReactPageProvider.ts +5 -3
- package/src/{core → router}/providers/ReactServerProvider.ts +9 -28
- package/src/{core → router}/services/ReactPageServerService.ts +3 -0
- package/src/{core → router}/services/ReactPageService.ts +5 -5
- package/src/{core → router}/services/ReactRouter.ts +26 -5
- package/dist/core/index.browser.js.map +0 -1
- package/dist/core/index.native.js +0 -403
- package/dist/core/index.native.js.map +0 -1
- package/src/core/components/Link.tsx +0 -18
- package/src/core/components/NotFound.tsx +0 -27
- package/src/core/errors/Redirection.ts +0 -13
- package/src/core/hooks/useSchema.ts +0 -88
- package/src/core/index.native.ts +0 -21
- package/src/core/index.shared.ts +0 -9
- /package/src/{core → router}/contexts/RouterLayerContext.ts +0 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError,
|
|
1
|
+
import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
|
|
2
2
|
import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
|
|
3
|
-
import { AlephaServer, HttpClient } from "alepha/server";
|
|
4
|
-
import { AlephaServerLinks, LinkProvider } from "alepha/server/links";
|
|
5
3
|
import { $logger } from "alepha/logger";
|
|
4
|
+
import { AlephaServerLinks, LinkProvider } from "alepha/server/links";
|
|
6
5
|
import { RouterProvider } from "alepha/router";
|
|
7
|
-
import
|
|
6
|
+
import { StrictMode, createContext, createElement, memo, use, useEffect, useRef, useState } from "react";
|
|
8
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { AlephaContext, AlephaReact, ClientOnly, ErrorBoundary, useAlepha, useEvents, useInject, useStore } from "@alepha/react";
|
|
9
9
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
10
|
+
import { AlephaServer } from "alepha/server";
|
|
10
11
|
|
|
11
|
-
//#region ../../src/
|
|
12
|
+
//#region ../../src/router/services/ReactPageService.ts
|
|
13
|
+
/**
|
|
14
|
+
* $page methods interface.
|
|
15
|
+
*/
|
|
12
16
|
var ReactPageService = class {
|
|
13
17
|
fetch(pathname, options = {}) {
|
|
14
18
|
throw new AlephaError("Fetch is not available for this environment.");
|
|
@@ -19,7 +23,7 @@ var ReactPageService = class {
|
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
//#endregion
|
|
22
|
-
//#region ../../src/
|
|
26
|
+
//#region ../../src/router/primitives/$page.ts
|
|
23
27
|
/**
|
|
24
28
|
* Main primitive for defining a React route in the application.
|
|
25
29
|
*
|
|
@@ -144,62 +148,44 @@ var PagePrimitive = class extends Primitive {
|
|
|
144
148
|
$page[KIND] = PagePrimitive;
|
|
145
149
|
|
|
146
150
|
//#endregion
|
|
147
|
-
//#region ../../src/
|
|
148
|
-
function NotFoundPage(props) {
|
|
149
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
150
|
-
style: {
|
|
151
|
-
width: "100%",
|
|
152
|
-
minHeight: "90vh",
|
|
153
|
-
boxSizing: "border-box",
|
|
154
|
-
display: "flex",
|
|
155
|
-
flexDirection: "column",
|
|
156
|
-
justifyContent: "center",
|
|
157
|
-
alignItems: "center",
|
|
158
|
-
textAlign: "center",
|
|
159
|
-
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
|
|
160
|
-
padding: "2rem",
|
|
161
|
-
...props.style
|
|
162
|
-
},
|
|
163
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
164
|
-
style: {
|
|
165
|
-
fontSize: "6rem",
|
|
166
|
-
fontWeight: 200,
|
|
167
|
-
lineHeight: 1
|
|
168
|
-
},
|
|
169
|
-
children: "404"
|
|
170
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
171
|
-
style: {
|
|
172
|
-
fontSize: "0.875rem",
|
|
173
|
-
marginTop: "1rem",
|
|
174
|
-
opacity: .6
|
|
175
|
-
},
|
|
176
|
-
children: "Page not found"
|
|
177
|
-
})]
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
//#endregion
|
|
182
|
-
//#region ../../src/core/components/ClientOnly.tsx
|
|
151
|
+
//#region ../../src/router/components/NotFound.tsx
|
|
183
152
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* Optionally, you can provide a fallback React node that will be rendered.
|
|
187
|
-
*
|
|
188
|
-
* You should use this component when
|
|
189
|
-
* - you have code that relies on browser-specific APIs
|
|
190
|
-
* - you want to avoid server-side rendering for a specific part of your application
|
|
191
|
-
* - you want to prevent pre-rendering of a component
|
|
153
|
+
* Default 404 Not Found page component.
|
|
192
154
|
*/
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
155
|
+
const NotFound = (props) => /* @__PURE__ */ jsxs("div", {
|
|
156
|
+
style: {
|
|
157
|
+
width: "100%",
|
|
158
|
+
minHeight: "90vh",
|
|
159
|
+
boxSizing: "border-box",
|
|
160
|
+
display: "flex",
|
|
161
|
+
flexDirection: "column",
|
|
162
|
+
justifyContent: "center",
|
|
163
|
+
alignItems: "center",
|
|
164
|
+
textAlign: "center",
|
|
165
|
+
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
|
|
166
|
+
padding: "2rem",
|
|
167
|
+
...props.style
|
|
168
|
+
},
|
|
169
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
170
|
+
style: {
|
|
171
|
+
fontSize: "6rem",
|
|
172
|
+
fontWeight: 200,
|
|
173
|
+
lineHeight: 1
|
|
174
|
+
},
|
|
175
|
+
children: "404"
|
|
176
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
177
|
+
style: {
|
|
178
|
+
fontSize: "0.875rem",
|
|
179
|
+
marginTop: "1rem",
|
|
180
|
+
opacity: .6
|
|
181
|
+
},
|
|
182
|
+
children: "Page not found"
|
|
183
|
+
})]
|
|
184
|
+
});
|
|
185
|
+
var NotFound_default = NotFound;
|
|
200
186
|
|
|
201
187
|
//#endregion
|
|
202
|
-
//#region ../../src/
|
|
188
|
+
//#region ../../src/router/components/ErrorViewer.tsx
|
|
203
189
|
/**
|
|
204
190
|
* Error viewer component that displays error details in development mode
|
|
205
191
|
*/
|
|
@@ -585,17 +571,30 @@ const styles = {
|
|
|
585
571
|
};
|
|
586
572
|
|
|
587
573
|
//#endregion
|
|
588
|
-
//#region ../../src/
|
|
574
|
+
//#region ../../src/router/contexts/RouterLayerContext.ts
|
|
589
575
|
const RouterLayerContext = createContext(void 0);
|
|
590
576
|
|
|
591
577
|
//#endregion
|
|
592
|
-
//#region ../../src/
|
|
578
|
+
//#region ../../src/router/errors/Redirection.ts
|
|
593
579
|
/**
|
|
594
580
|
* Used for Redirection during the page loading.
|
|
595
581
|
*
|
|
596
582
|
* Depends on the context, it can be thrown or just returned.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* import { Redirection } from "@alepha/react";
|
|
587
|
+
*
|
|
588
|
+
* const MyPage = $page({
|
|
589
|
+
* resolve: async () => {
|
|
590
|
+
* if (needRedirect) {
|
|
591
|
+
* throw new Redirection("/new-path");
|
|
592
|
+
* }
|
|
593
|
+
* },
|
|
594
|
+
* });
|
|
595
|
+
* ```
|
|
597
596
|
*/
|
|
598
|
-
var Redirection = class extends
|
|
597
|
+
var Redirection = class extends AlephaError {
|
|
599
598
|
redirect;
|
|
600
599
|
constructor(redirect) {
|
|
601
600
|
super("Redirection");
|
|
@@ -604,88 +603,7 @@ var Redirection = class extends Error {
|
|
|
604
603
|
};
|
|
605
604
|
|
|
606
605
|
//#endregion
|
|
607
|
-
//#region ../../src/
|
|
608
|
-
const AlephaContext = createContext(void 0);
|
|
609
|
-
|
|
610
|
-
//#endregion
|
|
611
|
-
//#region ../../src/core/hooks/useAlepha.ts
|
|
612
|
-
/**
|
|
613
|
-
* Main Alepha hook.
|
|
614
|
-
*
|
|
615
|
-
* It provides access to the Alepha instance within a React component.
|
|
616
|
-
*
|
|
617
|
-
* With Alepha, you can access the core functionalities of the framework:
|
|
618
|
-
*
|
|
619
|
-
* - alepha.state() for state management
|
|
620
|
-
* - alepha.inject() for dependency injection
|
|
621
|
-
* - alepha.events.emit() for event handling
|
|
622
|
-
* etc...
|
|
623
|
-
*/
|
|
624
|
-
const useAlepha = () => {
|
|
625
|
-
const alepha = useContext(AlephaContext);
|
|
626
|
-
if (!alepha) throw new AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
|
|
627
|
-
return alepha;
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
//#endregion
|
|
631
|
-
//#region ../../src/core/hooks/useEvents.ts
|
|
632
|
-
/**
|
|
633
|
-
* Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.
|
|
634
|
-
*
|
|
635
|
-
* useEvents is fully typed to ensure correct event callback signatures.
|
|
636
|
-
*
|
|
637
|
-
* @example
|
|
638
|
-
* ```tsx
|
|
639
|
-
* useEvents(
|
|
640
|
-
* {
|
|
641
|
-
* "react:transition:begin": (ev) => {
|
|
642
|
-
* console.log("Transition began to:", ev.to);
|
|
643
|
-
* },
|
|
644
|
-
* "react:transition:error": {
|
|
645
|
-
* priority: "first",
|
|
646
|
-
* callback: (ev) => {
|
|
647
|
-
* console.error("Transition error:", ev.error);
|
|
648
|
-
* },
|
|
649
|
-
* },
|
|
650
|
-
* },
|
|
651
|
-
* [],
|
|
652
|
-
* );
|
|
653
|
-
* ```
|
|
654
|
-
*/
|
|
655
|
-
const useEvents = (opts, deps) => {
|
|
656
|
-
const alepha = useAlepha();
|
|
657
|
-
useEffect(() => {
|
|
658
|
-
if (!alepha.isBrowser()) return;
|
|
659
|
-
const subs = [];
|
|
660
|
-
for (const [name, hook] of Object.entries(opts)) subs.push(alepha.events.on(name, hook));
|
|
661
|
-
return () => {
|
|
662
|
-
for (const clear of subs) clear();
|
|
663
|
-
};
|
|
664
|
-
}, deps);
|
|
665
|
-
};
|
|
666
|
-
|
|
667
|
-
//#endregion
|
|
668
|
-
//#region ../../src/core/hooks/useStore.ts
|
|
669
|
-
function useStore(target, defaultValue) {
|
|
670
|
-
const alepha = useAlepha();
|
|
671
|
-
useMemo(() => {
|
|
672
|
-
if (defaultValue != null && alepha.store.get(target) == null) alepha.store.set(target, defaultValue);
|
|
673
|
-
}, [defaultValue]);
|
|
674
|
-
const [state, setState] = useState(alepha.store.get(target));
|
|
675
|
-
useEffect(() => {
|
|
676
|
-
if (!alepha.isBrowser()) return;
|
|
677
|
-
const key = target instanceof Atom ? target.key : target;
|
|
678
|
-
return alepha.events.on("state:mutate", (ev) => {
|
|
679
|
-
if (ev.key === key) setState(ev.value);
|
|
680
|
-
});
|
|
681
|
-
}, []);
|
|
682
|
-
return [state, (value) => {
|
|
683
|
-
alepha.store.set(target, value);
|
|
684
|
-
}];
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
//#endregion
|
|
688
|
-
//#region ../../src/core/hooks/useRouterState.ts
|
|
606
|
+
//#region ../../src/router/hooks/useRouterState.ts
|
|
689
607
|
const useRouterState = () => {
|
|
690
608
|
const [state] = useStore("alepha.react.router.state");
|
|
691
609
|
if (!state) throw new AlephaError("Missing react router state");
|
|
@@ -693,38 +611,7 @@ const useRouterState = () => {
|
|
|
693
611
|
};
|
|
694
612
|
|
|
695
613
|
//#endregion
|
|
696
|
-
//#region ../../src/
|
|
697
|
-
/**
|
|
698
|
-
* A reusable error boundary for catching rendering errors
|
|
699
|
-
* in any part of the React component tree.
|
|
700
|
-
*/
|
|
701
|
-
var ErrorBoundary = class extends React.Component {
|
|
702
|
-
constructor(props) {
|
|
703
|
-
super(props);
|
|
704
|
-
this.state = {};
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* Update state so the next render shows the fallback UI.
|
|
708
|
-
*/
|
|
709
|
-
static getDerivedStateFromError(error) {
|
|
710
|
-
return { error };
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Lifecycle method called when an error is caught.
|
|
714
|
-
* You can log the error or perform side effects here.
|
|
715
|
-
*/
|
|
716
|
-
componentDidCatch(error, info) {
|
|
717
|
-
if (this.props.onError) this.props.onError(error, info);
|
|
718
|
-
}
|
|
719
|
-
render() {
|
|
720
|
-
if (this.state.error) return this.props.fallback(this.state.error);
|
|
721
|
-
return this.props.children;
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
|
-
var ErrorBoundary_default = ErrorBoundary;
|
|
725
|
-
|
|
726
|
-
//#endregion
|
|
727
|
-
//#region ../../src/core/components/NestedView.tsx
|
|
614
|
+
//#region ../../src/router/components/NestedView.tsx
|
|
728
615
|
/**
|
|
729
616
|
* A component that renders the current view of the nested router layer.
|
|
730
617
|
*
|
|
@@ -809,7 +696,7 @@ const NestedView = (props) => {
|
|
|
809
696
|
})
|
|
810
697
|
});
|
|
811
698
|
if (props.errorBoundary === false) return /* @__PURE__ */ jsx(Fragment, { children: element });
|
|
812
|
-
if (props.errorBoundary) return /* @__PURE__ */ jsx(
|
|
699
|
+
if (props.errorBoundary) return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
813
700
|
fallback: props.errorBoundary,
|
|
814
701
|
children: element
|
|
815
702
|
});
|
|
@@ -821,7 +708,7 @@ const NestedView = (props) => {
|
|
|
821
708
|
if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
|
|
822
709
|
return result;
|
|
823
710
|
};
|
|
824
|
-
return /* @__PURE__ */ jsx(
|
|
711
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
825
712
|
fallback,
|
|
826
713
|
children: element
|
|
827
714
|
});
|
|
@@ -854,8 +741,11 @@ function parseAnimation(animationLike, state, type = "enter") {
|
|
|
854
741
|
}
|
|
855
742
|
|
|
856
743
|
//#endregion
|
|
857
|
-
//#region ../../src/
|
|
744
|
+
//#region ../../src/router/providers/ReactPageProvider.ts
|
|
858
745
|
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
746
|
+
/**
|
|
747
|
+
* Handle page routes for React applications. (Browser and Server)
|
|
748
|
+
*/
|
|
859
749
|
var ReactPageProvider = class {
|
|
860
750
|
log = $logger();
|
|
861
751
|
env = $env(envSchema$1);
|
|
@@ -1097,7 +987,7 @@ var ReactPageProvider = class {
|
|
|
1097
987
|
}
|
|
1098
988
|
renderView(index, path, view, page) {
|
|
1099
989
|
view ??= this.renderEmptyView();
|
|
1100
|
-
const element = page.client ? createElement(
|
|
990
|
+
const element = page.client ? createElement(ClientOnly, typeof page.client === "object" ? page.client : {}, view) : view;
|
|
1101
991
|
return createElement(RouterLayerContext.Provider, { value: {
|
|
1102
992
|
index,
|
|
1103
993
|
path,
|
|
@@ -1122,7 +1012,7 @@ var ReactPageProvider = class {
|
|
|
1122
1012
|
path: "/*",
|
|
1123
1013
|
name: "notFound",
|
|
1124
1014
|
cache: true,
|
|
1125
|
-
component:
|
|
1015
|
+
component: NotFound_default,
|
|
1126
1016
|
onServerResponse: ({ reply }) => {
|
|
1127
1017
|
reply.status = 404;
|
|
1128
1018
|
}
|
|
@@ -1177,7 +1067,10 @@ const isPageRoute = (it) => {
|
|
|
1177
1067
|
};
|
|
1178
1068
|
|
|
1179
1069
|
//#endregion
|
|
1180
|
-
//#region ../../src/
|
|
1070
|
+
//#region ../../src/router/providers/ReactBrowserRouterProvider.ts
|
|
1071
|
+
/**
|
|
1072
|
+
* Implementation of AlephaRouter for React in browser environment.
|
|
1073
|
+
*/
|
|
1181
1074
|
var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
1182
1075
|
log = $logger();
|
|
1183
1076
|
alepha = $inject(Alepha);
|
|
@@ -1222,7 +1115,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
1222
1115
|
}
|
|
1223
1116
|
if (state.layers.length === 0) state.layers.push({
|
|
1224
1117
|
name: "not-found",
|
|
1225
|
-
element: createElement(
|
|
1118
|
+
element: createElement(NotFound_default),
|
|
1226
1119
|
index: 0,
|
|
1227
1120
|
path: "/"
|
|
1228
1121
|
});
|
|
@@ -1259,7 +1152,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
1259
1152
|
};
|
|
1260
1153
|
|
|
1261
1154
|
//#endregion
|
|
1262
|
-
//#region ../../src/
|
|
1155
|
+
//#region ../../src/router/providers/ReactBrowserProvider.ts
|
|
1263
1156
|
const envSchema = t.object({ REACT_ROOT_ID: t.text({ default: "root" }) });
|
|
1264
1157
|
/**
|
|
1265
1158
|
* React browser renderer configuration atom
|
|
@@ -1424,27 +1317,12 @@ var ReactBrowserProvider = class {
|
|
|
1424
1317
|
};
|
|
1425
1318
|
|
|
1426
1319
|
//#endregion
|
|
1427
|
-
//#region ../../src/
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
handler: async ({ hydration, root, element }) => {
|
|
1434
|
-
if (hydration?.layers) {
|
|
1435
|
-
this.root = hydrateRoot(root, element);
|
|
1436
|
-
this.log.info("Hydrated root element");
|
|
1437
|
-
} else {
|
|
1438
|
-
this.root ??= createRoot(root);
|
|
1439
|
-
this.root.render(element);
|
|
1440
|
-
this.log.info("Created root element");
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
});
|
|
1444
|
-
};
|
|
1445
|
-
|
|
1446
|
-
//#endregion
|
|
1447
|
-
//#region ../../src/core/services/ReactRouter.ts
|
|
1320
|
+
//#region ../../src/router/services/ReactRouter.ts
|
|
1321
|
+
/**
|
|
1322
|
+
* Friendly browser router API.
|
|
1323
|
+
*
|
|
1324
|
+
* Can be safely used server-side, but most methods will be no-op.
|
|
1325
|
+
*/
|
|
1448
1326
|
var ReactRouter = class {
|
|
1449
1327
|
alepha = $inject(Alepha);
|
|
1450
1328
|
pageApi = $inject(ReactPageProvider);
|
|
@@ -1468,6 +1346,11 @@ var ReactRouter = class {
|
|
|
1468
1346
|
}
|
|
1469
1347
|
node(name, config = {}) {
|
|
1470
1348
|
const page = this.pageApi.page(name);
|
|
1349
|
+
if (!page.lazy && !page.component) return {
|
|
1350
|
+
...page,
|
|
1351
|
+
label: page.label ?? page.name,
|
|
1352
|
+
children: void 0
|
|
1353
|
+
};
|
|
1471
1354
|
return {
|
|
1472
1355
|
...page,
|
|
1473
1356
|
label: page.label ?? page.name,
|
|
@@ -1566,311 +1449,30 @@ var ReactRouter = class {
|
|
|
1566
1449
|
};
|
|
1567
1450
|
|
|
1568
1451
|
//#endregion
|
|
1569
|
-
//#region ../../src/
|
|
1570
|
-
/**
|
|
1571
|
-
* AlephaProvider component to initialize and provide Alepha instance to the app.
|
|
1572
|
-
* This isn't recommended for apps using alepha/react/router, as Router will handle this for you.
|
|
1573
|
-
*/
|
|
1574
|
-
const AlephaProvider = (props) => {
|
|
1575
|
-
const alepha = useMemo(() => Alepha.create(), []);
|
|
1576
|
-
const [started, setStarted] = useState(false);
|
|
1577
|
-
const [error, setError] = useState();
|
|
1578
|
-
useEffect(() => {
|
|
1579
|
-
alepha.start().then(() => setStarted(true)).catch((err) => setError(err));
|
|
1580
|
-
}, [alepha]);
|
|
1581
|
-
if (error) return props.onError(error);
|
|
1582
|
-
if (!started) return props.onLoading();
|
|
1583
|
-
return /* @__PURE__ */ jsx(AlephaContext.Provider, {
|
|
1584
|
-
value: alepha,
|
|
1585
|
-
children: props.children
|
|
1586
|
-
});
|
|
1587
|
-
};
|
|
1588
|
-
|
|
1589
|
-
//#endregion
|
|
1590
|
-
//#region ../../src/core/hooks/useInject.ts
|
|
1591
|
-
/**
|
|
1592
|
-
* Hook to inject a service instance.
|
|
1593
|
-
* It's a wrapper of `useAlepha().inject(service)` with a memoization.
|
|
1594
|
-
*/
|
|
1595
|
-
const useInject = (service) => {
|
|
1596
|
-
const alepha = useAlepha();
|
|
1597
|
-
return useMemo(() => alepha.inject(service), []);
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
//#endregion
|
|
1601
|
-
//#region ../../src/core/hooks/useAction.ts
|
|
1602
|
-
/**
|
|
1603
|
-
* Hook for handling async actions with automatic error handling and event emission.
|
|
1604
|
-
*
|
|
1605
|
-
* By default, prevents concurrent executions - if an action is running and you call it again,
|
|
1606
|
-
* the second call will be ignored. Use `debounce` option to delay execution instead.
|
|
1607
|
-
*
|
|
1608
|
-
* Emits lifecycle events:
|
|
1609
|
-
* - `react:action:begin` - When action starts
|
|
1610
|
-
* - `react:action:success` - When action completes successfully
|
|
1611
|
-
* - `react:action:error` - When action throws an error
|
|
1612
|
-
* - `react:action:end` - Always emitted at the end
|
|
1613
|
-
*
|
|
1614
|
-
* @example Basic usage
|
|
1615
|
-
* ```tsx
|
|
1616
|
-
* const action = useAction({
|
|
1617
|
-
* handler: async (data) => {
|
|
1618
|
-
* await api.save(data);
|
|
1619
|
-
* }
|
|
1620
|
-
* }, []);
|
|
1621
|
-
*
|
|
1622
|
-
* <button onClick={() => action.run(data)} disabled={action.loading}>
|
|
1623
|
-
* Save
|
|
1624
|
-
* </button>
|
|
1625
|
-
* ```
|
|
1626
|
-
*
|
|
1627
|
-
* @example With debounce (search input)
|
|
1628
|
-
* ```tsx
|
|
1629
|
-
* const search = useAction({
|
|
1630
|
-
* handler: async (query: string) => {
|
|
1631
|
-
* await api.search(query);
|
|
1632
|
-
* },
|
|
1633
|
-
* debounce: 300 // Wait 300ms after last call
|
|
1634
|
-
* }, []);
|
|
1635
|
-
*
|
|
1636
|
-
* <input onChange={(e) => search.run(e.target.value)} />
|
|
1637
|
-
* ```
|
|
1638
|
-
*
|
|
1639
|
-
* @example Run on component mount
|
|
1640
|
-
* ```tsx
|
|
1641
|
-
* const fetchData = useAction({
|
|
1642
|
-
* handler: async () => {
|
|
1643
|
-
* const data = await api.getData();
|
|
1644
|
-
* return data;
|
|
1645
|
-
* },
|
|
1646
|
-
* runOnInit: true // Runs once when component mounts
|
|
1647
|
-
* }, []);
|
|
1648
|
-
* ```
|
|
1649
|
-
*
|
|
1650
|
-
* @example Run periodically (polling)
|
|
1651
|
-
* ```tsx
|
|
1652
|
-
* const pollStatus = useAction({
|
|
1653
|
-
* handler: async () => {
|
|
1654
|
-
* const status = await api.getStatus();
|
|
1655
|
-
* return status;
|
|
1656
|
-
* },
|
|
1657
|
-
* runEvery: 5000 // Run every 5 seconds
|
|
1658
|
-
* }, []);
|
|
1659
|
-
*
|
|
1660
|
-
* // Or with duration tuple
|
|
1661
|
-
* const pollStatus = useAction({
|
|
1662
|
-
* handler: async () => {
|
|
1663
|
-
* const status = await api.getStatus();
|
|
1664
|
-
* return status;
|
|
1665
|
-
* },
|
|
1666
|
-
* runEvery: [30, 'seconds'] // Run every 30 seconds
|
|
1667
|
-
* }, []);
|
|
1668
|
-
* ```
|
|
1669
|
-
*
|
|
1670
|
-
* @example With AbortController
|
|
1671
|
-
* ```tsx
|
|
1672
|
-
* const fetch = useAction({
|
|
1673
|
-
* handler: async (url, { signal }) => {
|
|
1674
|
-
* const response = await fetch(url, { signal });
|
|
1675
|
-
* return response.json();
|
|
1676
|
-
* }
|
|
1677
|
-
* }, []);
|
|
1678
|
-
* // Automatically cancelled on unmount or when new request starts
|
|
1679
|
-
* ```
|
|
1680
|
-
*
|
|
1681
|
-
* @example With error handling
|
|
1682
|
-
* ```tsx
|
|
1683
|
-
* const deleteAction = useAction({
|
|
1684
|
-
* handler: async (id: string) => {
|
|
1685
|
-
* await api.delete(id);
|
|
1686
|
-
* },
|
|
1687
|
-
* onError: (error) => {
|
|
1688
|
-
* if (error.code === 'NOT_FOUND') {
|
|
1689
|
-
* // Custom error handling
|
|
1690
|
-
* }
|
|
1691
|
-
* }
|
|
1692
|
-
* }, []);
|
|
1693
|
-
*
|
|
1694
|
-
* {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}
|
|
1695
|
-
* ```
|
|
1696
|
-
*
|
|
1697
|
-
* @example Global error handling
|
|
1698
|
-
* ```tsx
|
|
1699
|
-
* // In your root app setup
|
|
1700
|
-
* alepha.events.on("react:action:error", ({ error }) => {
|
|
1701
|
-
* toast.danger(error.message);
|
|
1702
|
-
* Sentry.captureException(error);
|
|
1703
|
-
* });
|
|
1704
|
-
* ```
|
|
1705
|
-
*/
|
|
1706
|
-
function useAction(options, deps) {
|
|
1707
|
-
const alepha = useAlepha();
|
|
1708
|
-
const dateTimeProvider = useInject(DateTimeProvider);
|
|
1709
|
-
const [loading, setLoading] = useState(false);
|
|
1710
|
-
const [error, setError] = useState();
|
|
1711
|
-
const isExecutingRef = useRef(false);
|
|
1712
|
-
const debounceTimerRef = useRef(void 0);
|
|
1713
|
-
const abortControllerRef = useRef(void 0);
|
|
1714
|
-
const isMountedRef = useRef(true);
|
|
1715
|
-
const intervalRef = useRef(void 0);
|
|
1716
|
-
useEffect(() => {
|
|
1717
|
-
return () => {
|
|
1718
|
-
isMountedRef.current = false;
|
|
1719
|
-
if (debounceTimerRef.current) {
|
|
1720
|
-
dateTimeProvider.clearTimeout(debounceTimerRef.current);
|
|
1721
|
-
debounceTimerRef.current = void 0;
|
|
1722
|
-
}
|
|
1723
|
-
if (intervalRef.current) {
|
|
1724
|
-
dateTimeProvider.clearInterval(intervalRef.current);
|
|
1725
|
-
intervalRef.current = void 0;
|
|
1726
|
-
}
|
|
1727
|
-
if (abortControllerRef.current) {
|
|
1728
|
-
abortControllerRef.current.abort();
|
|
1729
|
-
abortControllerRef.current = void 0;
|
|
1730
|
-
}
|
|
1731
|
-
};
|
|
1732
|
-
}, []);
|
|
1733
|
-
const executeAction = useCallback(async (...args) => {
|
|
1734
|
-
if (isExecutingRef.current) return;
|
|
1735
|
-
if (abortControllerRef.current) abortControllerRef.current.abort();
|
|
1736
|
-
const abortController = new AbortController();
|
|
1737
|
-
abortControllerRef.current = abortController;
|
|
1738
|
-
isExecutingRef.current = true;
|
|
1739
|
-
setLoading(true);
|
|
1740
|
-
setError(void 0);
|
|
1741
|
-
await alepha.events.emit("react:action:begin", {
|
|
1742
|
-
type: "custom",
|
|
1743
|
-
id: options.id
|
|
1744
|
-
});
|
|
1745
|
-
try {
|
|
1746
|
-
const result = await options.handler(...args, { signal: abortController.signal });
|
|
1747
|
-
if (!isMountedRef.current || abortController.signal.aborted) return;
|
|
1748
|
-
await alepha.events.emit("react:action:success", {
|
|
1749
|
-
type: "custom",
|
|
1750
|
-
id: options.id
|
|
1751
|
-
});
|
|
1752
|
-
if (options.onSuccess) await options.onSuccess(result);
|
|
1753
|
-
return result;
|
|
1754
|
-
} catch (err) {
|
|
1755
|
-
if (err instanceof Error && err.name === "AbortError") return;
|
|
1756
|
-
if (!isMountedRef.current) return;
|
|
1757
|
-
const error$1 = err;
|
|
1758
|
-
setError(error$1);
|
|
1759
|
-
await alepha.events.emit("react:action:error", {
|
|
1760
|
-
type: "custom",
|
|
1761
|
-
id: options.id,
|
|
1762
|
-
error: error$1
|
|
1763
|
-
});
|
|
1764
|
-
if (options.onError) await options.onError(error$1);
|
|
1765
|
-
else throw error$1;
|
|
1766
|
-
} finally {
|
|
1767
|
-
isExecutingRef.current = false;
|
|
1768
|
-
setLoading(false);
|
|
1769
|
-
await alepha.events.emit("react:action:end", {
|
|
1770
|
-
type: "custom",
|
|
1771
|
-
id: options.id
|
|
1772
|
-
});
|
|
1773
|
-
if (abortControllerRef.current === abortController) abortControllerRef.current = void 0;
|
|
1774
|
-
}
|
|
1775
|
-
}, [
|
|
1776
|
-
...deps,
|
|
1777
|
-
options.id,
|
|
1778
|
-
options.onError,
|
|
1779
|
-
options.onSuccess
|
|
1780
|
-
]);
|
|
1781
|
-
const handler = useCallback(async (...args) => {
|
|
1782
|
-
if (options.debounce) {
|
|
1783
|
-
if (debounceTimerRef.current) dateTimeProvider.clearTimeout(debounceTimerRef.current);
|
|
1784
|
-
return new Promise((resolve) => {
|
|
1785
|
-
debounceTimerRef.current = dateTimeProvider.createTimeout(async () => {
|
|
1786
|
-
resolve(await executeAction(...args));
|
|
1787
|
-
}, options.debounce ?? 0);
|
|
1788
|
-
});
|
|
1789
|
-
}
|
|
1790
|
-
return executeAction(...args);
|
|
1791
|
-
}, [executeAction, options.debounce]);
|
|
1792
|
-
const cancel = useCallback(() => {
|
|
1793
|
-
if (debounceTimerRef.current) {
|
|
1794
|
-
dateTimeProvider.clearTimeout(debounceTimerRef.current);
|
|
1795
|
-
debounceTimerRef.current = void 0;
|
|
1796
|
-
}
|
|
1797
|
-
if (abortControllerRef.current) {
|
|
1798
|
-
abortControllerRef.current.abort();
|
|
1799
|
-
abortControllerRef.current = void 0;
|
|
1800
|
-
}
|
|
1801
|
-
if (isMountedRef.current) {
|
|
1802
|
-
isExecutingRef.current = false;
|
|
1803
|
-
setLoading(false);
|
|
1804
|
-
}
|
|
1805
|
-
}, []);
|
|
1806
|
-
useEffect(() => {
|
|
1807
|
-
if (options.runOnInit) handler(...[]);
|
|
1808
|
-
}, deps);
|
|
1809
|
-
useEffect(() => {
|
|
1810
|
-
if (!options.runEvery) return;
|
|
1811
|
-
intervalRef.current = dateTimeProvider.createInterval(() => handler(...[]), options.runEvery, true);
|
|
1812
|
-
return () => {
|
|
1813
|
-
if (intervalRef.current) {
|
|
1814
|
-
dateTimeProvider.clearInterval(intervalRef.current);
|
|
1815
|
-
intervalRef.current = void 0;
|
|
1816
|
-
}
|
|
1817
|
-
};
|
|
1818
|
-
}, [handler, options.runEvery]);
|
|
1819
|
-
return {
|
|
1820
|
-
run: handler,
|
|
1821
|
-
loading,
|
|
1822
|
-
error,
|
|
1823
|
-
cancel
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
//#endregion
|
|
1828
|
-
//#region ../../src/core/hooks/useClient.ts
|
|
1829
|
-
/**
|
|
1830
|
-
* Hook to get a virtual client for the specified scope.
|
|
1831
|
-
*
|
|
1832
|
-
* It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
|
|
1833
|
-
*/
|
|
1834
|
-
const useClient = (scope) => {
|
|
1835
|
-
return useInject(LinkProvider).client(scope);
|
|
1836
|
-
};
|
|
1837
|
-
|
|
1838
|
-
//#endregion
|
|
1839
|
-
//#region ../../src/core/hooks/useSchema.ts
|
|
1840
|
-
const useSchema = (action) => {
|
|
1841
|
-
const name = action.name;
|
|
1842
|
-
const alepha = useAlepha();
|
|
1843
|
-
const httpClient = useInject(HttpClient);
|
|
1844
|
-
const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
|
|
1845
|
-
useEffect(() => {
|
|
1846
|
-
if (!schema.loading) return;
|
|
1847
|
-
httpClient.fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, { localCache: true }).then((it) => setSchema(it.data));
|
|
1848
|
-
}, [name]);
|
|
1849
|
-
return schema;
|
|
1850
|
-
};
|
|
1452
|
+
//#region ../../src/router/providers/ReactBrowserRendererProvider.ts
|
|
1851
1453
|
/**
|
|
1852
|
-
*
|
|
1454
|
+
* Browser specific React renderer (react-dom/client interface)
|
|
1853
1455
|
*/
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
if (
|
|
1861
|
-
|
|
1862
|
-
|
|
1456
|
+
var ReactBrowserRendererProvider = class {
|
|
1457
|
+
log = $logger();
|
|
1458
|
+
root;
|
|
1459
|
+
onBrowserRender = $hook({
|
|
1460
|
+
on: "react:browser:render",
|
|
1461
|
+
handler: async ({ hydration, root, element }) => {
|
|
1462
|
+
if (hydration?.layers) {
|
|
1463
|
+
this.root = hydrateRoot(root, element);
|
|
1464
|
+
this.log.info("Hydrated root element");
|
|
1465
|
+
} else {
|
|
1466
|
+
this.root ??= createRoot(root);
|
|
1467
|
+
this.root.render(element);
|
|
1468
|
+
this.log.info("Created root element");
|
|
1863
1469
|
}
|
|
1864
1470
|
}
|
|
1865
|
-
|
|
1866
|
-
}
|
|
1867
|
-
const schema = alepha.inject(LinkProvider).links.find((it) => it.name === name)?.schema;
|
|
1868
|
-
if (schema) return schema;
|
|
1869
|
-
return { loading: true };
|
|
1471
|
+
});
|
|
1870
1472
|
};
|
|
1871
1473
|
|
|
1872
1474
|
//#endregion
|
|
1873
|
-
//#region ../../src/
|
|
1475
|
+
//#region ../../src/router/hooks/useRouter.ts
|
|
1874
1476
|
/**
|
|
1875
1477
|
* Use this hook to access the React Router instance.
|
|
1876
1478
|
*
|
|
@@ -1890,23 +1492,31 @@ const useRouter = () => {
|
|
|
1890
1492
|
};
|
|
1891
1493
|
|
|
1892
1494
|
//#endregion
|
|
1893
|
-
//#region ../../src/
|
|
1495
|
+
//#region ../../src/router/components/Link.tsx
|
|
1496
|
+
/**
|
|
1497
|
+
* Link component for client-side navigation.
|
|
1498
|
+
*
|
|
1499
|
+
* It's a simple wrapper around an anchor (`<a>`) element using the `useRouter` hook.
|
|
1500
|
+
*/
|
|
1894
1501
|
const Link = (props) => {
|
|
1895
1502
|
const router = useRouter();
|
|
1896
|
-
return
|
|
1503
|
+
return createElement("a", {
|
|
1897
1504
|
...props,
|
|
1898
|
-
...router.anchor(props.href)
|
|
1899
|
-
|
|
1900
|
-
});
|
|
1505
|
+
...router.anchor(props.href)
|
|
1506
|
+
}, props.children);
|
|
1901
1507
|
};
|
|
1902
1508
|
var Link_default = Link;
|
|
1903
1509
|
|
|
1904
1510
|
//#endregion
|
|
1905
|
-
//#region ../../src/
|
|
1511
|
+
//#region ../../src/router/hooks/useActive.ts
|
|
1512
|
+
/**
|
|
1513
|
+
* Hook to determine if a given route is active and to provide anchor props for navigation.
|
|
1514
|
+
* This hook refreshes on router state changes.
|
|
1515
|
+
*/
|
|
1906
1516
|
const useActive = (args) => {
|
|
1517
|
+
useRouterState();
|
|
1907
1518
|
const router = useRouter();
|
|
1908
1519
|
const [isPending, setPending] = useState(false);
|
|
1909
|
-
useRouterState().url.pathname;
|
|
1910
1520
|
const options = typeof args === "string" ? { href: args } : {
|
|
1911
1521
|
...args,
|
|
1912
1522
|
href: args.href
|
|
@@ -1935,9 +1545,9 @@ const useActive = (args) => {
|
|
|
1935
1545
|
};
|
|
1936
1546
|
|
|
1937
1547
|
//#endregion
|
|
1938
|
-
//#region ../../src/
|
|
1548
|
+
//#region ../../src/router/hooks/useQueryParams.ts
|
|
1939
1549
|
/**
|
|
1940
|
-
*
|
|
1550
|
+
* Hook to manage query parameters in the URL using a defined schema.
|
|
1941
1551
|
*/
|
|
1942
1552
|
const useQueryParams = (schema, options = {}) => {
|
|
1943
1553
|
const alepha = useAlepha();
|
|
@@ -1970,9 +1580,9 @@ const decode = (alepha, schema, data) => {
|
|
|
1970
1580
|
};
|
|
1971
1581
|
|
|
1972
1582
|
//#endregion
|
|
1973
|
-
//#region ../../src/
|
|
1974
|
-
const
|
|
1975
|
-
name: "alepha.react",
|
|
1583
|
+
//#region ../../src/router/index.browser.ts
|
|
1584
|
+
const AlephaReactRouter = $module({
|
|
1585
|
+
name: "alepha.react.router",
|
|
1976
1586
|
primitives: [$page],
|
|
1977
1587
|
services: [
|
|
1978
1588
|
ReactPageProvider,
|
|
@@ -1982,9 +1592,9 @@ const AlephaReact = $module({
|
|
|
1982
1592
|
ReactBrowserRendererProvider,
|
|
1983
1593
|
ReactPageService
|
|
1984
1594
|
],
|
|
1985
|
-
register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
|
|
1595
|
+
register: (alepha) => alepha.with(AlephaReact).with(AlephaDateTime).with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
|
|
1986
1596
|
});
|
|
1987
1597
|
|
|
1988
1598
|
//#endregion
|
|
1989
|
-
export { $page,
|
|
1599
|
+
export { $page, AlephaReactRouter, ErrorViewer_default as ErrorViewer, Link_default as Link, NestedView_default as NestedView, NotFound_default as NotFound, PagePrimitive, ReactBrowserProvider, ReactBrowserRendererProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactPageService, ReactRouter, Redirection, RouterLayerContext, isPageRoute, reactBrowserOptions, useActive, useQueryParams, useRouter, useRouterState };
|
|
1990
1600
|
//# sourceMappingURL=index.browser.js.map
|