@alepha/react 0.6.10 → 0.7.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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/index.browser.cjs +21 -20
  3. package/dist/index.browser.js +2 -3
  4. package/dist/index.cjs +168 -82
  5. package/dist/index.d.ts +415 -232
  6. package/dist/index.js +146 -62
  7. package/dist/{useActive-4QlZKGbw.cjs → useRouterState-AdK-XeM2.cjs} +358 -170
  8. package/dist/{useActive-ClUsghB5.js → useRouterState-qoMq7Y9J.js} +358 -172
  9. package/package.json +11 -10
  10. package/src/components/ClientOnly.tsx +35 -0
  11. package/src/components/ErrorBoundary.tsx +72 -0
  12. package/src/components/ErrorViewer.tsx +161 -0
  13. package/src/components/Link.tsx +10 -4
  14. package/src/components/NestedView.tsx +28 -4
  15. package/src/descriptors/$page.ts +143 -38
  16. package/src/errors/RedirectionError.ts +4 -1
  17. package/src/hooks/RouterHookApi.ts +58 -35
  18. package/src/hooks/useAlepha.ts +12 -0
  19. package/src/hooks/useClient.ts +8 -6
  20. package/src/hooks/useInject.ts +3 -9
  21. package/src/hooks/useQueryParams.ts +4 -7
  22. package/src/hooks/useRouter.ts +6 -0
  23. package/src/index.browser.ts +1 -1
  24. package/src/index.shared.ts +11 -4
  25. package/src/index.ts +7 -4
  26. package/src/providers/BrowserRouterProvider.ts +27 -33
  27. package/src/providers/PageDescriptorProvider.ts +90 -40
  28. package/src/providers/ReactBrowserProvider.ts +21 -27
  29. package/src/providers/ReactServerProvider.ts +215 -77
  30. package/dist/index.browser.cjs.map +0 -1
  31. package/dist/index.browser.js.map +0 -1
  32. package/dist/index.cjs.map +0 -1
  33. package/dist/index.js.map +0 -1
  34. package/dist/useActive-4QlZKGbw.cjs.map +0 -1
  35. package/dist/useActive-ClUsghB5.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var React = require('react');
5
4
  var core = require('@alepha/core');
5
+ var React = require('react');
6
6
  var server = require('@alepha/server');
7
7
  var client = require('react-dom/client');
8
8
  var router = require('@alepha/router');
@@ -28,27 +28,161 @@ const $page = (options) => {
28
28
  [core.OPTIONS]: options,
29
29
  render: () => {
30
30
  throw new core.NotImplementedError(KEY);
31
- },
32
- go: () => {
33
- throw new core.NotImplementedError(KEY);
34
- },
35
- createAnchorProps: () => {
36
- throw new core.NotImplementedError(KEY);
37
- },
38
- can: () => {
39
- if (options.can) {
40
- return options.can();
41
- }
42
- return true;
43
31
  }
44
32
  };
45
33
  };
46
34
  $page[core.KIND] = KEY;
47
35
 
36
+ const ClientOnly = (props) => {
37
+ const [mounted, setMounted] = React.useState(false);
38
+ React.useEffect(() => setMounted(true), []);
39
+ if (props.disabled) {
40
+ return props.children;
41
+ }
42
+ return mounted ? props.children : props.fallback;
43
+ };
44
+
48
45
  const RouterContext = React.createContext(
49
46
  void 0
50
47
  );
51
48
 
