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 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 q, Fragment as V } from "react/jsx-runtime";
2
- import { useState as M, useEffect as z, useMemo as J, createContext as N, Component as Q, useContext as X } from "react";
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 I(i, e, r = null) {
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: w = "",
20
+ className: m = "",
21
21
  style: O = {},
22
- width: S,
23
- height: E,
24
- loading: j,
22
+ width: E,
23
+ height: S,
24
+ loading: I,
25
25
  decoding: L = "async",
26
- fetchpriority: h,
27
- ...x
28
- } = i || {}, U = typeof window > "u", b = !U && 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, w), T = j || (c ? "eager" : "lazy"), k = h || (c ? "high" : void 0), [y, s] = M(null);
29
- z(() => {
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 d = new window.Image();
32
- d.onload = () => {
31
+ const h = new window.Image();
32
+ h.onload = () => {
33
33
  s({
34
- width: d.naturalWidth,
35
- height: d.naturalHeight
34
+ width: h.naturalWidth,
35
+ height: h.naturalHeight
36
36
  });
37
- }, d.src = e;
37
+ }, h.src = e;
38
38
  }, [e, b, R]);
39
- const v = J(() => y ? u.filter((d) => y.width >= d) : u, [y, u]), [A, B] = M(null);
40
- z(() => {
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 d = !1;
42
+ let h = !1;
43
43
  return (async () => {
44
- const W = e.replace(/\.(jpe?g|png|webp)$/i, ""), Z = e.match(/\.(jpe?g|png|webp)$/i)?.[0] || ".jpg", D = [];
45
- for (const F of v) {
46
- const C = `${W}-${F}w${Z}`;
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(F);
48
+ H.get(C) && D.push(M);
49
49
  continue;
50
50
  }
51
51
  try {
52
- const _ = await fetch(C, { method: "HEAD" });
53
- H.set(C, _.ok), _.ok && D.push(F);
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
- d || B(
58
+ h || F(
59
59
  D.length > 0 ? D : v
60
60
  );
61
61
  })(), () => {
62
- d = !0;
62
+ h = !0;
63
63
  };
64
64
  }, [e, v, b, R]);
65
- const P = A ?? v, t = {};
66
- S != null && (t.width = S), E != null && (t.height = E), y && S == null && E == null && (t.width = y.width, t.height = y.height);
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: T,
78
+ loading: k,
79
79
  decoding: L,
80
- fetchPriority: k,
81
- className: w,
80
+ fetchPriority: P,
81
+ className: m,
82
82
  style: l,
83
83
  ...t,
84
- ...x
84
+ ...T
85
85
  }
86
86
  );
87
- if (U)
88
- return /* @__PURE__ */ q("picture", { "data-routerino-image": "true", "data-original-src": e, children: [
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: I(e, u, "webp"),
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: I(e, u),
102
+ srcSet: q(e, u),
103
103
  sizes: a,
104
- loading: T,
104
+ loading: k,
105
105
  decoding: "async",
106
- fetchPriority: k,
107
- className: w,
106
+ fetchPriority: P,
107
+ className: m,
108
108
  style: l,
109
109
  ...t,
110
- ...x
110
+ ...T
111
111
  }
112
112
  )
113
113
  ] });
