@alepha/react 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/index.browser.js +196 -77
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +203 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +240 -195
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +219 -174
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -81
- package/dist/index.js.map +1 -1
- package/package.json +14 -12
- package/src/components/Link.tsx +2 -5
- package/src/components/NestedView.tsx +159 -16
- package/src/descriptors/$page.ts +84 -1
- package/src/hooks/useActive.ts +0 -1
- package/src/hooks/useRouterEvents.ts +27 -19
- package/src/index.browser.ts +3 -0
- package/src/index.ts +6 -1
- package/src/providers/ReactBrowserProvider.ts +19 -14
- package/src/providers/ReactBrowserRendererProvider.ts +22 -0
- package/src/providers/ReactBrowserRouterProvider.ts +8 -3
- package/src/providers/ReactPageProvider.ts +46 -1
- package/src/providers/ReactServerProvider.ts +22 -3
- package/src/services/ReactRouter.ts +5 -8
package/README.md
CHANGED
|
@@ -10,12 +10,6 @@ This package is part of the Alepha framework and can be installed via the all-in
|
|
|
10
10
|
npm install alepha
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Alternatively, you can install it individually:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install @alepha/core @alepha/react
|
|
17
|
-
```
|
|
18
|
-
|
|
19
13
|
## Module
|
|
20
14
|
|
|
21
15
|
Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
|
|
@@ -24,16 +18,34 @@ The React module enables building modern React applications using the `$page` de
|
|
|
24
18
|
It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
25
19
|
type safety and schema validation for route parameters and data.
|
|
26
20
|
|
|
21
|
+
This module can be imported and used as follows:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Alepha, run } from "alepha";
|
|
25
|
+
import { AlephaReact } from "alepha/react";
|
|
26
|
+
|
|
27
|
+
const alepha = Alepha.create()
|
|
28
|
+
.with(AlephaReact);
|
|
29
|
+
|
|
30
|
+
run(alepha);
|
|
31
|
+
```
|
|
32
|
+
|
|
27
33
|
## API Reference
|
|
28
34
|
|
|
29
35
|
### Descriptors
|
|
30
36
|
|
|
37
|
+
Descriptors are functions that define and configure various aspects of your application. They follow the convention of starting with `$` and return configured descriptor instances.
|
|
38
|
+
|
|
39
|
+
For more details, see the [Descriptors documentation](https://feunard.github.io/alepha/docs/descriptors).
|
|
40
|
+
|
|
31
41
|
#### $page()
|
|
32
42
|
|
|
33
43
|
Main descriptor for defining a React route in the application.
|
|
34
44
|
|
|
35
45
|
### Hooks
|
|
36
46
|
|
|
47
|
+
Hooks provide a way to tap into various lifecycle events and extend functionality. They follow the convention of starting with `use` and return configured hook instances.
|
|
48
|
+
|
|
37
49
|
#### useAlepha()
|
|
38
50
|
|
|
39
51
|
Main Alepha hook.
|
package/dist/index.browser.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
|
|
1
|
+
import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, TypeGuard, createDescriptor, t } from "@alepha/core";
|
|
2
2
|
import { AlephaServer, HttpClient } from "@alepha/server";
|
|
3
3
|
import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
|
|
4
4
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
5
|
import { $logger } from "@alepha/logger";
|
|
6
|
-
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
7
6
|
import { RouterProvider } from "@alepha/router";
|
|
8
|
-
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
9
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import React, { StrictMode, createContext, createElement, memo, use, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
10
10
|
|
|
11
11
|
//#region src/descriptors/$page.ts
|
|
12
12
|
/**
|
|
@@ -30,7 +30,10 @@ var PageDescriptor = class extends Descriptor {
|
|
|
30
30
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
31
31
|
*/
|
|
32
32
|
async render(options) {
|
|
33
|
-
throw new
|
|
33
|
+
throw new AlephaError("render() method is not implemented in this environment");
|
|
34
|
+
}
|
|
35
|
+
async fetch(options) {
|
|
36
|
+
throw new AlephaError("fetch() method is not implemented in this environment");
|
|
34
37
|
}
|
|
35
38
|
match(url) {
|
|
36
39
|
return false;
|
|
@@ -84,7 +87,6 @@ const ClientOnly = (props) => {
|
|
|
84
87
|
if (props.disabled) return props.children;
|
|
85
88
|
return mounted ? props.children : props.fallback;
|
|
86
89
|
};
|
|
87
|
-
var ClientOnly_default = ClientOnly;
|
|
88
90
|
|
|
89
91
|
//#endregion
|
|
90
92
|
//#region src/components/ErrorViewer.tsx
|
|
@@ -192,7 +194,6 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
192
194
|
})] })]
|
|
193
195
|
});
|
|
194
196
|
};
|
|
195
|
-
var ErrorViewer_default = ErrorViewer;
|
|
196
197
|
const ErrorViewerProduction = () => {
|
|
197
198
|
const styles = {
|
|
198
199
|
container: {
|
|
@@ -286,19 +287,55 @@ const useRouterEvents = (opts = {}, deps = []) => {
|
|
|
286
287
|
const alepha = useAlepha();
|
|
287
288
|
useEffect(() => {
|
|
288
289
|
if (!alepha.isBrowser()) return;
|
|
290
|
+
const cb = (callback) => {
|
|
291
|
+
if (typeof callback === "function") return { callback };
|
|
292
|
+
return callback;
|
|
293
|
+
};
|
|
289
294
|
const subs = [];
|
|
290
295
|
const onBegin = opts.onBegin;
|
|
291
296
|
const onEnd = opts.onEnd;
|
|
292
297
|
const onError = opts.onError;
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
if (
|
|
298
|
+
const onSuccess = opts.onSuccess;
|
|
299
|
+
if (onBegin) subs.push(alepha.on("react:transition:begin", cb(onBegin)));
|
|
300
|
+
if (onEnd) subs.push(alepha.on("react:transition:end", cb(onEnd)));
|
|
301
|
+
if (onError) subs.push(alepha.on("react:transition:error", cb(onError)));
|
|
302
|
+
if (onSuccess) subs.push(alepha.on("react:transition:success", cb(onSuccess)));
|
|
296
303
|
return () => {
|
|
297
304
|
for (const sub of subs) sub();
|
|
298
305
|
};
|
|
299
306
|
}, deps);
|
|
300
307
|
};
|
|
301
308
|
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/hooks/useStore.ts
|
|
311
|
+
/**
|
|
312
|
+
* Hook to access and mutate the Alepha state.
|
|
313
|
+
*/
|
|
314
|
+
const useStore = (key, defaultValue) => {
|
|
315
|
+
const alepha = useAlepha();
|
|
316
|
+
useMemo(() => {
|
|
317
|
+
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
318
|
+
}, [defaultValue]);
|
|
319
|
+
const [state, setState] = useState(alepha.state(key));
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
if (!alepha.isBrowser()) return;
|
|
322
|
+
return alepha.on("state:mutate", (ev) => {
|
|
323
|
+
if (ev.key === key) setState(ev.value);
|
|
324
|
+
});
|
|
325
|
+
}, []);
|
|
326
|
+
return [state, (value) => {
|
|
327
|
+
alepha.state(key, value);
|
|
328
|
+
}];
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/hooks/useRouterState.ts
|
|
333
|
+
const useRouterState = () => {
|
|
334
|
+
const [state] = useStore("react.router.state");
|
|
335
|
+
if (!state) throw new AlephaError("Missing react router state");
|
|
336
|
+
return state;
|
|
337
|
+
};
|
|
338
|
+
|
|
302
339
|
//#endregion
|
|
303
340
|
//#region src/components/ErrorBoundary.tsx
|
|
304
341
|
/**
|
|
@@ -328,7 +365,6 @@ var ErrorBoundary = class extends React.Component {
|
|
|
328
365
|
return this.props.children;
|
|
329
366
|
}
|
|
330
367
|
};
|
|
331
|
-
var ErrorBoundary_default = ErrorBoundary;
|
|
332
368
|
|
|
333
369
|
//#endregion
|
|
334
370
|
//#region src/components/NestedView.tsx
|
|
@@ -339,7 +375,7 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
339
375
|
*
|
|
340
376
|
* @example
|
|
341
377
|
* ```tsx
|
|
342
|
-
* import { NestedView } from "
|
|
378
|
+
* import { NestedView } from "alepha/react";
|
|
343
379
|
*
|
|
344
380
|
* class App {
|
|
345
381
|
* parent = $page({
|
|
@@ -354,17 +390,69 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
354
390
|
* ```
|
|
355
391
|
*/
|
|
356
392
|
const NestedView = (props) => {
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
const alepha = useAlepha();
|
|
360
|
-
const state = alepha.state("react.router.state");
|
|
361
|
-
if (!state) throw new Error("<NestedView/> must be used inside a RouterLayerContext.");
|
|
393
|
+
const index = use(RouterLayerContext)?.index ?? 0;
|
|
394
|
+
const state = useRouterState();
|
|
362
395
|
const [view, setView] = useState(state.layers[index]?.element);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
396
|
+
const [animation, setAnimation] = useState("");
|
|
397
|
+
const animationExitDuration = useRef(0);
|
|
398
|
+
const animationExitNow = useRef(0);
|
|
399
|
+
useRouterEvents({
|
|
400
|
+
onBegin: async ({ previous, state: state$1 }) => {
|
|
401
|
+
const layer = previous.layers[index];
|
|
402
|
+
if (`${state$1.url.pathname}/`.startsWith(`${layer?.path}/`)) return;
|
|
403
|
+
const animationExit = parseAnimation(layer.route?.animation, state$1, "exit");
|
|
404
|
+
if (animationExit) {
|
|
405
|
+
const duration = animationExit.duration || 200;
|
|
406
|
+
animationExitNow.current = Date.now();
|
|
407
|
+
animationExitDuration.current = duration;
|
|
408
|
+
setAnimation(animationExit.animation);
|
|
409
|
+
} else {
|
|
410
|
+
animationExitNow.current = 0;
|
|
411
|
+
animationExitDuration.current = 0;
|
|
412
|
+
setAnimation("");
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
onEnd: async ({ state: state$1 }) => {
|
|
416
|
+
const layer = state$1.layers[index];
|
|
417
|
+
if (animationExitNow.current) {
|
|
418
|
+
const duration = animationExitDuration.current;
|
|
419
|
+
const diff = Date.now() - animationExitNow.current;
|
|
420
|
+
if (diff < duration) await new Promise((resolve) => setTimeout(resolve, duration - diff));
|
|
421
|
+
}
|
|
422
|
+
if (!layer?.cache) {
|
|
423
|
+
setView(layer?.element);
|
|
424
|
+
const animationEnter = parseAnimation(layer?.route?.animation, state$1, "enter");
|
|
425
|
+
if (animationEnter) setAnimation(animationEnter.animation);
|
|
426
|
+
else setAnimation("");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}, []);
|
|
430
|
+
let element = view ?? props.children ?? null;
|
|
431
|
+
if (animation) element = /* @__PURE__ */ jsx("div", {
|
|
432
|
+
style: {
|
|
433
|
+
display: "flex",
|
|
434
|
+
flex: 1,
|
|
435
|
+
height: "100%",
|
|
436
|
+
width: "100%",
|
|
437
|
+
position: "relative",
|
|
438
|
+
overflow: "hidden"
|
|
439
|
+
},
|
|
440
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
441
|
+
style: {
|
|
442
|
+
height: "100%",
|
|
443
|
+
width: "100%",
|
|
444
|
+
display: "flex",
|
|
445
|
+
animation
|
|
446
|
+
},
|
|
447
|
+
children: element
|
|
448
|
+
})
|
|
449
|
+
});
|
|
450
|
+
if (props.errorBoundary === false) return /* @__PURE__ */ jsx(Fragment, { children: element });
|
|
451
|
+
if (props.errorBoundary) return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
452
|
+
fallback: props.errorBoundary,
|
|
453
|
+
children: element
|
|
454
|
+
});
|
|
455
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
368
456
|
fallback: (error) => {
|
|
369
457
|
const result = state.onError(error, state);
|
|
370
458
|
if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
|
|
@@ -373,7 +461,37 @@ const NestedView = (props) => {
|
|
|
373
461
|
children: element
|
|
374
462
|
});
|
|
375
463
|
};
|
|
376
|
-
var NestedView_default = NestedView;
|
|
464
|
+
var NestedView_default = memo(NestedView);
|
|
465
|
+
function parseAnimation(animationLike, state, type = "enter") {
|
|
466
|
+
if (!animationLike) return void 0;
|
|
467
|
+
const DEFAULT_DURATION = 300;
|
|
468
|
+
const animation = typeof animationLike === "function" ? animationLike(state) : animationLike;
|
|
469
|
+
if (typeof animation === "string") {
|
|
470
|
+
if (type === "exit") return;
|
|
471
|
+
return {
|
|
472
|
+
duration: DEFAULT_DURATION,
|
|
473
|
+
animation: `${DEFAULT_DURATION}ms ${animation}`
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (typeof animation === "object") {
|
|
477
|
+
const anim = animation[type];
|
|
478
|
+
const duration = typeof anim === "object" ? anim.duration ?? DEFAULT_DURATION : DEFAULT_DURATION;
|
|
479
|
+
const name = typeof anim === "object" ? anim.name : anim;
|
|
480
|
+
if (type === "exit") {
|
|
481
|
+
const timing$1 = typeof anim === "object" ? anim.timing ?? "" : "";
|
|
482
|
+
return {
|
|
483
|
+
duration,
|
|
484
|
+
animation: `${duration}ms ${timing$1} ${name}`
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const timing = typeof anim === "object" ? anim.timing ?? "" : "";
|
|
488
|
+
return {
|
|
489
|
+
duration,
|
|
490
|
+
animation: `${duration}ms ${timing} ${name}`
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return void 0;
|
|
494
|
+
}
|
|
377
495
|
|
|
378
496
|
//#endregion
|
|
379
497
|
//#region src/providers/ReactPageProvider.ts
|
|
@@ -414,6 +532,14 @@ var ReactPageProvider = class {
|
|
|
414
532
|
if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
|
|
415
533
|
return root;
|
|
416
534
|
}
|
|
535
|
+
convertStringObjectToObject = (schema, value) => {
|
|
536
|
+
if (TypeGuard.IsObject(schema) && typeof value === "object") {
|
|
537
|
+
for (const key in schema.properties) if (TypeGuard.IsObject(schema.properties[key]) && typeof value[key] === "string") try {
|
|
538
|
+
value[key] = this.alepha.parse(schema.properties[key], decodeURIComponent(value[key]));
|
|
539
|
+
} catch (e) {}
|
|
540
|
+
}
|
|
541
|
+
return value;
|
|
542
|
+
};
|
|
417
543
|
/**
|
|
418
544
|
* Create a new RouterState based on a given route and request.
|
|
419
545
|
* This method resolves the layers for the route, applying any query and params schemas defined in the route.
|
|
@@ -433,6 +559,7 @@ var ReactPageProvider = class {
|
|
|
433
559
|
const route$1 = it.route;
|
|
434
560
|
const config = {};
|
|
435
561
|
try {
|
|
562
|
+
this.convertStringObjectToObject(route$1.schema?.query, state.query);
|
|
436
563
|
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, state.query) : {};
|
|
437
564
|
} catch (e) {
|
|
438
565
|
it.error = e;
|
|
@@ -559,6 +686,7 @@ var ReactPageProvider = class {
|
|
|
559
686
|
}
|
|
560
687
|
}
|
|
561
688
|
async createElement(page, props) {
|
|
689
|
+
if (page.lazy && page.component) this.log.warn(`Page ${page.name} has both lazy and component options, lazy will be used`);
|
|
562
690
|
if (page.lazy) {
|
|
563
691
|
const component = await page.lazy();
|
|
564
692
|
return createElement(component.default, props);
|
|
@@ -567,7 +695,7 @@ var ReactPageProvider = class {
|
|
|
567
695
|
return void 0;
|
|
568
696
|
}
|
|
569
697
|
renderError(error) {
|
|
570
|
-
return createElement(
|
|
698
|
+
return createElement(ErrorViewer, {
|
|
571
699
|
error,
|
|
572
700
|
alepha: this.alepha
|
|
573
701
|
});
|
|
@@ -593,7 +721,7 @@ var ReactPageProvider = class {
|
|
|
593
721
|
}
|
|
594
722
|
renderView(index, path, view, page) {
|
|
595
723
|
view ??= this.renderEmptyView();
|
|
596
|
-
const element = page.client ? createElement(
|
|
724
|
+
const element = page.client ? createElement(ClientOnly, typeof page.client === "object" ? page.client : {}, view) : view;
|
|
597
725
|
return createElement(RouterLayerContext.Provider, { value: {
|
|
598
726
|
index,
|
|
599
727
|
path
|
|
@@ -692,17 +820,21 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
692
820
|
});
|
|
693
821
|
}
|
|
694
822
|
});
|
|
695
|
-
async transition(url, previous = []) {
|
|
823
|
+
async transition(url, previous = [], meta = {}) {
|
|
696
824
|
const { pathname, search } = url;
|
|
697
825
|
const entry = {
|
|
698
826
|
url,
|
|
699
827
|
query: {},
|
|
700
828
|
params: {},
|
|
701
829
|
layers: [],
|
|
702
|
-
onError: () => null
|
|
830
|
+
onError: () => null,
|
|
831
|
+
meta
|
|
703
832
|
};
|
|
704
833
|
const state = entry;
|
|
705
|
-
await this.alepha.emit("react:transition:begin", {
|
|
834
|
+
await this.alepha.emit("react:transition:begin", {
|
|
835
|
+
previous: this.alepha.state("react.router.state"),
|
|
836
|
+
state
|
|
837
|
+
});
|
|
706
838
|
try {
|
|
707
839
|
const { route, params } = this.match(pathname);
|
|
708
840
|
const query = {};
|
|
@@ -737,8 +869,8 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
737
869
|
const layer = previous[i];
|
|
738
870
|
if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
|
|
739
871
|
}
|
|
740
|
-
await this.alepha.emit("react:transition:end", { state });
|
|
741
872
|
this.alepha.state("react.router.state", state);
|
|
873
|
+
await this.alepha.emit("react:transition:end", { state });
|
|
742
874
|
}
|
|
743
875
|
root(state) {
|
|
744
876
|
return this.pageApi.root(state);
|
|
@@ -755,7 +887,6 @@ var ReactBrowserProvider = class {
|
|
|
755
887
|
alepha = $inject(Alepha);
|
|
756
888
|
router = $inject(ReactBrowserRouterProvider);
|
|
757
889
|
dateTimeProvider = $inject(DateTimeProvider);
|
|
758
|
-
root;
|
|
759
890
|
options = { scrollRestoration: "top" };
|
|
760
891
|
getRootElement() {
|
|
761
892
|
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
@@ -831,7 +962,8 @@ var ReactBrowserProvider = class {
|
|
|
831
962
|
});
|
|
832
963
|
await this.render({
|
|
833
964
|
url,
|
|
834
|
-
previous: options.force ? [] : this.state.layers
|
|
965
|
+
previous: options.force ? [] : this.state.layers,
|
|
966
|
+
meta: options.meta
|
|
835
967
|
});
|
|
836
968
|
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
837
969
|
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
@@ -848,7 +980,7 @@ var ReactBrowserProvider = class {
|
|
|
848
980
|
from: this.state?.url.pathname
|
|
849
981
|
};
|
|
850
982
|
this.log.debug("Transitioning...", { to: url });
|
|
851
|
-
const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous);
|
|
983
|
+
const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous, options.meta);
|
|
852
984
|
if (redirect) {
|
|
853
985
|
this.log.info("Redirecting to", { redirect });
|
|
854
986
|
return await this.render({ url: redirect });
|
|
@@ -870,7 +1002,7 @@ var ReactBrowserProvider = class {
|
|
|
870
1002
|
onTransitionEnd = $hook({
|
|
871
1003
|
on: "react:transition:end",
|
|
872
1004
|
handler: () => {
|
|
873
|
-
if (this.options.scrollRestoration === "top" && typeof window !== "undefined") {
|
|
1005
|
+
if (this.options.scrollRestoration === "top" && typeof window !== "undefined" && !this.alepha.isTest()) {
|
|
874
1006
|
this.log.trace("Restoring scroll position to top");
|
|
875
1007
|
window.scrollTo(0, 0);
|
|
876
1008
|
}
|
|
@@ -886,19 +1018,37 @@ var ReactBrowserProvider = class {
|
|
|
886
1018
|
}
|
|
887
1019
|
await this.render({ previous });
|
|
888
1020
|
const element = this.router.root(this.state);
|
|
1021
|
+
await this.alepha.emit("react:browser:render", {
|
|
1022
|
+
element,
|
|
1023
|
+
root: this.getRootElement(),
|
|
1024
|
+
hydration,
|
|
1025
|
+
state: this.state
|
|
1026
|
+
});
|
|
1027
|
+
window.addEventListener("popstate", () => {
|
|
1028
|
+
if (this.base + this.state.url.pathname === this.location.pathname) return;
|
|
1029
|
+
this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
|
|
1030
|
+
this.render();
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/providers/ReactBrowserRendererProvider.ts
|
|
1038
|
+
var ReactBrowserRendererProvider = class {
|
|
1039
|
+
log = $logger();
|
|
1040
|
+
root;
|
|
1041
|
+
onBrowserRender = $hook({
|
|
1042
|
+
on: "react:browser:render",
|
|
1043
|
+
handler: async ({ hydration, root, element }) => {
|
|
889
1044
|
if (hydration?.layers) {
|
|
890
|
-
this.root = hydrateRoot(
|
|
1045
|
+
this.root = hydrateRoot(root, element);
|
|
891
1046
|
this.log.info("Hydrated root element");
|
|
892
1047
|
} else {
|
|
893
|
-
this.root ??= createRoot(
|
|
1048
|
+
this.root ??= createRoot(root);
|
|
894
1049
|
this.root.render(element);
|
|
895
1050
|
this.log.info("Created root element");
|
|
896
1051
|
}
|
|
897
|
-
window.addEventListener("popstate", () => {
|
|
898
|
-
if (this.base + this.state.url.pathname === this.location.pathname) return;
|
|
899
|
-
this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
|
|
900
|
-
this.render();
|
|
901
|
-
});
|
|
902
1052
|
}
|
|
903
1053
|
});
|
|
904
1054
|
};
|
|
@@ -1032,44 +1182,12 @@ const useRouter = () => {
|
|
|
1032
1182
|
//#region src/components/Link.tsx
|
|
1033
1183
|
const Link = (props) => {
|
|
1034
1184
|
const router = useRouter();
|
|
1035
|
-
const { to,...anchorProps } = props;
|
|
1036
1185
|
return /* @__PURE__ */ jsx("a", {
|
|
1037
|
-
...
|
|
1038
|
-
...
|
|
1186
|
+
...props,
|
|
1187
|
+
...router.anchor(props.href),
|
|
1039
1188
|
children: props.children
|
|
1040
1189
|
});
|
|
1041
1190
|
};
|
|
1042
|
-
var Link_default = Link;
|
|
1043
|
-
|
|
1044
|
-
//#endregion
|
|
1045
|
-
//#region src/hooks/useStore.ts
|
|
1046
|
-
/**
|
|
1047
|
-
* Hook to access and mutate the Alepha state.
|
|
1048
|
-
*/
|
|
1049
|
-
const useStore = (key, defaultValue) => {
|
|
1050
|
-
const alepha = useAlepha();
|
|
1051
|
-
useMemo(() => {
|
|
1052
|
-
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
1053
|
-
}, [defaultValue]);
|
|
1054
|
-
const [state, setState] = useState(alepha.state(key));
|
|
1055
|
-
useEffect(() => {
|
|
1056
|
-
if (!alepha.isBrowser()) return;
|
|
1057
|
-
return alepha.on("state:mutate", (ev) => {
|
|
1058
|
-
if (ev.key === key) setState(ev.value);
|
|
1059
|
-
});
|
|
1060
|
-
}, []);
|
|
1061
|
-
return [state, (value) => {
|
|
1062
|
-
alepha.state(key, value);
|
|
1063
|
-
}];
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
//#endregion
|
|
1067
|
-
//#region src/hooks/useRouterState.ts
|
|
1068
|
-
const useRouterState = () => {
|
|
1069
|
-
const [state] = useStore("react.router.state");
|
|
1070
|
-
if (!state) throw new AlephaError("Missing react router state");
|
|
1071
|
-
return state;
|
|
1072
|
-
};
|
|
1073
1191
|
|
|
1074
1192
|
//#endregion
|
|
1075
1193
|
//#region src/hooks/useActive.ts
|
|
@@ -1196,11 +1314,12 @@ const AlephaReact = $module({
|
|
|
1196
1314
|
ReactPageProvider,
|
|
1197
1315
|
ReactBrowserRouterProvider,
|
|
1198
1316
|
ReactBrowserProvider,
|
|
1199
|
-
ReactRouter
|
|
1317
|
+
ReactRouter,
|
|
1318
|
+
ReactBrowserRendererProvider
|
|
1200
1319
|
],
|
|
1201
|
-
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactRouter)
|
|
1320
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
|
|
1202
1321
|
});
|
|
1203
1322
|
|
|
1204
1323
|
//#endregion
|
|
1205
|
-
export { $page, AlephaContext, AlephaReact,
|
|
1324
|
+
export { $page, AlephaContext, AlephaReact, ClientOnly, ErrorBoundary, ErrorViewer, Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, ReactBrowserProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactRouter, Redirection, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
|
|
1206
1325
|
//# sourceMappingURL=index.browser.js.map
|