@bettercms-ai/ui 0.6.0 → 0.6.1

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/index.js CHANGED
@@ -30,42 +30,312 @@ function Heading({
30
30
 
31
31
  // src/blocks/portableText.tsx
32
32
  import React from "react";
33
- import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
34
- var MARK_TAGS = {
35
- strong: "strong",
36
- em: "em",
37
- underline: "u",
38
- "strike-through": "s",
39
- code: "code"
33
+ import { PortableText as PT } from "@portabletext/react";
34
+
35
+ // ../richtext/src/types.ts
36
+ var PT_TYPES = {
37
+ /** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */
38
+ block: "bcmsBlock",
39
+ /** An inline mention. A PT inline object is natively void, which is what Lexical's `token` mode was faking. */
40
+ mention: "bcmsMention",
41
+ divider: "bcmsDivider",
42
+ code: "bcmsCode",
43
+ /**
44
+ * A table. Spelled `table`, unprefixed, because that is what every other Portable Text
45
+ * producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
46
+ * as foreign content now lands on OUR type and renders, rather than being data nothing draws.
47
+ */
48
+ table: "table",
49
+ /** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */
50
+ link: "link"
40
51
  };
41
- var BLOCK_TAGS = {
42
- normal: "p",
43
- h1: "h1",
44
- h2: "h2",
45
- h3: "h3",
46
- h4: "h4",
47
- h5: "h5",
48
- h6: "h6",
49
- blockquote: "blockquote"
52
+ var BUILTIN_TABLE_KEY = "builtin:table";
53
+ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
54
+ "block",
55
+ PT_TYPES.block,
56
+ PT_TYPES.table,
57
+ PT_TYPES.code,
58
+ PT_TYPES.divider,
59
+ PT_TYPES.mention
60
+ ]);
61
+
62
+ // ../richtext/src/href.ts
63
+ var SAFE_HREF_SCHEMES = ["http:", "https:", "mailto:", "tel:"];
64
+ function safeHref(raw) {
65
+ const trimmed = raw.trim();
66
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) return trimmed;
67
+ try {
68
+ return SAFE_HREF_SCHEMES.includes(new URL(trimmed).protocol) ? trimmed : null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ // ../richtext/src/key.ts
75
+ var ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
76
+ var KEY_PATTERN = /^[A-Za-z0-9-]{1,32}$/;
77
+ var isValidKey = (k) => typeof k === "string" && KEY_PATTERN.test(k);
78
+ function randomKey(length = 12) {
79
+ let out = "";
80
+ for (let i = 0; i < length; i += 1) {
81
+ out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
82
+ }
83
+ return out;
84
+ }
85
+ function deterministicKeys(prefix = "k") {
86
+ let n = 0;
87
+ return () => {
88
+ n += 1;
89
+ return `${prefix}${n}`;
90
+ };
91
+ }
92
+
93
+ // ../richtext/src/table.ts
94
+ var isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
95
+ var isPortableTextTable = (n) => isRecord(n) && n._type === PT_TYPES.table && Array.isArray(n.rows);
96
+ var isBuiltInTableBlock = (n) => isRecord(n) && n._type === PT_TYPES.block && n.schemaKey === BUILTIN_TABLE_KEY;
97
+ var asIs = (v) => Array.isArray(v) ? v : [];
98
+ function keyMinter(base, reserved) {
99
+ const prefix = (base.match(/[A-Za-z0-9-]/g) ?? []).join("").slice(0, 16) || "t";
100
+ return (...parts) => {
101
+ const candidate = `${prefix}-${parts.join("")}`.slice(0, 30);
102
+ let key = candidate;
103
+ for (let n = 1; reserved.has(key); n += 1) key = `${candidate}-${n}`;
104
+ reserved.add(key);
105
+ return key;
106
+ };
107
+ }
108
+ function reservedKeys(rows) {
109
+ const out = /* @__PURE__ */ new Set();
110
+ for (const row of rows) {
111
+ if (!isRecord(row)) continue;
112
+ if (isValidKey(row._key)) out.add(row._key);
113
+ for (const cell of Array.isArray(row.cells) ? row.cells : []) {
114
+ if (isRecord(cell) && isValidKey(cell._key)) out.add(cell._key);
115
+ }
116
+ }
117
+ return out;
118
+ }
119
+ var textContent = (text, key) => text === "" ? [] : [{
120
+ _type: "block",
121
+ _key: key(),
122
+ style: "normal",
123
+ markDefs: [],
124
+ children: [{ _type: "span", _key: key(), text, marks: [] }]
125
+ }];
126
+ var spanOf = (value) => {
127
+ const n = typeof value === "number" ? value : Number(value);
128
+ return Number.isInteger(n) && n > 1 ? n : void 0;
50
129
  };
51
- function Span({ node, index }) {
52
- const marks = node.marks ?? [];
53
- const colour = marks.find((m) => m.startsWith("color:"))?.slice("color:".length);
54
- let out = node.text ?? "";
55
- for (const m of marks) {
56
- const Tag = MARK_TAGS[m];
57
- if (Tag) out = /* @__PURE__ */ jsx2(Tag, { children: out });
130
+ function normalizeCell(raw, at, mint, read) {
131
+ const [row, col] = at;
132
+ const key = isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", row, "c", col);
133
+ const inner = deterministicKeys(`${key}-`);
134
+ if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") {
135
+ return { _key: key, content: textContent(String(raw), inner) };
58
136
  }
59
- if (colour && /^[a-z0-9-]+$/i.test(colour)) {
60
- out = /* @__PURE__ */ jsx2("span", { className: `bcms-color-${colour}`, children: out });
137
+ if (!isRecord(raw)) return { _key: key, content: [] };
138
+ const span = spanOf(raw.span ?? raw.colSpan ?? raw.colspan);
139
+ const rowSpan = spanOf(raw.rowSpan ?? raw.rowspan);
140
+ return {
141
+ _key: key,
142
+ // `text` is the spelling a string matrix uses once its cells have been given objects; it is
143
+ // read here so a half-migrated value cannot lose its words.
144
+ content: Array.isArray(raw.content) ? read(raw.content, inner) : textContent(typeof raw.text === "string" ? raw.text : "", inner),
145
+ ...raw.header === true ? { header: true } : {},
146
+ ...span === void 0 ? {} : { span },
147
+ ...rowSpan === void 0 ? {} : { rowSpan }
148
+ };
149
+ }
150
+ function normalizeRow(raw, at, mint, read) {
151
+ const cells = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.cells) ? raw.cells : [];
152
+ return {
153
+ _key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", at),
154
+ cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),
155
+ ...isRecord(raw) && raw.head === true ? { head: true } : {}
156
+ };
157
+ }
158
+ function builtInTableToTable(node, key = randomKey) {
159
+ if (!isBuiltInTableBlock(node)) return null;
160
+ const fields = node.fields;
161
+ const raw = isRecord(fields) && Array.isArray(fields.rows) ? fields.rows : [];
162
+ const hasHeader = isRecord(fields) ? fields.hasHeader !== false : true;
163
+ const own = node._key;
164
+ const tableKey = isValidKey(own) ? own : key();
165
+ const mint = keyMinter(tableKey, reservedKeys(raw));
166
+ const rows = raw.map((row, i) => {
167
+ const normalized = normalizeRow(row, i, mint, asIs);
168
+ if (!(hasHeader && i === 0)) return normalized;
169
+ return {
170
+ ...normalized,
171
+ head: true,
172
+ cells: normalized.cells.map((c) => ({ ...c, header: true }))
173
+ };
174
+ });
175
+ const width = rows.reduce((max, r) => Math.max(max, r.cells.length), 0);
176
+ for (const [i, row] of rows.entries()) {
177
+ while (row.cells.length < width) {
178
+ row.cells.push({
179
+ _key: mint("r", i, "c", row.cells.length),
180
+ content: [],
181
+ ...row.head === true ? { header: true } : {}
182
+ });
183
+ }
61
184
  }
62
- return /* @__PURE__ */ jsx2(React.Fragment, { children: out }, node._key ?? index);
185
+ return { _type: PT_TYPES.table, _key: tableKey, rows, legacy: true };
186
+ }
187
+ function migrateTable(node, key = randomKey, read = asIs) {
188
+ const builtIn = builtInTableToTable(node, key);
189
+ if (builtIn) return builtIn;
190
+ if (!isPortableTextTable(node)) return null;
191
+ const rows = node.rows;
192
+ const tableKey = isValidKey(node._key) ? node._key : key();
193
+ const mint = keyMinter(tableKey, reservedKeys(rows));
194
+ return {
195
+ _type: PT_TYPES.table,
196
+ _key: tableKey,
197
+ rows: rows.map((r, i) => normalizeRow(r, i, mint, read)),
198
+ // Carried through: a migrated table that an author saves back is still the same table, and
199
+ // dropping the flag on that save would change the page the next time it renders.
200
+ ...node.legacy === true ? { legacy: true } : {}
201
+ };
202
+ }
203
+
204
+ // ../richtext/src/read.ts
205
+ var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
206
+ function normalizePortableText(value, key = randomKey) {
207
+ if (!Array.isArray(value)) return [];
208
+ return value.flatMap((raw) => {
209
+ if (!isRecord2(raw) || typeof raw._type !== "string") return [];
210
+ const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
211
+ if (table) return [table];
212
+ const node = { ...raw, _key: isRecord2(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
213
+ if (node._type === "block") {
214
+ if (!Array.isArray(node.markDefs)) node.markDefs = [];
215
+ if (typeof node.style !== "string") node.style = "normal";
216
+ node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
217
+ if (!isRecord2(c)) return c;
218
+ const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
219
+ if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
220
+ return child;
221
+ });
222
+ }
223
+ return [node];
224
+ });
63
225
  }
64
- function PortableText({ doc }) {
65
- return /* @__PURE__ */ jsx2(Fragment, { children: doc.content.map((block, i) => {
66
- const Tag = BLOCK_TAGS[block.style ?? "normal"] ?? "p";
67
- return /* @__PURE__ */ jsx2(Tag, { children: (block.children ?? []).map((c, j) => /* @__PURE__ */ jsx2(Span, { node: c, index: j }, c._key ?? j)) }, block._key ?? i);
68
- }) });
226
+ function readStructuredContent(props) {
227
+ if (!isRecord2(props)) return null;
228
+ const candidate = props.content ?? props.value;
229
+ const arr = Array.isArray(candidate) ? candidate : isRecord2(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
230
+ return arr && arr.length > 0 ? arr : null;
231
+ }
232
+
233
+ // src/blocks/portableText.tsx
234
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
235
+ var TOKEN = /^[a-z0-9-]+$/i;
236
+ function Bound({
237
+ base,
238
+ nodeKey,
239
+ children
240
+ }) {
241
+ if (!base || !nodeKey) return /* @__PURE__ */ jsx2(Fragment, { children });
242
+ return /* @__PURE__ */ jsx2("div", { "data-bcms-field": `${base}#${nodeKey}`, "data-bcms-kind": "richtext", children });
243
+ }
244
+ function Table({ value }) {
245
+ const components = React.useMemo(buildComponents, []);
246
+ const inline = React.useMemo(
247
+ () => ({ ...components, block: { normal: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children }) } }),
248
+ [components]
249
+ );
250
+ const rows = Array.isArray(value?.rows) ? value.rows : [];
251
+ if (rows.length === 0) return null;
252
+ const cells = (row) => (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {
253
+ const Tag = cell?.header === true ? "th" : "td";
254
+ const span = typeof cell?.span === "number" && cell.span > 1 ? cell.span : void 0;
255
+ const rowSpan = typeof cell?.rowSpan === "number" && cell.rowSpan > 1 ? cell.rowSpan : void 0;
256
+ const content = Array.isArray(cell?.content) ? cell.content : [];
257
+ const lone = content.length === 1 && content[0]._type === "block" && content[0].style === "normal" && !content[0].listItem;
258
+ return /* @__PURE__ */ jsx2(Tag, { ...span ? { colSpan: span } : {}, ...rowSpan ? { rowSpan } : {}, children: /* @__PURE__ */ jsx2(PT, { value: content, components: lone ? inline : components }) }, cell?._key);
259
+ });
260
+ const rendered = rows.map((row) => /* @__PURE__ */ jsx2("tr", { children: cells(row) }, row?._key));
261
+ const headCount = rows.findIndex((r) => r?.head !== true);
262
+ const head = headCount === -1 ? rows.length : headCount;
263
+ return /* @__PURE__ */ jsxs("table", { ...value.legacy === true ? { className: "bcms-table" } : {}, children: [
264
+ head > 0 && /* @__PURE__ */ jsx2("thead", { children: rendered.slice(0, head) }),
265
+ head < rows.length && /* @__PURE__ */ jsx2("tbody", { children: rendered.slice(head) })
266
+ ] });
267
+ }
268
+ function buildComponents() {
269
+ return {
270
+ types: {
271
+ [PT_TYPES.divider]: () => /* @__PURE__ */ jsx2("hr", {}),
272
+ [PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx2(Table, { value }),
273
+ [PT_TYPES.code]: ({ value }) => {
274
+ const v = value;
275
+ const lang = typeof v.language === "string" && TOKEN.test(v.language) ? v.language : void 0;
276
+ return /* @__PURE__ */ jsx2("pre", { children: /* @__PURE__ */ jsx2("code", { className: lang ? `language-${lang}` : void 0, children: typeof v.code === "string" ? v.code : "" }) });
277
+ },
278
+ /**
279
+ * A generic embedded block renders as a marker, not as content: its payload references
280
+ * something (a component, a media id) that has to be RESOLVED against the project, and
281
+ * this renderer has no project. A marker keeps the node visible and addressable rather
282
+ * than rendering nothing at all, which is how embedded content used to vanish.
283
+ */
284
+ [PT_TYPES.block]: ({ value }) => {
285
+ const v = value;
286
+ const key = typeof v.schemaKey === "string" ? v.schemaKey : "block";
287
+ return /* @__PURE__ */ jsx2("div", { className: "bcms-block", "data-bcms-schema-key": key });
288
+ },
289
+ [PT_TYPES.mention]: ({ value }) => {
290
+ const v = value;
291
+ return /* @__PURE__ */ jsx2("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
292
+ }
293
+ },
294
+ marks: {
295
+ /**
296
+ * The only attribute this renderer writes from author data. An unsafe href renders the
297
+ * LABEL rather than deleting it — blocking a link must not lose the words.
298
+ */
299
+ [PT_TYPES.link]: ({ value, children }) => {
300
+ const v = value ?? {};
301
+ const href = typeof v.href === "string" ? safeHref(v.href) : null;
302
+ if (!href) return /* @__PURE__ */ jsx2(Fragment, { children });
303
+ const blank = v.target === "_blank";
304
+ return /* @__PURE__ */ jsx2("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
305
+ },
306
+ highlight: ({ children }) => /* @__PURE__ */ jsx2("mark", { children }),
307
+ "strike-through": ({ children }) => /* @__PURE__ */ jsx2("s", { children }),
308
+ /**
309
+ * A colour mark carries a TOKEN name (`color:brand-accent`), never a baked hex, so the
310
+ * consuming site styles it and a rebrand does not orphan the content.
311
+ */
312
+ color: ({ children, markType }) => {
313
+ const token = markType.startsWith("color:") ? markType.slice(6) : "";
314
+ if (!TOKEN.test(token)) return /* @__PURE__ */ jsx2(Fragment, { children });
315
+ return /* @__PURE__ */ jsx2("span", { className: `bcms-color-${token}`, children });
316
+ }
317
+ },
318
+ /** Unknown marks and types render their children. Never swallow content. */
319
+ unknownMark: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children }),
320
+ unknownType: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children: children ?? null }),
321
+ unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx2("p", { children })
322
+ };
323
+ }
324
+ function PortableText({ value, bindingBase }) {
325
+ const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);
326
+ const components = React.useMemo(buildComponents, []);
327
+ if (blocks.length === 0) return null;
328
+ if (!bindingBase) return /* @__PURE__ */ jsx2(PT, { value: blocks, components });
329
+ return /* @__PURE__ */ jsx2(Fragment, { children: blocks.map((node) => /* @__PURE__ */ jsx2(Bound, { base: bindingBase, nodeKey: node._key, children: /* @__PURE__ */ jsx2(PT, { value: [node], components }) }, node._key)) });
330
+ }
331
+ function readValue(value) {
332
+ if (Array.isArray(value)) return value;
333
+ if (value && typeof value === "object") {
334
+ const o = value;
335
+ if (Array.isArray(o.value)) return o.value;
336
+ if (Array.isArray(o.content)) return o.content;
337
+ }
338
+ return [];
69
339
  }
70
340
 
71
341
  // src/blocks/Text.tsx
@@ -84,16 +354,16 @@ function Text({
84
354
  right: "text-right"
85
355
  }[align];
86
356
  const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;
87
- if (content && content.content.length > 0) {
88
- return /* @__PURE__ */ jsx3("div", { className: cls, children: /* @__PURE__ */ jsx3(PortableText, { doc: content }) });
89
- }
357
+ const structured = readStructuredContent({ content });
90
358
  return /* @__PURE__ */ jsx3(
91
359
  "div",
92
360
  {
93
361
  className: cls,
94
- "data-bcms-field": field,
95
- "data-bcms-kind": kind,
96
- dangerouslySetInnerHTML: { __html: html }
362
+ ...structured ? {} : { "data-bcms-field": field, "data-bcms-kind": kind },
363
+ children: structured ? /* @__PURE__ */ jsx3(PortableText, { value: structured, bindingBase: field }) : (
364
+ // eslint-disable-next-line react/no-danger
365
+ /* @__PURE__ */ jsx3("span", { dangerouslySetInnerHTML: { __html: html } })
366
+ )
97
367
  }
98
368
  );
99
369
  }
@@ -106,7 +376,7 @@ function cn(...inputs) {
106
376
  }
107
377
 
108
378
  // src/blocks/Image.tsx
109
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
379
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
110
380
  function Image({
111
381
  src,
112
382
  alt,
@@ -118,7 +388,7 @@ function Image({
118
388
  "data-bcms-field": field,
119
389
  "data-bcms-kind": kind
120
390
  }) {
121
- return /* @__PURE__ */ jsxs("figure", { className: cn("w-full", className), children: [
391
+ return /* @__PURE__ */ jsxs2("figure", { className: cn("w-full", className), children: [
122
392
  /* @__PURE__ */ jsx4(
123
393
  "img",
124
394
  {
@@ -171,7 +441,7 @@ function Button({
171
441
  }
172
442
 
173
443
  // src/blocks/Spacer.tsx
174
- import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
444
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
175
445
  function Spacer({ height = 32, className = "" }) {
176
446
  return /* @__PURE__ */ jsx6(
177
447
  "div",
@@ -179,7 +449,7 @@ function Spacer({ height = 32, className = "" }) {
179
449
  className: `w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`,
180
450
  style: { height: `${height}px` },
181
451
  "aria-hidden": "true",
182
- children: /* @__PURE__ */ jsxs2("span", { className: "text-xs text-muted-foreground/40 select-none", children: [
452
+ children: /* @__PURE__ */ jsxs3("span", { className: "text-xs text-muted-foreground/40 select-none", children: [
183
453
  height,
184
454
  "px"
185
455
  ] })
@@ -211,7 +481,7 @@ function Columns({
211
481
 
212
482
  // src/blocks/Video.tsx
213
483
  import { useState } from "react";
214
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
484
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
215
485
  function getEmbedUrl(url) {
216
486
  const ytMatch = url.match(
217
487
  /(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
@@ -254,7 +524,7 @@ function Video({
254
524
  }
255
525
  );
256
526
  }
257
- return /* @__PURE__ */ jsxs3("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
527
+ return /* @__PURE__ */ jsxs4("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
258
528
  !isLoaded && poster && // eslint-disable-next-line @next/next/no-img-element
259
529
  /* @__PURE__ */ jsx8(
260
530
  "img",
@@ -301,9 +571,9 @@ function Section({
301
571
  }
302
572
 
303
573
  // src/blocks/Navbar.tsx
304
- import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
574
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
305
575
  function Navbar({ logo, links, cta, className = "" }) {
306
- return /* @__PURE__ */ jsxs4("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
576
+ return /* @__PURE__ */ jsxs5("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
307
577
  logo?.src ? /* @__PURE__ */ jsx10("a", { href: logo.href ?? "/", className: "shrink-0", children: /* @__PURE__ */ jsx10("img", { src: logo.src, alt: logo.alt, className: "h-8 w-auto" }) }) : null,
308
578
  /* @__PURE__ */ jsx10("div", { className: "flex flex-1 gap-5", children: (links ?? []).map((l, i) => /* @__PURE__ */ jsx10("a", { href: l.href, className: "text-inherit no-underline", children: l.label }, i)) }),
309
579
  cta ? /* @__PURE__ */ jsx10(
@@ -318,10 +588,10 @@ function Navbar({ logo, links, cta, className = "" }) {
318
588
  }
319
589
 
320
590
  // src/blocks/Footer.tsx
321
- import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
591
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
322
592
  function Footer({ columns, copyright, className = "" }) {
323
- return /* @__PURE__ */ jsxs5("footer", { className: cn("mt-12 border-t pt-8", className), children: [
324
- /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1.5", children: [
593
+ return /* @__PURE__ */ jsxs6("footer", { className: cn("mt-12 border-t pt-8", className), children: [
594
+ /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1.5", children: [
325
595
  col.heading ? /* @__PURE__ */ jsx11("h4", { className: "mb-2 font-semibold", children: col.heading }) : null,
326
596
  (col.links ?? []).map((l, j) => /* @__PURE__ */ jsx11("a", { href: l.href, className: "text-neutral-500 no-underline", children: l.label }, j))
327
597
  ] }, i)) }),
@@ -331,13 +601,13 @@ function Footer({ columns, copyright, className = "" }) {
331
601
 
332
602
  // src/blocks/Slider.tsx
333
603
  import { useState as useState2 } from "react";
334
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
604
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
335
605
  function Slider({ slides, className = "", renderBlock }) {
336
606
  const [i, setI] = useState2(0);
337
607
  const list = slides ?? [];
338
608
  const go = (n) => setI((n + list.length) % (list.length || 1));
339
609
  const btn = "absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer";
340
- return /* @__PURE__ */ jsxs6("div", { className: cn("relative w-full", className), children: [
610
+ return /* @__PURE__ */ jsxs7("div", { className: cn("relative w-full", className), children: [
341
611
  list.map((s, idx) => /* @__PURE__ */ jsx12("div", { hidden: idx !== i, className: "flex flex-col gap-4", children: (s.children ?? []).map((b) => /* @__PURE__ */ jsx12("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, s.id)),
342
612
  /* @__PURE__ */ jsx12("button", { type: "button", "aria-label": "Previous", onClick: () => go(i - 1), className: cn(btn, "left-2"), children: "\u2039" }),
343
613
  /* @__PURE__ */ jsx12("button", { type: "button", "aria-label": "Next", onClick: () => go(i + 1), className: cn(btn, "right-2"), children: "\u203A" })
@@ -346,11 +616,11 @@ function Slider({ slides, className = "", renderBlock }) {
346
616
 
347
617
  // src/blocks/Tabs.tsx
348
618
  import { useState as useState3 } from "react";
349
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
619
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
350
620
  function Tabs({ tabs, className = "", renderBlock }) {
351
621
  const [active, setActive] = useState3(0);
352
622
  const list = tabs ?? [];
353
- return /* @__PURE__ */ jsxs7("div", { className: cn("w-full", className), children: [
623
+ return /* @__PURE__ */ jsxs8("div", { className: cn("w-full", className), children: [
354
624
  /* @__PURE__ */ jsx13("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx13(
355
625
  "button",
356
626
  {
@@ -371,19 +641,19 @@ function Tabs({ tabs, className = "", renderBlock }) {
371
641
  }
372
642
 
373
643
  // src/blocks/Placeholders.tsx
374
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
644
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
375
645
  var box = "rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500";
376
646
  function ComponentInstance({
377
647
  componentId,
378
648
  className = ""
379
649
  }) {
380
- return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
650
+ return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
381
651
  "Component instance: ",
382
652
  /* @__PURE__ */ jsx14("code", { children: componentId })
383
653
  ] });
384
654
  }
385
655
  function FormPlaceholder({ formId, className = "" }) {
386
- return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
656
+ return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
387
657
  "Form: ",
388
658
  /* @__PURE__ */ jsx14("code", { children: formId })
389
659
  ] });
@@ -392,9 +662,9 @@ function CollectionPlaceholder({
392
662
  cardComponentId,
393
663
  className = ""
394
664
  }) {
395
- return /* @__PURE__ */ jsxs8("div", { className: cn(box, className), children: [
665
+ return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
396
666
  "Collection: entries of this page",
397
- cardComponentId ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
667
+ cardComponentId ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
398
668
  " as ",
399
669
  /* @__PURE__ */ jsx14("code", { children: cardComponentId })
400
670
  ] }) : /* @__PURE__ */ jsx14(Fragment2, { children: " (default card)" })
@@ -499,7 +769,7 @@ function blockStyleToReactStyle(style) {
499
769
 
500
770
  // src/renderer.tsx
501
771
  import "react";
502
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
772
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
503
773
  var CONTAINER_TYPES = /* @__PURE__ */ new Set(["columns", "section", "slider", "tabs"]);
504
774
  function bindingProps(block, annotate) {
505
775
  if (!annotate || !block.id) return {};
@@ -519,7 +789,7 @@ function bindingProps(block, annotate) {
519
789
  function resolveBlock(block, className, annotate = false) {
520
790
  const entry = BLOCK_REGISTRY[block.type];
521
791
  if (!entry) {
522
- return /* @__PURE__ */ jsxs9(
792
+ return /* @__PURE__ */ jsxs10(
523
793
  "div",
524
794
  {
525
795
  className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? ""}`,
@@ -543,7 +813,7 @@ function BlockRenderer({
543
813
  }) {
544
814
  const entry = BLOCK_REGISTRY[block.type];
545
815
  if (!entry) {
546
- return /* @__PURE__ */ jsxs9(
816
+ return /* @__PURE__ */ jsxs10(
547
817
  "div",
548
818
  {
549
819
  className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`,