114
- const m = I(e, P, "webp"), f = I(e, P);
115
- return /* @__PURE__ */ q("picture", { "data-routerino-image": "true", "data-original-src": e, children: [
116
- /* @__PURE__ */ g("source", { srcSet: m, type: "image/webp", sizes: a }),
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: f,
122
+ srcSet: d,
123
123
  sizes: a,
124
- loading: T,
124
+ loading: k,
125
125
  decoding: L,
126
- fetchPriority: k,
127
- className: w,
126
+ fetchPriority: P,
127
+ className: m,
128
128
  style: l,
129
129
  ...t,
130
- ...x
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 G = N(null);
161
+ const K = N(null);
162
162
  function le() {
163
- const i = X(G);
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, w) => {
186
- a.startsWith(":") && (r[a.slice(1)] = u[w]);
185
+ return n.forEach((a, m) => {
186
+ a.startsWith(":") && (r[a.slice(1)] = u[m]);
187
187
  }), r;
188
188
  }
189
- class K extends Q {
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
- K.propTypes = {
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__ */ q(V, { children: [
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__ */ q(V, { children: [
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: w = !1,
243
+ usePrerenderTags: m = !1,
244
244
  baseUrl: O = null,
245
- title: S = "",
246
- separator: E = " | ",
247
- imageUrl: j = null,
245
+ title: E = "",
246
+ separator: S = " | ",
247
+ imageUrl: I = null,
248
248
  touchIconUrl: L = null,
249
- debug: h = !1
249
+ debug: f = !1
250
250
  }) {
251
- const x = `${u}${E}${S}`, U = `${r}${E}${S}`;
251
+ const T = `${u}${S}${E}`, W = `${r}${S}${E}`;
252
252
  try {
253
- if (h) {
254
- const t = i.map((m) => m.path), l = t.filter(
255
- (m, f) => t.indexOf(m) !== f
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] = M(window?.location?.href ?? "/");
269
- z(() => {
268
+ const [b, R] = z(window?.location?.href ?? "/");
269
+ _(() => {
270
270
  if (typeof window > "u" || typeof document > "u")
271
271
  return;
272
- const t = (m) => {
273
- h && console.debug(
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 f = m.target;
279
- for (; f.tagName !== "A" && f.parentElement; )
280
- f = f.parentElement;
281
- if (f.tagName !== "A") {
282
- h && console.debug(
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 d = f.getAttribute("href") || f.href;
290
- if (!d) {
291
- h && console.debug(
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(d)) {
299
- h && console.debug(
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
- d
303
+ h
304
304
  );
305
305
  return;
306
306
  }
307
- h && console.debug(
307
+ f && console.debug(
308
308
  "%c[Routerino]%c click target href:",
309
309
  "color: #6b7280; font-weight: bold",
310
310
  "",
311
- d
311
+ h
312
312
  );
313
313
  let $;
314
314
  try {
315
- $ = new URL(d, window.location.href);
316
- } catch (W) {
317
- h && console.debug(
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
- d,
322
- W
321
+ h,
322
+ x
323
323
  );
324
324
  return;
325
325
  }
326
- h && console.debug(
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 ? (h && console.debug(
334
- "%c[Routerino]%c target link is same origin, will use push-state transitioning",
335
- "color: #6b7280; font-weight: bold",
336
- ""
337
- ), m.preventDefault(), f.href !== window.location.href && (R(f.href), window.history.pushState({}, "", f.href)), f.hash || window.scrollTo({
338
- top: 0,
339
- behavior: "auto"
340
- })) : h && console.debug(
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
- h && console.debug(
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 T = i.find((t) => t.path === c), k = i.find(
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, m = c.endsWith("/") ? c.slice(0, -1) : c, f = l.split("/").filter(Boolean), d = m.split("/").filter(Boolean);
365
- return f.length !== d.length ? !1 : f.every(($, W) => $.startsWith(":") ? !0 : $ === d[W]);
366
- }), s = T ?? k ?? y;
367
- if (h && console.debug(
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: T, addSlashMatch: k, paramsMatch: y }
380
+ { match: s, exactMatch: k, addSlashMatch: P, paramsMatch: y }
372
381
  ), !s)
373
- return h && (console.group(
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 = U, w && p({ name: "prerender-status-code", content: "404" }), e;
388
- if (w) {
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 !== "/", A = !a && c.endsWith("/") && c !== "/", B = v ? `${c}/` : A ? c.slice(0, -1) : c, P = `${O ?? window?.location?.origin ?? ""}${B}`;
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}${E}${S}`;
409
+ const t = `${s.title}${S}${E}`;
401
410
  document.title = t, p({
402
411
  tag: "link",
403
412
  rel: "canonical",
404
- href: P
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: P
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
- })), (j || s.imageUrl) && p({
425
+ })), (I || s.imageUrl) && p({
417
426
  property: "og:image",
418
- content: s.imageUrl ?? j
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
- }), w && (v || A) && (p({ name: "prerender-status-code", content: "301" }), p({
435
+ }), m && (v || j) && (p({ name: "prerender-status-code", content: "301" }), p({
427
436
  name: "prerender-header",
428
- content: `Location: ${P}`
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(G.Provider, { value: l, children: /* @__PURE__ */ g(
440
- K,
448
+ return /* @__PURE__ */ g(K.Provider, { value: l, children: /* @__PURE__ */ g(
449
+ Z,
441
450
  {
442
451
  fallback: n,
443
- errorTitleString: x,
444
- usePrerenderTags: w,
452
+ errorTitleString: T,
453
+ usePrerenderTags: m,
445
454
  routePath: c,
446
- debug: h,
455
+ debug: f,
447
456
  children: s.element
448
457
  }
449
458
  ) });
450
459
  }
451
- return h && console.error(
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 = U, w && p({ name: "prerender-status-code", content: "404" }), e;
465
+ ), document.title = W, m && p({ name: "prerender-status-code", content: "404" }), e;
457
466
  } catch (b) {
458
- return h && (console.group(
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()), w && p({ name: "prerender-status-code", content: "500" }), document.title = x, n;
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
- K as ErrorBoundary,
533
+ Z as ErrorBoundary,
525
534
  oe as Image,
526
535
  re as Routerino,
527
536
  re as default,
@@ -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"}})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routerino",
3
- "version": "2.3.4-rc9",
3
+ "version": "2.4.0",
4
4
  "description": "A lightweight, SEO-optimized React router for modern web applications",
5
5
  "repository": {
6
6
  "type": "git",