routerino 2.3.2 → 2.3.4
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/dist/routerino.js +274 -165
- package/dist/routerino.umd.cjs +1 -1
- package/package.json +1 -1
- package/routerino-image.jsx +62 -3
package/dist/routerino.js
CHANGED
|
@@ -1,54 +1,162 @@
|
|
|
1
|
-
import { jsx as g, jsxs as
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as g, jsxs as q, Fragment as F } from "react/jsx-runtime";
|
|
2
|
+
import { useState as I, useEffect as O, createContext as _, Component as V, useContext as G } from "react";
|
|
3
3
|
import t from "prop-types";
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
const i =
|
|
7
|
-
|
|
4
|
+
const K = [480, 800, 1200, 1920], Z = "(max-width: 480px) 100vw, (max-width: 800px) 800px, (max-width: 1200px) 1200px, 1920px";
|
|
5
|
+
function J(r, e = "") {
|
|
6
|
+
const i = r.toLowerCase(), n = e.toLowerCase();
|
|
7
|
+
return !!(i.includes("hero") || i.includes("banner") || n.includes("hero") || n.includes("banner") || n.includes("h-screen") || n.includes("h-full"));
|
|
8
|
+
}
|
|
9
|
+
function H(r, e, i = null) {
|
|
10
|
+
const n = r.replace(/\.(jpe?g|png|webp)$/i, ""), h = i ? `.${i}` : r.match(/\.(jpe?g|png|webp)$/i)?.[0] || ".jpg";
|
|
11
|
+
return e.map((l) => `${n}-${l}w${h} ${l}w`).join(", ");
|
|
12
|
+
}
|
|
13
|
+
function B(r) {
|
|
14
|
+
const {
|
|
15
|
+
src: e = "",
|
|
16
|
+
alt: i = "",
|
|
17
|
+
priority: n,
|
|
18
|
+
widths: h = K,
|
|
19
|
+
sizes: l = Z,
|
|
20
|
+
className: p = "",
|
|
21
|
+
style: L = {},
|
|
22
|
+
loading: $,
|
|
23
|
+
decoding: E = "async",
|
|
24
|
+
fetchpriority: v,
|
|
25
|
+
...x
|
|
26
|
+
} = r || {};
|
|
27
|
+
if (typeof window < "u" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"))
|
|
28
|
+
return /* @__PURE__ */ g(
|
|
29
|
+
"img",
|
|
30
|
+
{
|
|
31
|
+
src: e,
|
|
32
|
+
alt: i,
|
|
33
|
+
loading: $ || "lazy",
|
|
34
|
+
decoding: E,
|
|
35
|
+
fetchPriority: v,
|
|
36
|
+
className: p,
|
|
37
|
+
style: L,
|
|
38
|
+
...x
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
const [T, D] = I(null), S = e && typeof window < "u";
|
|
42
|
+
O(() => {
|
|
43
|
+
if (!S) return;
|
|
44
|
+
const y = new B();
|
|
45
|
+
y.onload = () => {
|
|
46
|
+
D({
|
|
47
|
+
width: y.naturalWidth,
|
|
48
|
+
height: y.naturalHeight
|
|
49
|
+
});
|
|
50
|
+
}, y.src = e;
|
|
51
|
+
}, [e, S]);
|
|
52
|
+
const b = T ? h.filter((y) => T.width >= y) : h, [c, k] = I(b);
|
|
53
|
+
O(() => {
|
|
54
|
+
(async () => {
|
|
55
|
+
if (typeof window > "u") {
|
|
56
|
+
k(b);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const U = e.replace(/\.(jpe?g|png|webp)$/i, ""), o = e.match(/\.(jpe?g|png|webp)$/i)?.[0] || ".jpg", a = [];
|
|
60
|
+
for (const w of b) {
|
|
61
|
+
const f = `${U}-${w}w${o}`;
|
|
62
|
+
try {
|
|
63
|
+
(await fetch(f, { method: "HEAD" })).ok && a.push(w);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
a.length === 0 && b.length > 0 && a.push(Math.min(...b)), k(a);
|
|
68
|
+
})();
|
|
69
|
+
}, [e, b]);
|
|
70
|
+
const P = n ?? J(e, p), W = $ || (P ? "eager" : "lazy"), s = v || (P ? "high" : void 0), j = H(e, c, "webp"), A = H(e, c);
|
|
71
|
+
return /* @__PURE__ */ q("picture", { "data-routerino-image": "true", "data-original-src": e, children: [
|
|
72
|
+
/* @__PURE__ */ g("source", { srcSet: j, type: "image/webp", sizes: l }),
|
|
73
|
+
/* @__PURE__ */ g(
|
|
74
|
+
"img",
|
|
75
|
+
{
|
|
76
|
+
src: e,
|
|
77
|
+
alt: i,
|
|
78
|
+
srcSet: A,
|
|
79
|
+
sizes: l,
|
|
80
|
+
loading: W,
|
|
81
|
+
decoding: E,
|
|
82
|
+
fetchPriority: s,
|
|
83
|
+
className: p,
|
|
84
|
+
style: L,
|
|
85
|
+
...x
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
B.propTypes = {
|
|
91
|
+
/** Image source URL (required) */
|
|
92
|
+
src: t.string.isRequired,
|
|
93
|
+
/** Alt text for accessibility (required) */
|
|
94
|
+
alt: t.string.isRequired,
|
|
95
|
+
/** Override lazy loading for hero/LCP images */
|
|
96
|
+
priority: t.bool,
|
|
97
|
+
/** Responsive widths to generate (defaults to [480, 800, 1200, 1920]) */
|
|
98
|
+
widths: t.arrayOf(t.number),
|
|
99
|
+
/** Responsive sizes attribute (has smart default) */
|
|
100
|
+
sizes: t.string,
|
|
101
|
+
/** CSS classes (Tailwind/DaisyUI ready) */
|
|
102
|
+
className: t.string,
|
|
103
|
+
/** Inline styles */
|
|
104
|
+
style: t.object,
|
|
105
|
+
/** Loading behavior (auto-set based on priority) */
|
|
106
|
+
loading: t.oneOf(["lazy", "eager"]),
|
|
107
|
+
/** Decode timing */
|
|
108
|
+
decoding: t.oneOf(["sync", "async", "auto"]),
|
|
109
|
+
/** Fetch priority (auto-set based on priority) */
|
|
110
|
+
fetchpriority: t.oneOf(["high", "low", "auto"])
|
|
111
|
+
};
|
|
112
|
+
const z = _(null);
|
|
113
|
+
function oe() {
|
|
114
|
+
const r = G(z);
|
|
115
|
+
if (!r)
|
|
8
116
|
throw new Error(
|
|
9
117
|
"useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component."
|
|
10
118
|
);
|
|
11
|
-
return
|
|
119
|
+
return r;
|
|
12
120
|
}
|
|
13
|
-
function
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
121
|
+
function u({ tag: r = "meta", soft: e = !1, ...i }) {
|
|
122
|
+
const n = Object.keys(i);
|
|
123
|
+
if (n.length < 1)
|
|
16
124
|
return console.error(
|
|
17
|
-
`[Routerino] updateHeadTag() received no attributes to set for ${
|
|
125
|
+
`[Routerino] updateHeadTag() received no attributes to set for ${r} tag`
|
|
18
126
|
);
|
|
19
127
|
let h = null;
|
|
20
|
-
for (let
|
|
21
|
-
`${
|
|
22
|
-
)), !h);
|
|
128
|
+
for (let l = 0; l < n.length && (n[l] !== "content" && (h = document.querySelector(
|
|
129
|
+
`${r}[${n[l]}='${i[n[l]]}']`
|
|
130
|
+
)), !h); l++)
|
|
23
131
|
;
|
|
24
|
-
h &&
|
|
132
|
+
h && e || (h || (h = document.createElement(r)), n.forEach((l) => h.setAttribute(l, i[l])), document.querySelector("head").appendChild(h));
|
|
25
133
|
}
|
|
26
|
-
function
|
|
27
|
-
let
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
}),
|
|
134
|
+
function N({ routePattern: r, currentRoute: e }) {
|
|
135
|
+
let i = {}, n = r.split("/"), h = e.split("/");
|
|
136
|
+
return n.forEach((l, p) => {
|
|
137
|
+
l.startsWith(":") && (i[l.slice(1)] = h[p]);
|
|
138
|
+
}), i;
|
|
31
139
|
}
|
|
32
|
-
class
|
|
33
|
-
constructor(
|
|
34
|
-
super(
|
|
140
|
+
class M extends V {
|
|
141
|
+
constructor(e) {
|
|
142
|
+
super(e), this.state = { hasError: !1 };
|
|
35
143
|
}
|
|
36
144
|
static getDerivedStateFromError() {
|
|
37
145
|
return { hasError: !0 };
|
|
38
146
|
}
|
|
39
|
-
componentDidCatch(
|
|
147
|
+
componentDidCatch(e, i) {
|
|
40
148
|
this.props.debug && (console.group(
|
|
41
149
|
"%c[Routerino]%c Error Boundary Caught an Error",
|
|
42
150
|
"color: #ff6b6b; font-weight: bold",
|
|
43
151
|
"",
|
|
44
|
-
|
|
45
|
-
), console.error("[Routerino] Component Stack:",
|
|
152
|
+
e
|
|
153
|
+
), console.error("[Routerino] Component Stack:", i.componentStack), this.props.routePath && console.error("[Routerino] Failed Route:", this.props.routePath), console.error("[Routerino] Error occurred at:", (/* @__PURE__ */ new Date()).toISOString()), console.groupEnd()), document.title = this.props.errorTitleString, this.props.usePrerenderTags && u({ name: "prerender-status-code", content: "500" });
|
|
46
154
|
}
|
|
47
155
|
render() {
|
|
48
156
|
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
49
157
|
}
|
|
50
158
|
}
|
|
51
|
-
|
|
159
|
+
M.propTypes = {
|
|
52
160
|
/** The child components to render when there's no error */
|
|
53
161
|
children: t.node,
|
|
54
162
|
/** The fallback UI to display when an error is caught */
|
|
@@ -62,8 +170,8 @@ H.propTypes = {
|
|
|
62
170
|
/** Whether to log debug messages to console (optional) */
|
|
63
171
|
debug: t.bool
|
|
64
172
|
};
|
|
65
|
-
function
|
|
66
|
-
routes:
|
|
173
|
+
function Q({
|
|
174
|
+
routes: r = [
|
|
67
175
|
{
|
|
68
176
|
path: "/",
|
|
69
177
|
element: /* @__PURE__ */ g("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." }),
|
|
@@ -72,148 +180,148 @@ function K({
|
|
|
72
180
|
tags: [{ property: "og:locale", content: "en_US" }]
|
|
73
181
|
}
|
|
74
182
|
],
|
|
75
|
-
notFoundTemplate:
|
|
183
|
+
notFoundTemplate: e = /* @__PURE__ */ q(F, { children: [
|
|
76
184
|
/* @__PURE__ */ g("p", { children: "No page found for this URL. [404]" }),
|
|
77
185
|
/* @__PURE__ */ g("p", { children: /* @__PURE__ */ g("a", { href: "/", children: "Home" }) })
|
|
78
186
|
] }),
|
|
79
|
-
notFoundTitle:
|
|
80
|
-
errorTemplate:
|
|
187
|
+
notFoundTitle: i = "Page not found [404]",
|
|
188
|
+
errorTemplate: n = /* @__PURE__ */ q(F, { children: [
|
|
81
189
|
/* @__PURE__ */ g("p", { children: "Page failed to load. [500]" }),
|
|
82
190
|
/* @__PURE__ */ g("p", { children: /* @__PURE__ */ g("a", { href: "/", children: "Home" }) })
|
|
83
191
|
] }),
|
|
84
192
|
errorTitle: h = "Page error [500]",
|
|
85
|
-
useTrailingSlash:
|
|
86
|
-
usePrerenderTags:
|
|
87
|
-
baseUrl:
|
|
88
|
-
title:
|
|
89
|
-
separator:
|
|
193
|
+
useTrailingSlash: l = !0,
|
|
194
|
+
usePrerenderTags: p = !1,
|
|
195
|
+
baseUrl: L = null,
|
|
196
|
+
title: $ = "",
|
|
197
|
+
separator: E = " | ",
|
|
90
198
|
imageUrl: v = null,
|
|
91
|
-
touchIconUrl:
|
|
92
|
-
debug:
|
|
199
|
+
touchIconUrl: x = null,
|
|
200
|
+
debug: d = !1
|
|
93
201
|
}) {
|
|
94
|
-
const
|
|
202
|
+
const T = `${h}${E}${$}`, D = `${i}${E}${$}`;
|
|
95
203
|
try {
|
|
96
|
-
if (
|
|
97
|
-
const
|
|
98
|
-
(w,
|
|
204
|
+
if (d) {
|
|
205
|
+
const o = r.map((w) => w.path), a = o.filter(
|
|
206
|
+
(w, f) => o.indexOf(w) !== f
|
|
99
207
|
);
|
|
100
|
-
|
|
208
|
+
a.length > 0 && (console.warn(
|
|
101
209
|
"%c[Routerino]%c Duplicate route paths detected:",
|
|
102
210
|
"color: #f59e0b; font-weight: bold",
|
|
103
211
|
"",
|
|
104
|
-
[...new Set(
|
|
212
|
+
[...new Set(a)]
|
|
105
213
|
), console.warn(
|
|
106
214
|
"%c[Routerino]%c The first matching route will be used",
|
|
107
215
|
"color: #f59e0b; font-weight: bold",
|
|
108
216
|
""
|
|
109
217
|
));
|
|
110
218
|
}
|
|
111
|
-
const [
|
|
112
|
-
|
|
219
|
+
const [S, b] = I(window?.location?.href ?? "/");
|
|
220
|
+
O(() => {
|
|
113
221
|
if (typeof window > "u" || typeof document > "u")
|
|
114
222
|
return;
|
|
115
|
-
const
|
|
116
|
-
|
|
223
|
+
const o = (w) => {
|
|
224
|
+
d && console.debug(
|
|
117
225
|
"%c[Routerino]%c click occurred",
|
|
118
226
|
"color: #6b7280; font-weight: bold",
|
|
119
227
|
""
|
|
120
228
|
);
|
|
121
|
-
let
|
|
122
|
-
for (;
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
229
|
+
let f = w.target;
|
|
230
|
+
for (; f.tagName !== "A" && f.parentElement; )
|
|
231
|
+
f = f.parentElement;
|
|
232
|
+
if (f.tagName !== "A") {
|
|
233
|
+
d && console.debug(
|
|
126
234
|
"%c[Routerino]%c no anchor tag found during click",
|
|
127
235
|
"color: #6b7280; font-weight: bold",
|
|
128
236
|
""
|
|
129
237
|
);
|
|
130
238
|
return;
|
|
131
239
|
}
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
134
|
-
|
|
240
|
+
const m = f.getAttribute("href") || f.href;
|
|
241
|
+
if (!m) {
|
|
242
|
+
d && console.debug(
|
|
135
243
|
"%c[Routerino]%c anchor tag has no href",
|
|
136
244
|
"color: #6b7280; font-weight: bold",
|
|
137
245
|
""
|
|
138
246
|
);
|
|
139
247
|
return;
|
|
140
248
|
}
|
|
141
|
-
if (!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(
|
|
142
|
-
|
|
249
|
+
if (!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(m)) {
|
|
250
|
+
d && console.debug(
|
|
143
251
|
"%c[Routerino]%c skipping non-http URL:",
|
|
144
252
|
"color: #6b7280; font-weight: bold",
|
|
145
253
|
"",
|
|
146
|
-
|
|
254
|
+
m
|
|
147
255
|
);
|
|
148
256
|
return;
|
|
149
257
|
}
|
|
150
|
-
|
|
258
|
+
d && console.debug(
|
|
151
259
|
"%c[Routerino]%c click target href:",
|
|
152
260
|
"color: #6b7280; font-weight: bold",
|
|
153
261
|
"",
|
|
154
|
-
|
|
262
|
+
m
|
|
155
263
|
);
|
|
156
|
-
let
|
|
264
|
+
let R;
|
|
157
265
|
try {
|
|
158
|
-
|
|
159
|
-
} catch (
|
|
160
|
-
|
|
266
|
+
R = new URL(m, window.location.href);
|
|
267
|
+
} catch (C) {
|
|
268
|
+
d && console.debug(
|
|
161
269
|
"%c[Routerino]%c Invalid URL:",
|
|
162
270
|
"color: #6b7280; font-weight: bold",
|
|
163
271
|
"",
|
|
164
|
-
|
|
165
|
-
|
|
272
|
+
m,
|
|
273
|
+
C
|
|
166
274
|
);
|
|
167
275
|
return;
|
|
168
276
|
}
|
|
169
|
-
|
|
277
|
+
d && console.debug(
|
|
170
278
|
"%c[Routerino]%c targetUrl:",
|
|
171
279
|
"color: #6b7280; font-weight: bold",
|
|
172
280
|
"",
|
|
173
|
-
|
|
281
|
+
R,
|
|
174
282
|
"current:",
|
|
175
283
|
window.location
|
|
176
|
-
),
|
|
284
|
+
), R && window.location.origin === R.origin ? (d && console.debug(
|
|
177
285
|
"%c[Routerino]%c target link is same origin, will use push-state transitioning",
|
|
178
286
|
"color: #6b7280; font-weight: bold",
|
|
179
287
|
""
|
|
180
|
-
), w.preventDefault(),
|
|
288
|
+
), w.preventDefault(), f.href !== window.location.href && (b(f.href), window.history.pushState({}, "", f.href)), window.scrollTo({
|
|
181
289
|
top: 0,
|
|
182
290
|
behavior: "auto"
|
|
183
|
-
})) :
|
|
291
|
+
})) : d && console.debug(
|
|
184
292
|
"%c[Routerino]%c target link does not share an origin, standard browser link handling applies",
|
|
185
293
|
"color: #6b7280; font-weight: bold",
|
|
186
294
|
""
|
|
187
295
|
);
|
|
188
296
|
};
|
|
189
|
-
document.addEventListener("click",
|
|
190
|
-
const
|
|
191
|
-
|
|
297
|
+
document.addEventListener("click", o);
|
|
298
|
+
const a = () => {
|
|
299
|
+
d && console.debug(
|
|
192
300
|
"%c[Routerino]%c route change ->",
|
|
193
301
|
"color: #6b7280; font-weight: bold",
|
|
194
302
|
"",
|
|
195
303
|
window.location.pathname
|
|
196
|
-
),
|
|
304
|
+
), b(window.location.href);
|
|
197
305
|
};
|
|
198
|
-
return window.addEventListener("popstate",
|
|
199
|
-
document.removeEventListener("click",
|
|
306
|
+
return window.addEventListener("popstate", a), () => {
|
|
307
|
+
document.removeEventListener("click", o), window.removeEventListener("popstate", a);
|
|
200
308
|
};
|
|
201
|
-
}, [
|
|
202
|
-
let
|
|
203
|
-
(
|
|
204
|
-
const
|
|
205
|
-
(
|
|
206
|
-
),
|
|
207
|
-
const
|
|
208
|
-
return
|
|
209
|
-
}),
|
|
210
|
-
if (
|
|
309
|
+
}, [S]);
|
|
310
|
+
let c = window?.location?.pathname ?? "/";
|
|
311
|
+
(c === "/index.html" || c === "") && (c = "/");
|
|
312
|
+
const k = r.find((o) => o.path === c), P = r.find(
|
|
313
|
+
(o) => `${o.path}/` === c || o.path === `${c}/`
|
|
314
|
+
), W = r.find((o) => {
|
|
315
|
+
const a = o.path.endsWith("/") ? o.path.slice(0, -1) : o.path, w = c.endsWith("/") ? c.slice(0, -1) : c, f = a.split("/").filter(Boolean), m = w.split("/").filter(Boolean);
|
|
316
|
+
return f.length !== m.length ? !1 : f.every((R, C) => R.startsWith(":") ? !0 : R === m[C]);
|
|
317
|
+
}), s = k ?? P ?? W;
|
|
318
|
+
if (d && console.debug(
|
|
211
319
|
"%c[Routerino]%c Route matching:",
|
|
212
320
|
"color: #6b7280; font-weight: bold",
|
|
213
321
|
"",
|
|
214
|
-
{ match:
|
|
215
|
-
), !
|
|
216
|
-
return
|
|
322
|
+
{ match: s, exactMatch: k, addSlashMatch: P, paramsMatch: W }
|
|
323
|
+
), !s)
|
|
324
|
+
return d && (console.group(
|
|
217
325
|
"%c[Routerino]%c 404 - No matching route",
|
|
218
326
|
"color: #f59e0b; font-weight: bold",
|
|
219
327
|
""
|
|
@@ -221,84 +329,84 @@ function K({
|
|
|
221
329
|
"%c[Routerino]%c Requested path:",
|
|
222
330
|
"color: #f59e0b; font-weight: bold",
|
|
223
331
|
"",
|
|
224
|
-
|
|
332
|
+
c
|
|
225
333
|
), console.warn(
|
|
226
334
|
"%c[Routerino]%c Available routes:",
|
|
227
335
|
"color: #f59e0b; font-weight: bold",
|
|
228
336
|
"",
|
|
229
|
-
|
|
230
|
-
), console.groupEnd()), document.title =
|
|
231
|
-
if (
|
|
232
|
-
const
|
|
337
|
+
r.map((o) => o.path)
|
|
338
|
+
), console.groupEnd()), document.title = D, p && u({ name: "prerender-status-code", content: "404" }), e;
|
|
339
|
+
if (p) {
|
|
340
|
+
const o = document.querySelector(
|
|
233
341
|
'meta[name="prerender-status-code"]'
|
|
234
342
|
);
|
|
235
|
-
|
|
236
|
-
const
|
|
343
|
+
o && o.remove();
|
|
344
|
+
const a = document.querySelector(
|
|
237
345
|
'meta[name="prerender-header"]'
|
|
238
346
|
);
|
|
239
|
-
|
|
347
|
+
a && a.remove();
|
|
240
348
|
}
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
const
|
|
244
|
-
document.title =
|
|
349
|
+
const j = l && !c.endsWith("/") && c !== "/", A = !l && c.endsWith("/") && c !== "/", y = j ? `${c}/` : A ? c.slice(0, -1) : c, U = `${L ?? window?.location?.origin ?? ""}${y}`;
|
|
350
|
+
if (s.title) {
|
|
351
|
+
const o = `${s.title}${E}${$}`;
|
|
352
|
+
document.title = o, u({
|
|
245
353
|
tag: "link",
|
|
246
354
|
rel: "canonical",
|
|
247
|
-
href:
|
|
248
|
-
}),
|
|
355
|
+
href: U
|
|
356
|
+
}), s.tags?.find(({ property: a }) => a === "og:title") || u({
|
|
249
357
|
property: "og:title",
|
|
250
|
-
content:
|
|
251
|
-
}),
|
|
358
|
+
content: o
|
|
359
|
+
}), s.tags?.find(({ property: a }) => a === "og:url") || u({
|
|
252
360
|
property: "og:url",
|
|
253
|
-
content:
|
|
361
|
+
content: U
|
|
254
362
|
});
|
|
255
363
|
}
|
|
256
|
-
if (
|
|
364
|
+
if (s.description && (u({ name: "description", content: s.description }), s.tags?.find(({ property: o }) => o === "og:description") || u({
|
|
257
365
|
property: "og:description",
|
|
258
|
-
content:
|
|
259
|
-
})), (v ||
|
|
366
|
+
content: s.description
|
|
367
|
+
})), (v || s.imageUrl) && u({
|
|
260
368
|
property: "og:image",
|
|
261
|
-
content:
|
|
262
|
-
}),
|
|
369
|
+
content: s.imageUrl ?? v
|
|
370
|
+
}), s.tags?.find(({ property: o }) => o === "twitter:card") || u({
|
|
263
371
|
name: "twitter:card",
|
|
264
372
|
content: "summary_large_image"
|
|
265
|
-
}),
|
|
373
|
+
}), x && u({
|
|
266
374
|
tag: "link",
|
|
267
375
|
rel: "apple-touch-icon",
|
|
268
|
-
href:
|
|
269
|
-
}),
|
|
376
|
+
href: x
|
|
377
|
+
}), p && (j || A) && (u({ name: "prerender-status-code", content: "301" }), u({
|
|
270
378
|
name: "prerender-header",
|
|
271
|
-
content: `Location: ${
|
|
272
|
-
})),
|
|
273
|
-
const
|
|
274
|
-
routePattern:
|
|
275
|
-
currentRoute:
|
|
276
|
-
}),
|
|
277
|
-
currentRoute:
|
|
278
|
-
params:
|
|
279
|
-
routePattern:
|
|
280
|
-
updateHeadTag:
|
|
379
|
+
content: `Location: ${U}`
|
|
380
|
+
})), s.tags && s.tags.length ? (s.tags.find(({ property: o }) => o === "og:type") || u({ property: "og:type", content: "website" }), s.tags.forEach((o) => u(o))) : u({ property: "og:type", content: "website" }), s.element) {
|
|
381
|
+
const o = N({
|
|
382
|
+
routePattern: s.path,
|
|
383
|
+
currentRoute: c
|
|
384
|
+
}), a = {
|
|
385
|
+
currentRoute: c,
|
|
386
|
+
params: o,
|
|
387
|
+
routePattern: s.path,
|
|
388
|
+
updateHeadTag: u
|
|
281
389
|
};
|
|
282
|
-
return /* @__PURE__ */ g(
|
|
283
|
-
|
|
390
|
+
return /* @__PURE__ */ g(z.Provider, { value: a, children: /* @__PURE__ */ g(
|
|
391
|
+
M,
|
|
284
392
|
{
|
|
285
|
-
fallback:
|
|
286
|
-
errorTitleString:
|
|
287
|
-
usePrerenderTags:
|
|
288
|
-
routePath:
|
|
289
|
-
debug:
|
|
290
|
-
children:
|
|
393
|
+
fallback: n,
|
|
394
|
+
errorTitleString: T,
|
|
395
|
+
usePrerenderTags: p,
|
|
396
|
+
routePath: c,
|
|
397
|
+
debug: d,
|
|
398
|
+
children: s.element
|
|
291
399
|
}
|
|
292
400
|
) });
|
|
293
401
|
}
|
|
294
|
-
return
|
|
402
|
+
return d && console.error(
|
|
295
403
|
"%c[Routerino]%c No route found for",
|
|
296
404
|
"color: #ff6b6b; font-weight: bold",
|
|
297
405
|
"",
|
|
298
|
-
|
|
299
|
-
), document.title =
|
|
300
|
-
} catch (
|
|
301
|
-
return
|
|
406
|
+
c
|
|
407
|
+
), document.title = D, p && u({ name: "prerender-status-code", content: "404" }), e;
|
|
408
|
+
} catch (S) {
|
|
409
|
+
return d && (console.group(
|
|
302
410
|
"%c[Routerino]%c Fatal Error",
|
|
303
411
|
"color: #ff6b6b; font-weight: bold",
|
|
304
412
|
""
|
|
@@ -310,23 +418,23 @@ function K({
|
|
|
310
418
|
"%c[Routerino]%c Error:",
|
|
311
419
|
"color: #ff6b6b; font-weight: bold",
|
|
312
420
|
"",
|
|
313
|
-
|
|
421
|
+
S
|
|
314
422
|
), console.error(
|
|
315
423
|
"%c[Routerino]%c This typically means an issue with route configuration or router setup",
|
|
316
424
|
"color: #ff6b6b; font-weight: bold",
|
|
317
425
|
""
|
|
318
|
-
), console.groupEnd()),
|
|
426
|
+
), console.groupEnd()), p && u({ name: "prerender-status-code", content: "500" }), document.title = T, n;
|
|
319
427
|
}
|
|
320
428
|
}
|
|
321
|
-
const
|
|
322
|
-
path: (
|
|
323
|
-
const
|
|
324
|
-
return
|
|
325
|
-
`The prop \`${
|
|
326
|
-
) : typeof
|
|
327
|
-
`Invalid prop \`${
|
|
328
|
-
) :
|
|
329
|
-
`Invalid prop \`${
|
|
429
|
+
const X = t.exact({
|
|
430
|
+
path: (r, e, i) => {
|
|
431
|
+
const n = r[e];
|
|
432
|
+
return n == null ? new Error(
|
|
433
|
+
`The prop \`${e}\` is marked as required in \`${i}\`, but its value is \`${n}\`.`
|
|
434
|
+
) : typeof n != "string" ? new Error(
|
|
435
|
+
`Invalid prop \`${e}\` of type \`${typeof n}\` supplied to \`${i}\`, expected \`string\`.`
|
|
436
|
+
) : n.startsWith("/") ? null : new Error(
|
|
437
|
+
`Invalid prop \`${e}\` value \`${n}\` supplied to \`${i}\`. Route paths must start with a forward slash (/).`
|
|
330
438
|
);
|
|
331
439
|
},
|
|
332
440
|
element: t.element.isRequired,
|
|
@@ -335,8 +443,8 @@ const J = t.exact({
|
|
|
335
443
|
tags: t.arrayOf(t.object),
|
|
336
444
|
imageUrl: t.string
|
|
337
445
|
});
|
|
338
|
-
|
|
339
|
-
routes: t.arrayOf(
|
|
446
|
+
Q.propTypes = {
|
|
447
|
+
routes: t.arrayOf(X),
|
|
340
448
|
title: t.string,
|
|
341
449
|
separator: t.string,
|
|
342
450
|
notFoundTemplate: t.element,
|
|
@@ -345,16 +453,16 @@ K.propTypes = {
|
|
|
345
453
|
errorTitle: t.string,
|
|
346
454
|
useTrailingSlash: t.bool,
|
|
347
455
|
usePrerenderTags: t.bool,
|
|
348
|
-
baseUrl: (
|
|
349
|
-
const
|
|
350
|
-
if (
|
|
351
|
-
if (typeof
|
|
456
|
+
baseUrl: (r, e, i) => {
|
|
457
|
+
const n = r[e];
|
|
458
|
+
if (n != null) {
|
|
459
|
+
if (typeof n != "string")
|
|
352
460
|
return new Error(
|
|
353
|
-
`Invalid prop \`${
|
|
461
|
+
`Invalid prop \`${e}\` of type \`${typeof n}\` supplied to \`${i}\`, expected \`string\`.`
|
|
354
462
|
);
|
|
355
|
-
if (
|
|
463
|
+
if (n.endsWith("/"))
|
|
356
464
|
return new Error(
|
|
357
|
-
`Invalid prop \`${
|
|
465
|
+
`Invalid prop \`${e}\` supplied to \`${i}\`. The baseUrl should not end with a slash. Got: "${n}"`
|
|
358
466
|
);
|
|
359
467
|
}
|
|
360
468
|
return null;
|
|
@@ -364,9 +472,10 @@ K.propTypes = {
|
|
|
364
472
|
debug: t.bool
|
|
365
473
|
};
|
|
366
474
|
export {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
475
|
+
M as ErrorBoundary,
|
|
476
|
+
B as Image,
|
|
477
|
+
Q as Routerino,
|
|
478
|
+
Q as default,
|
|
479
|
+
u as updateHeadTag,
|
|
480
|
+
oe as useRouterino
|
|
372
481
|
};
|
package/dist/routerino.umd.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(g,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react/jsx-runtime"),require("react"),require("prop-types")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","prop-types"],a):(g=typeof globalThis<"u"?globalThis:g||self,a(g.routerino={},g["react/jsx-runtime"],g.React,g.PropTypes))})(this,function(g,a,$,t){"use strict";const q=$.createContext(null);function O(){const c=$.useContext(q);if(!c)throw new Error("useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component.");return c}function i({tag:c="meta",soft:l=!1,...s}){const o=Object.keys(s);if(o.length<1)return console.error(`[Routerino] updateHeadTag() received no attributes to set for ${c} tag`);let p=null;for(let h=0;h<o.length&&(o[h]!=="content"&&(p=document.querySelector(`${c}[${o[h]}='${s[o[h]]}']`)),!p);h++);p&&l||(p||(p=document.createElement(c)),o.forEach(h=>p.setAttribute(h,s[h])),document.querySelector("head").appendChild(p))}function D({routePattern:c,currentRoute:l}){let s={},o=c.split("/"),p=l.split("/");return o.forEach((h,b)=>{h.startsWith(":")&&(s[h.slice(1)]=p[b])}),s}class E extends $.Component{constructor(l){super(l),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(l,s){this.props.debug&&(console.group("%c[Routerino]%c Error Boundary Caught an Error","color: #ff6b6b; font-weight: bold","",l),console.error("[Routerino] Component Stack:",s.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&&i({name:"prerender-status-code",content:"500"})}render(){return this.state.hasError?this.props.fallback:this.props.children}}E.propTypes={children:t.node,fallback:t.node,errorTitleString:t.string.isRequired,usePrerenderTags:t.bool,routePath:t.string,debug:t.bool};function S({routes:c=[{path:"/",element:a.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:l=a.jsxs(a.Fragment,{children:[a.jsx("p",{children:"No page found for this URL. [404]"}),a.jsx("p",{children:a.jsx("a",{href:"/",children:"Home"})})]}),notFoundTitle:s="Page not found [404]",errorTemplate:o=a.jsxs(a.Fragment,{children:[a.jsx("p",{children:"Page failed to load. [500]"}),a.jsx("p",{children:a.jsx("a",{href:"/",children:"Home"})})]}),errorTitle:p="Page error [500]",useTrailingSlash:h=!0,usePrerenderTags:b=!1,baseUrl:z=null,title:v="",separator:y=" | ",imageUrl:C=null,touchIconUrl:B=null,debug:u=!1}){const L=`${p}${y}${v}`,W=`${s}${y}${v}`;try{if(u){const e=c.map(m=>m.path),d=e.filter((m,f)=>e.indexOf(m)!==f);d.length>0&&(console.warn("%c[Routerino]%c Duplicate route paths detected:","color: #f59e0b; font-weight: bold","",[...new Set(d)]),console.warn("%c[Routerino]%c The first matching route will be used","color: #f59e0b; font-weight: bold",""))}const[k,F]=$.useState(window?.location?.href??"/");$.useEffect(()=>{if(typeof window>"u"||typeof document>"u")return;const e=m=>{u&&console.debug("%c[Routerino]%c click occurred","color: #6b7280; font-weight: bold","");let f=m.target;for(;f.tagName!=="A"&&f.parentElement;)f=f.parentElement;if(f.tagName!=="A"){u&&console.debug("%c[Routerino]%c no anchor tag found during click","color: #6b7280; font-weight: bold","");return}const w=f.getAttribute("href")||f.href;if(!w){u&&console.debug("%c[Routerino]%c anchor tag has no href","color: #6b7280; font-weight: bold","");return}if(!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(w)){u&&console.debug("%c[Routerino]%c skipping non-http URL:","color: #6b7280; font-weight: bold","",w);return}u&&console.debug("%c[Routerino]%c click target href:","color: #6b7280; font-weight: bold","",w);let R;try{R=new URL(w,window.location.href)}catch(U){u&&console.debug("%c[Routerino]%c Invalid URL:","color: #6b7280; font-weight: bold","",w,U);return}u&&console.debug("%c[Routerino]%c targetUrl:","color: #6b7280; font-weight: bold","",R,"current:",window.location),R&&window.location.origin===R.origin?(u&&console.debug("%c[Routerino]%c target link is same origin, will use push-state transitioning","color: #6b7280; font-weight: bold",""),m.preventDefault(),f.href!==window.location.href&&(F(f.href),window.history.pushState({},"",f.href)),window.scrollTo({top:0,behavior:"auto"})):u&&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",e);const d=()=>{u&&console.debug("%c[Routerino]%c route change ->","color: #6b7280; font-weight: bold","",window.location.pathname),F(window.location.href)};return window.addEventListener("popstate",d),()=>{document.removeEventListener("click",e),window.removeEventListener("popstate",d)}},[k]);let r=window?.location?.pathname??"/";(r==="/index.html"||r==="")&&(r="/");const H=c.find(e=>e.path===r),j=c.find(e=>`${e.path}/`===r||e.path===`${r}/`),A=c.find(e=>{const d=e.path.endsWith("/")?e.path.slice(0,-1):e.path,m=r.endsWith("/")?r.slice(0,-1):r,f=d.split("/").filter(Boolean),w=m.split("/").filter(Boolean);return f.length!==w.length?!1:f.every((R,U)=>R.startsWith(":")?!0:R===w[U])}),n=H??j??A;if(u&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:n,exactMatch:H,addSlashMatch:j,paramsMatch:A}),!n)return u&&(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","",r),console.warn("%c[Routerino]%c Available routes:","color: #f59e0b; font-weight: bold","",c.map(e=>e.path)),console.groupEnd()),document.title=W,b&&i({name:"prerender-status-code",content:"404"}),l;if(b){const e=document.querySelector('meta[name="prerender-status-code"]');e&&e.remove();const d=document.querySelector('meta[name="prerender-header"]');d&&d.remove()}const I=h&&!r.endsWith("/")&&r!=="/",M=!h&&r.endsWith("/")&&r!=="/",T=I?`${r}/`:M?r.slice(0,-1):r,x=`${z??window?.location?.origin??""}${T}`;if(n.title){const e=`${n.title}${y}${v}`;document.title=e,i({tag:"link",rel:"canonical",href:x}),n.tags?.find(({property:d})=>d==="og:title")||i({property:"og:title",content:e}),n.tags?.find(({property:d})=>d==="og:url")||i({property:"og:url",content:x})}if(n.description&&(i({name:"description",content:n.description}),n.tags?.find(({property:e})=>e==="og:description")||i({property:"og:description",content:n.description})),(C||n.imageUrl)&&i({property:"og:image",content:n.imageUrl??C}),n.tags?.find(({property:e})=>e==="twitter:card")||i({name:"twitter:card",content:"summary_large_image"}),B&&i({tag:"link",rel:"apple-touch-icon",href:B}),b&&(I||M)&&(i({name:"prerender-status-code",content:"301"}),i({name:"prerender-header",content:`Location: ${x}`})),n.tags&&n.tags.length?(n.tags.find(({property:e})=>e==="og:type")||i({property:"og:type",content:"website"}),n.tags.forEach(e=>i(e))):i({property:"og:type",content:"website"}),n.element){const e=D({routePattern:n.path,currentRoute:r}),d={currentRoute:r,params:e,routePattern:n.path,updateHeadTag:i};return a.jsx(q.Provider,{value:d,children:a.jsx(E,{fallback:o,errorTitleString:L,usePrerenderTags:b,routePath:r,debug:u,children:n.element})})}return u&&console.error("%c[Routerino]%c No route found for","color: #ff6b6b; font-weight: bold","",r),document.title=W,b&&i({name:"prerender-status-code",content:"404"}),l}catch(k){return u&&(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","",k),console.error("%c[Routerino]%c This typically means an issue with route configuration or router setup","color: #ff6b6b; font-weight: bold",""),console.groupEnd()),b&&i({name:"prerender-status-code",content:"500"}),document.title=L,o}}const _=t.exact({path:(c,l,s)=>{const o=c[l];return o==null?new Error(`The prop \`${l}\` is marked as required in \`${s}\`, but its value is \`${o}\`.`):typeof o!="string"?new Error(`Invalid prop \`${l}\` of type \`${typeof o}\` supplied to \`${s}\`, expected \`string\`.`):o.startsWith("/")?null:new Error(`Invalid prop \`${l}\` value \`${o}\` supplied to \`${s}\`. Route paths must start with a forward slash (/).`)},element:t.element.isRequired,title:t.string,description:t.string,tags:t.arrayOf(t.object),imageUrl:t.string});S.propTypes={routes:t.arrayOf(_),title:t.string,separator:t.string,notFoundTemplate:t.element,notFoundTitle:t.string,errorTemplate:t.element,errorTitle:t.string,useTrailingSlash:t.bool,usePrerenderTags:t.bool,baseUrl:(c,l,s)=>{const o=c[l];if(o!=null){if(typeof o!="string")return new Error(`Invalid prop \`${l}\` of type \`${typeof o}\` supplied to \`${s}\`, expected \`string\`.`);if(o.endsWith("/"))return new Error(`Invalid prop \`${l}\` supplied to \`${s}\`. The baseUrl should not end with a slash. Got: "${o}"`)}return null},imageUrl:t.string,touchIconUrl:t.string,debug:t.bool},g.ErrorBoundary=E,g.Routerino=S,g.default=S,g.updateHeadTag=i,g.useRouterino=O,Object.defineProperties(g,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
|
1
|
+
(function(p,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("react/jsx-runtime"),require("react"),require("prop-types")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","prop-types"],l):(p=typeof globalThis<"u"?globalThis:p||self,l(p.routerino={},p["react/jsx-runtime"],p.React,p.PropTypes))})(this,function(p,l,$,e){"use strict";const V=[480,800,1200,1920],G="(max-width: 480px) 100vw, (max-width: 800px) 800px, (max-width: 1200px) 1200px, 1920px";function K(r,t=""){const i=r.toLowerCase(),n=t.toLowerCase();return!!(i.includes("hero")||i.includes("banner")||n.includes("hero")||n.includes("banner")||n.includes("h-screen")||n.includes("h-full"))}function M(r,t,i=null){const n=r.replace(/\.(jpe?g|png|webp)$/i,""),g=i?`.${i}`:r.match(/\.(jpe?g|png|webp)$/i)?.[0]||".jpg";return t.map(u=>`${n}-${u}w${g} ${u}w`).join(", ")}function F(r){const{src:t="",alt:i="",priority:n,widths:g=V,sizes:u=G,className:w="",style:q={},loading:E,decoding:v="async",fetchpriority:k,...U}=r||{};if(typeof window<"u"&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"))return l.jsx("img",{src:t,alt:i,loading:E||"lazy",decoding:v,fetchPriority:k,className:w,style:q,...U});const[L,A]=$.useState(null),x=t&&typeof window<"u";$.useEffect(()=>{if(!x)return;const S=new F;S.onload=()=>{A({width:S.naturalWidth,height:S.naturalHeight})},S.src=t},[t,x]);const R=L?g.filter(S=>L.width>=S):g,[c,D]=$.useState(R);$.useEffect(()=>{(async()=>{if(typeof window>"u"){D(R);return}const j=t.replace(/\.(jpe?g|png|webp)$/i,""),o=t.match(/\.(jpe?g|png|webp)$/i)?.[0]||".jpg",s=[];for(const b of R){const h=`${j}-${b}w${o}`;try{(await fetch(h,{method:"HEAD"})).ok&&s.push(b)}catch{}}s.length===0&&R.length>0&&s.push(Math.min(...R)),D(s)})()},[t,R]);const W=n??K(t,w),C=E||(W?"eager":"lazy"),a=k||(W?"high":void 0),I=M(t,c,"webp"),O=M(t,c);return l.jsxs("picture",{"data-routerino-image":"true","data-original-src":t,children:[l.jsx("source",{srcSet:I,type:"image/webp",sizes:u}),l.jsx("img",{src:t,alt:i,srcSet:O,sizes:u,loading:C,decoding:v,fetchPriority:a,className:w,style:q,...U})]})}F.propTypes={src:e.string.isRequired,alt:e.string.isRequired,priority:e.bool,widths:e.arrayOf(e.number),sizes:e.string,className:e.string,style:e.object,loading:e.oneOf(["lazy","eager"]),decoding:e.oneOf(["sync","async","auto"]),fetchpriority:e.oneOf(["high","low","auto"])};const _=$.createContext(null);function Z(){const r=$.useContext(_);if(!r)throw new Error("useRouterino must be used within a Routerino router. Make sure your component is rendered inside a <Routerino> component.");return r}function d({tag:r="meta",soft:t=!1,...i}){const n=Object.keys(i);if(n.length<1)return console.error(`[Routerino] updateHeadTag() received no attributes to set for ${r} tag`);let g=null;for(let u=0;u<n.length&&(n[u]!=="content"&&(g=document.querySelector(`${r}[${n[u]}='${i[n[u]]}']`)),!g);u++);g&&t||(g||(g=document.createElement(r)),n.forEach(u=>g.setAttribute(u,i[u])),document.querySelector("head").appendChild(g))}function J({routePattern:r,currentRoute:t}){let i={},n=r.split("/"),g=t.split("/");return n.forEach((u,w)=>{u.startsWith(":")&&(i[u.slice(1)]=g[w])}),i}class H extends $.Component{constructor(t){super(t),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(t,i){this.props.debug&&(console.group("%c[Routerino]%c Error Boundary Caught an Error","color: #ff6b6b; font-weight: bold","",t),console.error("[Routerino] Component Stack:",i.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}}H.propTypes={children:e.node,fallback:e.node,errorTitleString:e.string.isRequired,usePrerenderTags:e.bool,routePath:e.string,debug:e.bool};function B({routes:r=[{path:"/",element:l.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:t=l.jsxs(l.Fragment,{children:[l.jsx("p",{children:"No page found for this URL. [404]"}),l.jsx("p",{children:l.jsx("a",{href:"/",children:"Home"})})]}),notFoundTitle:i="Page not found [404]",errorTemplate:n=l.jsxs(l.Fragment,{children:[l.jsx("p",{children:"Page failed to load. [500]"}),l.jsx("p",{children:l.jsx("a",{href:"/",children:"Home"})})]}),errorTitle:g="Page error [500]",useTrailingSlash:u=!0,usePrerenderTags:w=!1,baseUrl:q=null,title:E="",separator:v=" | ",imageUrl:k=null,touchIconUrl:U=null,debug:f=!1}){const L=`${g}${v}${E}`,A=`${i}${v}${E}`;try{if(f){const o=r.map(b=>b.path),s=o.filter((b,h)=>o.indexOf(b)!==h);s.length>0&&(console.warn("%c[Routerino]%c Duplicate route paths detected:","color: #f59e0b; font-weight: bold","",[...new Set(s)]),console.warn("%c[Routerino]%c The first matching route will be used","color: #f59e0b; font-weight: bold",""))}const[x,R]=$.useState(window?.location?.href??"/");$.useEffect(()=>{if(typeof window>"u"||typeof document>"u")return;const o=b=>{f&&console.debug("%c[Routerino]%c click occurred","color: #6b7280; font-weight: bold","");let h=b.target;for(;h.tagName!=="A"&&h.parentElement;)h=h.parentElement;if(h.tagName!=="A"){f&&console.debug("%c[Routerino]%c no anchor tag found during click","color: #6b7280; font-weight: bold","");return}const m=h.getAttribute("href")||h.href;if(!m){f&&console.debug("%c[Routerino]%c anchor tag has no href","color: #6b7280; font-weight: bold","");return}if(!/^(https?:\/\/|\/|\.\/|\.\.\/|[^:]+$)/i.test(m)){f&&console.debug("%c[Routerino]%c skipping non-http URL:","color: #6b7280; font-weight: bold","",m);return}f&&console.debug("%c[Routerino]%c click target href:","color: #6b7280; font-weight: bold","",m);let y;try{y=new URL(m,window.location.href)}catch(z){f&&console.debug("%c[Routerino]%c Invalid URL:","color: #6b7280; font-weight: bold","",m,z);return}f&&console.debug("%c[Routerino]%c targetUrl:","color: #6b7280; font-weight: bold","",y,"current:",window.location),y&&window.location.origin===y.origin?(f&&console.debug("%c[Routerino]%c target link is same origin, will use push-state transitioning","color: #6b7280; font-weight: bold",""),b.preventDefault(),h.href!==window.location.href&&(R(h.href),window.history.pushState({},"",h.href)),window.scrollTo({top:0,behavior:"auto"})):f&&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",o);const s=()=>{f&&console.debug("%c[Routerino]%c route change ->","color: #6b7280; font-weight: bold","",window.location.pathname),R(window.location.href)};return window.addEventListener("popstate",s),()=>{document.removeEventListener("click",o),window.removeEventListener("popstate",s)}},[x]);let c=window?.location?.pathname??"/";(c==="/index.html"||c==="")&&(c="/");const D=r.find(o=>o.path===c),W=r.find(o=>`${o.path}/`===c||o.path===`${c}/`),C=r.find(o=>{const s=o.path.endsWith("/")?o.path.slice(0,-1):o.path,b=c.endsWith("/")?c.slice(0,-1):c,h=s.split("/").filter(Boolean),m=b.split("/").filter(Boolean);return h.length!==m.length?!1:h.every((y,z)=>y.startsWith(":")?!0:y===m[z])}),a=D??W??C;if(f&&console.debug("%c[Routerino]%c Route matching:","color: #6b7280; font-weight: bold","",{match:a,exactMatch:D,addSlashMatch:W,paramsMatch:C}),!a)return f&&(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","",c),console.warn("%c[Routerino]%c Available routes:","color: #f59e0b; font-weight: bold","",r.map(o=>o.path)),console.groupEnd()),document.title=A,w&&d({name:"prerender-status-code",content:"404"}),t;if(w){const o=document.querySelector('meta[name="prerender-status-code"]');o&&o.remove();const s=document.querySelector('meta[name="prerender-header"]');s&&s.remove()}const I=u&&!c.endsWith("/")&&c!=="/",O=!u&&c.endsWith("/")&&c!=="/",S=I?`${c}/`:O?c.slice(0,-1):c,j=`${q??window?.location?.origin??""}${S}`;if(a.title){const o=`${a.title}${v}${E}`;document.title=o,d({tag:"link",rel:"canonical",href:j}),a.tags?.find(({property:s})=>s==="og:title")||d({property:"og:title",content:o}),a.tags?.find(({property:s})=>s==="og:url")||d({property:"og:url",content:j})}if(a.description&&(d({name:"description",content:a.description}),a.tags?.find(({property:o})=>o==="og:description")||d({property:"og:description",content:a.description})),(k||a.imageUrl)&&d({property:"og:image",content:a.imageUrl??k}),a.tags?.find(({property:o})=>o==="twitter:card")||d({name:"twitter:card",content:"summary_large_image"}),U&&d({tag:"link",rel:"apple-touch-icon",href:U}),w&&(I||O)&&(d({name:"prerender-status-code",content:"301"}),d({name:"prerender-header",content:`Location: ${j}`})),a.tags&&a.tags.length?(a.tags.find(({property:o})=>o==="og:type")||d({property:"og:type",content:"website"}),a.tags.forEach(o=>d(o))):d({property:"og:type",content:"website"}),a.element){const o=J({routePattern:a.path,currentRoute:c}),s={currentRoute:c,params:o,routePattern:a.path,updateHeadTag:d};return l.jsx(_.Provider,{value:s,children:l.jsx(H,{fallback:n,errorTitleString:L,usePrerenderTags:w,routePath:c,debug:f,children:a.element})})}return f&&console.error("%c[Routerino]%c No route found for","color: #ff6b6b; font-weight: bold","",c),document.title=A,w&&d({name:"prerender-status-code",content:"404"}),t}catch(x){return f&&(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","",x),console.error("%c[Routerino]%c This typically means an issue with route configuration or router setup","color: #ff6b6b; font-weight: bold",""),console.groupEnd()),w&&d({name:"prerender-status-code",content:"500"}),document.title=L,n}}const N=e.exact({path:(r,t,i)=>{const n=r[t];return n==null?new Error(`The prop \`${t}\` is marked as required in \`${i}\`, but its value is \`${n}\`.`):typeof n!="string"?new Error(`Invalid prop \`${t}\` of type \`${typeof n}\` supplied to \`${i}\`, expected \`string\`.`):n.startsWith("/")?null:new Error(`Invalid prop \`${t}\` value \`${n}\` supplied to \`${i}\`. Route paths must start with a forward slash (/).`)},element:e.element.isRequired,title:e.string,description:e.string,tags:e.arrayOf(e.object),imageUrl:e.string});B.propTypes={routes:e.arrayOf(N),title:e.string,separator:e.string,notFoundTemplate:e.element,notFoundTitle:e.string,errorTemplate:e.element,errorTitle:e.string,useTrailingSlash:e.bool,usePrerenderTags:e.bool,baseUrl:(r,t,i)=>{const n=r[t];if(n!=null){if(typeof n!="string")return new Error(`Invalid prop \`${t}\` of type \`${typeof n}\` supplied to \`${i}\`, expected \`string\`.`);if(n.endsWith("/"))return new Error(`Invalid prop \`${t}\` supplied to \`${i}\`. The baseUrl should not end with a slash. Got: "${n}"`)}return null},imageUrl:e.string,touchIconUrl:e.string,debug:e.bool},p.ErrorBoundary=H,p.Image=F,p.Routerino=B,p.default=B,p.updateHeadTag=d,p.useRouterino=Z,Object.defineProperties(p,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/package.json
CHANGED
package/routerino-image.jsx
CHANGED
|
@@ -70,6 +70,28 @@ export function Image(props) {
|
|
|
70
70
|
...rest
|
|
71
71
|
} = props || {};
|
|
72
72
|
|
|
73
|
+
// In development, skip responsive images entirely to avoid 404s
|
|
74
|
+
const isDevelopment =
|
|
75
|
+
typeof window !== "undefined" &&
|
|
76
|
+
(window.location.hostname === "localhost" ||
|
|
77
|
+
window.location.hostname === "127.0.0.1");
|
|
78
|
+
|
|
79
|
+
if (isDevelopment) {
|
|
80
|
+
return (
|
|
81
|
+
<img
|
|
82
|
+
src={src}
|
|
83
|
+
alt={alt}
|
|
84
|
+
loading={explicitLoading || "lazy"}
|
|
85
|
+
decoding={decoding}
|
|
86
|
+
fetchPriority={explicitFetchPriority}
|
|
87
|
+
className={className}
|
|
88
|
+
style={style}
|
|
89
|
+
{...rest}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Production mode: full responsive image functionality
|
|
73
95
|
// Detect image dimensions to filter applicable widths
|
|
74
96
|
const [imageDimensions, setImageDimensions] = useState(null);
|
|
75
97
|
|
|
@@ -94,6 +116,43 @@ export function Image(props) {
|
|
|
94
116
|
? widths.filter((width) => imageDimensions.width >= width)
|
|
95
117
|
: widths; // Fallback to all widths during loading
|
|
96
118
|
|
|
119
|
+
// Check which responsive variants actually exist at runtime
|
|
120
|
+
const [availableWidths, setAvailableWidths] = useState(applicableWidths);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
const checkAvailableVariants = async () => {
|
|
124
|
+
if (typeof window === "undefined") {
|
|
125
|
+
setAvailableWidths(applicableWidths);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const base = src.replace(/\.(jpe?g|png|webp)$/i, "");
|
|
130
|
+
const ext = src.match(/\.(jpe?g|png|webp)$/i)?.[0] || ".jpg";
|
|
131
|
+
|
|
132
|
+
const existingWidths = [];
|
|
133
|
+
for (const width of applicableWidths) {
|
|
134
|
+
const variantUrl = `${base}-${width}w${ext}`;
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(variantUrl, { method: "HEAD" });
|
|
137
|
+
if (response.ok) {
|
|
138
|
+
existingWidths.push(width);
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// Variant doesn't exist, skip it
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Always include at least the smallest width if no variants exist
|
|
146
|
+
if (existingWidths.length === 0 && applicableWidths.length > 0) {
|
|
147
|
+
existingWidths.push(Math.min(...applicableWidths));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setAvailableWidths(existingWidths);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
checkAvailableVariants();
|
|
154
|
+
}, [src, applicableWidths]);
|
|
155
|
+
|
|
97
156
|
// Smart priority detection if not explicitly set
|
|
98
157
|
const autoPriority = priority ?? shouldUsePriority(src, className);
|
|
99
158
|
|
|
@@ -102,9 +161,9 @@ export function Image(props) {
|
|
|
102
161
|
const fetchPriority =
|
|
103
162
|
explicitFetchPriority || (autoPriority ? "high" : undefined);
|
|
104
163
|
|
|
105
|
-
// Generate srcsets for different formats using
|
|
106
|
-
const srcSetWebP = generateSrcSet(src,
|
|
107
|
-
const srcSetOriginal = generateSrcSet(src,
|
|
164
|
+
// Generate srcsets for different formats using available widths only
|
|
165
|
+
const srcSetWebP = generateSrcSet(src, availableWidths, "webp");
|
|
166
|
+
const srcSetOriginal = generateSrcSet(src, availableWidths);
|
|
108
167
|
|
|
109
168
|
// For SSG: This will be processed by routerino-forge.js to include LQIP
|
|
110
169
|
// The data-routerino-image attribute signals to the forge to process this
|