49
+ const useAlepha = () => {
50
+ const routerContext = React.useContext(RouterContext);
51
+ if (!routerContext) {
52
+ throw new Error("useAlepha must be used within a RouterProvider");
53
+ }
54
+ return routerContext.alepha;
55
+ };
56
+
57
+ const ErrorViewer = ({ error }) => {
58
+ const [expanded, setExpanded] = React.useState(false);
59
+ const isProduction = useAlepha().isProduction();
60
+ if (isProduction) {
61
+ return /* @__PURE__ */ jsxRuntime.jsx(ErrorViewerProduction, {});
62
+ }
63
+ const stackLines = error.stack?.split("\n") ?? [];
64
+ const previewLines = stackLines.slice(0, 5);
65
+ const hiddenLineCount = stackLines.length - previewLines.length;
66
+ const copyToClipboard = (text) => {
67
+ navigator.clipboard.writeText(text).catch((err) => {
68
+ console.error("Clipboard error:", err);
69
+ });
70
+ };
71
+ const styles = {
72
+ container: {
73
+ padding: "24px",
74
+ backgroundColor: "#FEF2F2",
75
+ color: "#7F1D1D",
76
+ border: "1px solid #FECACA",
77
+ borderRadius: "16px",
78
+ boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
79
+ fontFamily: "monospace",
80
+ maxWidth: "768px",
81
+ margin: "40px auto"
82
+ },
83
+ heading: {
84
+ fontSize: "20px",
85
+ fontWeight: "bold",
86
+ marginBottom: "4px"
87
+ },
88
+ name: {
89
+ fontSize: "16px",
90
+ fontWeight: 600
91
+ },
92
+ message: {
93
+ fontSize: "14px",
94
+ marginBottom: "16px"
95
+ },
96
+ sectionHeader: {
97
+ display: "flex",
98
+ justifyContent: "space-between",
99
+ alignItems: "center",
100
+ fontSize: "12px",
101
+ marginBottom: "4px",
102
+ color: "#991B1B"
103
+ },
104
+ copyButton: {
105
+ fontSize: "12px",
106
+ color: "#DC2626",
107
+ background: "none",
108
+ border: "none",
109
+ cursor: "pointer",
110
+ textDecoration: "underline"
111
+ },
112
+ stackContainer: {
113
+ backgroundColor: "#FEE2E2",
114
+ padding: "12px",
115
+ borderRadius: "8px",
116
+ fontSize: "13px",
117
+ lineHeight: "1.4",
118
+ overflowX: "auto",
119
+ whiteSpace: "pre-wrap"
120
+ },
121
+ expandLine: {
122
+ color: "#F87171",
123
+ cursor: "pointer",
124
+ marginTop: "8px"
125
+ }
126
+ };
127
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.container, children: [
128
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
129
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.heading, children: "\u{1F525} Error" }),
130
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.name, children: error.name }),
131
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.message, children: error.message })
132
+ ] }),
133
+ stackLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
134
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.sectionHeader, children: [
135
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Stack trace" }),
136
+ /* @__PURE__ */ jsxRuntime.jsx(
137
+ "button",
138
+ {
139
+ onClick: () => copyToClipboard(error.stack),
140
+ style: styles.copyButton,
141
+ children: "Copy all"
142
+ }
143
+ )
144
+ ] }),
145
+ /* @__PURE__ */ jsxRuntime.jsxs("pre", { style: styles.stackContainer, children: [
146
+ (expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: line }, i)),
147
+ !expanded && hiddenLineCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.expandLine, onClick: () => setExpanded(true), children: [
148
+ "+ ",
149
+ hiddenLineCount,
150
+ " more lines..."
151
+ ] })
152
+ ] })
153
+ ] })
154
+ ] });
155
+ };
156
+ const ErrorViewerProduction = () => {
157
+ const styles = {
158
+ container: {
159
+ padding: "24px",
160
+ backgroundColor: "#FEF2F2",
161
+ color: "#7F1D1D",
162
+ border: "1px solid #FECACA",
163
+ borderRadius: "16px",
164
+ boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
165
+ fontFamily: "monospace",
166
+ maxWidth: "768px",
167
+ margin: "40px auto",
168
+ textAlign: "center"
169
+ },
170
+ heading: {
171
+ fontSize: "20px",
172
+ fontWeight: "bold",
173
+ marginBottom: "8px"
174
+ },
175
+ message: {
176
+ fontSize: "14px",
177
+ opacity: 0.85
178
+ }
179
+ };
180
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.container, children: [
181
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.heading, children: "\u{1F6A8} An error occurred" }),
182
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.message, children: "Something went wrong. Please try again later." })
183
+ ] });
184
+ };
185
+
52
186
  const RouterLayerContext = React.createContext(void 0);
