@alepha/react 0.7.4 → 0.7.6

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.
@@ -1,960 +0,0 @@
1
- 'use strict';
2
-
3
- var core = require('@alepha/core');
4
- var server = require('@alepha/server');
5
- var router = require('@alepha/router');
6
- var React = require('react');
7
- var jsxRuntime = require('react/jsx-runtime');
8
-
9
- const KEY = "PAGE";
10
- const $page = (options) => {
11
- core.__descriptor(KEY);
12
- if (options.children) {
13
- for (const child of options.children) {
14
- child[core.OPTIONS].parent = {
15
- [core.OPTIONS]: options
16
- };
17
- }
18
- }
19
- if (options.parent) {
20
- options.parent[core.OPTIONS].children ??= [];
21
- options.parent[core.OPTIONS].children.push({
22
- [core.OPTIONS]: options
23
- });
24
- }
25
- return {
26
- [core.KIND]: KEY,
27
- [core.OPTIONS]: options,
28
- render: () => {
29
- throw new core.NotImplementedError(KEY);
30
- }
31
- };
32
- };
33
- $page[core.KIND] = KEY;
34
-
35
- const ClientOnly = (props) => {
36
- const [mounted, setMounted] = React.useState(false);
37
- React.useEffect(() => setMounted(true), []);
38
- if (props.disabled) {
39
- return props.children;
40
- }
41
- return mounted ? props.children : props.fallback;
42
- };
43
-
44
- const RouterContext = React.createContext(
45
- void 0
46
- );
47
-
48
- const useAlepha = () => {
49
- const routerContext = React.useContext(RouterContext);
50
- if (!routerContext) {
51
- throw new Error("useAlepha must be used within a RouterProvider");
52
- }
53
- return routerContext.alepha;
54
- };
55
-
56
- const ErrorViewer = ({ error }) => {
57
- const [expanded, setExpanded] = React.useState(false);
58
- const isProduction = useAlepha().isProduction();
59
- if (isProduction) {
60
- return /* @__PURE__ */ jsxRuntime.jsx(ErrorViewerProduction, {});
61
- }
62
- const stackLines = error.stack?.split("\n") ?? [];
63
- const previewLines = stackLines.slice(0, 5);
64
- const hiddenLineCount = stackLines.length - previewLines.length;
65
- const copyToClipboard = (text) => {
66
- navigator.clipboard.writeText(text).catch((err) => {
67
- console.error("Clipboard error:", err);
68
- });
69
- };
70
- const styles = {
71
- container: {
72
- padding: "24px",
73
- backgroundColor: "#FEF2F2",
74
- color: "#7F1D1D",
75
- border: "1px solid #FECACA",
76
- borderRadius: "16px",
77
- boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
78
- fontFamily: "monospace",
79
- maxWidth: "768px",
80
- margin: "40px auto"
81
- },
82
- heading: {
83
- fontSize: "20px",
84
- fontWeight: "bold",
85
- marginBottom: "4px"
86
- },
87
- name: {
88
- fontSize: "16px",
89
- fontWeight: 600
90
- },
91
- message: {
92
- fontSize: "14px",
93
- marginBottom: "16px"
94
- },
95
- sectionHeader: {
96
- display: "flex",
97
- justifyContent: "space-between",
98
- alignItems: "center",
99
- fontSize: "12px",
100
- marginBottom: "4px",
101
- color: "#991B1B"
102
- },
103
- copyButton: {
104
- fontSize: "12px",
105
- color: "#DC2626",
106
- background: "none",
107
- border: "none",
108
- cursor: "pointer",
109
- textDecoration: "underline"
110
- },
111
- stackContainer: {
112
- backgroundColor: "#FEE2E2",
113
- padding: "12px",
114
- borderRadius: "8px",
115
- fontSize: "13px",
116
- lineHeight: "1.4",
117
- overflowX: "auto",
118
- whiteSpace: "pre-wrap"
119
- },
120
- expandLine: {
121
- color: "#F87171",
122
- cursor: "pointer",
123
- marginTop: "8px"
124
- }
125
- };
126
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.container, children: [
127
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
128
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.heading, children: "\u{1F525} Error" }),
129
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.name, children: error.name }),
130
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.message, children: error.message })
131
- ] }),
132
- stackLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
133
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.sectionHeader, children: [
134
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Stack trace" }),
135
- /* @__PURE__ */ jsxRuntime.jsx(
136
- "button",
137
- {
138
- onClick: () => copyToClipboard(error.stack),
139
- style: styles.copyButton,
140
- children: "Copy all"
141
- }
142
- )
143
- ] }),
144
- /* @__PURE__ */ jsxRuntime.jsxs("pre", { style: styles.stackContainer, children: [
145
- (expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: line }, i)),
146
- !expanded && hiddenLineCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.expandLine, onClick: () => setExpanded(true), children: [
147
- "+ ",
148
- hiddenLineCount,
149
- " more lines..."
150
- ] })
151
- ] })
152
- ] })
153
- ] });
154
- };
155
- const ErrorViewerProduction = () => {
156
- const styles = {
157
- container: {
158
- padding: "24px",
159
- backgroundColor: "#FEF2F2",
160
- color: "#7F1D1D",
161
- border: "1px solid #FECACA",
162
- borderRadius: "16px",
163
- boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
164
- fontFamily: "monospace",
165
- maxWidth: "768px",
166
- margin: "40px auto",
167
- textAlign: "center"
168
- },
169
- heading: {
170
- fontSize: "20px",
171
- fontWeight: "bold",
172
- marginBottom: "8px"
173
- },
174
- message: {
175
- fontSize: "14px",
176
- opacity: 0.85
177
- }
178
- };
179
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.container, children: [
180
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.heading, children: "\u{1F6A8} An error occurred" }),
181
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.message, children: "Something went wrong. Please try again later." })
182
- ] });
183
- };
184
-
185
- const RouterLayerContext = React.createContext(void 0);
186
-
187
- const useRouterEvents = (opts = {}, deps = []) => {
188
- const ctx = React.useContext(RouterContext);
189
- if (!ctx) {
190
- throw new Error("useRouter must be used within a RouterProvider");
191
- }
192
- React.useEffect(() => {
193
- if (!ctx.alepha.isBrowser()) {
194
- return;
195
- }
196
- const subs = [];
197
- const onBegin = opts.onBegin;
198
- const onEnd = opts.onEnd;
199
- const onError = opts.onError;
200
- if (onBegin) {
201
- subs.push(
202
- ctx.alepha.on("react:transition:begin", {
203
- callback: onBegin
204
- })
205
- );
206
- }
207
- if (onEnd) {
208
- subs.push(
209
- ctx.alepha.on("react:transition:end", {
210
- callback: onEnd
211
- })
212
- );
213
- }
214
- if (onError) {
215
- subs.push(
216
- ctx.alepha.on("react:transition:error", {
217
- callback: onError
218
- })
219
- );
220
- }
221
- return () => {
222
- for (const sub of subs) {
223
- sub();
224
- }
225
- };
226
- }, deps);
227
- };
228
-
229
- class ErrorBoundary extends React.Component {
230
- constructor(props) {
231
- super(props);
232
- this.state = {};
233
- }
234
- /**
235
- * Update state so the next render shows the fallback UI.
236
- */
237
- static getDerivedStateFromError(error) {
238
- return {
239
- error
240
- };
241
- }
242
- /**
243
- * Lifecycle method called when an error is caught.
244
- * You can log the error or perform side effects here.
245
- */
246
- componentDidCatch(error, info) {
247
- if (this.props.onError) {
248
- this.props.onError(error, info);
249
- }
250
- }
251
- render() {
252
- if (this.state.error) {
253
- return this.props.fallback(this.state.error);
254
- }
255
- return this.props.children;
256
- }
257
- }
258
-
259
- const NestedView = (props) => {
260
- const app = React.useContext(RouterContext);
261
- const layer = React.useContext(RouterLayerContext);
262
- const index = layer?.index ?? 0;
263
- const [view, setView] = React.useState(
264
- app?.state.layers[index]?.element
265
- );
266
- useRouterEvents(
267
- {
268
- onEnd: ({ state }) => {
269
- setView(state.layers[index]?.element);
270
- }
271
- },
272
- [app]
273
- );
274
- if (!app) {
275
- throw new Error("NestedView must be used within a RouterContext.");
276
- }
277
- const element = view ?? props.children ?? null;
278
- return /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { fallback: app.context.onError, children: element });
279
- };
280
-
281
- function NotFoundPage() {
282
- return /* @__PURE__ */ jsxRuntime.jsxs(
283
- "div",
284
- {
285
- style: {
286
- height: "100vh",
287
- display: "flex",
288
- flexDirection: "column",
289
- justifyContent: "center",
290
- alignItems: "center",
291
- textAlign: "center",
292
- fontFamily: "sans-serif",
293
- padding: "1rem"
294
- },
295
- children: [
296
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "1rem", marginBottom: "0.5rem" }, children: "This page does not exist" }),
297
- /* @__PURE__ */ jsxRuntime.jsx(
298
- "a",
299
- {
300
- href: "/",
301
- style: {
302
- fontSize: "0.7rem",
303
- color: "#007bff",
304
- textDecoration: "none"
305
- },
306
- children: "\u2190 Back to home"
307
- }
308
- )
309
- ]
310
- }
311
- );
312
- }
313
-
314
- class RedirectionError extends Error {
315
- page;
316
- constructor(page) {
317
- super("Redirection");
318
- this.page = page;
319
- }
320
- }
321
-
322
- const envSchema = core.t.object({
323
- REACT_STRICT_MODE: core.t.boolean({ default: true })
324
- });
325
- class PageDescriptorProvider {
326
- log = core.$logger();
327
- env = core.$inject(envSchema);
328
- alepha = core.$inject(core.Alepha);
329
- pages = [];
330
- getPages() {
331
- return this.pages;
332
- }
333
- page(name) {
334
- for (const page of this.pages) {
335
- if (page.name === name) {
336
- return page;
337
- }
338
- }
339
- throw new Error(`Page ${name} not found`);
340
- }
341
- url(name, options = {}) {
342
- const page = this.page(name);
343
- if (!page) {
344
- throw new Error(`Page ${name} not found`);
345
- }
346
- let url = page.path ?? "";
347
- let parent = page.parent;
348
- while (parent) {
349
- url = `${parent.path ?? ""}/${url}`;
350
- parent = parent.parent;
351
- }
352
- url = this.compile(url, options.params ?? {});
353
- return new URL(
354
- url.replace(/\/\/+/g, "/") || "/",
355
- options.base ?? `http://localhost`
356
- );
357
- }
358
- root(state, context) {
359
- const root = React.createElement(
360
- RouterContext.Provider,
361
- {
362
- value: {
363
- alepha: this.alepha,
364
- state,
365
- context
366
- }
367
- },
368
- React.createElement(NestedView, {}, state.layers[0]?.element)
369
- );
370
- if (this.env.REACT_STRICT_MODE) {
371
- return React.createElement(React.StrictMode, {}, root);
372
- }
373
- return root;
374
- }
375
- async createLayers(route, request) {
376
- const { pathname, search } = request.url;
377
- const layers = [];
378
- let context = {};
379
- const stack = [{ route }];
380
- request.onError = (error) => this.renderError(error);
381
- let parent = route.parent;
382
- while (parent) {
383
- stack.unshift({ route: parent });
384
- parent = parent.parent;
385
- }
386
- let forceRefresh = false;
387
- for (let i = 0; i < stack.length; i++) {
388
- const it = stack[i];
389
- const route2 = it.route;
390
- const config = {};
391
- try {
392
- config.query = route2.schema?.query ? this.alepha.parse(route2.schema.query, request.query) : request.query;
393
- } catch (e) {
394
- it.error = e;
395
- break;
396
- }
397
- try {
398
- config.params = route2.schema?.params ? this.alepha.parse(route2.schema.params, request.params) : request.params;
399
- } catch (e) {
400
- it.error = e;
401
- break;
402
- }
403
- it.config = {
404
- ...config
405
- };
406
- if (!route2.resolve) {
407
- continue;
408
- }
409
- const previous = request.previous;
410
- if (previous?.[i] && !forceRefresh && previous[i].name === route2.name) {
411
- const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
412
- const prev = JSON.stringify({
413
- part: url(previous[i].part),
414
- params: previous[i].config?.params ?? {}
415
- });
416
- const curr = JSON.stringify({
417
- part: url(route2.path),
418
- params: config.params ?? {}
419
- });
420
- if (prev === curr) {
421
- it.props = previous[i].props;
422
- it.error = previous[i].error;
423
- context = {
424
- ...context,
425
- ...it.props
426
- };
427
- continue;
428
- }
429
- forceRefresh = true;
430
- }
431
- try {
432
- const props = await route2.resolve?.({
433
- ...request,
434
- // request
435
- ...config,
436
- // params, query
437
- ...context
438
- // previous props
439
- }) ?? {};
440
- it.props = {
441
- ...props
442
- };
443
- context = {
444
- ...context,
445
- ...props
446
- };
447
- } catch (e) {
448
- if (e instanceof RedirectionError) {
449
- return {
450
- layers: [],
451
- redirect: typeof e.page === "string" ? e.page : this.href(e.page),
452
- pathname,
453
- search
454
- };
455
- }
456
- this.log.error(e);
457
- it.error = e;
458
- break;
459
- }
460
- }
461
- let acc = "";
462
- for (let i = 0; i < stack.length; i++) {
463
- const it = stack[i];
464
- const props = it.props ?? {};
465
- const params = { ...it.config?.params };
466
- for (const key of Object.keys(params)) {
467
- params[key] = String(params[key]);
468
- }
469
- if (it.route.head && !it.error) {
470
- this.fillHead(it.route, request, {
471
- ...props,
472
- ...context
473
- });
474
- }
475
- acc += "/";
476
- acc += it.route.path ? this.compile(it.route.path, params) : "";
477
- const path = acc.replace(/\/+/, "/");
478
- const localErrorHandler = this.getErrorHandler(it.route);
479
- if (localErrorHandler) {
480
- request.onError = localErrorHandler;
481
- }
482
- if (it.error) {
483
- let element2 = await request.onError(it.error);
484
- if (element2 === null) {
485
- element2 = this.renderError(it.error);
486
- }
487
- layers.push({
488
- props,
489
- error: it.error,
490
- name: it.route.name,
491
- part: it.route.path,
492
- config: it.config,
493
- element: this.renderView(i + 1, path, element2, it.route),
494
- index: i + 1,
495
- path
496
- });
497
- break;
498
- }
499
- const element = await this.createElement(it.route, {
500
- ...props,
501
- ...context
502
- });
503
- layers.push({
504
- name: it.route.name,
505
- props,
506
- part: it.route.path,
507
- config: it.config,
508
- element: this.renderView(i + 1, path, element, it.route),
509
- index: i + 1,
510
- path
511
- });
512
- }
513
- return { layers, pathname, search };
514
- }
515
- getErrorHandler(route) {
516
- if (route.errorHandler) return route.errorHandler;
517
- let parent = route.parent;
518
- while (parent) {
519
- if (parent.errorHandler) return parent.errorHandler;
520
- parent = parent.parent;
521
- }
522
- }
523
- async createElement(page, props) {
524
- if (page.lazy) {
525
- const component = await page.lazy();
526
- return React.createElement(component.default, props);
527
- }
528
- if (page.component) {
529
- return React.createElement(page.component, props);
530
- }
531
- return void 0;
532
- }
533
- fillHead(page, ctx, props) {
534
- if (!page.head) {
535
- return;
536
- }
537
- ctx.head ??= {};
538
- const head = typeof page.head === "function" ? page.head(props, ctx.head) : page.head;
539
- if (head.title) {
540
- ctx.head ??= {};
541
- if (ctx.head.titleSeparator) {
542
- ctx.head.title = `${head.title}${ctx.head.titleSeparator}${ctx.head.title}`;
543
- } else {
544
- ctx.head.title = head.title;
545
- }
546
- ctx.head.titleSeparator = head.titleSeparator;
547
- }
548
- if (head.htmlAttributes) {
549
- ctx.head.htmlAttributes = {
550
- ...ctx.head.htmlAttributes,
551
- ...head.htmlAttributes
552
- };
553
- }
554
- if (head.bodyAttributes) {
555
- ctx.head.bodyAttributes = {
556
- ...ctx.head.bodyAttributes,
557
- ...head.bodyAttributes
558
- };
559
- }
560
- if (head.meta) {
561
- ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
562
- }
563
- }
564
- renderError(error) {
565
- return React.createElement(ErrorViewer, { error });
566
- }
567
- renderEmptyView() {
568
- return React.createElement(NestedView, {});
569
- }
570
- href(page, params = {}) {
571
- const found = this.pages.find((it) => it.name === page.options.name);
572
- if (!found) {
573
- throw new Error(`Page ${page.options.name} not found`);
574
- }
575
- let url = found.path ?? "";
576
- let parent = found.parent;
577
- while (parent) {
578
- url = `${parent.path ?? ""}/${url}`;
579
- parent = parent.parent;
580
- }
581
- url = this.compile(url, params);
582
- return url.replace(/\/\/+/g, "/") || "/";
583
- }
584
- compile(path, params = {}) {
585
- for (const [key, value] of Object.entries(params)) {
586
- path = path.replace(`:${key}`, value);
587
- }
588
- return path;
589
- }
590
- renderView(index, path, view, page) {
591
- view ??= this.renderEmptyView();
592
- const element = page.client ? React.createElement(
593
- ClientOnly,
594
- typeof page.client === "object" ? page.client : {},
595
- view
596
- ) : view;
597
- return React.createElement(
598
- RouterLayerContext.Provider,
599
- {
600
- value: {
601
- index,
602
- path
603
- }
604
- },
605
- element
606
- );
607
- }
608
- configure = core.$hook({
609
- name: "configure",
610
- handler: () => {
611
- let hasNotFoundHandler = false;
612
- const pages = this.alepha.getDescriptorValues($page);
613
- for (const { value, key } of pages) {
614
- value[core.OPTIONS].name ??= key;
615
- }
616
- for (const { value } of pages) {
617
- if (value[core.OPTIONS].parent) {
618
- continue;
619
- }
620
- if (value[core.OPTIONS].path === "/*") {
621
- hasNotFoundHandler = true;
622
- }
623
- this.add(this.map(pages, value));
624
- }
625
- if (!hasNotFoundHandler && pages.length > 0) {
626
- this.add({
627
- path: "/*",
628
- name: "notFound",
629
- cache: true,
630
- component: NotFoundPage,
631
- afterHandler: ({ reply }) => {
632
- reply.status = 404;
633
- }
634
- });
635
- }
636
- }
637
- });
638
- map(pages, target) {
639
- const children = target[core.OPTIONS].children ?? [];
640
- return {
641
- ...target[core.OPTIONS],
642
- parent: void 0,
643
- children: children.map((it) => this.map(pages, it))
644
- };
645
- }
646
- add(entry) {
647
- if (this.alepha.isReady()) {
648
- throw new Error("Router is already initialized");
649
- }
650
- entry.name ??= this.nextId();
651
- const page = entry;
652
- page.match = this.createMatch(page);
653
- this.pages.push(page);
654
- if (page.children) {
655
- for (const child of page.children) {
656
- child.parent = page;
657
- this.add(child);
658
- }
659
- }
660
- }
661
- createMatch(page) {
662
- let url = page.path ?? "/";
663
- let target = page.parent;
664
- while (target) {
665
- url = `${target.path ?? ""}/${url}`;
666
- target = target.parent;
667
- }
668
- let path = url.replace(/\/\/+/g, "/");
669
- if (path.endsWith("/") && path !== "/") {
670
- path = path.slice(0, -1);
671
- }
672
- return path;
673
- }
674
- _next = 0;
675
- nextId() {
676
- this._next += 1;
677
- return `P${this._next}`;
678
- }
679
- }
680
- const isPageRoute = (it) => {
681
- return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
682
- };
683
-
684
- class BrowserHeadProvider {
685
- renderHead(document, head) {
686
- if (head.title) {
687
- document.title = head.title;
688
- }
689
- if (head.bodyAttributes) {
690
- for (const [key, value] of Object.entries(head.bodyAttributes)) {
691
- if (value) {
692
- document.body.setAttribute(key, value);
693
- } else {
694
- document.body.removeAttribute(key);
695
- }
696
- }
697
- }
698
- if (head.htmlAttributes) {
699
- for (const [key, value] of Object.entries(head.htmlAttributes)) {
700
- if (value) {
701
- document.documentElement.setAttribute(key, value);
702
- } else {
703
- document.documentElement.removeAttribute(key);
704
- }
705
- }
706
- }
707
- if (head.meta) {
708
- for (const [key, value] of Object.entries(head.meta)) {
709
- const meta = document.querySelector(`meta[name="${key}"]`);
710
- if (meta) {
711
- meta.setAttribute("content", value.content);
712
- } else {
713
- const newMeta = document.createElement("meta");
714
- newMeta.setAttribute("name", key);
715
- newMeta.setAttribute("content", value.content);
716
- document.head.appendChild(newMeta);
717
- }
718
- }
719
- }
720
- }
721
- }
722
-
723
- class BrowserRouterProvider extends router.RouterProvider {
724
- log = core.$logger();
725
- alepha = core.$inject(core.Alepha);
726
- pageDescriptorProvider = core.$inject(PageDescriptorProvider);
727
- add(entry) {
728
- this.pageDescriptorProvider.add(entry);
729
- }
730
- configure = core.$hook({
731
- name: "configure",
732
- handler: async () => {
733
- for (const page of this.pageDescriptorProvider.getPages()) {
734
- if (page.component || page.lazy) {
735
- this.push({
736
- path: page.match,
737
- page
738
- });
739
- }
740
- }
741
- }
742
- });
743
- async transition(url, options = {}) {
744
- const { pathname, search } = url;
745
- const state = {
746
- pathname,
747
- search,
748
- layers: []
749
- };
750
- const context = {
751
- url,
752
- query: {},
753
- params: {},
754
- head: {},
755
- onError: () => null,
756
- ...options.context ?? {}
757
- };
758
- await this.alepha.emit("react:transition:begin", { state, context });
759
- try {
760
- const previous = options.previous;
761
- const { route, params } = this.match(pathname);
762
- const query = {};
763
- if (search) {
764
- for (const [key, value] of new URLSearchParams(search).entries()) {
765
- query[key] = String(value);
766
- }
767
- }
768
- context.query = query;
769
- context.params = params ?? {};
770
- context.previous = previous;
771
- if (isPageRoute(route)) {
772
- const result = await this.pageDescriptorProvider.createLayers(
773
- route.page,
774
- context
775
- );
776
- if (result.redirect) {
777
- return {
778
- redirect: result.redirect,
779
- state,
780
- context
781
- };
782
- }
783
- state.layers = result.layers;
784
- }
785
- if (state.layers.length === 0) {
786
- state.layers.push({
787
- name: "not-found",
788
- element: React.createElement(NotFoundPage),
789
- index: 0,
790
- path: "/"
791
- });
792
- }
793
- await this.alepha.emit("react:transition:success", { state });
794
- } catch (e) {
795
- this.log.error(e);
796
- state.layers = [
797
- {
798
- name: "error",
799
- element: this.pageDescriptorProvider.renderError(e),
800
- index: 0,
801
- path: "/"
802
- }
803
- ];
804
- await this.alepha.emit("react:transition:error", {
805
- error: e,
806
- state,
807
- context
808
- });
809
- }
810
- if (options.state) {
811
- options.state.layers = state.layers;
812
- options.state.pathname = state.pathname;
813
- options.state.search = state.search;
814
- }
815
- await this.alepha.emit("react:transition:end", {
816
- state: options.state,
817
- context
818
- });
819
- return {
820
- context,
821
- state
822
- };
823
- }
824
- root(state, context) {
825
- return this.pageDescriptorProvider.root(state, context);
826
- }
827
- }
828
-
829
- class ReactBrowserProvider {
830
- log = core.$logger();
831
- client = core.$inject(server.HttpClient);
832
- alepha = core.$inject(core.Alepha);
833
- router = core.$inject(BrowserRouterProvider);
834
- headProvider = core.$inject(BrowserHeadProvider);
835
- root;
836
- transitioning;
837
- state = {
838
- layers: [],
839
- pathname: "",
840
- search: ""
841
- };
842
- get document() {
843
- return window.document;
844
- }
845
- get history() {
846
- return window.history;
847
- }
848
- get url() {
849
- return window.location.pathname + window.location.search;
850
- }
851
- async invalidate(props) {
852
- const previous = [];
853
- if (props) {
854
- const [key] = Object.keys(props);
855
- const value = props[key];
856
- for (const layer of this.state.layers) {
857
- if (layer.props?.[key]) {
858
- previous.push({
859
- ...layer,
860
- props: {
861
- ...layer.props,
862
- [key]: value
863
- }
864
- });
865
- break;
866
- }
867
- previous.push(layer);
868
- }
869
- }
870
- await this.render({ previous });
871
- }
872
- async go(url, options = {}) {
873
- const result = await this.render({
874
- url
875
- });
876
- if (result.context.url.pathname !== url) {
877
- this.history.replaceState({}, "", result.context.url.pathname);
878
- return;
879
- }
880
- if (options.replace) {
881
- this.history.replaceState({}, "", url);
882
- return;
883
- }
884
- this.history.pushState({}, "", url);
885
- }
886
- async render(options = {}) {
887
- const previous = options.previous ?? this.state.layers;
888
- const url = options.url ?? this.url;
889
- this.transitioning = { to: url };
890
- const result = await this.router.transition(
891
- new URL(`http://localhost${url}`),
892
- {
893
- previous,
894
- state: this.state
895
- }
896
- );
897
- if (result.redirect) {
898
- return await this.render({ url: result.redirect });
899
- }
900
- this.transitioning = void 0;
901
- return result;
902
- }
903
- /**
904
- * Get embedded layers from the server.
905
- */
906
- getHydrationState() {
907
- try {
908
- if ("__ssr" in window && typeof window.__ssr === "object") {
909
- return window.__ssr;
910
- }
911
- } catch (error) {
912
- console.error(error);
913
- }
914
- }
915
- // -------------------------------------------------------------------------------------------------------------------
916
- ready = core.$hook({
917
- name: "ready",
918
- handler: async () => {
919
- const hydration = this.getHydrationState();
920
- const previous = hydration?.layers ?? [];
921
- if (hydration?.links) {
922
- for (const link of hydration.links.links) {
923
- this.client.pushLink(link);
924
- }
925
- }
926
- const { context } = await this.render({ previous });
927
- if (context.head) {
928
- this.headProvider.renderHead(this.document, context.head);
929
- }
930
- await this.alepha.emit("react:browser:render", {
931
- state: this.state,
932
- context,
933
- hydration
934
- });
935
- window.addEventListener("popstate", () => {
936
- this.render();
937
- });
938
- }
939
- });
940
- onTransitionEnd = core.$hook({
941
- name: "react:transition:end",
942
- handler: async ({ context }) => {
943
- this.headProvider.renderHead(this.document, context.head);
944
- }
945
- });
946
- }
947
-
948
- exports.$page = $page;
949
- exports.BrowserRouterProvider = BrowserRouterProvider;
950
- exports.ClientOnly = ClientOnly;
951
- exports.ErrorBoundary = ErrorBoundary;
952
- exports.NestedView = NestedView;
953
- exports.PageDescriptorProvider = PageDescriptorProvider;
954
- exports.ReactBrowserProvider = ReactBrowserProvider;
955
- exports.RedirectionError = RedirectionError;
956
- exports.RouterContext = RouterContext;
957
- exports.RouterLayerContext = RouterLayerContext;
958
- exports.isPageRoute = isPageRoute;
959
- exports.useAlepha = useAlepha;
960
- exports.useRouterEvents = useRouterEvents;