@alepha/react 0.7.0 → 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.
- package/README.md +1 -1
- package/dist/index.browser.cjs +21 -21
- package/dist/index.browser.js +2 -3
- package/dist/index.cjs +151 -83
- package/dist/index.d.ts +360 -205
- package/dist/index.js +129 -62
- package/dist/{useActive-DjpZBEuB.cjs → useRouterState-AdK-XeM2.cjs} +270 -81
- package/dist/{useActive-BX41CqY8.js → useRouterState-qoMq7Y9J.js} +272 -84
- package/package.json +11 -10
- package/src/components/ClientOnly.tsx +35 -0
- package/src/components/ErrorBoundary.tsx +1 -1
- package/src/components/ErrorViewer.tsx +161 -0
- package/src/components/Link.tsx +9 -3
- package/src/components/NestedView.tsx +18 -3
- package/src/descriptors/$page.ts +139 -30
- package/src/errors/RedirectionError.ts +4 -1
- package/src/hooks/RouterHookApi.ts +42 -5
- package/src/hooks/useAlepha.ts +12 -0
- package/src/hooks/useClient.ts +8 -6
- package/src/hooks/useInject.ts +2 -2
- package/src/hooks/useQueryParams.ts +1 -1
- package/src/hooks/useRouter.ts +6 -0
- package/src/index.browser.ts +1 -1
- package/src/index.shared.ts +11 -5
- package/src/index.ts +3 -4
- package/src/providers/BrowserRouterProvider.ts +1 -1
- package/src/providers/PageDescriptorProvider.ts +72 -21
- package/src/providers/ReactBrowserProvider.ts +5 -8
- package/src/providers/ReactServerProvider.ts +197 -80
- package/dist/index.browser.cjs.map +0 -1
- package/dist/index.browser.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/useActive-BX41CqY8.js.map +0 -1
- package/dist/useActive-DjpZBEuB.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { __descriptor, OPTIONS, NotImplementedError, KIND, $logger, $inject, Alepha, $hook
|
|
3
|
-
import React, {
|
|
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 = []) => {
|
|
@@ -144,14 +278,19 @@ const NestedView = (props) => {
|
|
|
144
278
|
};
|
|
145
279
|
|
|
146
280
|
class RedirectionError extends Error {
|
|
281
|
+
page;
|
|
147
282
|
constructor(page) {
|
|
148
283
|
super("Redirection");
|
|
149
284
|
this.page = page;
|
|
150
285
|
}
|
|
151
286
|
}
|
|
152
287
|
|
|
288
|
+
const envSchema$1 = t.object({
|
|
289
|
+
REACT_STRICT_MODE: t.boolean({ default: true })
|
|
290
|
+
});
|
|
153
291
|
class PageDescriptorProvider {
|
|
154
292
|
log = $logger();
|
|
293
|
+
env = $inject(envSchema$1);
|
|
155
294
|
alepha = $inject(Alepha);
|
|
156
295
|
pages = [];
|
|
157
296
|
getPages() {
|
|
@@ -165,8 +304,25 @@ class PageDescriptorProvider {
|
|
|
165
304
|
}
|
|
166
305
|
throw new Error(`Page ${name} not found`);
|
|
167
306
|
}
|
|
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
|
+
}
|
|
168
324
|
root(state, context) {
|
|
169
|
-
|
|
325
|
+
const root = createElement(
|
|
170
326
|
RouterContext.Provider,
|
|
171
327
|
{
|
|
172
328
|
value: {
|
|
@@ -177,13 +333,17 @@ class PageDescriptorProvider {
|
|
|
177
333
|
},
|
|
178
334
|
createElement(NestedView, {}, state.layers[0]?.element)
|
|
179
335
|
);
|
|
336
|
+
if (this.env.REACT_STRICT_MODE) {
|
|
337
|
+
return createElement(StrictMode, {}, root);
|
|
338
|
+
}
|
|
339
|
+
return root;
|
|
180
340
|
}
|
|
181
341
|
async createLayers(route, request) {
|
|
182
342
|
const { pathname, search } = request.url;
|
|
183
343
|
const layers = [];
|
|
184
344
|
let context = {};
|
|
185
345
|
const stack = [{ route }];
|
|
186
|
-
|
|
346
|
+
request.onError = (error) => this.renderError(error);
|
|
187
347
|
let parent = route.parent;
|
|
188
348
|
while (parent) {
|
|
189
349
|
stack.unshift({ route: parent });
|
|
@@ -283,23 +443,26 @@ class PageDescriptorProvider {
|
|
|
283
443
|
const path = acc.replace(/\/+/, "/");
|
|
284
444
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
285
445
|
if (localErrorHandler) {
|
|
286
|
-
onError = localErrorHandler;
|
|
446
|
+
request.onError = localErrorHandler;
|
|
287
447
|
}
|
|
288
448
|
if (it.error) {
|
|
289
|
-
|
|
449
|
+
let element2 = await request.onError(it.error);
|
|
450
|
+
if (element2 === null) {
|
|
451
|
+
element2 = this.renderError(it.error);
|
|
452
|
+
}
|
|
290
453
|
layers.push({
|
|
291
454
|
props,
|
|
292
455
|
error: it.error,
|
|
293
456
|
name: it.route.name,
|
|
294
457
|
part: it.route.path,
|
|
295
458
|
config: it.config,
|
|
296
|
-
element: this.renderView(i + 1, path,
|
|
459
|
+
element: this.renderView(i + 1, path, element2, it.route),
|
|
297
460
|
index: i + 1,
|
|
298
461
|
path
|
|
299
462
|
});
|
|
300
463
|
break;
|
|
301
464
|
}
|
|
302
|
-
const
|
|
465
|
+
const element = await this.createElement(it.route, {
|
|
303
466
|
...props,
|
|
304
467
|
...context
|
|
305
468
|
});
|
|
@@ -308,7 +471,7 @@ class PageDescriptorProvider {
|
|
|
308
471
|
props,
|
|
309
472
|
part: it.route.path,
|
|
310
473
|
config: it.config,
|
|
311
|
-
element: this.renderView(i + 1, path,
|
|
474
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
312
475
|
index: i + 1,
|
|
313
476
|
path
|
|
314
477
|
});
|
|
@@ -364,8 +527,8 @@ class PageDescriptorProvider {
|
|
|
364
527
|
ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
|
|
365
528
|
}
|
|
366
529
|
}
|
|
367
|
-
renderError(
|
|
368
|
-
return createElement(
|
|
530
|
+
renderError(error) {
|
|
531
|
+
return createElement(ErrorViewer, { error });
|
|
369
532
|
}
|
|
370
533
|
renderEmptyView() {
|
|
371
534
|
return createElement(NestedView, {});
|
|
@@ -390,7 +553,13 @@ class PageDescriptorProvider {
|
|
|
390
553
|
}
|
|
391
554
|
return path;
|
|
392
555
|
}
|
|
393
|
-
renderView(index, path, view
|
|
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;
|
|
394
563
|
return createElement(
|
|
395
564
|
RouterLayerContext.Provider,
|
|
396
565
|
{
|
|
@@ -399,7 +568,7 @@ class PageDescriptorProvider {
|
|
|
399
568
|
path
|
|
400
569
|
}
|
|
401
570
|
},
|
|
402
|
-
|
|
571
|
+
element
|
|
403
572
|
);
|
|
404
573
|
}
|
|
405
574
|
configure = $hook({
|
|
@@ -408,6 +577,8 @@ class PageDescriptorProvider {
|
|
|
408
577
|
const pages = this.alepha.getDescriptorValues($page);
|
|
409
578
|
for (const { value, key } of pages) {
|
|
410
579
|
value[OPTIONS].name ??= key;
|
|
580
|
+
}
|
|
581
|
+
for (const { value } of pages) {
|
|
411
582
|
if (value[OPTIONS].parent) {
|
|
412
583
|
continue;
|
|
413
584
|
}
|
|
@@ -417,11 +588,6 @@ class PageDescriptorProvider {
|
|
|
417
588
|
});
|
|
418
589
|
map(pages, target) {
|
|
419
590
|
const children = target[OPTIONS].children ?? [];
|
|
420
|
-
for (const it of pages) {
|
|
421
|
-
if (it.value[OPTIONS].parent === target) {
|
|
422
|
-
children.push(it.value);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
591
|
return {
|
|
426
592
|
...target[OPTIONS],
|
|
427
593
|
parent: void 0,
|
|
@@ -733,7 +899,7 @@ class ReactBrowserProvider {
|
|
|
733
899
|
const hydration = this.getHydrationState();
|
|
734
900
|
const previous = hydration?.layers ?? [];
|
|
735
901
|
if (hydration?.links) {
|
|
736
|
-
for (const link of hydration.links) {
|
|
902
|
+
for (const link of hydration.links.links) {
|
|
737
903
|
this.client.pushLink(link);
|
|
738
904
|
}
|
|
739
905
|
}
|
|
@@ -769,7 +935,8 @@ class ReactBrowserProvider {
|
|
|
769
935
|
}
|
|
770
936
|
|
|
771
937
|
class RouterHookApi {
|
|
772
|
-
constructor(state, layer, browser) {
|
|
938
|
+
constructor(pages, state, layer, browser) {
|
|
939
|
+
this.pages = pages;
|
|
773
940
|
this.state = state;
|
|
774
941
|
this.layer = layer;
|
|
775
942
|
this.browser = browser;
|
|
@@ -823,23 +990,40 @@ class RouterHookApi {
|
|
|
823
990
|
* @param pathname
|
|
824
991
|
* @param layer
|
|
825
992
|
*/
|
|
826
|
-
createHref(pathname, layer = this.layer) {
|
|
993
|
+
createHref(pathname, layer = this.layer, options = {}) {
|
|
827
994
|
if (typeof pathname === "object") {
|
|
828
995
|
pathname = pathname.options.path ?? "";
|
|
829
996
|
}
|
|
997
|
+
if (options.params) {
|
|
998
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
999
|
+
pathname = pathname.replace(`:${key}`, String(value));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
830
1002
|
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
831
1003
|
}
|
|
832
1004
|
async go(path, options) {
|
|
833
|
-
|
|
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);
|
|
834
1012
|
}
|
|
835
|
-
anchor(path) {
|
|
836
|
-
const
|
|
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);
|
|
837
1021
|
return {
|
|
838
1022
|
href,
|
|
839
1023
|
onClick: (ev) => {
|
|
840
1024
|
ev.stopPropagation();
|
|
841
1025
|
ev.preventDefault();
|
|
842
|
-
this.go(path).catch(console.error);
|
|
1026
|
+
this.go(path, options).catch(console.error);
|
|
843
1027
|
}
|
|
844
1028
|
};
|
|
845
1029
|
}
|
|
@@ -867,8 +1051,12 @@ const useRouter = () => {
|
|
|
867
1051
|
if (!ctx || !layer) {
|
|
868
1052
|
throw new Error("useRouter must be used within a RouterProvider");
|
|
869
1053
|
}
|
|
1054
|
+
const pages = useMemo(() => {
|
|
1055
|
+
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
1056
|
+
}, []);
|
|
870
1057
|
return useMemo(
|
|
871
1058
|
() => new RouterHookApi(
|
|
1059
|
+
pages,
|
|
872
1060
|
ctx.state,
|
|
873
1061
|
layer,
|
|
874
1062
|
ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0
|
|
@@ -879,6 +1067,7 @@ const useRouter = () => {
|
|
|
879
1067
|
|
|
880
1068
|
const Link = (props) => {
|
|
881
1069
|
React.useContext(RouterContext);
|
|
1070
|
+
const router = useRouter();
|
|
882
1071
|
const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
|
|
883
1072
|
if (!to) {
|
|
884
1073
|
return null;
|
|
@@ -888,8 +1077,49 @@ const Link = (props) => {
|
|
|
888
1077
|
return null;
|
|
889
1078
|
}
|
|
890
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) => {
|
|
891
1088
|
const router = useRouter();
|
|
892
|
-
|
|
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
|
+
};
|
|
893
1123
|
};
|
|
894
1124
|
|
|
895
1125
|
const useInject = (clazz) => {
|
|
@@ -900,10 +1130,7 @@ const useInject = (clazz) => {
|
|
|
900
1130
|
return useMemo(() => ctx.alepha.get(clazz), []);
|
|
901
1131
|
};
|
|
902
1132
|
|
|
903
|
-
const useClient = () => {
|
|
904
|
-
return useInject(HttpClient);
|
|
905
|
-
};
|
|
906
|
-
const useApi = () => {
|
|
1133
|
+
const useClient = (_scope) => {
|
|
907
1134
|
return useInject(HttpClient).of();
|
|
908
1135
|
};
|
|
909
1136
|
|
|
@@ -937,7 +1164,7 @@ const encode = (alepha, schema, data) => {
|
|
|
937
1164
|
const decode = (alepha, schema, data) => {
|
|
938
1165
|
try {
|
|
939
1166
|
return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
|
|
940
|
-
} catch (
|
|
1167
|
+
} catch (_error) {
|
|
941
1168
|
return {};
|
|
942
1169
|
}
|
|
943
1170
|
};
|
|
@@ -955,43 +1182,4 @@ const useRouterState = () => {
|
|
|
955
1182
|
return state;
|
|
956
1183
|
};
|
|
957
1184
|
|
|
958
|
-
|
|
959
|
-
const router = useRouter();
|
|
960
|
-
const ctx = useContext(RouterContext);
|
|
961
|
-
const layer = useContext(RouterLayerContext);
|
|
962
|
-
if (!ctx || !layer) {
|
|
963
|
-
throw new Error("useRouter must be used within a RouterProvider");
|
|
964
|
-
}
|
|
965
|
-
let name;
|
|
966
|
-
if (typeof path === "object" && path.options.name) {
|
|
967
|
-
name = path.options.name;
|
|
968
|
-
}
|
|
969
|
-
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
970
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
971
|
-
const [isPending, setPending] = useState(false);
|
|
972
|
-
const isActive = current === href;
|
|
973
|
-
useRouterEvents({
|
|
974
|
-
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
975
|
-
});
|
|
976
|
-
return {
|
|
977
|
-
name,
|
|
978
|
-
isPending,
|
|
979
|
-
isActive,
|
|
980
|
-
anchorProps: {
|
|
981
|
-
href,
|
|
982
|
-
onClick: (ev) => {
|
|
983
|
-
ev.stopPropagation();
|
|
984
|
-
ev.preventDefault();
|
|
985
|
-
if (isActive) return;
|
|
986
|
-
if (isPending) return;
|
|
987
|
-
setPending(true);
|
|
988
|
-
router.go(href).then(() => {
|
|
989
|
-
setPending(false);
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
export { $page as $, BrowserRouterProvider as B, ErrorBoundary as E, 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 };
|
|
997
|
-
//# sourceMappingURL=useActive-BX41CqY8.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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alepha/react",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,25 +13,26 @@
|
|
|
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-static": "0.7.
|
|
16
|
+
"@alepha/core": "0.7.1",
|
|
17
|
+
"@alepha/router": "0.7.1",
|
|
18
|
+
"@alepha/server": "0.7.1",
|
|
19
|
+
"@alepha/server-static": "0.7.1",
|
|
20
20
|
"react-dom": "^19.1.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@types/react": "^19.1.
|
|
24
|
-
"@types/react-dom": "^19.1.
|
|
25
|
-
"pkgroll": "^2.
|
|
23
|
+
"@types/react": "^19.1.8",
|
|
24
|
+
"@types/react-dom": "^19.1.6",
|
|
25
|
+
"pkgroll": "^2.13.1",
|
|
26
26
|
"react": "^19.1.0",
|
|
27
|
-
"vitest": "^3.
|
|
27
|
+
"vitest": "^3.2.4"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@types/react": "^19",
|
|
31
31
|
"react": "^19"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"build": "pkgroll --clean-dist"
|
|
35
36
|
},
|
|
36
37
|
"homepage": "https://github.com/feunard/alepha",
|
|
37
38
|
"repository": {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PropsWithChildren,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
export interface ClientOnlyProps {
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A small utility component that renders its children only on the client side.
|
|
15
|
+
*
|
|
16
|
+
* Optionally, you can provide a fallback React node that will be rendered.
|
|
17
|
+
*
|
|
18
|
+
* You should use this component when
|
|
19
|
+
* - you have code that relies on browser-specific APIs
|
|
20
|
+
* - you want to avoid server-side rendering for a specific part of your application
|
|
21
|
+
* - you want to prevent pre-rendering of a component
|
|
22
|
+
*/
|
|
23
|
+
const ClientOnly = (props: PropsWithChildren<ClientOnlyProps>) => {
|
|
24
|
+
const [mounted, setMounted] = useState(false);
|
|
25
|
+
|
|
26
|
+
useEffect(() => setMounted(true), []);
|
|
27
|
+
|
|
28
|
+
if (props.disabled) {
|
|
29
|
+
return props.children;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return mounted ? props.children : props.fallback;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default ClientOnly;
|