53
187
 
54
188
  const useRouterEvents = (opts = {}, deps = []) => {
@@ -93,6 +227,36 @@ const useRouterEvents = (opts = {}, deps = []) => {
93
227
  }, deps);
94
228
  };
95
229
 
230
+ class ErrorBoundary extends React.Component {
231
+ constructor(props) {
232
+ super(props);
233
+ this.state = {};
234
+ }
235
+ /**
236
+ * Update state so the next render shows the fallback UI.
237
+ */
238
+ static getDerivedStateFromError(error) {
239
+ return {
240
+ error
241
+ };
242
+ }
243
+ /**
244
+ * Lifecycle method called when an error is caught.
245
+ * You can log the error or perform side effects here.
246
+ */
247
+ componentDidCatch(error, info) {
248
+ if (this.props.onError) {
249
+ this.props.onError(error, info);
250
+ }
251
+ }
252
+ render() {
253
+ if (this.state.error) {
254
+ return this.props.fallback(this.state.error);
255
+ }
256
+ return this.props.children;
257
+ }
258
+ }
259
+
96
260
  const NestedView = (props) => {
97
261
  const app = React.useContext(RouterContext);
98
262
  const layer = React.useContext(RouterLayerContext);
@@ -108,18 +272,27 @@ const NestedView = (props) => {
108
272
  },
109
273
  [app]
110
274
  );
111
- return view ?? props.children ?? null;
275
+ if (!app) {
276
+ throw new Error("NestedView must be used within a RouterContext.");
277
+ }
278
+ const element = view ?? props.children ?? null;
279
+ return /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { fallback: app.context.onError, children: element });
112
280
  };
113
281
 
114
282
  class RedirectionError extends Error {
283
+ page;
115
284
  constructor(page) {
116
285
  super("Redirection");
117
286
  this.page = page;
118
287
  }
119
288
  }
120
289
 
