routerino 2.3.4-rc9 → 2.4.0
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 +15 -0
- package/dist/routerino.js +134 -125
- package/dist/routerino.umd.cjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,7 @@ This simple configuration automatically handles routing, meta tags, and SEO opti
|
|
|
91
91
|
- Enhanced User Experience
|
|
92
92
|
- Support for sharing and social preview metadata
|
|
93
93
|
- Snappy page transitions with automatic scroll reset, eliminating the jarring experience of landing mid-page when navigating
|
|
94
|
+
- Hash link support: SPA navigation to `/page#section` correctly scrolls to the target element, mimicking native browser behavior
|
|
94
95
|
|
|
95
96
|
## Installation
|
|
96
97
|
|
|
@@ -812,6 +813,20 @@ This creates an engaging Twitter preview with a large image, title, and descript
|
|
|
812
813
|
|
|
813
814
|
By following these practices, you'll improve your site's SEO performance and social media presence when using Routerino.
|
|
814
815
|
|
|
816
|
+
### Hash Links
|
|
817
|
+
|
|
818
|
+
Routerino supports standard `<a href="/page#section">` links for SPA navigation — after React renders the new page, it finds the element with the matching `id` and scrolls it into view.
|
|
819
|
+
|
|
820
|
+
**Sticky headers**: If your site has a fixed header, use the CSS [`scroll-margin-top`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin-top) property to offset the scroll target so content isn't hidden behind the header:
|
|
821
|
+
|
|
822
|
+
```css
|
|
823
|
+
[id] {
|
|
824
|
+
scroll-margin-top: 80px; /* match your header height */
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
This works for SPA navigation, native browser hash navigation, and direct page loads — no extra props or router changes needed.
|
|
829
|
+
|
|
815
830
|
## Sitemap and robots.txt Generation
|
|
816
831
|
|
|
817
832
|
When using Routerino Forge for static site generation, `sitemap.xml` and `robots.txt` files are automatically generated during the build process:
|
package/dist/routerino.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { jsx as g, jsxs as
|
|
2
|
-
import { useState as
|
|
1
|
+
import { jsx as g, jsxs as B, Fragment as G } from "react/jsx-runtime";
|
|
2
|
+
import { useState as z, useEffect as _, useMemo as J, createContext as N, Component as Q, useContext as X } from "react";
|
|
3
3
|
import o from "prop-types";
|
|
4
4
|
const Y = [480, 800, 1200, 1920], ee = "(max-width: 480px) 100vw, (max-width: 800px) 800px, (max-width: 1200px) 1200px, 1920px", H = /* @__PURE__ */ new Map();
|
|
5
5
|
function te(i, e = "") {
|
|
6
6
|
const r = i.toLowerCase(), n = e.toLowerCase();
|
|
7
7
|
return !!(r.includes("hero") || r.includes("banner") || n.includes("hero") || n.includes("banner") || n.includes("h-screen") || n.includes("h-full"));
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function q(i, e, r = null) {
|
|
10
10
|
const n = i.replace(/\.(jpe?g|png|webp)$/i, ""), u = r ? `.${r}` : i.match(/\.(jpe?g|png|webp)$/i)?.[0] || ".jpg";
|
|
11
11
|
return e.map((a) => `${n}-${a}w${u} ${a}w`).join(", ");
|
|
12
12
|
}
|
|
@@ -17,53 +17,53 @@ function oe(i) {
|
|
|
17
17
|
priority: n,
|
|
18
18
|
widths: u = Y,
|
|
19
19
|
sizes: a = ee,
|
|
20
|
-
className:
|
|
20
|
+
className: m = "",
|
|
21
21
|
style: O = {},
|
|
22
|
-
width:
|
|
23
|
-
height:
|
|
24
|
-
loading:
|
|
22
|
+
width: E,
|
|
23
|
+
height: S,
|
|
24
|
+
loading: I,
|
|
25
25
|
decoding: L = "async",
|
|
26
|
-
fetchpriority:
|
|
27
|
-
...
|
|
28
|
-
} = i || {},
|
|
29
|
-
|
|
26
|
+
fetchpriority: f,
|
|
27
|
+
...T
|
|
28
|
+
} = i || {}, W = typeof window > "u", b = !W && typeof document < "u" && typeof HTMLElement < "u" && document.createElement("div") instanceof HTMLElement, R = b && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"), c = n ?? te(e, m), k = I || (c ? "eager" : "lazy"), P = f || (c ? "high" : void 0), [y, s] = z(null);
|
|
29
|
+
_(() => {
|
|
30
30
|
if (!b || R || !e) return;
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const h = new window.Image();
|
|
32
|
+
h.onload = () => {
|
|
33
33
|
s({
|
|
34
|
-
width:
|
|
35
|
-
height:
|
|
34
|
+
width: h.naturalWidth,
|
|
35
|
+
height: h.naturalHeight
|
|
36
36
|
});
|
|
37
|
-
},
|
|
37
|
+
}, h.src = e;
|
|
38
38
|
}, [e, b, R]);
|
|
39
|
-
const v = J(() => y ? u.filter((
|
|
40
|
-
|
|
39
|
+
const v = J(() => y ? u.filter((h) => y.width >= h) : u, [y, u]), [j, F] = z(null);
|
|
40
|
+
_(() => {
|
|
41
41
|
if (!b || R) return;
|
|
42
|
-
let
|
|
42
|
+
let h = !1;
|
|
43
43
|
return (async () => {
|
|
44
|
-
const
|
|
45
|
-
for (const
|
|
46
|
-
const C = `${
|
|
44
|
+
const x = e.replace(/\.(jpe?g|png|webp)$/i, ""), A = e.match(/\.(jpe?g|png|webp)$/i)?.[0] || ".jpg", D = [];
|
|
45
|
+
for (const M of v) {
|
|
46
|
+
const C = `${x}-${M}w${A}`;
|
|
47
47
|
if (H.has(C)) {
|
|
48
|
-
H.get(C) && D.push(
|
|
48
|
+
H.get(C) && D.push(M);
|
|
49
49
|
continue;
|
|
50
50
|
}
|
|
51
51
|
try {
|
|
52
|
-
const
|
|
53
|
-
H.set(C,
|
|
52
|
+
const V = await fetch(C, { method: "HEAD" });
|
|
53
|
+
H.set(C, V.ok), V.ok && D.push(M);
|
|
54
54
|
} catch {
|
|
55
55
|
H.set(C, !1);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
h || F(
|
|
59
59
|
D.length > 0 ? D : v
|
|
60
60
|
);
|
|
61
61
|
})(), () => {
|
|
62
|
-
|
|
62
|
+
h = !0;
|
|
63
63
|
};
|
|
64
64
|
}, [e, v, b, R]);
|
|
65
|
-
const
|
|
66
|
-
|
|
65
|
+
const U = j ?? v, t = {};
|
|
66
|
+
E != null && (t.width = E), S != null && (t.height = S), y && E == null && S == null && (t.width = y.width, t.height = y.height);
|
|
67
67
|
const l = {
|
|
68
68
|
maxWidth: "100%",
|
|
69
69
|
height: "auto",
|
|
@@ -75,21 +75,21 @@ function oe(i) {
|
|
|
75
75
|
{
|
|
76
76
|
src: e,
|
|
77
77
|
alt: r,
|
|
78
|
-
loading:
|
|
78
|
+
loading: k,
|
|
79
79
|
decoding: L,
|
|
80
|
-
fetchPriority:
|
|
81
|
-
className:
|
|
80
|
+
fetchPriority: P,
|
|
81
|
+
className: m,
|
|
82
82
|
style: l,
|
|
83
83
|
...t,
|
|
84
|
-
...
|
|
84
|
+
...T
|
|
85
85
|
}
|
|
86
86
|
);
|
|
87
|
-
if (
|
|
88
|
-
return /* @__PURE__ */
|
|
87
|
+
if (W)
|
|
88
|
+
return /* @__PURE__ */ B("picture", { "data-routerino-image": "true", "data-original-src": e, children: [
|
|
89
89
|
/* @__PURE__ */ g(
|
|
90
90
|
"source",
|
|
91
91
|
{
|
|
92
|
-
srcSet:
|
|
92
|
+
srcSet: q(e, u, "webp"),
|
|
93
93
|
type: "image/webp",
|
|
94
94
|
sizes: a
|
|
95
95
|
}
|
|
@@ -99,35 +99,35 @@ function oe(i) {
|
|
|
99
99
|
{
|
|
100
100
|
src: e,
|
|
101
101
|
alt: r,
|
|
102
|
-
srcSet:
|
|
102
|
+
srcSet: q(e, u),
|
|
103
103
|
sizes: a,
|
|
104
|
-
loading:
|
|
104
|
+
loading: k,
|
|
105
105
|
decoding: "async",
|
|
106
|
-
fetchPriority:
|
|
107
|
-
className:
|
|
106
|
+
fetchPriority: P,
|
|
107
|
+
className: m,
|
|
108
108
|
style: l,
|
|
109
109
|
...t,
|
|
110
|
-
...
|
|
110
|
+
...T
|
|
111
111
|
}
|
|
112
112
|
)
|
|
113
113
|
] });
|
|
114
|
-
const
|
|
115
|
-
return /* @__PURE__ */
|
|
116
|
-
/* @__PURE__ */ g("source", { srcSet:
|
|
114
|
+
const w = q(e, U, "webp"), d = q(e, U);
|
|
115
|
+
return /* @__PURE__ */ B("picture", { "data-routerino-image": "true", "data-original-src": e, children: [
|
|
116
|
+
/* @__PURE__ */ g("source", { srcSet: w, type: "image/webp", sizes: a }),
|
|
117
117
|
/* @__PURE__ */ g(
|
|
118
118
|
"img",
|
|
119
119
|
{
|
|
120
120
|
src: e,
|
|
121
121
|
alt: r,
|
|
122
|
-
srcSet:
|
|
122
|
+
srcSet: d,
|
|
123
123
|
sizes: a,
|
|
124
|
-
loading:
|
|
124
|
+
loading: k,
|
|
125
125
|
decoding: L,
|
|
126
|
-
fetchPriority:
|
|
127
|
-
className:
|
|
126
|
+
fetchPriority: P,
|
|
127
|
+
className: m,
|
|
128
128
|
style: l,
|
|
129
129
|
...t,
|
|
130
|
-
...
|
|
130
|
+
...T
|
|
131
131
|
}
|
|
132
132
|
)
|
|
133
133
|
] });
|
|
@@ -158,9 +158,9 @@ oe.propTypes = {
|
|
|
158
158
|
/** Fetch priority (auto-set based on priority) */
|
|
159
159
|
fetchpriority: o.oneOf(["high", "low", "auto"])
|
|
160
160
|
};
|
|
161
|
-
const
|
|
161
|
+
const K = N(null);
|
|
162
162
|
function le() {
|
|
163
|
-
const i = X(
|
|
163
|
+
const i = X(K);
|
|
164
164
|
if (!i)
|
|
165
165
|
throw new Error(
|
|
166
166
|
"useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component."
|
|
@@ -182,11 +182,11 @@ function p({ tag: i = "meta", soft: e = !1, ...r }) {
|
|
|
182
182
|
}
|
|
183
183
|
function ne({ routePattern: i, currentRoute: e }) {
|
|
184
184
|
let r = {}, n = i.split("/"), u = e.split("/");
|
|
185
|
-
return n.forEach((a,
|
|
186
|
-
a.startsWith(":") && (r[a.slice(1)] = u[
|
|
185
|
+
return n.forEach((a, m) => {
|
|
186
|
+
a.startsWith(":") && (r[a.slice(1)] = u[m]);
|
|
187
187
|
}), r;
|
|
188
188
|
}
|
|
189
|
-
class
|
|
189
|
+
class Z extends Q {
|
|
190
190
|
constructor(e) {
|
|
191
191
|
super(e), this.state = { hasError: !1 };
|
|
192
192
|
}
|
|
@@ -205,7 +205,7 @@ class K extends Q {
|
|
|
205
205
|
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
Z.propTypes = {
|
|
209
209
|
/** The child components to render when there's no error */
|
|
210
210
|
children: o.node,
|
|
211
211
|
/** The fallback UI to display when an error is caught */
|
|
@@ -229,30 +229,30 @@ function re({
|
|
|
229
229
|
tags: [{ property: "og:locale", content: "en_US" }]
|
|
230
230
|
}
|
|
231
231
|
],
|
|
232
|
-
notFoundTemplate: e = /* @__PURE__ */
|
|
232
|
+
notFoundTemplate: e = /* @__PURE__ */ B(G, { children: [
|
|
233
233
|
/* @__PURE__ */ g("p", { children: "No page found for this URL. [404]" }),
|
|
234
234
|
/* @__PURE__ */ g("p", { children: /* @__PURE__ */ g("a", { href: "/", children: "Home" }) })
|
|
235
235
|
] }),
|
|
236
236
|
notFoundTitle: r = "Page not found [404]",
|
|
237
|
-
errorTemplate: n = /* @__PURE__ */
|
|
237
|
+
errorTemplate: n = /* @__PURE__ */ B(G, { children: [
|
|
238
238
|
/* @__PURE__ */ g("p", { children: "Page failed to load. [500]" }),
|
|
239
239
|
/* @__PURE__ */ g("p", { children: /* @__PURE__ */ g("a", { href: "/", children: "Home" }) })
|
|
240
240
|
] }),
|
|
241
241
|
errorTitle: u = "Page error [500]",
|
|
242
242
|
useTrailingSlash: a = !0,
|
|
243
|
-
usePrerenderTags:
|
|
243
|
+
usePrerenderTags: m = !1,
|
|
244
244
|
baseUrl: O = null,
|
|
245
|
-
title:
|
|
246
|
-
separator:
|
|
247
|
-
imageUrl:
|
|
245
|
+
title: E = "",
|
|
246
|
+
separator: S = " | ",
|
|
247
|
+
imageUrl: I = null,
|
|
248
248
|
touchIconUrl: L = null,
|
|
249
|
-
debug:
|
|
249
|
+
debug: f = !1
|
|
250
250
|
}) {
|
|
251
|
-
const
|
|
251
|
+
const T = `${u}${S}${E}`, W = `${r}${S}${E}`;
|
|
252
252
|
try {
|
|
253
|
-
if (
|
|
254
|
-
const t = i.map((
|
|
255
|
-
(
|
|
253
|
+
if (f) {
|
|
254
|
+
const t = i.map((w) => w.path), l = t.filter(
|
|
255
|
+
(w, d) => t.indexOf(w) !== d
|
|
256
256
|
);
|
|
257
257
|
l.length > 0 && (console.warn(
|
|
258
258
|
"%c[Routerino]%c Duplicate route paths detected:",
|
|
@@ -265,79 +265,88 @@ function re({
|
|
|
265
265
|
""
|
|
266
266
|
));
|
|
267
267
|
}
|
|
268
|
-
const [b, R] =
|
|
269
|
-
|
|
268
|
+
const [b, R] = z(window?.location?.href ?? "/");
|
|
269
|
+
_(() => {
|
|
270
270
|
if (typeof window > "u" || typeof document > "u")
|
|
271
271
|
return;
|
|
272
|
-
const t = (
|
|
273
|
-
|
|
272
|
+
const t = (w) => {
|
|
273
|
+
f && console.debug(
|
|
274
274
|
"%c[Routerino]%c click occurred",
|
|
275
275
|
"color: #6b7280; font-weight: bold",
|
|
276
276
|
""
|
|
277
277
|
);
|
|
278
|
-
let
|
|
279
|
-
for (;
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
278
|
+
let d = w.target;
|
|
279
|
+
for (; d.tagName !== "A" && d.parentElement; )
|
|
280
|
+
d = d.parentElement;
|
|
281
|
+
if (d.tagName !== "A") {
|
|
282
|
+
f && console.debug(
|
|
283
283
|
"%c[Routerino]%c no anchor tag found during click",
|
|
284
284
|
"color: #6b7280; font-weight: bold",
|
|
285
285
|
""
|
|
286
286
|
);
|
|
287
287
|
return;
|
|
288
288
|
}
|
|
289
|
-
const
|
|
290
|
-
if (!
|
|
291
|
-
|
|
289
|
+
const h = d.getAttribute("href") || d.href;
|
|
290
|
+
if (!h) {
|
|
291
|
+
f && console.debug(
|
|
292
292
|
"%c[Routerino]%c anchor tag has no href",
|
|
293
293
|
"color: #6b7280; font-weight: bold",
|
|
294
294
|
""
|
|
295
295
|
);
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
|
-
if (!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(
|
|
299
|
-
|
|
298
|
+
if (!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(h)) {
|
|
299
|
+
f && console.debug(
|
|
300
300
|
"%c[Routerino]%c skipping non-http URL:",
|
|
301
301
|
"color: #6b7280; font-weight: bold",
|
|
302
302
|
"",
|
|
303
|
-
|
|
303
|
+
h
|
|
304
304
|
);
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
|
-
|
|
307
|
+
f && console.debug(
|
|
308
308
|
"%c[Routerino]%c click target href:",
|
|
309
309
|
"color: #6b7280; font-weight: bold",
|
|
310
310
|
"",
|
|
311
|
-
|
|
311
|
+
h
|
|
312
312
|
);
|
|
313
313
|
let $;
|
|
314
314
|
try {
|
|
315
|
-
$ = new URL(
|
|
316
|
-
} catch (
|
|
317
|
-
|
|
315
|
+
$ = new URL(h, window.location.href);
|
|
316
|
+
} catch (x) {
|
|
317
|
+
f && console.debug(
|
|
318
318
|
"%c[Routerino]%c Invalid URL:",
|
|
319
319
|
"color: #6b7280; font-weight: bold",
|
|
320
320
|
"",
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
h,
|
|
322
|
+
x
|
|
323
323
|
);
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
326
|
-
|
|
326
|
+
if (f && console.debug(
|
|
327
327
|
"%c[Routerino]%c targetUrl:",
|
|
328
328
|
"color: #6b7280; font-weight: bold",
|
|
329
329
|
"",
|
|
330
330
|
$,
|
|
331
331
|
"current:",
|
|
332
332
|
window.location
|
|
333
|
-
), $ && window.location.origin === $.origin
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
333
|
+
), $ && window.location.origin === $.origin)
|
|
334
|
+
if (f && console.debug(
|
|
335
|
+
"%c[Routerino]%c target link is same origin, will use push-state transitioning",
|
|
336
|
+
"color: #6b7280; font-weight: bold",
|
|
337
|
+
""
|
|
338
|
+
), w.preventDefault(), d.href !== window.location.href && (R(d.href), window.history.pushState({}, "", d.href)), d.hash) {
|
|
339
|
+
const x = decodeURIComponent(d.hash.slice(1));
|
|
340
|
+
setTimeout(() => {
|
|
341
|
+
const A = document.getElementById(x);
|
|
342
|
+
A ? A.scrollIntoView({ behavior: "auto" }) : window.scrollTo({ top: 0, behavior: "auto" });
|
|
343
|
+
}, 0);
|
|
344
|
+
} else
|
|
345
|
+
window.scrollTo({
|
|
346
|
+
top: 0,
|
|
347
|
+
behavior: "auto"
|
|
348
|
+
});
|
|
349
|
+
else f && console.debug(
|
|
341
350
|
"%c[Routerino]%c target link does not share an origin, standard browser link handling applies",
|
|
342
351
|
"color: #6b7280; font-weight: bold",
|
|
343
352
|
""
|
|
@@ -345,7 +354,7 @@ function re({
|
|
|
345
354
|
};
|
|
346
355
|
document.addEventListener("click", t);
|
|
347
356
|
const l = () => {
|
|
348
|
-
|
|
357
|
+
f && console.debug(
|
|
349
358
|
"%c[Routerino]%c route change ->",
|
|
350
359
|
"color: #6b7280; font-weight: bold",
|
|
351
360
|
"",
|
|
@@ -358,19 +367,19 @@ function re({
|
|
|
358
367
|
}, [b]);
|
|
359
368
|
let c = window?.location?.pathname ?? "/";
|
|
360
369
|
(c === "/index.html" || c === "") && (c = "/");
|
|
361
|
-
const
|
|
370
|
+
const k = i.find((t) => t.path === c), P = i.find(
|
|
362
371
|
(t) => `${t.path}/` === c || t.path === `${c}/`
|
|
363
372
|
), y = i.find((t) => {
|
|
364
|
-
const l = t.path.endsWith("/") ? t.path.slice(0, -1) : t.path,
|
|
365
|
-
return
|
|
366
|
-
}), s =
|
|
367
|
-
if (
|
|
373
|
+
const l = t.path.endsWith("/") ? t.path.slice(0, -1) : t.path, w = c.endsWith("/") ? c.slice(0, -1) : c, d = l.split("/").filter(Boolean), h = w.split("/").filter(Boolean);
|
|
374
|
+
return d.length !== h.length ? !1 : d.every(($, x) => $.startsWith(":") ? !0 : $ === h[x]);
|
|
375
|
+
}), s = k ?? P ?? y;
|
|
376
|
+
if (f && console.debug(
|
|
368
377
|
"%c[Routerino]%c Route matching:",
|
|
369
378
|
"color: #6b7280; font-weight: bold",
|
|
370
379
|
"",
|
|
371
|
-
{ match: s, exactMatch:
|
|
380
|
+
{ match: s, exactMatch: k, addSlashMatch: P, paramsMatch: y }
|
|
372
381
|
), !s)
|
|
373
|
-
return
|
|
382
|
+
return f && (console.group(
|
|
374
383
|
"%c[Routerino]%c 404 - No matching route",
|
|
375
384
|
"color: #f59e0b; font-weight: bold",
|
|
376
385
|
""
|
|
@@ -384,8 +393,8 @@ function re({
|
|
|
384
393
|
"color: #f59e0b; font-weight: bold",
|
|
385
394
|
"",
|
|
386
395
|
i.map((t) => t.path)
|
|
387
|
-
), console.groupEnd()), document.title =
|
|
388
|
-
if (
|
|
396
|
+
), console.groupEnd()), document.title = W, m && p({ name: "prerender-status-code", content: "404" }), e;
|
|
397
|
+
if (m) {
|
|
389
398
|
const t = document.querySelector(
|
|
390
399
|
'meta[name="prerender-status-code"]'
|
|
391
400
|
);
|
|
@@ -395,27 +404,27 @@ function re({
|
|
|
395
404
|
);
|
|
396
405
|
l && l.remove();
|
|
397
406
|
}
|
|
398
|
-
const v = a && !c.endsWith("/") && c !== "/",
|
|
407
|
+
const v = a && !c.endsWith("/") && c !== "/", j = !a && c.endsWith("/") && c !== "/", F = v ? `${c}/` : j ? c.slice(0, -1) : c, U = `${O ?? window?.location?.origin ?? ""}${F}`;
|
|
399
408
|
if (s.title) {
|
|
400
|
-
const t = `${s.title}${
|
|
409
|
+
const t = `${s.title}${S}${E}`;
|
|
401
410
|
document.title = t, p({
|
|
402
411
|
tag: "link",
|
|
403
412
|
rel: "canonical",
|
|
404
|
-
href:
|
|
413
|
+
href: U
|
|
405
414
|
}), s.tags?.find(({ property: l }) => l === "og:title") || p({
|
|
406
415
|
property: "og:title",
|
|
407
416
|
content: t
|
|
408
417
|
}), s.tags?.find(({ property: l }) => l === "og:url") || p({
|
|
409
418
|
property: "og:url",
|
|
410
|
-
content:
|
|
419
|
+
content: U
|
|
411
420
|
});
|
|
412
421
|
}
|
|
413
422
|
if (s.description && (p({ name: "description", content: s.description }), s.tags?.find(({ property: t }) => t === "og:description") || p({
|
|
414
423
|
property: "og:description",
|
|
415
424
|
content: s.description
|
|
416
|
-
})), (
|
|
425
|
+
})), (I || s.imageUrl) && p({
|
|
417
426
|
property: "og:image",
|
|
418
|
-
content: s.imageUrl ??
|
|
427
|
+
content: s.imageUrl ?? I
|
|
419
428
|
}), s.tags?.find(({ property: t }) => t === "twitter:card") || p({
|
|
420
429
|
name: "twitter:card",
|
|
421
430
|
content: "summary_large_image"
|
|
@@ -423,9 +432,9 @@ function re({
|
|
|
423
432
|
tag: "link",
|
|
424
433
|
rel: "apple-touch-icon",
|
|
425
434
|
href: L
|
|
426
|
-
}),
|
|
435
|
+
}), m && (v || j) && (p({ name: "prerender-status-code", content: "301" }), p({
|
|
427
436
|
name: "prerender-header",
|
|
428
|
-
content: `Location: ${
|
|
437
|
+
content: `Location: ${U}`
|
|
429
438
|
})), s.tags && s.tags.length ? (s.tags.find(({ property: t }) => t === "og:type") || p({ property: "og:type", content: "website" }), s.tags.forEach((t) => p(t))) : p({ property: "og:type", content: "website" }), s.element) {
|
|
430
439
|
const t = ne({
|
|
431
440
|
routePattern: s.path,
|
|
@@ -436,26 +445,26 @@ function re({
|
|
|
436
445
|
routePattern: s.path,
|
|
437
446
|
updateHeadTag: p
|
|
438
447
|
};
|
|
439
|
-
return /* @__PURE__ */ g(
|
|
440
|
-
|
|
448
|
+
return /* @__PURE__ */ g(K.Provider, { value: l, children: /* @__PURE__ */ g(
|
|
449
|
+
Z,
|
|
441
450
|
{
|
|
442
451
|
fallback: n,
|
|
443
|
-
errorTitleString:
|
|
444
|
-
usePrerenderTags:
|
|
452
|
+
errorTitleString: T,
|
|
453
|
+
usePrerenderTags: m,
|
|
445
454
|
routePath: c,
|
|
446
|
-
debug:
|
|
455
|
+
debug: f,
|
|
447
456
|
children: s.element
|
|
448
457
|
}
|
|
449
458
|
) });
|
|
450
459
|
}
|
|
451
|
-
return
|
|
460
|
+
return f && console.error(
|
|
452
461
|
"%c[Routerino]%c No route found for",
|
|
453
462
|
"color: #ff6b6b; font-weight: bold",
|
|
454
463
|
"",
|
|
455
464
|
c
|
|
456
|
-
), document.title =
|
|
465
|
+
), document.title = W, m && p({ name: "prerender-status-code", content: "404" }), e;
|
|
457
466
|
} catch (b) {
|
|
458
|
-
return
|
|
467
|
+
return f && (console.group(
|
|
459
468
|
"%c[Routerino]%c Fatal Error",
|
|
460
469
|
"color: #ff6b6b; font-weight: bold",
|
|
461
470
|
""
|
|
@@ -472,7 +481,7 @@ function re({
|
|
|
472
481
|
"%c[Routerino]%c This typically means an issue with route configuration or router setup",
|
|
473
482
|
"color: #ff6b6b; font-weight: bold",
|
|
474
483
|
""
|
|
475
|
-
), console.groupEnd()),
|
|
484
|
+
), console.groupEnd()), m && p({ name: "prerender-status-code", content: "500" }), document.title = T, n;
|
|
476
485
|
}
|
|
477
486
|
}
|
|
478
487
|
const ie = o.exact({
|
|
@@ -521,7 +530,7 @@ re.propTypes = {
|
|
|
521
530
|
debug: o.bool
|
|
522
531
|
};
|
|
523
532
|
export {
|
|
524
|
-
|
|
533
|
+
Z as ErrorBoundary,
|
|
525
534
|
oe as Image,
|
|
526
535
|
re as Routerino,
|
|
527
536
|
re as default,
|
package/dist/routerino.umd.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(w,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("react/jsx-runtime"),require("react"),require("prop-types")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","prop-types"],c):(w=typeof globalThis<"u"?globalThis:w||self,c(w.routerino={},w["react/jsx-runtime"],w.React,w.PropTypes))})(this,(function(w,c,b,o){"use strict";const N=[480,800,1200,1920],Q="(max-width: 480px) 100vw, (max-width: 800px) 800px, (max-width: 1200px) 1200px, 1920px",D=new Map;function X(i,e=""){const r=i.toLowerCase(),n=e.toLowerCase();return!!(r.includes("hero")||r.includes("banner")||n.includes("hero")||n.includes("banner")||n.includes("h-screen")||n.includes("h-full"))}function I(i,e,r=null){const n=i.replace(/\.(jpe?g|png|webp)$/i,""),f=r?`.${r}`:i.match(/\.(jpe?g|png|webp)$/i)?.[0]||".jpg";return e.map(s=>`${n}-${s}w${f} ${s}w`).join(", ")}function K(i){const{src:e="",alt:r="",priority:n,widths:f=N,sizes:s=Q,className:m="",style:z={},width:R,height:x,loading:O,decoding:q="async",fetchpriority:g,...L}=i||{},C=typeof window>"u",S=!C&&typeof document<"u"&&typeof HTMLElement<"u"&&document.createElement("div")instanceof HTMLElement,y=S&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"),a=n??X(e,m),U=O||(a?"eager":"lazy"),W=g||(a?"high":void 0),[E,l]=b.useState(null);b.useEffect(()=>{if(!S||y||!e)return;const h=new window.Image;h.onload=()=>{l({width:h.naturalWidth,height:h.naturalHeight})},h.src=e},[e,S,y]);const k=b.useMemo(()=>E?f.filter(h=>E.width>=h):f,[E,f]),[B,V]=b.useState(null);b.useEffect(()=>{if(!S||y)return;let h=!1;return(async()=>{const H=e.replace(/\.(jpe?g|png|webp)$/i,""),ee=e.match(/\.(jpe?g|png|webp)$/i)?.[0]||".jpg",F=[];for(const G of k){const A=`${H}-${G}w${ee}`;if(D.has(A)){D.get(A)&&F.push(G);continue}try{const J=await fetch(A,{method:"HEAD"});D.set(A,J.ok),J.ok&&F.push(G)}catch{D.set(A,!1)}}h||V(F.length>0?F:k)})(),()=>{h=!0}},[e,k,S,y]);const j=B??k,t={};R!=null&&(t.width=R),x!=null&&(t.height=x),E&&R==null&&x==null&&(t.width=E.width,t.height=E.height);const u={maxWidth:"100%",height:"auto",...z};if(y)return c.jsx("img",{src:e,alt:r,loading:U,decoding:q,fetchPriority:W,className:m,style:u,...t,...L});if(C)return c.jsxs("picture",{"data-routerino-image":"true","data-original-src":e,children:[c.jsx("source",{srcSet:I(e,f,"webp"),type:"image/webp",sizes:s}),c.jsx("img",{src:e,alt:r,srcSet:I(e,f),sizes:s,loading:U,decoding:"async",fetchPriority:W,className:m,style:u,...t,...L})]});const $=I(e,j,"webp"),p=I(e,j);return c.jsxs("picture",{"data-routerino-image":"true","data-original-src":e,children:[c.jsx("source",{srcSet:$,type:"image/webp",sizes:s}),c.jsx("img",{src:e,alt:r,srcSet:p,sizes:s,loading:U,decoding:q,fetchPriority:W,className:m,style:u,...t,...L})]})}K.propTypes={src:o.string.isRequired,alt:o.string.isRequired,priority:o.bool,widths:o.arrayOf(o.number),sizes:o.string,className:o.string,style:o.object,width:o.number,height:o.number,loading:o.oneOf(["lazy","eager"]),decoding:o.oneOf(["sync","async","auto"]),fetchpriority:o.oneOf(["high","low","auto"])};const Z=b.createContext(null);function Y(){const i=b.useContext(Z);if(!i)throw new Error("useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component.");return i}function d({tag:i="meta",soft:e=!1,...r}){const n=Object.keys(r);if(n.length<1)return console.error(`[Routerino] updateHeadTag() received no attributes to set for ${i} tag`);let f=null;for(let s=0;s<n.length&&(n[s]!=="content"&&(f=document.querySelector(`${i}[${n[s]}='${r[n[s]]}']`)),!f);s++);f&&e||(f||(f=document.createElement(i)),n.forEach(s=>f.setAttribute(s,r[s])),document.querySelector("head").appendChild(f))}function T({routePattern:i,currentRoute:e}){let r={},n=i.split("/"),f=e.split("/");return n.forEach((s,m)=>{s.startsWith(":")&&(r[s.slice(1)]=f[m])}),r}class M extends b.Component{constructor(e){super(e),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,r){this.props.debug&&(console.group("%c[Routerino]%c Error Boundary Caught an Error","color: #ff6b6b; font-weight: bold","",e),console.error("[Routerino] Component Stack:",r.componentStack),this.props.routePath&&console.error("[Routerino] Failed Route:",this.props.routePath),console.error("[Routerino] Error occurred at:",new Date().toISOString()),console.groupEnd()),document.title=this.props.errorTitleString,this.props.usePrerenderTags&&d({name:"prerender-status-code",content:"500"})}render(){return this.state.hasError?this.props.fallback:this.props.children}}M.propTypes={children:o.node,fallback:o.node,errorTitleString:o.string.isRequired,usePrerenderTags:o.bool,routePath:o.string,debug:o.bool};function _({routes:i=[{path:"/",element:c.jsx("p",{children:"This is the default route. Pass an array of routes to the Routerino component in order to configure your own pages. Each route is a dictionary with at least `path` and `element` defined."}),title:"Routerino default route example",description:"The default route example description.",tags:[{property:"og:locale",content:"en_US"}]}],notFoundTemplate:e=c.jsxs(c.Fragment,{children:[c.jsx("p",{children:"No page found for this URL. [404]"}),c.jsx("p",{children:c.jsx("a",{href:"/",children:"Home"})})]}),notFoundTitle:r="Page not found [404]",errorTemplate:n=c.jsxs(c.Fragment,{children:[c.jsx("p",{children:"Page failed to load. [500]"}),c.jsx("p",{children:c.jsx("a",{href:"/",children:"Home"})})]}),errorTitle:f="Page error [500]",useTrailingSlash:s=!0,usePrerenderTags:m=!1,baseUrl:z=null,title:R="",separator:x=" | ",imageUrl:O=null,touchIconUrl:q=null,debug:g=!1}){const L=`${f}${x}${R}`,C=`${r}${x}${R}`;try{if(g){const t=i.map($=>$.path),u=t.filter(($,p)=>t.indexOf($)!==p);u.length>0&&(console.warn("%c[Routerino]%c Duplicate route paths detected:","color: #f59e0b; font-weight: bold","",[...new Set(u)]),console.warn("%c[Routerino]%c The first matching route will be used","color: #f59e0b; font-weight: bold",""))}const[S,y]=b.useState(window?.location?.href??"/");b.useEffect(()=>{if(typeof window>"u"||typeof document>"u")return;const t=$=>{g&&console.debug("%c[Routerino]%c click occurred","color: #6b7280; font-weight: bold","");let p=$.target;for(;p.tagName!=="A"&&p.parentElement;)p=p.parentElement;if(p.tagName!=="A"){g&&console.debug("%c[Routerino]%c no anchor tag found during click","color: #6b7280; font-weight: bold","");return}const h=p.getAttribute("href")||p.href;if(!h){g&&console.debug("%c[Routerino]%c anchor tag has no href","color: #6b7280; font-weight: bold","");return}if(!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(h)){g&&console.debug("%c[Routerino]%c skipping non-http URL:","color: #6b7280; font-weight: bold","",h);return}g&&console.debug("%c[Routerino]%c click target href:","color: #6b7280; font-weight: bold","",h);let v;try{v=new URL(h,window.location.href)}catch(H){g&&console.debug("%c[Routerino]%c Invalid URL:","color: #6b7280; font-weight: bold","",h,H);return}g&&console.debug("%c[Routerino]%c targetUrl:","color: #6b7280; font-weight: bold","",v,"current:",window.location),v&&window.location.origin===v.origin?(g&&console.debug("%c[Routerino]%c target link is same origin, will use push-state transitioning","color: #6b7280; font-weight: bold",""),$.preventDefault(),p.href!==window.location.href&&(y(p.href),window.history.pushState({},"",p.href)),p.hash||window.scrollTo({top:0,behavior:"auto"})):g&&console.debug("%c[Routerino]%c target link does not share an origin, standard browser link handling applies","color: #6b7280; font-weight: bold","")};document.addEventListener("click",t);const u=()=>{g&&console.debug("%c[Routerino]%c route change ->","color: #6b7280; font-weight: bold","",window.location.pathname),y(window.location.href)};return window.addEventListener("popstate",u),()=>{document.removeEventListener("click",t),window.removeEventListener("popstate",u)}},[S]);let a=window?.location?.pathname??"/";(a==="/index.html"||a==="")&&(a="/");const U=i.find(t=>t.path===a),W=i.find(t=>`${t.path}/`===a||t.path===`${a}/`),E=i.find(t=>{const u=t.path.endsWith("/")?t.path.slice(0,-1):t.path,$=a.endsWith("/")?a.slice(0,-1):a,p=u.split("/").filter(Boolean),h=$.split("/").filter(Boolean);return p.length!==h.length?!1:p.every((v,H)=>v.startsWith(":")?!0:v===h[H])}),l=U??W??E;if(g&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:l,exactMatch:U,addSlashMatch:W,paramsMatch:E}),!l)return g&&(console.group("%c[Routerino]%c 404 - No matching route","color: #f59e0b; font-weight: bold",""),console.warn("%c[Routerino]%c Requested path:","color: #f59e0b; font-weight: bold","",a),console.warn("%c[Routerino]%c Available routes:","color: #f59e0b; font-weight: bold","",i.map(t=>t.path)),console.groupEnd()),document.title=C,m&&d({name:"prerender-status-code",content:"404"}),e;if(m){const t=document.querySelector('meta[name="prerender-status-code"]');t&&t.remove();const u=document.querySelector('meta[name="prerender-header"]');u&&u.remove()}const k=s&&!a.endsWith("/")&&a!=="/",B=!s&&a.endsWith("/")&&a!=="/",V=k?`${a}/`:B?a.slice(0,-1):a,j=`${z??window?.location?.origin??""}${V}`;if(l.title){const t=`${l.title}${x}${R}`;document.title=t,d({tag:"link",rel:"canonical",href:j}),l.tags?.find(({property:u})=>u==="og:title")||d({property:"og:title",content:t}),l.tags?.find(({property:u})=>u==="og:url")||d({property:"og:url",content:j})}if(l.description&&(d({name:"description",content:l.description}),l.tags?.find(({property:t})=>t==="og:description")||d({property:"og:description",content:l.description})),(O||l.imageUrl)&&d({property:"og:image",content:l.imageUrl??O}),l.tags?.find(({property:t})=>t==="twitter:card")||d({name:"twitter:card",content:"summary_large_image"}),q&&d({tag:"link",rel:"apple-touch-icon",href:q}),m&&(k||B)&&(d({name:"prerender-status-code",content:"301"}),d({name:"prerender-header",content:`Location: ${j}`})),l.tags&&l.tags.length?(l.tags.find(({property:t})=>t==="og:type")||d({property:"og:type",content:"website"}),l.tags.forEach(t=>d(t))):d({property:"og:type",content:"website"}),l.element){const t=T({routePattern:l.path,currentRoute:a}),u={currentRoute:a,params:t,routePattern:l.path,updateHeadTag:d};return c.jsx(Z.Provider,{value:u,children:c.jsx(M,{fallback:n,errorTitleString:L,usePrerenderTags:m,routePath:a,debug:g,children:l.element})})}return g&&console.error("%c[Routerino]%c No route found for","color: #ff6b6b; font-weight: bold","",a),document.title=C,m&&d({name:"prerender-status-code",content:"404"}),e}catch(S){return g&&(console.group("%c[Routerino]%c Fatal Error","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c An error occurred in the router itself (not in a route component)","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c Error:","color: #ff6b6b; font-weight: bold","",S),console.error("%c[Routerino]%c This typically means an issue with route configuration or router setup","color: #ff6b6b; font-weight: bold",""),console.groupEnd()),m&&d({name:"prerender-status-code",content:"500"}),document.title=L,n}}const P=o.exact({path:(i,e,r)=>{const n=i[e];return n==null?new Error(`The prop \`${e}\` is marked as required in \`${r}\`, but its value is \`${n}\`.`):typeof n!="string"?new Error(`Invalid prop \`${e}\` of type \`${typeof n}\` supplied to \`${r}\`, expected \`string\`.`):n.startsWith("/")?null:new Error(`Invalid prop \`${e}\` value \`${n}\` supplied to \`${r}\`. Route paths must start with a forward slash (/).`)},element:o.element.isRequired,title:o.string,description:o.string,tags:o.arrayOf(o.object),imageUrl:o.string});_.propTypes={routes:o.arrayOf(P),title:o.string,separator:o.string,notFoundTemplate:o.element,notFoundTitle:o.string,errorTemplate:o.element,errorTitle:o.string,useTrailingSlash:o.bool,usePrerenderTags:o.bool,baseUrl:(i,e,r)=>{const n=i[e];if(n!=null){if(typeof n!="string")return new Error(`Invalid prop \`${e}\` of type \`${typeof n}\` supplied to \`${r}\`, expected \`string\`.`);if(n.endsWith("/"))return new Error(`Invalid prop \`${e}\` supplied to \`${r}\`. The baseUrl should not end with a slash. Got: "${n}"`)}return null},imageUrl:o.string,touchIconUrl:o.string,debug:o.bool},w.ErrorBoundary=M,w.Image=K,w.Routerino=_,w.default=_,w.updateHeadTag=d,w.useRouterino=Y,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
|
1
|
+
(function(w,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("react/jsx-runtime"),require("react"),require("prop-types")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","prop-types"],c):(w=typeof globalThis<"u"?globalThis:w||self,c(w.routerino={},w["react/jsx-runtime"],w.React,w.PropTypes))})(this,(function(w,c,b,o){"use strict";const Q=[480,800,1200,1920],X="(max-width: 480px) 100vw, (max-width: 800px) 800px, (max-width: 1200px) 1200px, 1920px",A=new Map;function Y(i,e=""){const r=i.toLowerCase(),n=e.toLowerCase();return!!(r.includes("hero")||r.includes("banner")||n.includes("hero")||n.includes("banner")||n.includes("h-screen")||n.includes("h-full"))}function D(i,e,r=null){const n=i.replace(/\.(jpe?g|png|webp)$/i,""),f=r?`.${r}`:i.match(/\.(jpe?g|png|webp)$/i)?.[0]||".jpg";return e.map(s=>`${n}-${s}w${f} ${s}w`).join(", ")}function Z(i){const{src:e="",alt:r="",priority:n,widths:f=Q,sizes:s=X,className:m="",style:V={},width:R,height:x,loading:B,decoding:C="async",fetchpriority:p,...L}=i||{},q=typeof window>"u",S=!q&&typeof document<"u"&&typeof HTMLElement<"u"&&document.createElement("div")instanceof HTMLElement,y=S&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"),a=n??Y(e,m),W=B||(a?"eager":"lazy"),I=p||(a?"high":void 0),[E,l]=b.useState(null);b.useEffect(()=>{if(!S||y||!e)return;const g=new window.Image;g.onload=()=>{l({width:g.naturalWidth,height:g.naturalHeight})},g.src=e},[e,S,y]);const k=b.useMemo(()=>E?f.filter(g=>E.width>=g):f,[E,f]),[O,G]=b.useState(null);b.useEffect(()=>{if(!S||y)return;let g=!1;return(async()=>{const U=e.replace(/\.(jpe?g|png|webp)$/i,""),F=e.match(/\.(jpe?g|png|webp)$/i)?.[0]||".jpg",M=[];for(const K of k){const H=`${U}-${K}w${F}`;if(A.has(H)){A.get(H)&&M.push(K);continue}try{const N=await fetch(H,{method:"HEAD"});A.set(H,N.ok),N.ok&&M.push(K)}catch{A.set(H,!1)}}g||G(M.length>0?M:k)})(),()=>{g=!0}},[e,k,S,y]);const j=O??k,t={};R!=null&&(t.width=R),x!=null&&(t.height=x),E&&R==null&&x==null&&(t.width=E.width,t.height=E.height);const u={maxWidth:"100%",height:"auto",...V};if(y)return c.jsx("img",{src:e,alt:r,loading:W,decoding:C,fetchPriority:I,className:m,style:u,...t,...L});if(q)return c.jsxs("picture",{"data-routerino-image":"true","data-original-src":e,children:[c.jsx("source",{srcSet:D(e,f,"webp"),type:"image/webp",sizes:s}),c.jsx("img",{src:e,alt:r,srcSet:D(e,f),sizes:s,loading:W,decoding:"async",fetchPriority:I,className:m,style:u,...t,...L})]});const $=D(e,j,"webp"),h=D(e,j);return c.jsxs("picture",{"data-routerino-image":"true","data-original-src":e,children:[c.jsx("source",{srcSet:$,type:"image/webp",sizes:s}),c.jsx("img",{src:e,alt:r,srcSet:h,sizes:s,loading:W,decoding:C,fetchPriority:I,className:m,style:u,...t,...L})]})}Z.propTypes={src:o.string.isRequired,alt:o.string.isRequired,priority:o.bool,widths:o.arrayOf(o.number),sizes:o.string,className:o.string,style:o.object,width:o.number,height:o.number,loading:o.oneOf(["lazy","eager"]),decoding:o.oneOf(["sync","async","auto"]),fetchpriority:o.oneOf(["high","low","auto"])};const J=b.createContext(null);function T(){const i=b.useContext(J);if(!i)throw new Error("useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component.");return i}function d({tag:i="meta",soft:e=!1,...r}){const n=Object.keys(r);if(n.length<1)return console.error(`[Routerino] updateHeadTag() received no attributes to set for ${i} tag`);let f=null;for(let s=0;s<n.length&&(n[s]!=="content"&&(f=document.querySelector(`${i}[${n[s]}='${r[n[s]]}']`)),!f);s++);f&&e||(f||(f=document.createElement(i)),n.forEach(s=>f.setAttribute(s,r[s])),document.querySelector("head").appendChild(f))}function P({routePattern:i,currentRoute:e}){let r={},n=i.split("/"),f=e.split("/");return n.forEach((s,m)=>{s.startsWith(":")&&(r[s.slice(1)]=f[m])}),r}class _ extends b.Component{constructor(e){super(e),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,r){this.props.debug&&(console.group("%c[Routerino]%c Error Boundary Caught an Error","color: #ff6b6b; font-weight: bold","",e),console.error("[Routerino] Component Stack:",r.componentStack),this.props.routePath&&console.error("[Routerino] Failed Route:",this.props.routePath),console.error("[Routerino] Error occurred at:",new Date().toISOString()),console.groupEnd()),document.title=this.props.errorTitleString,this.props.usePrerenderTags&&d({name:"prerender-status-code",content:"500"})}render(){return this.state.hasError?this.props.fallback:this.props.children}}_.propTypes={children:o.node,fallback:o.node,errorTitleString:o.string.isRequired,usePrerenderTags:o.bool,routePath:o.string,debug:o.bool};function z({routes:i=[{path:"/",element:c.jsx("p",{children:"This is the default route. Pass an array of routes to the Routerino component in order to configure your own pages. Each route is a dictionary with at least `path` and `element` defined."}),title:"Routerino default route example",description:"The default route example description.",tags:[{property:"og:locale",content:"en_US"}]}],notFoundTemplate:e=c.jsxs(c.Fragment,{children:[c.jsx("p",{children:"No page found for this URL. [404]"}),c.jsx("p",{children:c.jsx("a",{href:"/",children:"Home"})})]}),notFoundTitle:r="Page not found [404]",errorTemplate:n=c.jsxs(c.Fragment,{children:[c.jsx("p",{children:"Page failed to load. [500]"}),c.jsx("p",{children:c.jsx("a",{href:"/",children:"Home"})})]}),errorTitle:f="Page error [500]",useTrailingSlash:s=!0,usePrerenderTags:m=!1,baseUrl:V=null,title:R="",separator:x=" | ",imageUrl:B=null,touchIconUrl:C=null,debug:p=!1}){const L=`${f}${x}${R}`,q=`${r}${x}${R}`;try{if(p){const t=i.map($=>$.path),u=t.filter(($,h)=>t.indexOf($)!==h);u.length>0&&(console.warn("%c[Routerino]%c Duplicate route paths detected:","color: #f59e0b; font-weight: bold","",[...new Set(u)]),console.warn("%c[Routerino]%c The first matching route will be used","color: #f59e0b; font-weight: bold",""))}const[S,y]=b.useState(window?.location?.href??"/");b.useEffect(()=>{if(typeof window>"u"||typeof document>"u")return;const t=$=>{p&&console.debug("%c[Routerino]%c click occurred","color: #6b7280; font-weight: bold","");let h=$.target;for(;h.tagName!=="A"&&h.parentElement;)h=h.parentElement;if(h.tagName!=="A"){p&&console.debug("%c[Routerino]%c no anchor tag found during click","color: #6b7280; font-weight: bold","");return}const g=h.getAttribute("href")||h.href;if(!g){p&&console.debug("%c[Routerino]%c anchor tag has no href","color: #6b7280; font-weight: bold","");return}if(!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(g)){p&&console.debug("%c[Routerino]%c skipping non-http URL:","color: #6b7280; font-weight: bold","",g);return}p&&console.debug("%c[Routerino]%c click target href:","color: #6b7280; font-weight: bold","",g);let v;try{v=new URL(g,window.location.href)}catch(U){p&&console.debug("%c[Routerino]%c Invalid URL:","color: #6b7280; font-weight: bold","",g,U);return}if(p&&console.debug("%c[Routerino]%c targetUrl:","color: #6b7280; font-weight: bold","",v,"current:",window.location),v&&window.location.origin===v.origin)if(p&&console.debug("%c[Routerino]%c target link is same origin, will use push-state transitioning","color: #6b7280; font-weight: bold",""),$.preventDefault(),h.href!==window.location.href&&(y(h.href),window.history.pushState({},"",h.href)),h.hash){const U=decodeURIComponent(h.hash.slice(1));setTimeout(()=>{const F=document.getElementById(U);F?F.scrollIntoView({behavior:"auto"}):window.scrollTo({top:0,behavior:"auto"})},0)}else window.scrollTo({top:0,behavior:"auto"});else p&&console.debug("%c[Routerino]%c target link does not share an origin, standard browser link handling applies","color: #6b7280; font-weight: bold","")};document.addEventListener("click",t);const u=()=>{p&&console.debug("%c[Routerino]%c route change ->","color: #6b7280; font-weight: bold","",window.location.pathname),y(window.location.href)};return window.addEventListener("popstate",u),()=>{document.removeEventListener("click",t),window.removeEventListener("popstate",u)}},[S]);let a=window?.location?.pathname??"/";(a==="/index.html"||a==="")&&(a="/");const W=i.find(t=>t.path===a),I=i.find(t=>`${t.path}/`===a||t.path===`${a}/`),E=i.find(t=>{const u=t.path.endsWith("/")?t.path.slice(0,-1):t.path,$=a.endsWith("/")?a.slice(0,-1):a,h=u.split("/").filter(Boolean),g=$.split("/").filter(Boolean);return h.length!==g.length?!1:h.every((v,U)=>v.startsWith(":")?!0:v===g[U])}),l=W??I??E;if(p&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:l,exactMatch:W,addSlashMatch:I,paramsMatch:E}),!l)return p&&(console.group("%c[Routerino]%c 404 - No matching route","color: #f59e0b; font-weight: bold",""),console.warn("%c[Routerino]%c Requested path:","color: #f59e0b; font-weight: bold","",a),console.warn("%c[Routerino]%c Available routes:","color: #f59e0b; font-weight: bold","",i.map(t=>t.path)),console.groupEnd()),document.title=q,m&&d({name:"prerender-status-code",content:"404"}),e;if(m){const t=document.querySelector('meta[name="prerender-status-code"]');t&&t.remove();const u=document.querySelector('meta[name="prerender-header"]');u&&u.remove()}const k=s&&!a.endsWith("/")&&a!=="/",O=!s&&a.endsWith("/")&&a!=="/",G=k?`${a}/`:O?a.slice(0,-1):a,j=`${V??window?.location?.origin??""}${G}`;if(l.title){const t=`${l.title}${x}${R}`;document.title=t,d({tag:"link",rel:"canonical",href:j}),l.tags?.find(({property:u})=>u==="og:title")||d({property:"og:title",content:t}),l.tags?.find(({property:u})=>u==="og:url")||d({property:"og:url",content:j})}if(l.description&&(d({name:"description",content:l.description}),l.tags?.find(({property:t})=>t==="og:description")||d({property:"og:description",content:l.description})),(B||l.imageUrl)&&d({property:"og:image",content:l.imageUrl??B}),l.tags?.find(({property:t})=>t==="twitter:card")||d({name:"twitter:card",content:"summary_large_image"}),C&&d({tag:"link",rel:"apple-touch-icon",href:C}),m&&(k||O)&&(d({name:"prerender-status-code",content:"301"}),d({name:"prerender-header",content:`Location: ${j}`})),l.tags&&l.tags.length?(l.tags.find(({property:t})=>t==="og:type")||d({property:"og:type",content:"website"}),l.tags.forEach(t=>d(t))):d({property:"og:type",content:"website"}),l.element){const t=P({routePattern:l.path,currentRoute:a}),u={currentRoute:a,params:t,routePattern:l.path,updateHeadTag:d};return c.jsx(J.Provider,{value:u,children:c.jsx(_,{fallback:n,errorTitleString:L,usePrerenderTags:m,routePath:a,debug:p,children:l.element})})}return p&&console.error("%c[Routerino]%c No route found for","color: #ff6b6b; font-weight: bold","",a),document.title=q,m&&d({name:"prerender-status-code",content:"404"}),e}catch(S){return p&&(console.group("%c[Routerino]%c Fatal Error","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c An error occurred in the router itself (not in a route component)","color: #ff6b6b; font-weight: bold",""),console.error("%c[Routerino]%c Error:","color: #ff6b6b; font-weight: bold","",S),console.error("%c[Routerino]%c This typically means an issue with route configuration or router setup","color: #ff6b6b; font-weight: bold",""),console.groupEnd()),m&&d({name:"prerender-status-code",content:"500"}),document.title=L,n}}const ee=o.exact({path:(i,e,r)=>{const n=i[e];return n==null?new Error(`The prop \`${e}\` is marked as required in \`${r}\`, but its value is \`${n}\`.`):typeof n!="string"?new Error(`Invalid prop \`${e}\` of type \`${typeof n}\` supplied to \`${r}\`, expected \`string\`.`):n.startsWith("/")?null:new Error(`Invalid prop \`${e}\` value \`${n}\` supplied to \`${r}\`. Route paths must start with a forward slash (/).`)},element:o.element.isRequired,title:o.string,description:o.string,tags:o.arrayOf(o.object),imageUrl:o.string});z.propTypes={routes:o.arrayOf(ee),title:o.string,separator:o.string,notFoundTemplate:o.element,notFoundTitle:o.string,errorTemplate:o.element,errorTitle:o.string,useTrailingSlash:o.bool,usePrerenderTags:o.bool,baseUrl:(i,e,r)=>{const n=i[e];if(n!=null){if(typeof n!="string")return new Error(`Invalid prop \`${e}\` of type \`${typeof n}\` supplied to \`${r}\`, expected \`string\`.`);if(n.endsWith("/"))return new Error(`Invalid prop \`${e}\` supplied to \`${r}\`. The baseUrl should not end with a slash. Got: "${n}"`)}return null},imageUrl:o.string,touchIconUrl:o.string,debug:o.bool},w.ErrorBoundary=_,w.Image=Z,w.Routerino=z,w.default=z,w.updateHeadTag=d,w.useRouterino=T,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|