@alepha/react 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.cjs +28 -11
- package/dist/index.browser.js +5 -5
- package/dist/index.cjs +27 -256
- package/dist/index.d.ts +62 -60
- package/dist/index.js +6 -235
- package/dist/{ReactBrowserProvider-CXDElhnK.cjs → useRouterState-BTmuHxkM.cjs} +241 -18
- package/dist/{ReactBrowserProvider-ufHSOTmv.js → useRouterState-cCucJfTC.js} +234 -19
- package/package.json +6 -6
- package/src/components/NotFound.tsx +0 -10
- package/src/descriptors/$page.ts +5 -2
- package/src/index.browser.ts +1 -0
- package/src/index.shared.ts +0 -4
- package/src/index.ts +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
1
2
|
import { __descriptor, OPTIONS, NotImplementedError, KIND, t, $logger, $inject, Alepha, $hook } from '@alepha/core';
|
|
3
|
+
import React, { useState, useEffect, createContext, useContext, createElement, StrictMode, useMemo } from 'react';
|
|
2
4
|
import { HttpClient } from '@alepha/server';
|
|
3
5
|
import { RouterProvider } from '@alepha/router';
|
|
4
|
-
import React, { useState, useEffect, createContext, useContext, createElement, StrictMode } from 'react';
|
|
5
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
const KEY = "PAGE";
|
|
8
8
|
const $page = (options) => {
|
|
@@ -277,7 +277,7 @@ const NestedView = (props) => {
|
|
|
277
277
|
};
|
|
278
278
|
|
|
279
279
|
function NotFoundPage() {
|
|
280
|
-
return /* @__PURE__ */
|
|
280
|
+
return /* @__PURE__ */ jsx(
|
|
281
281
|
"div",
|
|
282
282
|
{
|
|
283
283
|
style: {
|
|
@@ -290,21 +290,7 @@ function NotFoundPage() {
|
|
|
290
290
|
fontFamily: "sans-serif",
|
|
291
291
|
padding: "1rem"
|
|
292
292
|
},
|
|
293
|
-
children:
|
|
294
|
-
/* @__PURE__ */ jsx("h1", { style: { fontSize: "1rem", marginBottom: "0.5rem" }, children: "This page does not exist" }),
|
|
295
|
-
/* @__PURE__ */ jsx(
|
|
296
|
-
"a",
|
|
297
|
-
{
|
|
298
|
-
href: "/",
|
|
299
|
-
style: {
|
|
300
|
-
fontSize: "0.7rem",
|
|
301
|
-
color: "#007bff",
|
|
302
|
-
textDecoration: "none"
|
|
303
|
-
},
|
|
304
|
-
children: "\u2190 Back to home"
|
|
305
|
-
}
|
|
306
|
-
)
|
|
307
|
-
]
|
|
293
|
+
children: /* @__PURE__ */ jsx("h1", { style: { fontSize: "1rem", marginBottom: "0.5rem" }, children: "This page does not exist" })
|
|
308
294
|
}
|
|
309
295
|
);
|
|
310
296
|
}
|
|
@@ -943,4 +929,233 @@ class ReactBrowserProvider {
|
|
|
943
929
|
});
|
|
944
930
|
}
|
|
945
931
|
|
|
946
|
-
|
|
932
|
+
class RouterHookApi {
|
|
933
|
+
constructor(pages, state, layer, browser) {
|
|
934
|
+
this.pages = pages;
|
|
935
|
+
this.state = state;
|
|
936
|
+
this.layer = layer;
|
|
937
|
+
this.browser = browser;
|
|
938
|
+
}
|
|
939
|
+
get current() {
|
|
940
|
+
return this.state;
|
|
941
|
+
}
|
|
942
|
+
get pathname() {
|
|
943
|
+
return this.state.pathname;
|
|
944
|
+
}
|
|
945
|
+
get query() {
|
|
946
|
+
const query = {};
|
|
947
|
+
for (const [key, value] of new URLSearchParams(
|
|
948
|
+
this.state.search
|
|
949
|
+
).entries()) {
|
|
950
|
+
query[key] = String(value);
|
|
951
|
+
}
|
|
952
|
+
return query;
|
|
953
|
+
}
|
|
954
|
+
async back() {
|
|
955
|
+
this.browser?.history.back();
|
|
956
|
+
}
|
|
957
|
+
async forward() {
|
|
958
|
+
this.browser?.history.forward();
|
|
959
|
+
}
|
|
960
|
+
async invalidate(props) {
|
|
961
|
+
await this.browser?.invalidate(props);
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Create a valid href for the given pathname.
|
|
965
|
+
*
|
|
966
|
+
* @param pathname
|
|
967
|
+
* @param layer
|
|
968
|
+
*/
|
|
969
|
+
createHref(pathname, layer = this.layer, options = {}) {
|
|
970
|
+
if (typeof pathname === "object") {
|
|
971
|
+
pathname = pathname.options.path ?? "";
|
|
972
|
+
}
|
|
973
|
+
if (options.params) {
|
|
974
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
975
|
+
pathname = pathname.replace(`:${key}`, String(value));
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
979
|
+
}
|
|
980
|
+
async go(path, options) {
|
|
981
|
+
for (const page of this.pages) {
|
|
982
|
+
if (page.name === path) {
|
|
983
|
+
path = page.path ?? "";
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
await this.browser?.go(this.createHref(path, this.layer, options), options);
|
|
988
|
+
}
|
|
989
|
+
anchor(path, options = {}) {
|
|
990
|
+
for (const page of this.pages) {
|
|
991
|
+
if (page.name === path) {
|
|
992
|
+
path = page.path ?? "";
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const href = this.createHref(path, this.layer, options);
|
|
997
|
+
return {
|
|
998
|
+
href,
|
|
999
|
+
onClick: (ev) => {
|
|
1000
|
+
ev.stopPropagation();
|
|
1001
|
+
ev.preventDefault();
|
|
1002
|
+
this.go(path, options).catch(console.error);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Set query params.
|
|
1008
|
+
*
|
|
1009
|
+
* @param record
|
|
1010
|
+
* @param options
|
|
1011
|
+
*/
|
|
1012
|
+
setQueryParams(record, options = {}) {
|
|
1013
|
+
const func = typeof record === "function" ? record : () => record;
|
|
1014
|
+
const search = new URLSearchParams(func(this.query)).toString();
|
|
1015
|
+
const state = search ? `${this.pathname}?${search}` : this.pathname;
|
|
1016
|
+
if (options.push) {
|
|
1017
|
+
window.history.pushState({}, "", state);
|
|
1018
|
+
} else {
|
|
1019
|
+
window.history.replaceState({}, "", state);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const useRouter = () => {
|
|
1025
|
+
const ctx = useContext(RouterContext);
|
|
1026
|
+
const layer = useContext(RouterLayerContext);
|
|
1027
|
+
if (!ctx || !layer) {
|
|
1028
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
1029
|
+
}
|
|
1030
|
+
const pages = useMemo(() => {
|
|
1031
|
+
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
1032
|
+
}, []);
|
|
1033
|
+
return useMemo(
|
|
1034
|
+
() => new RouterHookApi(
|
|
1035
|
+
pages,
|
|
1036
|
+
ctx.state,
|
|
1037
|
+
layer,
|
|
1038
|
+
ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0
|
|
1039
|
+
),
|
|
1040
|
+
[layer]
|
|
1041
|
+
);
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
const Link = (props) => {
|
|
1045
|
+
React.useContext(RouterContext);
|
|
1046
|
+
const router = useRouter();
|
|
1047
|
+
const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
|
|
1048
|
+
if (!to) {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
const can = typeof props.to === "string" ? void 0 : props.to[OPTIONS].can;
|
|
1052
|
+
if (can && !can()) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
|
|
1056
|
+
const anchorProps = {
|
|
1057
|
+
...props,
|
|
1058
|
+
to: void 0
|
|
1059
|
+
};
|
|
1060
|
+
return /* @__PURE__ */ jsx("a", { ...router.anchor(to), ...anchorProps, children: props.children ?? name });
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const useActive = (path) => {
|
|
1064
|
+
const router = useRouter();
|
|
1065
|
+
const ctx = useContext(RouterContext);
|
|
1066
|
+
const layer = useContext(RouterLayerContext);
|
|
1067
|
+
if (!ctx || !layer) {
|
|
1068
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
1069
|
+
}
|
|
1070
|
+
let name;
|
|
1071
|
+
if (typeof path === "object" && path.options.name) {
|
|
1072
|
+
name = path.options.name;
|
|
1073
|
+
}
|
|
1074
|
+
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
1075
|
+
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
1076
|
+
const [isPending, setPending] = useState(false);
|
|
1077
|
+
const isActive = current === href;
|
|
1078
|
+
useRouterEvents({
|
|
1079
|
+
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
1080
|
+
});
|
|
1081
|
+
return {
|
|
1082
|
+
name,
|
|
1083
|
+
isPending,
|
|
1084
|
+
isActive,
|
|
1085
|
+
anchorProps: {
|
|
1086
|
+
href,
|
|
1087
|
+
onClick: (ev) => {
|
|
1088
|
+
ev.stopPropagation();
|
|
1089
|
+
ev.preventDefault();
|
|
1090
|
+
if (isActive) return;
|
|
1091
|
+
if (isPending) return;
|
|
1092
|
+
setPending(true);
|
|
1093
|
+
router.go(href).then(() => {
|
|
1094
|
+
setPending(false);
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
const useInject = (clazz) => {
|
|
1102
|
+
const ctx = useContext(RouterContext);
|
|
1103
|
+
if (!ctx) {
|
|
1104
|
+
throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1105
|
+
}
|
|
1106
|
+
return useMemo(() => ctx.alepha.get(clazz), []);
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
const useClient = (_scope) => {
|
|
1110
|
+
return useInject(HttpClient).of();
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
const useQueryParams = (schema, options = {}) => {
|
|
1114
|
+
const ctx = useContext(RouterContext);
|
|
1115
|
+
if (!ctx) {
|
|
1116
|
+
throw new Error("useQueryParams must be used within a RouterProvider");
|
|
1117
|
+
}
|
|
1118
|
+
const key = options.key ?? "q";
|
|
1119
|
+
const router = useRouter();
|
|
1120
|
+
const querystring = router.query[key];
|
|
1121
|
+
const [queryParams, setQueryParams] = useState(
|
|
1122
|
+
decode(ctx.alepha, schema, router.query[key])
|
|
1123
|
+
);
|
|
1124
|
+
useEffect(() => {
|
|
1125
|
+
setQueryParams(decode(ctx.alepha, schema, querystring));
|
|
1126
|
+
}, [querystring]);
|
|
1127
|
+
return [
|
|
1128
|
+
queryParams,
|
|
1129
|
+
(queryParams2) => {
|
|
1130
|
+
setQueryParams(queryParams2);
|
|
1131
|
+
router.setQueryParams((data) => {
|
|
1132
|
+
return { ...data, [key]: encode(ctx.alepha, schema, queryParams2) };
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
];
|
|
1136
|
+
};
|
|
1137
|
+
const encode = (alepha, schema, data) => {
|
|
1138
|
+
return btoa(JSON.stringify(alepha.parse(schema, data)));
|
|
1139
|
+
};
|
|
1140
|
+
const decode = (alepha, schema, data) => {
|
|
1141
|
+
try {
|
|
1142
|
+
return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
|
|
1143
|
+
} catch (_error) {
|
|
1144
|
+
return {};
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
const useRouterState = () => {
|
|
1149
|
+
const ctx = useContext(RouterContext);
|
|
1150
|
+
const layer = useContext(RouterLayerContext);
|
|
1151
|
+
if (!ctx || !layer) {
|
|
1152
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
1153
|
+
}
|
|
1154
|
+
const [state, setState] = useState(ctx.state);
|
|
1155
|
+
useRouterEvents({
|
|
1156
|
+
onEnd: ({ state: state2 }) => setState({ ...state2 })
|
|
1157
|
+
});
|
|
1158
|
+
return state;
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
export { $page as $, BrowserRouterProvider as B, ClientOnly as C, ErrorBoundary as E, Link as L, NestedView as N, PageDescriptorProvider as P, RouterContext as R, RouterLayerContext as a, RedirectionError as b, RouterHookApi as c, useAlepha as d, useClient as e, useInject as f, useQueryParams as g, useRouter as h, useRouterEvents as i, useRouterState as j, isPageRoute as k, ReactBrowserProvider as l, useActive as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alepha/react",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"src"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@alepha/core": "0.7.
|
|
17
|
-
"@alepha/router": "0.7.
|
|
18
|
-
"@alepha/server": "0.7.
|
|
19
|
-
"@alepha/server-cache": "0.7.
|
|
20
|
-
"@alepha/server-static": "0.7.
|
|
16
|
+
"@alepha/core": "0.7.5",
|
|
17
|
+
"@alepha/router": "0.7.5",
|
|
18
|
+
"@alepha/server": "0.7.5",
|
|
19
|
+
"@alepha/server-cache": "0.7.5",
|
|
20
|
+
"@alepha/server-static": "0.7.5",
|
|
21
21
|
"react-dom": "^19.1.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
@@ -15,16 +15,6 @@ export default function NotFoundPage() {
|
|
|
15
15
|
<h1 style={{ fontSize: "1rem", marginBottom: "0.5rem" }}>
|
|
16
16
|
This page does not exist
|
|
17
17
|
</h1>
|
|
18
|
-
<a
|
|
19
|
-
href="/"
|
|
20
|
-
style={{
|
|
21
|
-
fontSize: "0.7rem",
|
|
22
|
-
color: "#007bff",
|
|
23
|
-
textDecoration: "none",
|
|
24
|
-
}}
|
|
25
|
-
>
|
|
26
|
-
← Back to home
|
|
27
|
-
</a>
|
|
28
18
|
</div>
|
|
29
19
|
);
|
|
30
20
|
}
|
package/src/descriptors/$page.ts
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
type Static,
|
|
8
8
|
type TSchema,
|
|
9
9
|
} from "@alepha/core";
|
|
10
|
-
import type { ServerRequest
|
|
10
|
+
import type { ServerRequest } from "@alepha/server";
|
|
11
|
+
import type { ServerRouteCache } from "@alepha/server-cache";
|
|
11
12
|
import type { FC, ReactNode } from "react";
|
|
12
13
|
import type { ClientOnlyProps } from "../components/ClientOnly.tsx";
|
|
13
14
|
import type { PageReactContext } from "../providers/PageDescriptorProvider.ts";
|
|
@@ -27,7 +28,7 @@ export interface PageDescriptorOptions<
|
|
|
27
28
|
TConfig extends PageConfigSchema = PageConfigSchema,
|
|
28
29
|
TProps extends object = TPropsDefault,
|
|
29
30
|
TPropsParent extends object = TPropsParentDefault,
|
|
30
|
-
>
|
|
31
|
+
> {
|
|
31
32
|
/**
|
|
32
33
|
* Name your page.
|
|
33
34
|
*
|
|
@@ -117,6 +118,8 @@ export interface PageDescriptorOptions<
|
|
|
117
118
|
client?: boolean | ClientOnlyProps;
|
|
118
119
|
|
|
119
120
|
afterHandler?: (request: ServerRequest) => any;
|
|
121
|
+
|
|
122
|
+
cache?: ServerRouteCache;
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
export interface PageDescriptor<
|
package/src/index.browser.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ReactBrowserRenderer } from "./providers/ReactBrowserRenderer.ts";
|
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
9
9
|
|
|
10
|
+
export * from "./index.shared.ts";
|
|
10
11
|
export * from "./providers/BrowserRouterProvider.ts";
|
|
11
12
|
export * from "./providers/PageDescriptorProvider.ts";
|
|
12
13
|
export * from "./providers/ReactBrowserProvider.ts";
|
package/src/index.shared.ts
CHANGED
|
@@ -3,14 +3,10 @@ export { default as ErrorBoundary } from "./components/ErrorBoundary.tsx";
|
|
|
3
3
|
export * from "./components/ErrorViewer.tsx";
|
|
4
4
|
export { default as Link } from "./components/Link.tsx";
|
|
5
5
|
export { default as NestedView } from "./components/NestedView.tsx";
|
|
6
|
-
|
|
7
6
|
export * from "./contexts/RouterContext.ts";
|
|
8
7
|
export * from "./contexts/RouterLayerContext.ts";
|
|
9
|
-
|
|
10
8
|
export * from "./descriptors/$page.ts";
|
|
11
|
-
|
|
12
9
|
export * from "./errors/RedirectionError.ts";
|
|
13
|
-
|
|
14
10
|
export * from "./hooks/RouterHookApi.ts";
|
|
15
11
|
export * from "./hooks/useActive.ts";
|
|
16
12
|
export * from "./hooks/useAlepha.ts";
|
package/src/index.ts
CHANGED
|
@@ -13,8 +13,6 @@ import { ReactServerProvider } from "./providers/ReactServerProvider.ts";
|
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
15
15
|
|
|
16
|
-
export { default as NestedView } from "./components/NestedView.tsx";
|
|
17
|
-
export * from "./errors/RedirectionError.ts";
|
|
18
16
|
export * from "./index.shared.ts";
|
|
19
17
|
export * from "./providers/PageDescriptorProvider.ts";
|
|
20
18
|
export * from "./providers/ReactBrowserProvider.ts";
|