290
+ const envSchema$1 = core.t.object({
291
+ REACT_STRICT_MODE: core.t.boolean({ default: true })
292
+ });
121
293
  class PageDescriptorProvider {
122
294
  log = core.$logger();
295
+ env = core.$inject(envSchema$1);
123
296
  alepha = core.$inject(core.Alepha);
124
297
  pages = [];
125
298
  getPages() {
@@ -133,8 +306,25 @@ class PageDescriptorProvider {
133
306
  }
134
307
  throw new Error(`Page ${name} not found`);
135
308
  }
136
- root(state, context = {}) {
137
- return React.createElement(
309
+ url(name, options = {}) {
310
+ const page = this.page(name);
311
+ if (!page) {
312
+ throw new Error(`Page ${name} not found`);
313
+ }
314
+ let url = page.path ?? "";
315
+ let parent = page.parent;
316
+ while (parent) {
317
+ url = `${parent.path ?? ""}/${url}`;
318
+ parent = parent.parent;
319
+ }
320
+ url = this.compile(url, options.params ?? {});
321
+ return new URL(
322
+ url.replace(/\/\/+/g, "/") || "/",
323
+ options.base ?? `http://localhost`
324
+ );
325
+ }
326
+ root(state, context) {
327
+ const root = React.createElement(
138
328
  RouterContext.Provider,
139
329
  {
140
330
  value: {
@@ -145,12 +335,17 @@ class PageDescriptorProvider {
145
335
  },
146
336
  React.createElement(NestedView, {}, state.layers[0]?.element)
147
337
  );
338
+ if (this.env.REACT_STRICT_MODE) {
339
+ return React.createElement(React.StrictMode, {}, root);
340
+ }
341
+ return root;
148
342
  }
149
343
  async createLayers(route, request) {
150
344
  const { pathname, search } = request.url;
151
345
  const layers = [];
152
346
  let context = {};
153
347
  const stack = [{ route }];
348
+ request.onError = (error) => this.renderError(error);
154
349
  let parent = route.parent;
155
350
  while (parent) {
156
351
  stack.unshift({ route: parent });
@@ -222,7 +417,6 @@ class PageDescriptorProvider {
222
417
  return {
223
418
  layers: [],
224
419
  redirect: typeof e.page === "string" ? e.page : this.href(e.page),
225
- head: request.head,
226
420
  pathname,
227
421
  search
228
422
  };
@@ -249,26 +443,28 @@ class PageDescriptorProvider {
249
443
  acc += "/";
250
444
  acc += it.route.path ? this.compile(it.route.path, params) : "";
251
445
  const path = acc.replace(/\/+/, "/");
446
+ const localErrorHandler = this.getErrorHandler(it.route);
447
+ if (localErrorHandler) {
448
+ request.onError = localErrorHandler;
449
+ }
252
450
  if (it.error) {
253
- const errorHandler = this.getErrorHandler(it.route);
254
- const element = await (errorHandler ? errorHandler({
255
- ...it.config,
256
- error: it.error,
257
- url: ""
258
- }) : this.renderError(it.error));
451
+ let element2 = await request.onError(it.error);
452
+ if (element2 === null) {
453
+ element2 = this.renderError(it.error);
454
+ }
259
455
  layers.push({
260
456
  props,
261
457
  error: it.error,
262
458
  name: it.route.name,
263
459
  part: it.route.path,
264
460
  config: it.config,
265
- element: this.renderView(i + 1, path, element),
461
+ element: this.renderView(i + 1, path, element2, it.route),
266
462
  index: i + 1,
267
463
  path
268
464
  });
269
465
  break;
270
466
  }
271
- const layer = await this.createElement(it.route, {
467
+ const element = await this.createElement(it.route, {
272
468
  ...props,
273
469
  ...context
274
470
  });
@@ -277,12 +473,12 @@ class PageDescriptorProvider {
277
473
  props,
278
474
  part: it.route.path,
279
475
  config: it.config,
280
- element: this.renderView(i + 1, path, layer),
476
+ element: this.renderView(i + 1, path, element, it.route),
281
477
  index: i + 1,
282
478
  path
283
479
  });
284
480
  }
285
- return { layers, head: request.head, pathname, search };
481
+ return { layers, pathname, search };
286
482
  }
287
483
  getErrorHandler(route) {
288
484
  if (route.errorHandler) return route.errorHandler;
@@ -333,8 +529,8 @@ class PageDescriptorProvider {
333
529
  ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
334
530
  }
335
531
  }
336
- renderError(e) {
337
- return React.createElement("pre", { style: { overflow: "auto" } }, `${e.stack}`);
532
+ renderError(error) {
533
+ return React.createElement(ErrorViewer, { error });
338
534
  }
339
535
  renderEmptyView() {
340
536
  return React.createElement(NestedView, {});
@@ -359,7 +555,13 @@ class PageDescriptorProvider {
359
555
  }
360
556
  return path;
361
557
  }
362
- renderView(index, path, view = this.renderEmptyView()) {
558
+ renderView(index, path, view, page) {
559
+ view ??= this.renderEmptyView();
560
+ const element = page.client ? React.createElement(
561
+ ClientOnly,
562
+ typeof page.client === "object" ? page.client : {},
563
+ view
564
+ ) : view;
363
565
  return React.createElement(
364
566
  RouterLayerContext.Provider,
365
567
  {
@@ -368,7 +570,7 @@ class PageDescriptorProvider {
368
570
  path
369
571
  }
370
572
  },
371
- view
573
+ element
372
574
  );
373
575
  }
374
576
  configure = core.$hook({
@@ -377,6 +579,8 @@ class PageDescriptorProvider {
377
579
  const pages = this.alepha.getDescriptorValues($page);
378
580
  for (const { value, key } of pages) {
379
581
  value[core.OPTIONS].name ??= key;
582
+ }
583
+ for (const { value } of pages) {
380
584
  if (value[core.OPTIONS].parent) {
381
585
  continue;
382
586
  }
@@ -386,11 +590,6 @@ class PageDescriptorProvider {
386
590
  });
387
591
  map(pages, target) {
388
592
  const children = target[core.OPTIONS].children ?? [];
389
- for (const it of pages) {
390
- if (it.value[core.OPTIONS].parent === target) {
391
- children.push(it.value);
392
- }
393
- }
394
593
  return {
395
594
  ...target[core.OPTIONS],
396
595
  parent: void 0,
@@ -499,10 +698,17 @@ class BrowserRouterProvider extends router.RouterProvider {
499
698
  const state = {
500
699
  pathname,
501
700
  search,
502
- layers: [],
503
- head: {}
701
+ layers: []
504
702
  };
505
- await this.alepha.emit("react:transition:begin", { state });
703
+ const context = {
704
+ url,
705
+ query: {},
706
+ params: {},
707
+ head: {},
708
+ onError: () => null,
709
+ ...options.context ?? {}
710
+ };
711
+ await this.alepha.emit("react:transition:begin", { state, context });
506
712
  try {
507
713
  const previous = options.previous;
508
714
  const { route, params } = this.match(pathname);
@@ -512,29 +718,22 @@ class BrowserRouterProvider extends router.RouterProvider {
512
718
  query[key] = String(value);
513
719
  }
514
720
  }
721
+ context.query = query;
722
+ context.params = params ?? {};
723
+ context.previous = previous;
515
724
  if (isPageRoute(route)) {
516
725
  const result = await this.pageDescriptorProvider.createLayers(
517
726
  route.page,
518
- {
519
- url,
520
- params: params ?? {},
521
- query,
522
- previous,
523
- ...state,
524
- head: state.head,
525
- ...options.context ?? {}
526
- }
727
+ context
527
728
  );
528
729
  if (result.redirect) {
529
730
  return {
530
- element: null,
531
- layers: [],
532
731
  redirect: result.redirect,
533
- head: state.head
732
+ state,
733
+ context
534
734
  };
535
735
  }
536
736
  state.layers = result.layers;
537
- state.head = result.head;
538
737
  }
539
738
  if (state.layers.length === 0) {
540
739
  state.layers.push({
@@ -557,33 +756,25 @@ class BrowserRouterProvider extends router.RouterProvider {
557
756
  ];
558
757
  await this.alepha.emit("react:transition:error", {
559
758
  error: e,
560
- state
759
+ state,
760
+ context
561
761
  });
562
762
  }
563
- if (!options.state) {
564
- await this.alepha.emit("react:transition:end", {
565
- state
566
- });
567
- return {
568
- element: this.root(state, options.context),
569
- layers: state.layers,
570
- head: state.head
571
- };
763
+ if (options.state) {
764
+ options.state.layers = state.layers;
765
+ options.state.pathname = state.pathname;
766
+ options.state.search = state.search;
572
767
  }
573
- options.state.layers = state.layers;
574
- options.state.pathname = state.pathname;
575
- options.state.search = state.search;
576
- options.state.head = state.head;
577
768
  await this.alepha.emit("react:transition:end", {
578
- state: options.state
769
+ state: options.state,
770
+ context
579
771
  });
580
772
  return {
581
- element: this.root(state, options.context),
582
- layers: options.state.layers,
583
- head: state.head
773
+ context,
774
+ state
584
775
  };
585
776
  }
586
- root(state, context = {}) {
777
+ root(state, context) {
587
778
  return this.pageDescriptorProvider.root(state, context);
588
779
  }
589
780
  }
@@ -603,8 +794,7 @@ class ReactBrowserProvider {
603
794
  state = {
604
795
  layers: [],
605
796
  pathname: "",
606
- search: "",
607
- head: {}
797
+ search: ""
608
798
  };
609
799
  get document() {
610
800
  return window.document;
@@ -645,8 +835,8 @@ class ReactBrowserProvider {
645
835
  const result = await this.render({
646
836
  url
647
837
  });
648
- if (result.url !== url) {
649
- this.history.replaceState({}, "", result.url);
838
+ if (result.context.url.pathname !== url) {
839
+ this.history.replaceState({}, "", result.context.url.pathname);
650
840
  return;
651
841
  }
652
842
  if (options.replace) {
@@ -670,7 +860,7 @@ class ReactBrowserProvider {
670
860
  return await this.render({ url: result.redirect });
671
861
  }
672
862
  this.transitioning = void 0;
673
- return { url, head: result.head };
863
+ return result;
674
864
  }
675
865
  /**
676
866
  * Get embedded layers from the server.
@@ -711,14 +901,16 @@ class ReactBrowserProvider {
711
901
  const hydration = this.getHydrationState();
712
902
  const previous = hydration?.layers ?? [];
713
903
  if (hydration?.links) {
714
- this.client.links = hydration.links;
904
+ for (const link of hydration.links.links) {
905
+ this.client.pushLink(link);
906
+ }
715
907
  }
716
- const { head } = await this.render({ previous });
717
- if (head) {
718
- this.headProvider.renderHead(this.document, head);
908
+ const { context } = await this.render({ previous });
909
+ if (context.head) {
910
+ this.headProvider.renderHead(this.document, context.head);
719
911
  }
720
- const context = {};
721
912
  await this.alepha.emit("react:browser:render", {
913
+ state: this.state,
722
914
  context,
723
915
  hydration
724
916
  });
@@ -734,23 +926,19 @@ class ReactBrowserProvider {
734
926
  window.addEventListener("popstate", () => {
735
927
  this.render();
736
928
  });
737
- this.alepha.on("react:transition:end", {
738
- callback: ({ state }) => {
739
- this.headProvider.renderHead(this.document, state.head);
740
- }
741
- });
742
929
  }
743
930
  });
744
931
  onTransitionEnd = core.$hook({
745
932
  name: "react:transition:end",
746
- handler: async ({ state }) => {
747
- this.headProvider.renderHead(this.document, state.head);
933
+ handler: async ({ context }) => {
934
+ this.headProvider.renderHead(this.document, context.head);
748
935
  }
749
936
  });
750
937
  }
751
938
 
752
939
  class RouterHookApi {
753
- constructor(state, layer, browser) {
940
+ constructor(pages, state, layer, browser) {
941
+ this.pages = pages;
754
942
  this.state = state;
755
943
  this.layer = layer;
756
944
  this.browser = browser;
@@ -804,32 +992,40 @@ class RouterHookApi {
804
992
  * @param pathname
805
993
  * @param layer
806
994
  */
807
- createHref(pathname, layer = this.layer) {
995
+ createHref(pathname, layer = this.layer, options = {}) {
808
996
  if (typeof pathname === "object") {
809
997
  pathname = pathname.options.path ?? "";
810
998
  }
999
+ if (options.params) {
1000
+ for (const [key, value] of Object.entries(options.params)) {
1001
+ pathname = pathname.replace(`:${key}`, String(value));
1002
+ }
1003
+ }
811
1004
  return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
812
1005
  }
813
- /**
814
- *
815
- * @param path
816
- * @param options
817
- */
818
- async go(path, options = {}) {
819
- return await this.browser?.go(this.createHref(path, this.layer), options);
1006
+ async go(path, options) {
1007
+ for (const page of this.pages) {
1008
+ if (page.name === path) {
1009
+ path = page.path ?? "";
1010
+ break;
1011
+ }
1012
+ }
1013
+ await this.browser?.go(this.createHref(path, this.layer, options), options);
820
1014
  }
821
- /**
822
- *
823
- * @param path
824
- */
825
- createAnchorProps(path) {
826
- const href = this.createHref(path, this.layer);
1015
+ anchor(path, options = {}) {
1016
+ for (const page of this.pages) {
1017
+ if (page.name === path) {
1018
+ path = page.path ?? "";
1019
+ break;
1020
+ }
1021
+ }
1022
+ const href = this.createHref(path, this.layer, options);
827
1023
  return {
828
1024
  href,
829
1025
  onClick: (ev) => {
830
1026
  ev.stopPropagation();
831
1027
  ev.preventDefault();
832
- this.go(path).catch(console.error);
1028
+ this.go(path, options).catch(console.error);
833
1029
  }
834
1030
  };
835
1031
  }
@@ -840,14 +1036,8 @@ class RouterHookApi {
840
1036
  * @param options
841
1037
  */
842
1038
  setQueryParams(record, options = {}) {
843
- const search = new URLSearchParams(
844
- options.merge ? {
845
- ...this.query,
846
- ...record
847
- } : {
848
- ...record
849
- }
850
- ).toString();
1039
+ const func = typeof record === "function" ? record : () => record;
1040
+ const search = new URLSearchParams(func(this.query)).toString();
851
1041
  const state = search ? `${this.pathname}?${search}` : this.pathname;
852
1042
  if (options.push) {
853
1043
  window.history.pushState({}, "", state);
@@ -863,8 +1053,12 @@ const useRouter = () => {
863
1053
  if (!ctx || !layer) {
864
1054
  throw new Error("useRouter must be used within a RouterProvider");
865
1055
  }
1056
+ const pages = React.useMemo(() => {
1057
+ return ctx.alepha.get(PageDescriptorProvider).getPages();
1058
+ }, []);
866
1059
  return React.useMemo(
867
1060
  () => new RouterHookApi(
1061
+ pages,
868
1062
  ctx.state,
869
1063
  layer,
870
1064
  ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0
@@ -875,6 +1069,7 @@ const useRouter = () => {
875
1069
 
876
1070
  const Link = (props) => {
877
1071
  React.useContext(RouterContext);
1072
+ const router = useRouter();
878
1073
  const to = typeof props.to === "string" ? props.to : props.to[core.OPTIONS].path;
879
1074
  if (!to) {
880
1075
  return null;
@@ -884,8 +1079,49 @@ const Link = (props) => {
884
1079
  return null;
885
1080
  }
886
1081
  const name = typeof props.to === "string" ? void 0 : props.to[core.OPTIONS].name;
1082
+ const anchorProps = {
1083
+ ...props,
1084
+ to: void 0
1085
+ };
1086
+ return /* @__PURE__ */ jsxRuntime.jsx("a", { ...router.anchor(to), ...anchorProps, children: props.children ?? name });
1087
+ };
1088
+
1089
+ const useActive = (path) => {
887
1090
  const router = useRouter();
888
- return /* @__PURE__ */ jsxRuntime.jsx("a", { ...router.createAnchorProps(to), ...props, children: props.children ?? name });
1091
+ const ctx = React.useContext(RouterContext);
1092
+ const layer = React.useContext(RouterLayerContext);
1093
+ if (!ctx || !layer) {
1094
+ throw new Error("useRouter must be used within a RouterProvider");
1095
+ }
1096
+ let name;
1097
+ if (typeof path === "object" && path.options.name) {
1098
+ name = path.options.name;
1099
+ }
1100
+ const [current, setCurrent] = React.useState(ctx.state.pathname);
1101
+ const href = React.useMemo(() => router.createHref(path, layer), [path, layer]);
1102
+ const [isPending, setPending] = React.useState(false);
1103
+ const isActive = current === href;
1104
+ useRouterEvents({
1105
+ onEnd: ({ state }) => setCurrent(state.pathname)
1106
+ });
1107
+ return {
1108
+ name,
1109
+ isPending,
1110
+ isActive,
1111
+ anchorProps: {
1112
+ href,
1113
+ onClick: (ev) => {
1114
+ ev.stopPropagation();
1115
+ ev.preventDefault();
1116
+ if (isActive) return;
1117
+ if (isPending) return;
1118
+ setPending(true);
1119
+ router.go(href).then(() => {
1120
+ setPending(false);
1121
+ });
1122
+ }
1123
+ }
1124
+ };
889
1125
  };
890
1126
 
891
1127
  const useInject = (clazz) => {
@@ -893,18 +1129,10 @@ const useInject = (clazz) => {
893
1129
  if (!ctx) {
894
1130
  throw new Error("useRouter must be used within a <RouterProvider>");
895
1131
  }
896
- return React.useMemo(
897
- () => ctx.alepha.get(clazz, {
898
- skipRegistration: true
899
- }),
900
- []
901
- );
1132
+ return React.useMemo(() => ctx.alepha.get(clazz), []);
902
1133
  };
903
1134
 
904
- const useClient = () => {
905
- return useInject(server.HttpClient);
906
- };
907
- const useApi = () => {
1135
+ const useClient = (_scope) => {
908
1136
  return useInject(server.HttpClient).of();
909
1137
  };
910
1138
 
@@ -926,12 +1154,9 @@ const useQueryParams = (schema, options = {}) => {
926
1154
  queryParams,
927
1155
  (queryParams2) => {
928
1156
  setQueryParams(queryParams2);
929
- router.setQueryParams(
930
- { [key]: encode(ctx.alepha, schema, queryParams2) },
931
- {
932
- merge: true
933
- }
934
- );
1157
+ router.setQueryParams((data) => {
1158
+ return { ...data, [key]: encode(ctx.alepha, schema, queryParams2) };
1159
+ });
935
1160
  }
936
1161
  ];
937
1162
  };
@@ -941,7 +1166,7 @@ const encode = (alepha, schema, data) => {
941
1166
  const decode = (alepha, schema, data) => {
942
1167
  try {
943
1168
  return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
944
- } catch (error) {
1169
+ } catch (_error) {
945
1170
  return {};
946
1171
  }
947
1172
  };
@@ -959,46 +1184,10 @@ const useRouterState = () => {
959
1184
  return state;
960
1185
  };
961
1186
 
962
- const useActive = (path) => {
963
- const router = useRouter();
964
- const ctx = React.useContext(RouterContext);
965
- const layer = React.useContext(RouterLayerContext);
966
- if (!ctx || !layer) {
967
- throw new Error("useRouter must be used within a RouterProvider");
968
- }
969
- let name;
970
- if (typeof path === "object" && path.options.name) {
971
- name = path.options.name;
972
- }
973
- const [current, setCurrent] = React.useState(ctx.state.pathname);
974
- const href = React.useMemo(() => router.createHref(path, layer), [path, layer]);
975
- const [isPending, setPending] = React.useState(false);
976
- const isActive = current === href;
977
- useRouterEvents({
978
- onEnd: ({ state }) => setCurrent(state.pathname)
979
- });
980
- return {
981
- name,
982
- isPending,
983
- isActive,
984
- anchorProps: {
985
- href,
986
- onClick: (ev) => {
987
- ev.stopPropagation();
988
- ev.preventDefault();
989
- if (isActive) return;
990
- if (isPending) return;
991
- setPending(true);
992
- router.go(href).then(() => {
993
- setPending(false);
994
- });
995
- }
996
- }
997
- };
998
- };
999
-
1000
1187
  exports.$page = $page;
1001
1188
  exports.BrowserRouterProvider = BrowserRouterProvider;
1189
+ exports.ClientOnly = ClientOnly;
1190
+ exports.ErrorBoundary = ErrorBoundary;
1002
1191
  exports.Link = Link;
1003
1192
  exports.NestedView = NestedView;
1004
1193
  exports.PageDescriptorProvider = PageDescriptorProvider;
@@ -1009,11 +1198,10 @@ exports.RouterHookApi = RouterHookApi;
1009
1198
  exports.RouterLayerContext = RouterLayerContext;
1010
1199
  exports.isPageRoute = isPageRoute;
1011
1200
  exports.useActive = useActive;
1012
- exports.useApi = useApi;
1201
+ exports.useAlepha = useAlepha;
1013
1202
  exports.useClient = useClient;
1014
1203
  exports.useInject = useInject;
1015
1204
  exports.useQueryParams = useQueryParams;
1016
1205
  exports.useRouter = useRouter;
1017
1206
  exports.useRouterEvents = useRouterEvents;
1018
1207
  exports.useRouterState = useRouterState;
1019
- //# sourceMappingURL=useActive-4QlZKGbw.cjs.map