@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.
@@ -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__ */ jsxs(
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
- export { $page as $, BrowserRouterProvider as B, ClientOnly as C, ErrorBoundary as E, NestedView as N, PageDescriptorProvider as P, RouterContext as R, RouterLayerContext as a, ReactBrowserProvider as b, RedirectionError as c, useAlepha as d, isPageRoute as i, useRouterEvents as u };
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.4",
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.4",
17
- "@alepha/router": "0.7.4",
18
- "@alepha/server": "0.7.4",
19
- "@alepha/server-cache": "0.7.4",
20
- "@alepha/server-static": "0.7.4",
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
  }
@@ -7,7 +7,8 @@ import {
7
7
  type Static,
8
8
  type TSchema,
9
9
  } from "@alepha/core";
10
- import type { ServerRequest, ServerRoute } from "@alepha/server";
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
- > extends Pick<ServerRoute, "cache"> {
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<
@@ -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";
@@ -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";