@bapla/site-theme 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +169 -2
- package/dist/base.css +331 -0
- package/dist/chunk-RYCW4YWV.js +69 -0
- package/dist/imgproxy-rioGvvyv.d.ts +189 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.js +904 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.js +31 -0
- package/package.json +65 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MEDIA_WIDTHS,
|
|
3
|
+
imgproxyPath,
|
|
4
|
+
isTransformable,
|
|
5
|
+
mediaUrl,
|
|
6
|
+
objectPosition,
|
|
7
|
+
readMedia
|
|
8
|
+
} from "./chunk-RYCW4YWV.js";
|
|
9
|
+
|
|
10
|
+
// src/theme/themeCss.ts
|
|
11
|
+
var SHADES = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
|
|
12
|
+
var RAMPS = ["primary", "secondary", "accent", "neutral"];
|
|
13
|
+
var LEVELS = ["h1", "h2", "h3", "h4", "h5", "h6", "p"];
|
|
14
|
+
var HEX = /^#[0-9a-f]{6}$/i;
|
|
15
|
+
var LENGTH = /^-?(\d+(\.\d+)?|\.\d+)(rem|em|px|%)?$/;
|
|
16
|
+
var FONT_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
17
|
+
function hex(value) {
|
|
18
|
+
return typeof value === "string" && HEX.test(value) ? value.toLowerCase() : null;
|
|
19
|
+
}
|
|
20
|
+
function length(value) {
|
|
21
|
+
return typeof value === "string" && LENGTH.test(value.trim()) ? value.trim() : null;
|
|
22
|
+
}
|
|
23
|
+
function number(value, min, max) {
|
|
24
|
+
return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : null;
|
|
25
|
+
}
|
|
26
|
+
function familyName(name) {
|
|
27
|
+
return name.replace(/[^\p{L}\p{N} _.-]/gu, "").replace(/\s+/g, " ").trim();
|
|
28
|
+
}
|
|
29
|
+
function fontStack(font) {
|
|
30
|
+
const fallback = ["sans-serif", "serif", "monospace"].includes(font.fallback) ? font.fallback : "sans-serif";
|
|
31
|
+
const name = familyName(font.family);
|
|
32
|
+
return name === "" ? fallback : `"${name} Variable", "${name}", ${fallback}`;
|
|
33
|
+
}
|
|
34
|
+
function declarations(entries) {
|
|
35
|
+
return entries.filter((entry) => entry[1] !== null).map(([name, value]) => ` ${name}: ${value};`).join("\n");
|
|
36
|
+
}
|
|
37
|
+
function rampVars(name, ramp) {
|
|
38
|
+
return SHADES.map((shade) => [`--color-${name}-${shade}`, hex(ramp?.[shade])]);
|
|
39
|
+
}
|
|
40
|
+
function themeCss(appearance) {
|
|
41
|
+
const { colors, fonts, type } = appearance;
|
|
42
|
+
const theme = [
|
|
43
|
+
...RAMPS.flatMap((name) => rampVars(name, colors[name])),
|
|
44
|
+
["--color-background", hex(colors.background)],
|
|
45
|
+
["--color-text", hex(colors.text)],
|
|
46
|
+
["--font-body", fontStack(fonts.body)],
|
|
47
|
+
["--font-heading", fontStack(fonts.heading)]
|
|
48
|
+
];
|
|
49
|
+
for (const level of LEVELS) {
|
|
50
|
+
const style = type[level];
|
|
51
|
+
theme.push(
|
|
52
|
+
[`--text-${level}`, length(style?.size)],
|
|
53
|
+
[`--text-${level}--line-height`, number(style?.line_height, 0.5, 4)],
|
|
54
|
+
[`--text-${level}--letter-spacing`, length(style?.letter_spacing)],
|
|
55
|
+
[`--text-${level}--font-weight`, number(style?.weight, 100, 1e3)]
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
theme.push(["--radius-base", length(appearance.radius)]);
|
|
59
|
+
const headings = LEVELS.filter((level) => level !== "p").map(
|
|
60
|
+
(level) => ` ${level} {
|
|
61
|
+
font-family: var(--font-heading);
|
|
62
|
+
font-size: var(--text-${level});
|
|
63
|
+
line-height: var(--text-${level}--line-height);
|
|
64
|
+
letter-spacing: var(--text-${level}--letter-spacing);
|
|
65
|
+
font-weight: var(--text-${level}--font-weight);
|
|
66
|
+
}`
|
|
67
|
+
).join("\n");
|
|
68
|
+
const parts = [
|
|
69
|
+
`@theme {
|
|
70
|
+
${declarations(theme)}
|
|
71
|
+
}`,
|
|
72
|
+
`@layer base {
|
|
73
|
+
:root {
|
|
74
|
+
color-scheme: ${appearance.mode === "both" ? "light dark" : appearance.mode};
|
|
75
|
+
}
|
|
76
|
+
body {
|
|
77
|
+
background-color: var(--color-background);
|
|
78
|
+
color: var(--color-text);
|
|
79
|
+
font-family: var(--font-body);
|
|
80
|
+
font-size: var(--text-p);
|
|
81
|
+
line-height: var(--text-p--line-height);
|
|
82
|
+
letter-spacing: var(--text-p--letter-spacing);
|
|
83
|
+
font-weight: var(--text-p--font-weight);
|
|
84
|
+
}
|
|
85
|
+
${headings}
|
|
86
|
+
p {
|
|
87
|
+
font-size: var(--text-p);
|
|
88
|
+
line-height: var(--text-p--line-height);
|
|
89
|
+
}
|
|
90
|
+
}`
|
|
91
|
+
];
|
|
92
|
+
if (appearance.mode === "both") {
|
|
93
|
+
parts.push(`@media (prefers-color-scheme: dark) {
|
|
94
|
+
:root {
|
|
95
|
+
${declarations([
|
|
96
|
+
["--color-background", hex(colors.neutral?.["950"])],
|
|
97
|
+
["--color-text", hex(colors.neutral?.["50"])]
|
|
98
|
+
]).replace(/^ {2}/gm, " ")}
|
|
99
|
+
}
|
|
100
|
+
}`);
|
|
101
|
+
}
|
|
102
|
+
return parts.join("\n\n") + "\n";
|
|
103
|
+
}
|
|
104
|
+
function fontImports(appearance, options = {}) {
|
|
105
|
+
const variable = options.variable ?? true;
|
|
106
|
+
const weights = /* @__PURE__ */ new Map();
|
|
107
|
+
const add = (font, levels, extra = []) => {
|
|
108
|
+
if (!FONT_ID.test(font.id)) return;
|
|
109
|
+
const set = weights.get(font.id) ?? /* @__PURE__ */ new Set();
|
|
110
|
+
for (const level of levels) {
|
|
111
|
+
const weight = number(appearance.type[level]?.weight, 100, 900);
|
|
112
|
+
if (weight !== null) set.add(Math.round(weight / 100) * 100);
|
|
113
|
+
}
|
|
114
|
+
extra.forEach((weight) => set.add(weight));
|
|
115
|
+
weights.set(font.id, set);
|
|
116
|
+
};
|
|
117
|
+
add(appearance.fonts.body, ["p"], [700]);
|
|
118
|
+
add(appearance.fonts.heading, ["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
119
|
+
if (variable) {
|
|
120
|
+
return [...weights.keys()].map((id) => `@fontsource-variable/${id}`);
|
|
121
|
+
}
|
|
122
|
+
return [...weights.entries()].flatMap(
|
|
123
|
+
([id, set]) => [...set].sort((a, b) => a - b).map((weight) => `@fontsource/${id}/${weight}.css`)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/context.tsx
|
|
128
|
+
import { createContext, useContext, useMemo } from "react";
|
|
129
|
+
import { jsx } from "react/jsx-runtime";
|
|
130
|
+
var frenchLabels = {
|
|
131
|
+
mainNavigation: "Navigation principale",
|
|
132
|
+
menu: "Menu",
|
|
133
|
+
submenu: (label) => `Sous-menu ${label}`,
|
|
134
|
+
breadcrumb: "Fil d'Ariane",
|
|
135
|
+
carousel: "Carrousel",
|
|
136
|
+
previous: "Pr\xE9c\xE9dent",
|
|
137
|
+
next: "Suivant",
|
|
138
|
+
slide: (index, count) => `${index} sur ${count}`,
|
|
139
|
+
goToSlide: (index) => `Aller \xE0 la diapositive ${index}`,
|
|
140
|
+
pause: "Mettre en pause",
|
|
141
|
+
play: "Lire",
|
|
142
|
+
embed: "Contenu int\xE9gr\xE9"
|
|
143
|
+
};
|
|
144
|
+
var defaultImageSizes = {
|
|
145
|
+
thumbnail: { w: 150, h: 150, fit: "cover" },
|
|
146
|
+
card: { w: 300, h: 300, fit: "cover" },
|
|
147
|
+
medium: { w: 768 },
|
|
148
|
+
large: { w: 1280 }
|
|
149
|
+
};
|
|
150
|
+
var defaults = {
|
|
151
|
+
components: {},
|
|
152
|
+
resolveLink: () => null,
|
|
153
|
+
resolveMedia: () => null,
|
|
154
|
+
imageUrl: (media, transform2) => mediaUrl(media, transform2),
|
|
155
|
+
imageSizes: defaultImageSizes,
|
|
156
|
+
labels: frenchLabels
|
|
157
|
+
};
|
|
158
|
+
var SiteThemeContext = createContext(defaults);
|
|
159
|
+
function BlocksProvider({ components, resolveLink, resolveMedia, imageUrl, imageSizes, labels, children }) {
|
|
160
|
+
const parent = useContext(SiteThemeContext);
|
|
161
|
+
const value = useMemo(
|
|
162
|
+
() => ({
|
|
163
|
+
components: { ...parent.components, ...components },
|
|
164
|
+
resolveLink: resolveLink ?? parent.resolveLink,
|
|
165
|
+
resolveMedia: resolveMedia ?? parent.resolveMedia,
|
|
166
|
+
imageUrl: imageUrl ?? parent.imageUrl,
|
|
167
|
+
imageSizes: { ...parent.imageSizes, ...imageSizes },
|
|
168
|
+
labels: { ...parent.labels, ...labels }
|
|
169
|
+
}),
|
|
170
|
+
[parent, components, resolveLink, resolveMedia, imageUrl, imageSizes, labels]
|
|
171
|
+
);
|
|
172
|
+
return /* @__PURE__ */ jsx(SiteThemeContext.Provider, { value, children });
|
|
173
|
+
}
|
|
174
|
+
function useSiteTheme() {
|
|
175
|
+
return useContext(SiteThemeContext);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/blocks/Blocks.tsx
|
|
179
|
+
import { Fragment as Fragment4 } from "react";
|
|
180
|
+
|
|
181
|
+
// src/links.ts
|
|
182
|
+
var NODE_ID = /^web_nod_[0-9a-f-]{36}$/;
|
|
183
|
+
function safeHref(url, resolve = () => null) {
|
|
184
|
+
if (typeof url !== "string" || url === "" || url.length > 2048 || /[\s<>"]/.test(url)) return null;
|
|
185
|
+
if (NODE_ID.test(url)) {
|
|
186
|
+
const resolved = resolve(url);
|
|
187
|
+
return resolved !== null && !NODE_ID.test(resolved) ? safeHref(resolved) : null;
|
|
188
|
+
}
|
|
189
|
+
if (url.startsWith("/") && !url.startsWith("//")) return url;
|
|
190
|
+
if (url.startsWith("#")) return url.length > 1 ? url : null;
|
|
191
|
+
if (/^mailto:[^@\s]+@[^@\s]+$/i.test(url) || /^tel:\+?[0-9 ().-]{3,30}$/i.test(url)) return url;
|
|
192
|
+
return /^https?:\/\/[^/]/i.test(url) ? url : null;
|
|
193
|
+
}
|
|
194
|
+
function readLink(value) {
|
|
195
|
+
if (typeof value === "string") return { url: value, label: null, new_tab: false };
|
|
196
|
+
if (value === null || typeof value !== "object") return null;
|
|
197
|
+
const link = value;
|
|
198
|
+
if (typeof link.url !== "string") return null;
|
|
199
|
+
return { url: link.url, label: typeof link.label === "string" ? link.label : null, new_tab: link.new_tab === true };
|
|
200
|
+
}
|
|
201
|
+
function targetProps(newTab) {
|
|
202
|
+
return newTab ? { target: "_blank", rel: "noopener noreferrer" } : {};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/menu/context.ts
|
|
206
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
207
|
+
var MenuItemContext = createContext2(null);
|
|
208
|
+
function useMenuItem() {
|
|
209
|
+
return useContext2(MenuItemContext);
|
|
210
|
+
}
|
|
211
|
+
function findItem(items, id) {
|
|
212
|
+
for (const item of items) {
|
|
213
|
+
if (item.id === id) return item;
|
|
214
|
+
const found = findItem(item.children, id);
|
|
215
|
+
if (found) return found;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/menu/MenuLinks.tsx
|
|
221
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
222
|
+
function MenuItemLink({ item, className }) {
|
|
223
|
+
const { resolveLink } = useSiteTheme();
|
|
224
|
+
const href = item.kind === "link" ? safeHref(item.url, resolveLink) : null;
|
|
225
|
+
if (!href) return /* @__PURE__ */ jsx2("span", { className, children: item.label });
|
|
226
|
+
return /* @__PURE__ */ jsx2("a", { className, href, "aria-current": item.active && item.children.length === 0 ? "page" : void 0, ...targetProps(item.new_tab), children: item.label });
|
|
227
|
+
}
|
|
228
|
+
function MenuLinks({ props }) {
|
|
229
|
+
const current = useMenuItem();
|
|
230
|
+
if (!current) return null;
|
|
231
|
+
const root = typeof props.item === "string" ? findItem([current], props.item) : current;
|
|
232
|
+
if (!root || root.children.length === 0) return null;
|
|
233
|
+
return /* @__PURE__ */ jsx2("ul", { className: "bapla-menu-links", children: root.children.map((child) => /* @__PURE__ */ jsxs("li", { "data-kind": child.kind, children: [
|
|
234
|
+
/* @__PURE__ */ jsx2(MenuItemLink, { item: child, className: child.kind === "heading" ? "bapla-menu-heading" : void 0 }),
|
|
235
|
+
child.children.length > 0 && /* @__PURE__ */ jsx2("ul", { children: child.children.map((grandchild) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(MenuItemLink, { item: grandchild }) }, grandchild.id)) })
|
|
236
|
+
] }, child.id)) });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/media/Media.tsx
|
|
240
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
241
|
+
function Media({ media, alt, sizes = "100vw", cover = false, priority = false, className, style }) {
|
|
242
|
+
const { imageUrl } = useSiteTheme();
|
|
243
|
+
const text2 = alt ?? media.alt ?? "";
|
|
244
|
+
const classes = ["bapla-media", className].filter(Boolean).join(" ");
|
|
245
|
+
if (media.mime.startsWith("video/")) {
|
|
246
|
+
return /* @__PURE__ */ jsx3("video", { className: classes, style, src: mediaUrl(media), controls: true, preload: "metadata", width: media.width ?? void 0, height: media.height ?? void 0, "aria-label": text2 || void 0 });
|
|
247
|
+
}
|
|
248
|
+
const scalable = media.mime !== "image/svg+xml" && media.mime !== "image/gif";
|
|
249
|
+
const widths = scalable ? MEDIA_WIDTHS.filter((w) => media.width === null || w <= media.width) : [];
|
|
250
|
+
if (scalable && media.width !== null && !widths.includes(media.width) && media.width < MEDIA_WIDTHS[MEDIA_WIDTHS.length - 1]) {
|
|
251
|
+
widths.push(media.width);
|
|
252
|
+
}
|
|
253
|
+
const largest = widths.length > 0 ? Math.min(widths[widths.length - 1], 1280) : void 0;
|
|
254
|
+
return /* @__PURE__ */ jsx3(
|
|
255
|
+
"img",
|
|
256
|
+
{
|
|
257
|
+
className: classes,
|
|
258
|
+
src: largest !== void 0 ? imageUrl(media, { w: largest }) : mediaUrl(media),
|
|
259
|
+
srcSet: widths.length > 1 ? widths.map((w) => `${imageUrl(media, { w })} ${w}w`).join(", ") : void 0,
|
|
260
|
+
sizes: widths.length > 1 ? sizes : void 0,
|
|
261
|
+
alt: text2,
|
|
262
|
+
width: media.width ?? void 0,
|
|
263
|
+
height: media.height ?? void 0,
|
|
264
|
+
loading: priority ? "eager" : "lazy",
|
|
265
|
+
decoding: "async",
|
|
266
|
+
style: { ...cover ? { objectFit: "cover", objectPosition: objectPosition(media.focal_point) } : {}, ...style }
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/richtext/RichText.tsx
|
|
272
|
+
import { Fragment } from "react";
|
|
273
|
+
import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
|
|
274
|
+
var ALIGN = ["left", "center", "right", "justify"];
|
|
275
|
+
function align(node) {
|
|
276
|
+
const value = node.attrs?.textAlign;
|
|
277
|
+
return typeof value === "string" && ALIGN.includes(value) && value !== "left" ? { textAlign: value } : void 0;
|
|
278
|
+
}
|
|
279
|
+
function span(value) {
|
|
280
|
+
return typeof value === "number" && Number.isInteger(value) && value > 1 && value < 100 ? value : void 0;
|
|
281
|
+
}
|
|
282
|
+
function RichText({ doc, className }) {
|
|
283
|
+
const theme = useSiteTheme();
|
|
284
|
+
if (doc === null || typeof doc !== "object" || doc.type !== "doc") return null;
|
|
285
|
+
const marks = (node, text2) => (node.marks ?? []).reduceRight((inner, mark) => {
|
|
286
|
+
switch (mark.type) {
|
|
287
|
+
case "bold":
|
|
288
|
+
return /* @__PURE__ */ jsx4("strong", { children: inner });
|
|
289
|
+
case "italic":
|
|
290
|
+
return /* @__PURE__ */ jsx4("em", { children: inner });
|
|
291
|
+
case "underline":
|
|
292
|
+
return /* @__PURE__ */ jsx4("u", { children: inner });
|
|
293
|
+
case "strike":
|
|
294
|
+
return /* @__PURE__ */ jsx4("s", { children: inner });
|
|
295
|
+
case "code":
|
|
296
|
+
return /* @__PURE__ */ jsx4("code", { children: inner });
|
|
297
|
+
case "link": {
|
|
298
|
+
const href = safeHref(mark.attrs?.href, theme.resolveLink);
|
|
299
|
+
if (href === null) return inner;
|
|
300
|
+
const blank = mark.attrs?.target === "_blank";
|
|
301
|
+
return /* @__PURE__ */ jsx4("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children: inner });
|
|
302
|
+
}
|
|
303
|
+
default:
|
|
304
|
+
return inner;
|
|
305
|
+
}
|
|
306
|
+
}, text2);
|
|
307
|
+
const children = (node) => (node.content ?? []).map((child, index) => /* @__PURE__ */ jsx4(Fragment, { children: render(child) }, index));
|
|
308
|
+
function render(node) {
|
|
309
|
+
switch (node.type) {
|
|
310
|
+
case "text":
|
|
311
|
+
return typeof node.text === "string" ? marks(node, node.text) : null;
|
|
312
|
+
case "paragraph":
|
|
313
|
+
return /* @__PURE__ */ jsx4("p", { style: align(node), children: children(node) });
|
|
314
|
+
case "hardBreak":
|
|
315
|
+
return /* @__PURE__ */ jsx4("br", {});
|
|
316
|
+
case "heading": {
|
|
317
|
+
const level = node.attrs?.level;
|
|
318
|
+
const Tag = level === 3 ? "h3" : level === 4 ? "h4" : "h2";
|
|
319
|
+
return /* @__PURE__ */ jsx4(Tag, { style: align(node), children: children(node) });
|
|
320
|
+
}
|
|
321
|
+
case "bulletList":
|
|
322
|
+
return /* @__PURE__ */ jsx4("ul", { children: children(node) });
|
|
323
|
+
case "orderedList": {
|
|
324
|
+
const start = typeof node.attrs?.start === "number" && node.attrs.start !== 1 ? node.attrs.start : void 0;
|
|
325
|
+
return /* @__PURE__ */ jsx4("ol", { start, children: children(node) });
|
|
326
|
+
}
|
|
327
|
+
case "listItem":
|
|
328
|
+
return /* @__PURE__ */ jsx4("li", { children: children(node) });
|
|
329
|
+
case "blockquote":
|
|
330
|
+
return /* @__PURE__ */ jsx4("blockquote", { children: children(node) });
|
|
331
|
+
case "codeBlock": {
|
|
332
|
+
const language = typeof node.attrs?.language === "string" && /^[a-z0-9+#-]{1,30}$/i.test(node.attrs.language) ? `language-${node.attrs.language}` : void 0;
|
|
333
|
+
return /* @__PURE__ */ jsx4("pre", { children: /* @__PURE__ */ jsx4("code", { className: language, children: (node.content ?? []).map((c) => c.text ?? "").join("") }) });
|
|
334
|
+
}
|
|
335
|
+
case "horizontalRule":
|
|
336
|
+
return /* @__PURE__ */ jsx4("hr", {});
|
|
337
|
+
case "image": {
|
|
338
|
+
const src = node.attrs?.src;
|
|
339
|
+
const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
|
|
340
|
+
if (typeof src === "string" && src.startsWith("web_med_")) {
|
|
341
|
+
const media = readMedia(src, theme.resolveMedia);
|
|
342
|
+
return media ? /* @__PURE__ */ jsx4(Media, { media, alt: alt || null }) : null;
|
|
343
|
+
}
|
|
344
|
+
return typeof src === "string" && /^https:\/\/[^/]/.test(src) ? /* @__PURE__ */ jsx4("img", { src, alt, loading: "lazy", decoding: "async" }) : null;
|
|
345
|
+
}
|
|
346
|
+
case "table":
|
|
347
|
+
return /* @__PURE__ */ jsx4("div", { className: "bapla-table", children: /* @__PURE__ */ jsx4("table", { children: /* @__PURE__ */ jsx4("tbody", { children: children(node) }) }) });
|
|
348
|
+
case "tableRow":
|
|
349
|
+
return /* @__PURE__ */ jsx4("tr", { children: children(node) });
|
|
350
|
+
case "tableHeader":
|
|
351
|
+
return /* @__PURE__ */ jsx4("th", { colSpan: span(node.attrs?.colspan), rowSpan: span(node.attrs?.rowspan), scope: "col", children: children(node) });
|
|
352
|
+
case "tableCell":
|
|
353
|
+
return /* @__PURE__ */ jsx4("td", { colSpan: span(node.attrs?.colspan), rowSpan: span(node.attrs?.rowspan), children: children(node) });
|
|
354
|
+
default:
|
|
355
|
+
return node.content ? /* @__PURE__ */ jsx4(Fragment2, { children: children(node) }) : null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return /* @__PURE__ */ jsx4("div", { className: ["bapla-richtext", className].filter(Boolean).join(" "), children: children(doc) });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/blocks/props.ts
|
|
362
|
+
function choice(value, options, fallback) {
|
|
363
|
+
return typeof value === "string" && options.includes(value) ? value : fallback;
|
|
364
|
+
}
|
|
365
|
+
function integer(value, min, max) {
|
|
366
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max ? value : null;
|
|
367
|
+
}
|
|
368
|
+
function text(value) {
|
|
369
|
+
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
370
|
+
}
|
|
371
|
+
function flag(value) {
|
|
372
|
+
return value === true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/blocks/content.tsx
|
|
376
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
377
|
+
function Text({ props }) {
|
|
378
|
+
return /* @__PURE__ */ jsx5(RichText, { doc: props.content, className: "bapla-text" });
|
|
379
|
+
}
|
|
380
|
+
function MediaBlock({ props }) {
|
|
381
|
+
const { resolveMedia, resolveLink } = useSiteTheme();
|
|
382
|
+
const media = readMedia(props.media, resolveMedia);
|
|
383
|
+
if (!media) return null;
|
|
384
|
+
const aspect = choice(props.aspect, ["auto", "16:9", "4:3", "1:1", "3:4"], "auto");
|
|
385
|
+
const caption = text(props.caption);
|
|
386
|
+
const link = readLink(props.link);
|
|
387
|
+
const href = link ? safeHref(link.url, resolveLink) : null;
|
|
388
|
+
const image = /* @__PURE__ */ jsx5(Media, { media, alt: text(props.alt), cover: aspect !== "auto" });
|
|
389
|
+
return /* @__PURE__ */ jsxs2("figure", { className: "bapla-figure", "data-aspect": aspect, children: [
|
|
390
|
+
href ? /* @__PURE__ */ jsx5("a", { href, ...targetProps(link?.new_tab ?? false), children: image }) : image,
|
|
391
|
+
caption && /* @__PURE__ */ jsx5("figcaption", { children: caption })
|
|
392
|
+
] });
|
|
393
|
+
}
|
|
394
|
+
function Button({ props }) {
|
|
395
|
+
const { resolveLink } = useSiteTheme();
|
|
396
|
+
const label = text(props.label);
|
|
397
|
+
const link = readLink(props.link);
|
|
398
|
+
if (!label) return null;
|
|
399
|
+
const href = link ? safeHref(link.url, resolveLink) : null;
|
|
400
|
+
const attributes = {
|
|
401
|
+
className: "bapla-button",
|
|
402
|
+
"data-variant": choice(props.variant, ["primary", "secondary", "outline", "link"], "primary"),
|
|
403
|
+
"data-size": choice(props.size, ["sm", "md", "lg"], "md")
|
|
404
|
+
};
|
|
405
|
+
return href ? /* @__PURE__ */ jsx5("a", { href, ...attributes, ...targetProps(link?.new_tab ?? false), children: label }) : /* @__PURE__ */ jsx5("span", { ...attributes, "aria-disabled": "true", children: label });
|
|
406
|
+
}
|
|
407
|
+
function embedUrl(address) {
|
|
408
|
+
let url;
|
|
409
|
+
try {
|
|
410
|
+
url = new URL(address);
|
|
411
|
+
} catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
415
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
416
|
+
const id = (value) => value && /^[\w-]{1,64}$/.test(value) ? value : null;
|
|
417
|
+
if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") {
|
|
418
|
+
const video = url.pathname === "/watch" ? id(url.searchParams.get("v")) : id(url.pathname.match(/^\/(?:embed|shorts)\/([^/]+)/)?.[1]);
|
|
419
|
+
return video ? `https://www.youtube-nocookie.com/embed/${video}` : null;
|
|
420
|
+
}
|
|
421
|
+
if (host === "youtu.be") {
|
|
422
|
+
const video = id(url.pathname.slice(1));
|
|
423
|
+
return video ? `https://www.youtube-nocookie.com/embed/${video}` : null;
|
|
424
|
+
}
|
|
425
|
+
if (host === "vimeo.com" || host === "player.vimeo.com") {
|
|
426
|
+
const video = url.pathname.match(/(\d{3,12})/)?.[1];
|
|
427
|
+
return video ? `https://player.vimeo.com/video/${video}` : null;
|
|
428
|
+
}
|
|
429
|
+
if (host === "dailymotion.com") {
|
|
430
|
+
const video = id(url.pathname.match(/^\/(?:embed\/)?video\/([^/]+)/)?.[1]);
|
|
431
|
+
return video ? `https://www.dailymotion.com/embed/video/${video}` : null;
|
|
432
|
+
}
|
|
433
|
+
if (host === "google.com" && url.pathname.startsWith("/maps/embed")) return `https://www.google.com${url.pathname}${url.search}`;
|
|
434
|
+
if (host === "openstreetmap.org" && url.pathname === "/export/embed.html") return `https://www.openstreetmap.org${url.pathname}${url.search}`;
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
function Embed({ props }) {
|
|
438
|
+
const { labels } = useSiteTheme();
|
|
439
|
+
const link = readLink(props.url);
|
|
440
|
+
if (!link) return null;
|
|
441
|
+
const src = embedUrl(link.url);
|
|
442
|
+
if (!src) {
|
|
443
|
+
const href = safeHref(link.url);
|
|
444
|
+
return href ? /* @__PURE__ */ jsx5("p", { className: "bapla-embed-link", children: /* @__PURE__ */ jsx5("a", { href, ...targetProps(true), children: link.label ?? link.url }) }) : null;
|
|
445
|
+
}
|
|
446
|
+
return /* @__PURE__ */ jsx5("div", { className: "bapla-embed", "data-aspect": choice(props.aspect, ["16:9", "4:3", "1:1"], "16:9"), children: /* @__PURE__ */ jsx5("iframe", { src, title: link.label ?? labels.embed, loading: "lazy", allowFullScreen: true, referrerPolicy: "strict-origin-when-cross-origin", allow: "fullscreen; picture-in-picture" }) });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/blocks/interactive.tsx
|
|
450
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
451
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
452
|
+
function useReducedMotion() {
|
|
453
|
+
const [reduced, setReduced] = useState(false);
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
456
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
457
|
+
setReduced(query.matches);
|
|
458
|
+
const change = () => setReduced(query.matches);
|
|
459
|
+
query.addEventListener?.("change", change);
|
|
460
|
+
return () => query.removeEventListener?.("change", change);
|
|
461
|
+
}, []);
|
|
462
|
+
return reduced;
|
|
463
|
+
}
|
|
464
|
+
function Carousel({ block, props, render }) {
|
|
465
|
+
const { labels } = useSiteTheme();
|
|
466
|
+
const slides = block.children ?? [];
|
|
467
|
+
const [index, setIndex] = useState(0);
|
|
468
|
+
const [paused, setPaused] = useState(false);
|
|
469
|
+
const [hovered, setHovered] = useState(false);
|
|
470
|
+
const reduced = useReducedMotion();
|
|
471
|
+
const autoplay = flag(props.autoplay) && !reduced && slides.length > 1;
|
|
472
|
+
const interval = (integer(props.interval, 2, 30) ?? 5) * 1e3;
|
|
473
|
+
const playing = autoplay && !paused && !hovered;
|
|
474
|
+
const arrows = props.arrows === void 0 ? true : flag(props.arrows);
|
|
475
|
+
const dots = props.dots === void 0 ? true : flag(props.dots);
|
|
476
|
+
const count = slides.length;
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
if (!playing) return;
|
|
479
|
+
const timer = window.setInterval(() => setIndex((i) => (i + 1) % count), interval);
|
|
480
|
+
return () => window.clearInterval(timer);
|
|
481
|
+
}, [playing, interval, count]);
|
|
482
|
+
if (count === 0) return null;
|
|
483
|
+
const go = (next) => setIndex((next + count) % count);
|
|
484
|
+
const onKeyDown = (event) => {
|
|
485
|
+
if (event.key === "ArrowLeft") go(index - 1);
|
|
486
|
+
else if (event.key === "ArrowRight") go(index + 1);
|
|
487
|
+
else return;
|
|
488
|
+
event.preventDefault();
|
|
489
|
+
};
|
|
490
|
+
return /* @__PURE__ */ jsxs3(
|
|
491
|
+
"section",
|
|
492
|
+
{
|
|
493
|
+
className: "bapla-carousel",
|
|
494
|
+
"aria-roledescription": "carousel",
|
|
495
|
+
"aria-label": labels.carousel,
|
|
496
|
+
onKeyDown,
|
|
497
|
+
onMouseEnter: () => setHovered(true),
|
|
498
|
+
onMouseLeave: () => setHovered(false),
|
|
499
|
+
onFocus: () => setHovered(true),
|
|
500
|
+
onBlur: () => setHovered(false),
|
|
501
|
+
children: [
|
|
502
|
+
/* @__PURE__ */ jsxs3("div", { className: "bapla-carousel-controls", children: [
|
|
503
|
+
autoplay && /* @__PURE__ */ jsx6("button", { type: "button", className: "bapla-carousel-play", onClick: () => setPaused((p) => !p), children: paused ? labels.play : labels.pause }),
|
|
504
|
+
arrows && count > 1 && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
505
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "bapla-carousel-prev", onClick: () => go(index - 1), children: labels.previous }),
|
|
506
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "bapla-carousel-next", onClick: () => go(index + 1), children: labels.next })
|
|
507
|
+
] })
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsx6("div", { className: "bapla-carousel-slides", "aria-live": playing ? "off" : "polite", children: slides.map((slide, i) => /* @__PURE__ */ jsx6("div", { role: "group", "aria-roledescription": "slide", "aria-label": labels.slide(i + 1, count), className: "bapla-carousel-slide", hidden: i !== index, children: render([slide]) }, slide.id)) }),
|
|
510
|
+
dots && count > 1 && /* @__PURE__ */ jsx6("div", { className: "bapla-carousel-dots", children: slides.map((slide, i) => /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": labels.goToSlide(i + 1), "aria-current": i === index ? "true" : void 0, onClick: () => go(i) }, slide.id)) })
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
function Tabs({ block, props, render }) {
|
|
516
|
+
const tabs = block.children ?? [];
|
|
517
|
+
const [selected, setSelected] = useState(0);
|
|
518
|
+
const refs = useRef([]);
|
|
519
|
+
const base = useId();
|
|
520
|
+
if (tabs.length === 0) return null;
|
|
521
|
+
const select = (i) => {
|
|
522
|
+
const next = (i + tabs.length) % tabs.length;
|
|
523
|
+
setSelected(next);
|
|
524
|
+
refs.current[next]?.focus();
|
|
525
|
+
};
|
|
526
|
+
const onKeyDown = (event) => {
|
|
527
|
+
const keys = { ArrowRight: selected + 1, ArrowLeft: selected - 1, Home: 0, End: tabs.length - 1 };
|
|
528
|
+
if (!(event.key in keys)) return;
|
|
529
|
+
event.preventDefault();
|
|
530
|
+
select(keys[event.key]);
|
|
531
|
+
};
|
|
532
|
+
return /* @__PURE__ */ jsxs3("div", { className: "bapla-tabs", "data-alignment": choice(props.alignment, ["start", "center", "stretch"], "start"), children: [
|
|
533
|
+
/* @__PURE__ */ jsx6("div", { role: "tablist", className: "bapla-tablist", onKeyDown, children: tabs.map((tab, i) => /* @__PURE__ */ jsx6(
|
|
534
|
+
"button",
|
|
535
|
+
{
|
|
536
|
+
ref: (el) => {
|
|
537
|
+
refs.current[i] = el;
|
|
538
|
+
},
|
|
539
|
+
type: "button",
|
|
540
|
+
role: "tab",
|
|
541
|
+
id: `${base}-tab-${i}`,
|
|
542
|
+
"aria-selected": i === selected,
|
|
543
|
+
"aria-controls": `${base}-panel-${i}`,
|
|
544
|
+
tabIndex: i === selected ? 0 : -1,
|
|
545
|
+
onClick: () => setSelected(i),
|
|
546
|
+
children: text(tab.props?.label) ?? String(i + 1)
|
|
547
|
+
},
|
|
548
|
+
tab.id
|
|
549
|
+
)) }),
|
|
550
|
+
tabs.map((tab, i) => /* @__PURE__ */ jsx6("div", { role: "tabpanel", id: `${base}-panel-${i}`, "aria-labelledby": `${base}-tab-${i}`, tabIndex: 0, hidden: i !== selected, className: "bapla-tabpanel", children: render(tab.children) }, tab.id))
|
|
551
|
+
] });
|
|
552
|
+
}
|
|
553
|
+
function Accordion({ block, props, render }) {
|
|
554
|
+
const items = block.children ?? [];
|
|
555
|
+
const multiple = flag(props.multiple);
|
|
556
|
+
const base = useId();
|
|
557
|
+
const [open, setOpen] = useState(() => {
|
|
558
|
+
const initial = items.filter((item) => flag(item.props?.open)).map((item) => item.id);
|
|
559
|
+
return new Set(multiple ? initial : initial.slice(0, 1));
|
|
560
|
+
});
|
|
561
|
+
const toggle = (id) => setOpen((current) => {
|
|
562
|
+
const next = new Set(multiple ? current : [...current].filter((x) => x === id));
|
|
563
|
+
if (next.has(id)) next.delete(id);
|
|
564
|
+
else next.add(id);
|
|
565
|
+
return next;
|
|
566
|
+
});
|
|
567
|
+
return /* @__PURE__ */ jsx6("div", { className: "bapla-accordion", children: items.map((item, i) => {
|
|
568
|
+
const expanded = open.has(item.id);
|
|
569
|
+
return /* @__PURE__ */ jsxs3("div", { className: "bapla-accordion-item", children: [
|
|
570
|
+
/* @__PURE__ */ jsx6("h3", { children: /* @__PURE__ */ jsx6("button", { type: "button", id: `${base}-header-${i}`, "aria-expanded": expanded, "aria-controls": `${base}-panel-${i}`, onClick: () => toggle(item.id), children: text(item.props?.label) ?? String(i + 1) }) }),
|
|
571
|
+
/* @__PURE__ */ jsx6("div", { role: "region", id: `${base}-panel-${i}`, "aria-labelledby": `${base}-header-${i}`, hidden: !expanded, className: "bapla-accordion-panel", children: render(item.children) })
|
|
572
|
+
] }, item.id);
|
|
573
|
+
}) });
|
|
574
|
+
}
|
|
575
|
+
function Passthrough({ children }) {
|
|
576
|
+
return /* @__PURE__ */ jsx6(Fragment3, { children });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/blocks/layout.tsx
|
|
580
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
581
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
582
|
+
var SIZES = ["none", "sm", "md", "lg"];
|
|
583
|
+
function Section({ props, children }) {
|
|
584
|
+
const { resolveMedia } = useSiteTheme();
|
|
585
|
+
const background = choice(props.background, ["none", "muted", "accent", "dark", "image"], "none");
|
|
586
|
+
const image = background === "image" ? readMedia(props.background_image, resolveMedia) : null;
|
|
587
|
+
const anchor = text(props.anchor);
|
|
588
|
+
return /* @__PURE__ */ jsx7(
|
|
589
|
+
"section",
|
|
590
|
+
{
|
|
591
|
+
className: "bapla-section",
|
|
592
|
+
id: anchor && /^[A-Za-z][\w-]*$/.test(anchor) ? anchor : void 0,
|
|
593
|
+
"data-width": choice(props.width, ["narrow", "default", "wide", "full"], "default"),
|
|
594
|
+
"data-background": background,
|
|
595
|
+
"data-padding": choice(props.padding, SIZES, "md"),
|
|
596
|
+
style: image ? { backgroundImage: `url("${mediaUrl(image, { w: 1920 }).replace(/["\\()\s]/g, (c) => encodeURIComponent(c))}")` } : void 0,
|
|
597
|
+
children: /* @__PURE__ */ jsx7("div", { className: "bapla-container", children })
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
var GridContext = createContext3({ mode: "equal", unit: "twelfths" });
|
|
602
|
+
function Grid({ block, props, children }) {
|
|
603
|
+
const mode = choice(props.mode, ["equal", "auto", "custom"], "equal");
|
|
604
|
+
const unit = choice(props.unit, ["twelfths", "percent"], "twelfths");
|
|
605
|
+
const columns = block.children ?? [];
|
|
606
|
+
let template;
|
|
607
|
+
if (mode === "auto") {
|
|
608
|
+
const min = integer(props.min_width, 120, 800) ?? 240;
|
|
609
|
+
template = `repeat(auto-fit, minmax(min(100%, ${min}px), 1fr))`;
|
|
610
|
+
} else if (mode === "custom" && unit === "percent") {
|
|
611
|
+
const widths = columns.map((column) => integer(column.props?.width, 1, 100) ?? Math.floor(100 / Math.max(columns.length, 1)));
|
|
612
|
+
const total = widths.reduce((sum, w) => sum + w, 0);
|
|
613
|
+
template = [...widths, ...total < 100 ? [100 - total] : []].map((w) => `minmax(0, ${w}fr)`).join(" ") || "minmax(0, 1fr)";
|
|
614
|
+
} else if (mode === "custom") {
|
|
615
|
+
template = "repeat(12, minmax(0, 1fr))";
|
|
616
|
+
} else {
|
|
617
|
+
const count = integer(props.columns, 1, 12) ?? Math.max(columns.length, 1);
|
|
618
|
+
template = `repeat(${count}, minmax(0, 1fr))`;
|
|
619
|
+
}
|
|
620
|
+
return /* @__PURE__ */ jsx7(GridContext.Provider, { value: { mode, unit }, children: /* @__PURE__ */ jsx7(
|
|
621
|
+
"div",
|
|
622
|
+
{
|
|
623
|
+
className: "bapla-grid",
|
|
624
|
+
"data-mode": mode,
|
|
625
|
+
"data-unit": mode === "custom" ? unit : void 0,
|
|
626
|
+
"data-gap": choice(props.gap, SIZES, "md"),
|
|
627
|
+
"data-align": choice(props.align, ["start", "center", "end", "stretch"], "stretch"),
|
|
628
|
+
"data-stack": choice(props.stack_below, ["sm", "md", "lg", "never"], "md"),
|
|
629
|
+
style: { "--bapla-grid-cols": template },
|
|
630
|
+
children
|
|
631
|
+
}
|
|
632
|
+
) });
|
|
633
|
+
}
|
|
634
|
+
function Column({ props, children }) {
|
|
635
|
+
const { mode, unit } = useContext3(GridContext);
|
|
636
|
+
const style = {};
|
|
637
|
+
if (mode === "custom" && unit === "twelfths") {
|
|
638
|
+
style["--bapla-col-span"] = `span ${integer(props.width, 1, 12) ?? 12}`;
|
|
639
|
+
}
|
|
640
|
+
const align2 = typeof props.align === "string" ? choice(props.align, ["start", "center", "end"], "start") : void 0;
|
|
641
|
+
return /* @__PURE__ */ jsx7("div", { className: "bapla-column", "data-align": align2, style, children });
|
|
642
|
+
}
|
|
643
|
+
function Divider({ props }) {
|
|
644
|
+
return /* @__PURE__ */ jsx7("hr", { className: "bapla-divider", "data-size": choice(props.size, SIZES, "md") });
|
|
645
|
+
}
|
|
646
|
+
function Spacer({ props }) {
|
|
647
|
+
return /* @__PURE__ */ jsx7("div", { className: "bapla-spacer", "aria-hidden": "true", "data-size": choice(props.size, ["sm", "md", "lg", "xl"], "md") });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/blocks/registry.ts
|
|
651
|
+
var standardBlocks = {
|
|
652
|
+
section: Section,
|
|
653
|
+
grid: Grid,
|
|
654
|
+
column: Column,
|
|
655
|
+
text: Text,
|
|
656
|
+
media: MediaBlock,
|
|
657
|
+
carousel: Carousel,
|
|
658
|
+
tabs: Tabs,
|
|
659
|
+
tab: Passthrough,
|
|
660
|
+
accordion: Accordion,
|
|
661
|
+
accordion_item: Passthrough,
|
|
662
|
+
button: Button,
|
|
663
|
+
embed: Embed,
|
|
664
|
+
divider: Divider,
|
|
665
|
+
spacer: Spacer,
|
|
666
|
+
menu_links: MenuLinks
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// src/blocks/Blocks.tsx
|
|
670
|
+
import { Fragment as Fragment5, jsx as jsx8 } from "react/jsx-runtime";
|
|
671
|
+
function Blocks({ blocks }) {
|
|
672
|
+
const { components } = useSiteTheme();
|
|
673
|
+
const registry = { ...standardBlocks, ...components };
|
|
674
|
+
const render = (list) => (Array.isArray(list) ? list : []).map((block, index) => {
|
|
675
|
+
const Component = block && typeof block.type === "string" ? registry[block.type] : void 0;
|
|
676
|
+
if (!Component) return null;
|
|
677
|
+
const props = block.props && typeof block.props === "object" && !Array.isArray(block.props) ? block.props : {};
|
|
678
|
+
return /* @__PURE__ */ jsx8(Fragment4, { children: /* @__PURE__ */ jsx8(Component, { block, props, render, children: block.children && block.children.length > 0 ? render(block.children) : null }) }, typeof block.id === "string" ? block.id : index);
|
|
679
|
+
});
|
|
680
|
+
return /* @__PURE__ */ jsx8(Fragment5, { children: render(blocks) });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/media/Image.tsx
|
|
684
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
685
|
+
function Image({ media, size, w, h, fit, q, alt, widths, sizes, formats, priority = false, className, style }) {
|
|
686
|
+
const { imageUrl, imageSizes } = useSiteTheme();
|
|
687
|
+
const named = size !== void 0 ? imageSizes[size] ?? {} : {};
|
|
688
|
+
const box = { ...named, ...defined({ w, h, fit, q }) };
|
|
689
|
+
const text2 = alt ?? media.alt ?? "";
|
|
690
|
+
const classes = ["bapla-image", className].filter(Boolean).join(" ");
|
|
691
|
+
const common = {
|
|
692
|
+
alt: text2,
|
|
693
|
+
className: classes,
|
|
694
|
+
loading: priority ? "eager" : "lazy",
|
|
695
|
+
decoding: "async",
|
|
696
|
+
...priority ? { fetchPriority: "high" } : {},
|
|
697
|
+
style: { ...box.fit === "cover" ? { objectFit: "cover", objectPosition: objectPosition(media.focal_point) } : {}, ...style }
|
|
698
|
+
};
|
|
699
|
+
if (!isTransformable(media)) {
|
|
700
|
+
return /* @__PURE__ */ jsx9("img", { ...common, src: mediaUrl(media), width: media.width ?? void 0, height: media.height ?? void 0 });
|
|
701
|
+
}
|
|
702
|
+
const [outW, outH] = output(media, box);
|
|
703
|
+
const candidates = (fm) => {
|
|
704
|
+
if (widths && widths.length > 0) {
|
|
705
|
+
const list = responsive(media, box, widths);
|
|
706
|
+
return list.map((width) => `${imageUrl(media, scaled(box, width, fm))} ${width}w`).join(", ");
|
|
707
|
+
}
|
|
708
|
+
const two = outW !== void 0 && media.width !== null && outW * 2 <= media.width;
|
|
709
|
+
return [`${imageUrl(media, transform(box, 1, fm))} 1x`, ...two ? [`${imageUrl(media, transform(box, 2, fm))} 2x`] : []].join(", ");
|
|
710
|
+
};
|
|
711
|
+
const responsiveSizes = widths && widths.length > 0 ? sizes ?? "100vw" : void 0;
|
|
712
|
+
const img = /* @__PURE__ */ jsx9(
|
|
713
|
+
"img",
|
|
714
|
+
{
|
|
715
|
+
...common,
|
|
716
|
+
src: imageUrl(media, widths && widths.length > 0 ? scaled(box, Math.min(Math.max(...responsive(media, box, widths)), 1280)) : transform(box, 1)),
|
|
717
|
+
srcSet: candidates(),
|
|
718
|
+
sizes: responsiveSizes,
|
|
719
|
+
width: outW,
|
|
720
|
+
height: outH
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
if (!formats || formats.length === 0) return img;
|
|
724
|
+
return /* @__PURE__ */ jsxs4("picture", { children: [
|
|
725
|
+
formats.map((fm) => /* @__PURE__ */ jsx9("source", { type: `image/${fm}`, srcSet: candidates(fm), sizes: responsiveSizes }, fm)),
|
|
726
|
+
img
|
|
727
|
+
] });
|
|
728
|
+
}
|
|
729
|
+
function output(media, box) {
|
|
730
|
+
const W = media.width;
|
|
731
|
+
const H = media.height;
|
|
732
|
+
if (W === null || H === null) return [box.w, box.h];
|
|
733
|
+
if (box.w === void 0 && box.h === void 0) return [W, H];
|
|
734
|
+
if (box.fit === "cover" && box.w !== void 0 && box.h !== void 0) {
|
|
735
|
+
const shrink = Math.min(1, W / box.w, H / box.h);
|
|
736
|
+
return [Math.round(box.w * shrink), Math.round(box.h * shrink)];
|
|
737
|
+
}
|
|
738
|
+
const scale = Math.min(1, box.w === void 0 ? 1 : box.w / W, box.h === void 0 ? 1 : box.h / H);
|
|
739
|
+
return [Math.round(W * scale), Math.round(H * scale)];
|
|
740
|
+
}
|
|
741
|
+
function transform(box, density, fm) {
|
|
742
|
+
return defined({
|
|
743
|
+
w: box.w === void 0 ? void 0 : box.w * density,
|
|
744
|
+
h: box.h === void 0 ? void 0 : box.h * density,
|
|
745
|
+
fit: box.fit,
|
|
746
|
+
q: box.q,
|
|
747
|
+
fm
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
function scaled(box, width, fm) {
|
|
751
|
+
const h = box.fit === "cover" && box.w !== void 0 && box.h !== void 0 ? Math.round(width * box.h / box.w) : void 0;
|
|
752
|
+
return defined({ w: width, h, fit: box.fit, q: box.q, fm });
|
|
753
|
+
}
|
|
754
|
+
function responsive(media, box, widths) {
|
|
755
|
+
const sorted = [...new Set(widths)].sort((a, b) => a - b);
|
|
756
|
+
if (media.width === null) return sorted;
|
|
757
|
+
const limit = box.fit === "cover" && box.w !== void 0 && box.h !== void 0 && media.height !== null ? Math.min(media.width, Math.floor(media.height * box.w / box.h)) : media.width;
|
|
758
|
+
const kept = sorted.filter((width) => width <= limit);
|
|
759
|
+
if (kept.length < sorted.length && !kept.includes(limit)) kept.push(limit);
|
|
760
|
+
return kept;
|
|
761
|
+
}
|
|
762
|
+
function defined(value) {
|
|
763
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/menu/NavBar.tsx
|
|
767
|
+
import { useEffect as useEffect2, useId as useId2, useRef as useRef2, useState as useState2 } from "react";
|
|
768
|
+
import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
769
|
+
function NavBar({ items, label, brand, className }) {
|
|
770
|
+
const { labels } = useSiteTheme();
|
|
771
|
+
const [open, setOpen] = useState2(false);
|
|
772
|
+
const [openItem, setOpenItem] = useState2(null);
|
|
773
|
+
const nav = useRef2(null);
|
|
774
|
+
const listId = useId2();
|
|
775
|
+
useEffect2(() => {
|
|
776
|
+
const close = (event) => {
|
|
777
|
+
if (nav.current && !nav.current.contains(event.target)) setOpenItem(null);
|
|
778
|
+
};
|
|
779
|
+
document.addEventListener("pointerdown", close);
|
|
780
|
+
return () => document.removeEventListener("pointerdown", close);
|
|
781
|
+
}, []);
|
|
782
|
+
const onKeyDown = (event) => {
|
|
783
|
+
if (event.key === "Escape" && open && openItem === null) {
|
|
784
|
+
setOpen(false);
|
|
785
|
+
nav.current?.querySelector(".bapla-nav-toggle")?.focus();
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
return /* @__PURE__ */ jsxs5(
|
|
789
|
+
"nav",
|
|
790
|
+
{
|
|
791
|
+
ref: nav,
|
|
792
|
+
className: ["bapla-nav", className].filter(Boolean).join(" "),
|
|
793
|
+
"aria-label": label ?? labels.mainNavigation,
|
|
794
|
+
onKeyDown,
|
|
795
|
+
onBlur: (event) => {
|
|
796
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setOpenItem(null);
|
|
797
|
+
},
|
|
798
|
+
children: [
|
|
799
|
+
brand && /* @__PURE__ */ jsx10("div", { className: "bapla-nav-brand", children: brand }),
|
|
800
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: "bapla-nav-toggle", "aria-expanded": open, "aria-controls": listId, onClick: () => setOpen((o) => !o), children: labels.menu }),
|
|
801
|
+
/* @__PURE__ */ jsx10("ul", { id: listId, className: "bapla-nav-list", "data-open": open ? "true" : "false", children: items.map((item) => /* @__PURE__ */ jsx10(NavItem, { item, level: 0, open: openItem === item.id, onToggle: (o) => setOpenItem(o ? item.id : null) }, item.id)) })
|
|
802
|
+
]
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
function NavItem({ item, level, open, onToggle }) {
|
|
807
|
+
const { labels } = useSiteTheme();
|
|
808
|
+
const [openChild, setOpenChild] = useState2(null);
|
|
809
|
+
const button = useRef2(null);
|
|
810
|
+
const id = useId2();
|
|
811
|
+
const hasPanel = item.kind === "panel" && item.panel !== null && item.panel.length > 0;
|
|
812
|
+
const hasChildren = item.children.length > 0;
|
|
813
|
+
if (level > 0 && item.kind === "heading") {
|
|
814
|
+
return /* @__PURE__ */ jsxs5("li", { className: "bapla-nav-item", "data-kind": "heading", children: [
|
|
815
|
+
/* @__PURE__ */ jsx10("span", { className: "bapla-nav-heading", children: item.label }),
|
|
816
|
+
hasChildren && /* @__PURE__ */ jsx10(SubList, { items: item.children, level: level + 1, openChild, setOpenChild })
|
|
817
|
+
] });
|
|
818
|
+
}
|
|
819
|
+
if (!hasPanel && !hasChildren) {
|
|
820
|
+
return /* @__PURE__ */ jsx10("li", { className: "bapla-nav-item", "data-kind": item.kind, "data-active": item.active ? "true" : void 0, children: /* @__PURE__ */ jsx10(MenuItemLink, { item, className: "bapla-nav-link" }) });
|
|
821
|
+
}
|
|
822
|
+
const onKeyDown = (event) => {
|
|
823
|
+
if (event.key === "Escape" && open) {
|
|
824
|
+
event.stopPropagation();
|
|
825
|
+
onToggle(false);
|
|
826
|
+
button.current?.focus();
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
const linked = item.kind === "link" && item.url !== null;
|
|
830
|
+
return /* @__PURE__ */ jsxs5("li", { className: "bapla-nav-item", "data-kind": item.kind, "data-active": item.active ? "true" : void 0, "data-open": open ? "true" : "false", onKeyDown, children: [
|
|
831
|
+
linked && /* @__PURE__ */ jsx10(MenuItemLink, { item, className: "bapla-nav-link" }),
|
|
832
|
+
/* @__PURE__ */ jsx10(
|
|
833
|
+
"button",
|
|
834
|
+
{
|
|
835
|
+
ref: button,
|
|
836
|
+
type: "button",
|
|
837
|
+
className: "bapla-nav-disclosure",
|
|
838
|
+
"aria-expanded": open,
|
|
839
|
+
"aria-controls": id,
|
|
840
|
+
"aria-label": linked ? labels.submenu(item.label) : void 0,
|
|
841
|
+
onClick: () => onToggle(!open),
|
|
842
|
+
children: linked ? /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: "bapla-nav-caret" }) : item.label
|
|
843
|
+
}
|
|
844
|
+
),
|
|
845
|
+
hasPanel ? /* @__PURE__ */ jsx10("div", { id, className: "bapla-mega", hidden: !open, children: /* @__PURE__ */ jsx10(MenuItemContext.Provider, { value: item, children: /* @__PURE__ */ jsx10(Blocks, { blocks: item.panel }) }) }) : /* @__PURE__ */ jsx10(SubList, { id, hidden: !open, items: item.children, level: level + 1, openChild, setOpenChild })
|
|
846
|
+
] });
|
|
847
|
+
}
|
|
848
|
+
function SubList({ id, hidden, items, level, openChild, setOpenChild }) {
|
|
849
|
+
return /* @__PURE__ */ jsx10("ul", { id, className: "bapla-nav-submenu", "data-level": level, hidden, children: items.map((child) => /* @__PURE__ */ jsx10(NavItem, { item: child, level, open: openChild === child.id, onToggle: (o) => setOpenChild(o ? child.id : null) }, child.id)) });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/menu/Breadcrumb.tsx
|
|
853
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
854
|
+
function Breadcrumb({ items, className }) {
|
|
855
|
+
const { labels, resolveLink } = useSiteTheme();
|
|
856
|
+
if (items.length === 0) return null;
|
|
857
|
+
return /* @__PURE__ */ jsx11("nav", { className: ["bapla-breadcrumb", className].filter(Boolean).join(" "), "aria-label": labels.breadcrumb, children: /* @__PURE__ */ jsx11("ol", { children: items.map((item, i) => {
|
|
858
|
+
const last = i === items.length - 1;
|
|
859
|
+
const href = last ? null : safeHref(item.url, resolveLink);
|
|
860
|
+
return /* @__PURE__ */ jsx11("li", { children: last ? /* @__PURE__ */ jsx11("span", { "aria-current": "page", children: item.label }) : href ? /* @__PURE__ */ jsx11("a", { href, children: item.label }) : /* @__PURE__ */ jsx11("span", { children: item.label }) }, i);
|
|
861
|
+
}) }) });
|
|
862
|
+
}
|
|
863
|
+
export {
|
|
864
|
+
Accordion,
|
|
865
|
+
Blocks,
|
|
866
|
+
BlocksProvider,
|
|
867
|
+
Breadcrumb,
|
|
868
|
+
Button,
|
|
869
|
+
Carousel,
|
|
870
|
+
Column,
|
|
871
|
+
Divider,
|
|
872
|
+
Embed,
|
|
873
|
+
Grid,
|
|
874
|
+
Image,
|
|
875
|
+
MEDIA_WIDTHS,
|
|
876
|
+
Media,
|
|
877
|
+
MediaBlock,
|
|
878
|
+
MenuItemContext,
|
|
879
|
+
MenuItemLink,
|
|
880
|
+
MenuLinks,
|
|
881
|
+
NavBar,
|
|
882
|
+
RichText,
|
|
883
|
+
Section,
|
|
884
|
+
Spacer,
|
|
885
|
+
Tabs,
|
|
886
|
+
Text,
|
|
887
|
+
defaultImageSizes,
|
|
888
|
+
embedUrl,
|
|
889
|
+
fontImports,
|
|
890
|
+
fontStack,
|
|
891
|
+
frenchLabels,
|
|
892
|
+
imgproxyPath,
|
|
893
|
+
isTransformable,
|
|
894
|
+
mediaUrl,
|
|
895
|
+
objectPosition,
|
|
896
|
+
readLink,
|
|
897
|
+
readMedia,
|
|
898
|
+
safeHref,
|
|
899
|
+
standardBlocks,
|
|
900
|
+
targetProps,
|
|
901
|
+
themeCss,
|
|
902
|
+
useMenuItem,
|
|
903
|
+
useSiteTheme
|
|
904
|
+
};
|