@alepha/react 0.8.0 → 0.8.1

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.cjs CHANGED
@@ -314,7 +314,7 @@ const NestedView = (props) => {
314
314
  const index = layer?.index ?? 0;
315
315
  const [view, setView] = (0, react.useState)(app?.state.layers[index]?.element);
316
316
  useRouterEvents({ onEnd: ({ state }) => {
317
- setView(state.layers[index]?.element);
317
+ if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
318
318
  } }, [app]);
319
319
  if (!app) throw new Error("NestedView must be used within a RouterContext.");
320
320
  const element = view ?? props.children ?? null;
@@ -412,19 +412,18 @@ var PageDescriptorProvider = class {
412
412
  const route$1 = it.route;
413
413
  const config = {};
414
414
  try {
415
- config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : request.query;
415
+ config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : {};
416
416
  } catch (e) {
417
417
  it.error = e;
418
418
  break;
419
419
  }
420
420
  try {
421
- config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : request.params;
421
+ config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : {};
422
422
  } catch (e) {
423
423
  it.error = e;
424
424
  break;
425
425
  }
426
426
  it.config = { ...config };
427
- if (!route$1.resolve) continue;
428
427
  const previous = request.previous;
429
428
  if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
430
429
  const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
@@ -439,6 +438,7 @@ var PageDescriptorProvider = class {
439
438
  if (prev === curr) {
440
439
  it.props = previous[i].props;
441
440
  it.error = previous[i].error;
441
+ it.cache = true;
442
442
  context = {
443
443
  ...context,
444
444
  ...it.props
@@ -447,6 +447,7 @@ var PageDescriptorProvider = class {
447
447
  }
448
448
  forceRefresh = true;
449
449
  }
450
+ if (!route$1.resolve) continue;
450
451
  try {
451
452
  const props = await route$1.resolve?.({
452
453
  ...request,
@@ -493,7 +494,7 @@ var PageDescriptorProvider = class {
493
494
  element: this.renderView(i + 1, path, element$1, it.route),
494
495
  index: i + 1,
495
496
  path,
496
- route
497
+ route: it.route
497
498
  });
498
499
  break;
499
500
  }
@@ -509,7 +510,8 @@ var PageDescriptorProvider = class {
509
510
  element: this.renderView(i + 1, path, element, it.route),
510
511
  index: i + 1,
511
512
  path,
512
- route
513
+ route: it.route,
514
+ cache: it.cache
513
515
  });
514
516
  }
515
517
  return {
@@ -577,8 +579,8 @@ var PageDescriptorProvider = class {
577
579
  };
578
580
  for (const { value, key } of pages) value[__alepha_core.OPTIONS].name ??= key;
579
581
  for (const { value } of pages) {
580
- if (hasParent(value)) continue;
581
582
  if (value[__alepha_core.OPTIONS].path === "/*") hasNotFoundHandler = true;
583
+ if (hasParent(value)) continue;
582
584
  this.add(this.map(pages, value));
583
585
  }
584
586
  if (!hasNotFoundHandler && pages.length > 0) this.add({
@@ -654,7 +656,7 @@ var ReactServerProvider = class {
654
656
  handler: async () => {
655
657
  const pages = this.alepha.getDescriptorValues($page);
656
658
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
657
- this.alepha.state("ReactServerProvider.ssr", ssrEnabled);
659
+ this.alepha.state("react.server.ssr", ssrEnabled);
658
660
  for (const { key, instance, value } of pages) {
659
661
  const name = value[__alepha_core.OPTIONS].name ?? key;
660
662
  instance[key].render = this.createRenderFunction(name);
@@ -694,7 +696,7 @@ var ReactServerProvider = class {
694
696
  }
695
697
  });
696
698
  get template() {
697
- return this.alepha.state("ReactServerProvider.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
699
+ return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
698
700
  }
699
701
  async registerPages(templateLoader) {
700
702
  for (const page of this.pageDescriptorProvider.getPages()) {
@@ -975,8 +977,17 @@ var ReactBrowserProvider = class {
975
977
  get history() {
976
978
  return window.history;
977
979
  }
980
+ get location() {
981
+ return window.location;
982
+ }
978
983
  get url() {
979
- return window.location.pathname + window.location.search;
984
+ let url = this.location.pathname + this.location.search;
985
+ return url;
986
+ }
987
+ pushState(url, replace) {
988
+ let path = url;
989
+ if (replace) this.history.replaceState({}, "", path);
990
+ else this.history.pushState({}, "", path);
980
991
  }
981
992
  async invalidate(props) {
982
993
  const previous = [];
@@ -1002,14 +1013,14 @@ var ReactBrowserProvider = class {
1002
1013
  async go(url, options = {}) {
1003
1014
  const result = await this.render({ url });
1004
1015
  if (result.context.url.pathname !== url) {
1005
- this.history.replaceState({}, "", result.context.url.pathname);
1016
+ this.pushState(result.context.url.pathname);
1006
1017
  return;
1007
1018
  }
1008
1019
  if (options.replace) {
1009
- this.history.replaceState({}, "", url);
1020
+ this.pushState(url);
1010
1021
  return;
1011
1022
  }
1012
- this.history.pushState({}, "", url);
1023
+ this.pushState(url);
1013
1024
  }
1014
1025
  async render(options = {}) {
1015
1026
  const previous = options.previous ?? this.state.layers;
@@ -1046,6 +1057,7 @@ var ReactBrowserProvider = class {
1046
1057
  hydration
1047
1058
  });
1048
1059
  window.addEventListener("popstate", () => {
1060
+ if (this.state.pathname === location.pathname) return;
1049
1061
  this.render();
1050
1062
  });
1051
1063
  }
@@ -1055,12 +1067,21 @@ var ReactBrowserProvider = class {
1055
1067
  //#endregion
1056
1068
  //#region src/hooks/RouterHookApi.ts
1057
1069
  var RouterHookApi = class {
1058
- constructor(pages, state, layer, browser) {
1070
+ constructor(pages, context, state, layer, browser) {
1059
1071
  this.pages = pages;
1072
+ this.context = context;
1060
1073
  this.state = state;
1061
1074
  this.layer = layer;
1062
1075
  this.browser = browser;
1063
1076
  }
1077
+ getURL() {
1078
+ if (!this.browser) return this.context.url;
1079
+ return new URL(this.location.href);
1080
+ }
1081
+ get location() {
1082
+ if (!this.browser) throw new Error("Browser is required");
1083
+ return this.browser.location;
1084
+ }
1064
1085
  get current() {
1065
1086
  return this.state;
1066
1087
  }
@@ -1138,7 +1159,7 @@ const useRouter = () => {
1138
1159
  const pages = (0, react.useMemo)(() => {
1139
1160
  return ctx.alepha.get(PageDescriptorProvider).getPages();
1140
1161
  }, []);
1141
- return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
1162
+ return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
1142
1163
  };
1143
1164
 
1144
1165
  //#endregion
@@ -1258,10 +1279,132 @@ const useRouterState = () => {
1258
1279
  //#endregion
1259
1280
  //#region src/index.ts
1260
1281
  /**
1261
- * Alepha React Module
1282
+ * Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
1283
+ *
1284
+ * The React module enables building modern React applications using the `$page` descriptor on class properties.
1285
+ * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1286
+ * type safety and schema validation for route parameters and data.
1287
+ *
1288
+ * **Key Features:**
1289
+ * - Declarative page definition with `$page` descriptor
1290
+ * - Server-side rendering (SSR) with automatic hydration
1291
+ * - Type-safe routing with parameter validation
1292
+ * - Schema-based data resolution and validation
1293
+ * - SEO-friendly meta tag management
1294
+ * - Automatic code splitting and lazy loading
1295
+ * - Client-side navigation with browser history
1296
+ *
1297
+ * **Basic Usage:**
1298
+ * ```ts
1299
+ * import { Alepha, run, t } from "alepha";
1300
+ * import { AlephaReact, $page } from "alepha/react";
1301
+ *
1302
+ * class AppRoutes {
1303
+ * // Home page
1304
+ * home = $page({
1305
+ * path: "/",
1306
+ * component: () => (
1307
+ * <div>
1308
+ * <h1>Welcome to Alepha</h1>
1309
+ * <p>Build amazing React applications!</p>
1310
+ * </div>
1311
+ * ),
1312
+ * });
1313
+ *
1314
+ * // About page with meta tags
1315
+ * about = $page({
1316
+ * path: "/about",
1317
+ * head: {
1318
+ * title: "About Us",
1319
+ * description: "Learn more about our mission",
1320
+ * },
1321
+ * component: () => (
1322
+ * <div>
1323
+ * <h1>About Us</h1>
1324
+ * <p>Learn more about our mission.</p>
1325
+ * </div>
1326
+ * ),
1327
+ * });
1328
+ * }
1329
+ *
1330
+ * const alepha = Alepha.create()
1331
+ * .with(AlephaReact)
1332
+ * .with(AppRoutes);
1333
+ *
1334
+ * run(alepha);
1335
+ * ```
1262
1336
  *
1263
- * Alepha React Module contains a router for client-side navigation and server-side rendering.
1264
- * Routes can be defined using the `$page` descriptor.
1337
+ * **Dynamic Routes with Parameters:**
1338
+ * ```tsx
1339
+ * class UserRoutes {
1340
+ * userProfile = $page({
1341
+ * path: "/users/:id",
1342
+ * schema: {
1343
+ * params: t.object({
1344
+ * id: t.string(),
1345
+ * }),
1346
+ * },
1347
+ * resolve: async ({ params }) => {
1348
+ * // Fetch user data server-side
1349
+ * const user = await getUserById(params.id);
1350
+ * return { user };
1351
+ * },
1352
+ * head: ({ user }) => ({
1353
+ * title: `${user.name} - Profile`,
1354
+ * description: `View ${user.name}'s profile`,
1355
+ * }),
1356
+ * component: ({ user }) => (
1357
+ * <div>
1358
+ * <h1>{user.name}</h1>
1359
+ * <p>Email: {user.email}</p>
1360
+ * </div>
1361
+ * ),
1362
+ * });
1363
+ *
1364
+ * userSettings = $page({
1365
+ * path: "/users/:id/settings",
1366
+ * schema: {
1367
+ * params: t.object({
1368
+ * id: t.string(),
1369
+ * }),
1370
+ * },
1371
+ * component: ({ params }) => (
1372
+ * <UserSettings userId={params.id} />
1373
+ * ),
1374
+ * });
1375
+ * }
1376
+ * ```
1377
+ *
1378
+ * **Static Generation:**
1379
+ * ```tsx
1380
+ * class BlogRoutes {
1381
+ * blogPost = $page({
1382
+ * path: "/blog/:slug",
1383
+ * schema: {
1384
+ * params: t.object({
1385
+ * slug: t.string(),
1386
+ * }),
1387
+ * },
1388
+ * static: {
1389
+ * entries: [
1390
+ * { params: { slug: "getting-started" } },
1391
+ * { params: { slug: "advanced-features" } },
1392
+ * { params: { slug: "deployment" } },
1393
+ * ],
1394
+ * },
1395
+ * resolve: ({ params }) => {
1396
+ * const post = getBlogPost(params.slug);
1397
+ * return { post };
1398
+ * },
1399
+ * component: ({ post }) => (
1400
+ * <article>
1401
+ * <h1>{post.title}</h1>
1402
+ * <div>{post.content}</div>
1403
+ * </article>
1404
+ * ),
1405
+ * });
1406
+ * }
1407
+ * ```
1265
1408
  *
1266
1409
  * @see {@link $page}
1267
1410
  * @module alepha.react
@@ -1279,6 +1422,7 @@ exports.ClientOnly = ClientOnly_default;
1279
1422
  exports.ErrorBoundary = ErrorBoundary_default;
1280
1423
  exports.Link = Link_default;
1281
1424
  exports.NestedView = NestedView_default;
1425
+ exports.NotFound = NotFoundPage;
1282
1426
  exports.PageDescriptorProvider = PageDescriptorProvider;
1283
1427
  exports.ReactBrowserProvider = ReactBrowserProvider;
1284
1428
  exports.ReactServerProvider = ReactServerProvider;