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