@bettercms-ai/ui 0.16.0 → 0.17.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/registry.js CHANGED
@@ -29,10 +29,136 @@ function Heading({
29
29
  }
30
30
 
31
31
  // src/blocks/portableText.tsx
32
- import React from "react";
32
+ import React2 from "react";
33
33
  import { PortableText as PT } from "@portabletext/react";
34
34
 
35
+ // src/renderer.tsx
36
+ import { RESPONSIVE_CSS, hiddenEverywhere, hideOnAttr, splitButtonStyle, usesHideOn, visibleBlocks, windowState } from "@bettercms-ai/types";
37
+
38
+ // src/blockStyle.ts
39
+ import { blockStyleToCss } from "@bettercms-ai/types";
40
+ function blockStyleToReactStyle(style) {
41
+ return blockStyleToCss(style);
42
+ }
43
+
44
+ // src/renderer.tsx
45
+ import "react";
46
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
47
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["columns", "section", "slider", "tabs"]);
48
+ function styleOf(block) {
49
+ if (block.type !== "button") return { wrapper: blockStyleToReactStyle(block.style), paint: {} };
50
+ const { placement, paint } = splitButtonStyle(block.style);
51
+ return { wrapper: blockStyleToReactStyle(placement), paint: paint ? { style: paint } : {} };
52
+ }
53
+ function withHide(block, wrapperStyle, el, stampAt) {
54
+ const hide = hideOnAttr(block.style);
55
+ const state = stampAt ? windowState(block, stampAt) : "live";
56
+ const hidden = !!stampAt && hiddenEverywhere(block);
57
+ if (!wrapperStyle && !hide && state === "live" && !hidden) return el;
58
+ return /* @__PURE__ */ jsx2("div", { style: wrapperStyle, ...hide ? { "data-bcms-hide": hide } : {}, ...state === "live" ? {} : { "data-bcms-window": state }, ...hidden ? { "data-bcms-hidden": "all" } : {}, children: el });
59
+ }
60
+ function bindingProps(block, annotate) {
61
+ if (!annotate || !block.id) return {};
62
+ switch (block.type) {
63
+ case "heading":
64
+ case "button":
65
+ return { "data-bcms-field": `${block.id}__text`, "data-bcms-kind": "text" };
66
+ case "text":
67
+ case "richtext":
68
+ return { "data-bcms-field": `${block.id}__html`, "data-bcms-kind": "richtext" };
69
+ case "image":
70
+ return { "data-bcms-field": `${block.id}__src`, "data-bcms-kind": "image" };
71
+ default:
72
+ return {};
73
+ }
74
+ }
75
+ function resolveBlock(block, className, annotate = false, stampAt) {
76
+ const entry = BLOCK_REGISTRY[block.type];
77
+ if (!entry) {
78
+ return /* @__PURE__ */ jsxs(
79
+ "div",
80
+ {
81
+ className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? ""}`,
82
+ children: [
83
+ "Unknown block type: ",
84
+ /* @__PURE__ */ jsx2("code", { children: block.type })
85
+ ]
86
+ }
87
+ );
88
+ }
89
+ const Component = entry.component;
90
+ const { wrapper: blockStyle, paint } = styleOf(block);
91
+ const el = /* @__PURE__ */ jsx2(Component, { ...block.props, ...paint, className, ...bindingProps(block, annotate) });
92
+ return withHide(block, blockStyle, el, stampAt);
93
+ }
94
+ function BlockRenderer({
95
+ block,
96
+ className = "",
97
+ renderBlock,
98
+ annotate = false,
99
+ stampAt
100
+ }) {
101
+ const entry = BLOCK_REGISTRY[block.type];
102
+ if (!entry) {
103
+ return /* @__PURE__ */ jsxs(
104
+ "div",
105
+ {
106
+ className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`,
107
+ children: [
108
+ "Unknown block type: ",
109
+ /* @__PURE__ */ jsx2("code", { children: block.type })
110
+ ]
111
+ }
112
+ );
113
+ }
114
+ const Component = entry.component;
115
+ const containerProps = CONTAINER_TYPES.has(block.type) ? { renderBlock: renderBlock ?? ((child) => resolveBlock(child, void 0, annotate, stampAt)) } : {};
116
+ const { wrapper: blockStyle, paint } = styleOf(block);
117
+ const el = /* @__PURE__ */ jsx2(
118
+ Component,
119
+ {
120
+ ...block.props,
121
+ ...paint,
122
+ className,
123
+ ...containerProps,
124
+ ...bindingProps(block, annotate)
125
+ }
126
+ );
127
+ return withHide(block, blockStyle, el, stampAt);
128
+ }
129
+ function BlocksRenderer({
130
+ blocks: all,
131
+ className = "",
132
+ renderBlock,
133
+ annotate = false,
134
+ now
135
+ }) {
136
+ const at = now ?? /* @__PURE__ */ new Date();
137
+ const blocks = annotate ? all : visibleBlocks(all, at);
138
+ if (blocks.length === 0) return null;
139
+ return /* @__PURE__ */ jsxs("div", { className, children: [
140
+ usesHideOn(blocks) && /* @__PURE__ */ jsx2("style", { dangerouslySetInnerHTML: { __html: RESPONSIVE_CSS } }),
141
+ blocks.map((block) => /* @__PURE__ */ jsx2(
142
+ BlockRenderer,
143
+ {
144
+ block,
145
+ renderBlock,
146
+ annotate,
147
+ stampAt: annotate ? at : void 0
148
+ },
149
+ block.id
150
+ ))
151
+ ] });
152
+ }
153
+
35
154
  // ../richtext/src/types.ts
155
+ function pullQuoteHref(link) {
156
+ if (typeof link === "string") return link.trim();
157
+ if (link && typeof link === "object" && typeof link.href === "string") {
158
+ return link.href.trim();
159
+ }
160
+ return "";
161
+ }
36
162
  var PT_TYPES = {
37
163
  /** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */
38
164
  block: "bcmsBlock",
@@ -50,6 +176,10 @@ var PT_TYPES = {
50
176
  video: "video",
51
177
  /** A downloadable document — a PDF, a spec sheet. `<a href download>`, nothing more. */
52
178
  file: "file",
179
+ /** A pull quote — see `PortableTextPullQuote`. The V2 kit's `<figure data-rt="pullquote">`. */
180
+ pullQuote: "pullQuote",
181
+ /** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt="takeaways">`. */
182
+ keyTakeaways: "keyTakeaways",
53
183
  /**
54
184
  * A table. Spelled `table`, unprefixed, because that is what every other Portable Text
55
185
  * producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
@@ -74,8 +204,11 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
74
204
  PT_TYPES.divider,
75
205
  PT_TYPES.mention,
76
206
  PT_TYPES.video,
77
- PT_TYPES.file
207
+ PT_TYPES.file,
208
+ PT_TYPES.pullQuote,
209
+ PT_TYPES.keyTakeaways
78
210
  ]);
211
+ var EMBEDDED_COMPONENT_KEY = "component";
79
212
 
80
213
  // ../richtext/src/styled.ts
81
214
  var STYLED_TAGS = [
@@ -246,7 +379,8 @@ function normalizeCell(raw, at, mint, read) {
246
379
  content: Array.isArray(raw.content) ? read(raw.content, inner) : textContent(typeof raw.text === "string" ? raw.text : "", inner),
247
380
  ...raw.header === true ? { header: true } : {},
248
381
  ...span === void 0 ? {} : { span },
249
- ...rowSpan === void 0 ? {} : { rowSpan }
382
+ ...rowSpan === void 0 ? {} : { rowSpan },
383
+ ...typeof raw.group === "string" ? { group: raw.group } : {}
250
384
  };
251
385
  }
252
386
  function normalizeRow(raw, at, mint, read) {
@@ -254,7 +388,8 @@ function normalizeRow(raw, at, mint, read) {
254
388
  return {
255
389
  _key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", at),
256
390
  cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),
257
- ...isRecord(raw) && raw.head === true ? { head: true } : {}
391
+ ...isRecord(raw) && raw.head === true ? { head: true } : {},
392
+ ...isRecord(raw) && typeof raw.group === "string" ? { group: raw.group } : {}
258
393
  };
259
394
  }
260
395
  function builtInTableToTable(node, key = randomKey) {
@@ -299,24 +434,93 @@ function migrateTable(node, key = randomKey, read = asIs) {
299
434
  rows: rows.map((r, i) => normalizeRow(r, i, mint, read)),
300
435
  // Carried through: a migrated table that an author saves back is still the same table, and
301
436
  // dropping the flag on that save would change the page the next time it renders.
302
- ...node.legacy === true ? { legacy: true } : {}
437
+ ...node.legacy === true ? { legacy: true } : {},
438
+ ...tableExtras(node)
439
+ };
440
+ }
441
+ function tableExtras(node) {
442
+ const groups = (list) => Array.isArray(list) ? list.filter((g) => isRecord(g) && typeof g._key === "string" && g._key !== "").map((g) => ({ _key: g._key, label: typeof g.label === "string" ? g.label : "" })) : void 0;
443
+ const rowGroups = groups(node.rowGroups);
444
+ const columnGroups = groups(node.columnGroups);
445
+ return {
446
+ ...typeof node.caption === "string" ? { caption: node.caption } : {},
447
+ ...typeof node.notes === "string" ? { notes: node.notes } : {},
448
+ ...rowGroups ? { rowGroups } : {},
449
+ ...columnGroups ? { columnGroups } : {}
450
+ };
451
+ }
452
+ function tableGroupLayout(table) {
453
+ const rows = Array.isArray(table.rows) ? table.rows : [];
454
+ const labels = (list) => new Map(
455
+ (Array.isArray(list) ? list : []).filter((g) => isRecord(g) && typeof g._key === "string").map((g) => [g._key, typeof g.label === "string" ? g.label : ""])
456
+ );
457
+ const cellsOf = (row) => isRecord(row) && Array.isArray(row.cells) ? row.cells : [];
458
+ const spanOf2 = (cell) => isRecord(cell) && typeof cell.span === "number" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;
459
+ const width = rows.reduce((max, row) => Math.max(max, cellsOf(row).reduce((n, c) => n + spanOf2(c), 0)), 0);
460
+ const colLabels = labels(table.columnGroups);
461
+ const columns = [];
462
+ for (const cell of cellsOf(rows[0])) {
463
+ const key = isRecord(cell) && typeof cell.group === "string" && colLabels.has(cell.group) ? cell.group : void 0;
464
+ const last = columns[columns.length - 1];
465
+ if (last && last.key === key) last.width += spanOf2(cell);
466
+ else columns.push({ width: spanOf2(cell), ...key === void 0 ? {} : { key, label: colLabels.get(key) } });
467
+ }
468
+ const rowLabels = labels(table.rowGroups);
469
+ const headCount = rows.findIndex((r) => !isRecord(r) || r.head !== true);
470
+ const body = headCount === -1 ? [] : rows.slice(headCount);
471
+ const runs = [];
472
+ for (const row of body) {
473
+ const key = isRecord(row) && typeof row.group === "string" && rowLabels.has(row.group) ? row.group : void 0;
474
+ const last = runs[runs.length - 1];
475
+ if (last && last.key === key) last.rows.push(row);
476
+ else runs.push({ rows: [row], ...key === void 0 ? {} : { key, label: rowLabels.get(key) } });
477
+ }
478
+ return {
479
+ columns: columns.some((r) => r.key !== void 0) ? columns : [],
480
+ rows: runs.some((r) => r.key !== void 0) ? runs : [],
481
+ width
303
482
  };
304
483
  }
305
484
 
306
- // ../richtext/src/read.ts
485
+ // ../richtext/src/key-takeaways.ts
307
486
  var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
487
+ function keyTakeawaysView(value) {
488
+ const v = isRecord2(value) ? value : {};
489
+ const title = typeof v.title === "string" ? v.title.trim() : "";
490
+ const points = (Array.isArray(v.points) ? v.points : []).map((p) => typeof p === "string" ? p.trim() : "").filter(Boolean);
491
+ return { title, points };
492
+ }
493
+
494
+ // ../richtext/src/component-copy.ts
495
+ var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
496
+ var fieldsOf = (value) => isRecord3(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord3(value.fields) ? value.fields : null;
497
+ function detachedComponentCopy(value) {
498
+ const f = fieldsOf(value);
499
+ if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
500
+ return {
501
+ id: isRecord3(value) && typeof value._key === "string" ? value._key : "",
502
+ componentId: typeof f.componentId === "string" ? f.componentId : "",
503
+ props: isRecord3(f.props) ? f.props : {},
504
+ blocks: f.blocks,
505
+ ...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
506
+ ...typeof f.variant === "string" ? { variant: f.variant } : {}
507
+ };
508
+ }
509
+
510
+ // ../richtext/src/read.ts
511
+ var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
308
512
  function normalizePortableText(value, key = randomKey) {
309
513
  if (!Array.isArray(value)) return [];
310
514
  return value.flatMap((raw) => {
311
- if (!isRecord2(raw) || typeof raw._type !== "string") return [];
515
+ if (!isRecord4(raw) || typeof raw._type !== "string") return [];
312
516
  const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
313
517
  if (table) return [table];
314
- const node = { ...raw, _key: isRecord2(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
518
+ const node = { ...raw, _key: isRecord4(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
315
519
  if (node._type === "block") {
316
520
  if (!Array.isArray(node.markDefs)) node.markDefs = [];
317
521
  if (typeof node.style !== "string") node.style = "normal";
318
522
  node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
319
- if (!isRecord2(c)) return c;
523
+ if (!isRecord4(c)) return c;
320
524
  const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
321
525
  if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
322
526
  return child;
@@ -326,14 +530,14 @@ function normalizePortableText(value, key = randomKey) {
326
530
  });
327
531
  }
328
532
  function readStructuredContent(props) {
329
- if (!isRecord2(props)) return null;
533
+ if (!isRecord4(props)) return null;
330
534
  const candidate = props.content ?? props.value;
331
- const arr = Array.isArray(candidate) ? candidate : isRecord2(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
535
+ const arr = Array.isArray(candidate) ? candidate : isRecord4(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
332
536
  return arr && arr.length > 0 ? arr : null;
333
537
  }
334
538
 
335
539
  // src/blocks/portableText.tsx
336
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
540
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
337
541
  var TOKEN = /^[a-z0-9-]+$/i;
338
542
  function styleObject(css) {
339
543
  const out = {};
@@ -352,42 +556,63 @@ function Bound({
352
556
  nodeKey,
353
557
  children
354
558
  }) {
355
- if (!base || !nodeKey) return /* @__PURE__ */ jsx2(Fragment, { children });
356
- return /* @__PURE__ */ jsx2("div", { "data-bcms-field": `${base}#${nodeKey}`, "data-bcms-kind": "richtext", children });
559
+ if (!base || !nodeKey) return /* @__PURE__ */ jsx3(Fragment, { children });
560
+ return /* @__PURE__ */ jsx3("div", { "data-bcms-field": `${base}#${nodeKey}`, "data-bcms-kind": "richtext", children });
357
561
  }
358
562
  function Table({ value }) {
359
- const components = React.useMemo(buildComponents, []);
360
- const inline = React.useMemo(
361
- () => ({ ...components, block: { normal: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children }) } }),
563
+ const components = React2.useMemo(buildComponents, []);
564
+ const inline = React2.useMemo(
565
+ () => ({ ...components, block: { normal: ({ children }) => /* @__PURE__ */ jsx3(Fragment, { children }) } }),
362
566
  [components]
363
567
  );
364
568
  const rows = Array.isArray(value?.rows) ? value.rows : [];
365
569
  if (rows.length === 0) return null;
366
570
  const cells = (row) => (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {
367
571
  const Tag = cell?.header === true ? "th" : "td";
368
- const span = typeof cell?.span === "number" && cell.span > 1 ? cell.span : void 0;
572
+ const span2 = typeof cell?.span === "number" && cell.span > 1 ? cell.span : void 0;
369
573
  const rowSpan = typeof cell?.rowSpan === "number" && cell.rowSpan > 1 ? cell.rowSpan : void 0;
370
574
  const content = Array.isArray(cell?.content) ? cell.content : [];
371
575
  const lone = content.length === 1 && content[0]._type === "block" && content[0].style === "normal" && !content[0].listItem;
372
- return /* @__PURE__ */ jsx2(Tag, { ...span ? { colSpan: span } : {}, ...rowSpan ? { rowSpan } : {}, children: /* @__PURE__ */ jsx2(PT, { value: content, components: lone ? inline : components }) }, cell?._key);
576
+ return /* @__PURE__ */ jsx3(Tag, { ...span2 ? { colSpan: span2 } : {}, ...rowSpan ? { rowSpan } : {}, children: /* @__PURE__ */ jsx3(PT, { value: content, components: lone ? inline : components }) }, cell?._key);
373
577
  });
374
- const rendered = rows.map((row) => /* @__PURE__ */ jsx2("tr", { children: cells(row) }, row?._key));
578
+ const rowEl = (row) => /* @__PURE__ */ jsx3("tr", { children: cells(row) }, row?._key);
375
579
  const headCount = rows.findIndex((r) => r?.head !== true);
376
580
  const head = headCount === -1 ? rows.length : headCount;
377
- return /* @__PURE__ */ jsxs("table", { ...value.legacy === true ? { className: "bcms-table" } : {}, children: [
378
- head > 0 && /* @__PURE__ */ jsx2("thead", { children: rendered.slice(0, head) }),
379
- head < rows.length && /* @__PURE__ */ jsx2("tbody", { children: rendered.slice(head) })
581
+ const layout = tableGroupLayout(value);
582
+ const caption = typeof value.caption === "string" ? value.caption.trim() : "";
583
+ const notes = typeof value.notes === "string" ? value.notes.trim() : "";
584
+ const notesId = notes && typeof value._key === "string" && value._key ? `${value._key}-notes` : void 0;
585
+ const span = (n) => n > 1 ? n : void 0;
586
+ const groupRow = layout.columns.length ? /* @__PURE__ */ jsx3("tr", { children: layout.columns.flatMap(
587
+ (run, i) => run.label === void 0 ? Array.from({ length: run.width }, (_, k) => /* @__PURE__ */ jsx3("td", {}, `${i}-${k}`)) : [/* @__PURE__ */ jsx3("th", { scope: "colgroup", colSpan: span(run.width), "data-group": run.key, children: run.label }, i)]
588
+ ) }) : null;
589
+ const bodies = layout.rows.length ? layout.rows.map((run, i) => /* @__PURE__ */ jsxs2("tbody", { "data-key": run.key, children: [
590
+ run.label !== void 0 && /* @__PURE__ */ jsx3("tr", { children: /* @__PURE__ */ jsx3("th", { scope: "rowgroup", colSpan: span(layout.width), children: run.label }) }),
591
+ run.rows.map(rowEl)
592
+ ] }, i)) : head < rows.length && /* @__PURE__ */ jsx3("tbody", { children: rows.slice(head).map(rowEl) });
593
+ const table = /* @__PURE__ */ jsxs2("table", { ...value.legacy === true ? { className: "bcms-table" } : {}, ...notesId ? { "aria-describedby": notesId } : {}, children: [
594
+ caption && /* @__PURE__ */ jsx3("caption", { children: caption }),
595
+ layout.columns.map((run, i) => /* @__PURE__ */ jsx3("colgroup", { span: span(run.width) }, i)),
596
+ (groupRow || head > 0) && /* @__PURE__ */ jsxs2("thead", { children: [
597
+ groupRow,
598
+ rows.slice(0, head).map(rowEl)
599
+ ] }),
600
+ bodies
380
601
  ] });
602
+ return notes ? /* @__PURE__ */ jsxs2("figure", { "data-rt": "table", children: [
603
+ table,
604
+ /* @__PURE__ */ jsx3("p", { "data-notes": "", id: notesId, children: notes })
605
+ ] }) : table;
381
606
  }
382
607
  function buildComponents(renderBlock) {
383
608
  return {
384
609
  types: {
385
- [PT_TYPES.divider]: () => /* @__PURE__ */ jsx2("hr", {}),
386
- [PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx2(Table, { value }),
610
+ [PT_TYPES.divider]: () => /* @__PURE__ */ jsx3("hr", {}),
611
+ [PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx3(Table, { value }),
387
612
  [PT_TYPES.code]: ({ value }) => {
388
613
  const v = value;
389
614
  const lang = typeof v.language === "string" && TOKEN.test(v.language) ? v.language : void 0;
390
- return /* @__PURE__ */ jsx2("pre", { children: /* @__PURE__ */ jsx2("code", { className: lang ? `language-${lang}` : void 0, children: typeof v.code === "string" ? v.code : "" }) });
615
+ return /* @__PURE__ */ jsx3("pre", { children: /* @__PURE__ */ jsx3("code", { className: lang ? `language-${lang}` : void 0, children: typeof v.code === "string" ? v.code : "" }) });
391
616
  },
392
617
  /**
393
618
  * A generic embedded block renders as a marker, not as content: its payload references
@@ -398,14 +623,51 @@ function buildComponents(renderBlock) {
398
623
  */
399
624
  [PT_TYPES.block]: ({ value }) => {
400
625
  const rendered = renderBlock?.(value);
401
- if (rendered !== void 0) return /* @__PURE__ */ jsx2(Fragment, { children: rendered });
626
+ if (rendered !== void 0) return /* @__PURE__ */ jsx3(Fragment, { children: rendered });
627
+ const copy = detachedComponentCopy(value);
628
+ if (copy) {
629
+ return /* @__PURE__ */ jsx3("div", { className: "bcms-block bcms-block--component", "data-bcms-component-copy": copy.componentId, "data-bcms-instance": copy.id, children: /* @__PURE__ */ jsx3(BlocksRenderer, { blocks: copy.blocks }) });
630
+ }
402
631
  const v = value;
403
632
  const key = typeof v.schemaKey === "string" ? v.schemaKey : "block";
404
- return /* @__PURE__ */ jsx2("div", { className: "bcms-block", "data-bcms-schema-key": key });
633
+ return /* @__PURE__ */ jsx3("div", { className: "bcms-block", "data-bcms-schema-key": key });
634
+ },
635
+ /**
636
+ * A pull quote — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
637
+ * `<figure data-rt="pullquote">`, the name linked (and the quote cited) only through
638
+ * `safeHref`. pull-quote.test.tsx compares the two.
639
+ */
640
+ [PT_TYPES.pullQuote]: ({ value }) => {
641
+ const v = value;
642
+ const text = (x) => typeof x === "string" ? x.trim() : "";
643
+ const quote = typeof v.quote === "string" ? v.quote : "";
644
+ const name = text(v.name);
645
+ const role = text(v.role);
646
+ const href = safeHref(pullQuoteHref(v.link)) ?? void 0;
647
+ return /* @__PURE__ */ jsxs2("figure", { "data-rt": "pullquote", children: [
648
+ /* @__PURE__ */ jsx3("blockquote", { cite: href, children: /* @__PURE__ */ jsx3("p", { children: quote }) }),
649
+ name || role ? /* @__PURE__ */ jsxs2("figcaption", { children: [
650
+ name ? href ? /* @__PURE__ */ jsx3("a", { href, "data-name": "", children: name }) : /* @__PURE__ */ jsx3("span", { "data-name": "", children: name }) : null,
651
+ name && role ? ", " : null,
652
+ role ? /* @__PURE__ */ jsx3("span", { "data-role": "", children: role }) : null
653
+ ] }) : null
654
+ ] });
655
+ },
656
+ /**
657
+ * Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
658
+ * `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
659
+ * portable-text-key-takeaways.test.tsx compares the two.
660
+ */
661
+ [PT_TYPES.keyTakeaways]: ({ value }) => {
662
+ const { title, points } = keyTakeawaysView(value);
663
+ return /* @__PURE__ */ jsxs2("aside", { "data-rt": "takeaways", children: [
664
+ title ? /* @__PURE__ */ jsx3("p", { "data-title": "", children: title }) : null,
665
+ /* @__PURE__ */ jsx3("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx3("li", { children: p }, i)) })
666
+ ] });
405
667
  },
406
668
  [PT_TYPES.mention]: ({ value }) => {
407
669
  const v = value;
408
- return /* @__PURE__ */ jsx2("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
670
+ return /* @__PURE__ */ jsx3("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
409
671
  }
410
672
  },
411
673
  marks: {
@@ -416,9 +678,9 @@ function buildComponents(renderBlock) {
416
678
  [PT_TYPES.link]: ({ value, children }) => {
417
679
  const v = value ?? {};
418
680
  const href = typeof v.href === "string" ? safeHref(v.href) : null;
419
- if (!href) return /* @__PURE__ */ jsx2(Fragment, { children });
681
+ if (!href) return /* @__PURE__ */ jsx3(Fragment, { children });
420
682
  const blank = v.target === "_blank";
421
- return /* @__PURE__ */ jsx2("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
683
+ return /* @__PURE__ */ jsx3("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
422
684
  },
423
685
  /**
424
686
  * The site's own inline markup, rendered as itself — the React half of
@@ -435,12 +697,12 @@ function buildComponents(renderBlock) {
435
697
  [PT_TYPES.styled]: ({ value, children }) => {
436
698
  const v = value ?? {};
437
699
  const tag = typeof v.tag === "string" ? v.tag.toLowerCase() : "";
438
- if (!isStyledTag(tag)) return /* @__PURE__ */ jsx2(Fragment, { children });
700
+ if (!isStyledTag(tag)) return /* @__PURE__ */ jsx3(Fragment, { children });
439
701
  const Tag = tag;
440
702
  const className = typeof v.className === "string" && v.className ? v.className : void 0;
441
703
  const attrs = styledAttrEntries(v.attrs);
442
704
  const style = attrs.find(([name]) => name === "style")?.[1];
443
- return /* @__PURE__ */ jsx2(
705
+ return /* @__PURE__ */ jsx3(
444
706
  Tag,
445
707
  {
446
708
  className,
@@ -450,22 +712,22 @@ function buildComponents(renderBlock) {
450
712
  }
451
713
  );
452
714
  },
453
- highlight: ({ children }) => /* @__PURE__ */ jsx2("mark", { children }),
454
- "strike-through": ({ children }) => /* @__PURE__ */ jsx2("s", { children }),
715
+ highlight: ({ children }) => /* @__PURE__ */ jsx3("mark", { children }),
716
+ "strike-through": ({ children }) => /* @__PURE__ */ jsx3("s", { children }),
455
717
  /**
456
718
  * A colour mark carries a TOKEN name (`color:brand-accent`), never a baked hex, so the
457
719
  * consuming site styles it and a rebrand does not orphan the content.
458
720
  */
459
721
  color: ({ children, markType }) => {
460
722
  const token = markType.startsWith("color:") ? markType.slice(6) : "";
461
- if (!TOKEN.test(token)) return /* @__PURE__ */ jsx2(Fragment, { children });
462
- return /* @__PURE__ */ jsx2("span", { className: `bcms-color-${token}`, children });
723
+ if (!TOKEN.test(token)) return /* @__PURE__ */ jsx3(Fragment, { children });
724
+ return /* @__PURE__ */ jsx3("span", { className: `bcms-color-${token}`, children });
463
725
  }
464
726
  },
465
727
  /** Unknown marks and types render their children. Never swallow content. */
466
- unknownMark: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children }),
467
- unknownType: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children: children ?? null }),
468
- unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx2("p", { children })
728
+ unknownMark: ({ children }) => /* @__PURE__ */ jsx3(Fragment, { children }),
729
+ unknownType: ({ children }) => /* @__PURE__ */ jsx3(Fragment, { children: children ?? null }),
730
+ unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx3("p", { children })
469
731
  };
470
732
  }
471
733
  var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
@@ -479,20 +741,20 @@ function runsOf(blocks) {
479
741
  return runs;
480
742
  }
481
743
  function PortableText({ value, bindingBase, renderBlock }) {
482
- const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);
483
- const components = React.useMemo(() => buildComponents(renderBlock), [renderBlock]);
484
- const listComponents = React.useMemo(() => {
744
+ const blocks = React2.useMemo(() => normalizePortableText(readValue(value)), [value]);
745
+ const components = React2.useMemo(() => buildComponents(renderBlock), [renderBlock]);
746
+ const listComponents = React2.useMemo(() => {
485
747
  if (!bindingBase) return null;
486
748
  const keys = new Set(blocks.map((b) => b._key));
487
749
  return {
488
750
  ...components,
489
- listItem: ({ value: item, children }) => keys.has(item._key) ? /* @__PURE__ */ jsx2("li", { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext", children }) : /* @__PURE__ */ jsx2("li", { children })
751
+ listItem: ({ value: item, children }) => keys.has(item._key) ? /* @__PURE__ */ jsx3("li", { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext", children }) : /* @__PURE__ */ jsx3("li", { children })
490
752
  };
491
753
  }, [bindingBase, blocks, components]);
492
754
  if (blocks.length === 0) return null;
493
- if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx2(PT, { value: blocks, components });
494
- return /* @__PURE__ */ jsx2(Fragment, { children: runsOf(blocks).map(
495
- (run) => isListItem(run[0]) ? /* @__PURE__ */ jsx2(PT, { value: run, components: listComponents }, run[0]._key) : /* @__PURE__ */ jsx2(Bound, { base: bindingBase, nodeKey: run[0]._key, children: /* @__PURE__ */ jsx2(PT, { value: run, components }) }, run[0]._key)
755
+ if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx3(PT, { value: blocks, components });
756
+ return /* @__PURE__ */ jsx3(Fragment, { children: runsOf(blocks).map(
757
+ (run) => isListItem(run[0]) ? /* @__PURE__ */ jsx3(PT, { value: run, components: listComponents }, run[0]._key) : /* @__PURE__ */ jsx3(Bound, { base: bindingBase, nodeKey: run[0]._key, children: /* @__PURE__ */ jsx3(PT, { value: run, components }) }, run[0]._key)
496
758
  ) });
497
759
  }
498
760
  function readValue(value) {
@@ -506,7 +768,7 @@ function readValue(value) {
506
768
  }
507
769
 
508
770
  // src/blocks/Text.tsx
509
- import { jsx as jsx3 } from "react/jsx-runtime";
771
+ import { jsx as jsx4 } from "react/jsx-runtime";
510
772
  function Text({
511
773
  html,
512
774
  content,
@@ -522,14 +784,14 @@ function Text({
522
784
  }[align];
523
785
  const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;
524
786
  const structured = readStructuredContent({ content });
525
- return /* @__PURE__ */ jsx3(
787
+ return /* @__PURE__ */ jsx4(
526
788
  "div",
527
789
  {
528
790
  className: cls,
529
791
  ...structured ? {} : { "data-bcms-field": field, "data-bcms-kind": kind },
530
- children: structured ? /* @__PURE__ */ jsx3(PortableText, { value: structured, bindingBase: field }) : (
792
+ children: structured ? /* @__PURE__ */ jsx4(PortableText, { value: structured, bindingBase: field }) : (
531
793
  // eslint-disable-next-line react/no-danger
532
- /* @__PURE__ */ jsx3("span", { dangerouslySetInnerHTML: { __html: html } })
794
+ /* @__PURE__ */ jsx4("span", { dangerouslySetInnerHTML: { __html: html } })
533
795
  )
534
796
  }
535
797
  );
@@ -543,7 +805,7 @@ function cn(...inputs) {
543
805
  }
544
806
 
545
807
  // src/blocks/Image.tsx
546
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
808
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
547
809
  function Image({
548
810
  src,
549
811
  alt,
@@ -556,8 +818,8 @@ function Image({
556
818
  "data-bcms-kind": kind
557
819
  }) {
558
820
  const sized = typeof width === "number" && width > 0;
559
- return /* @__PURE__ */ jsxs2("figure", { className: cn(sized ? "max-w-full" : "w-full", className), children: [
560
- /* @__PURE__ */ jsx4(
821
+ return /* @__PURE__ */ jsxs3("figure", { className: cn(sized ? "max-w-full" : "w-full", className), children: [
822
+ /* @__PURE__ */ jsx5(
561
823
  "img",
562
824
  {
563
825
  src,
@@ -572,13 +834,13 @@ function Image({
572
834
  loading: "lazy"
573
835
  }
574
836
  ),
575
- caption && /* @__PURE__ */ jsx4("figcaption", { className: "mt-2 text-sm text-muted-foreground text-center italic", children: caption })
837
+ caption && /* @__PURE__ */ jsx5("figcaption", { className: "mt-2 text-sm text-muted-foreground text-center italic", children: caption })
576
838
  ] });
577
839
  }
578
840
 
579
841
  // src/blocks/Button.tsx
580
842
  import { LINK_RELS, buttonIconOf, buttonIconSvg, buttonVariantOf, isScriptHref } from "@bettercms-ai/types";
581
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
843
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
582
844
  function Button({
583
845
  text,
584
846
  href,
@@ -622,13 +884,13 @@ function Button({
622
884
  const fullClass = fullWidth === true ? "flex w-full justify-center" : "";
623
885
  const relTokens = [...newTab === true ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
624
886
  if (action === "code") {
625
- return /* @__PURE__ */ jsxs3("button", { type: "button", "data-bcms-field": field, "data-bcms-kind": kind, style, className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className), children: [
626
- icon && /* @__PURE__ */ jsx5("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
887
+ return /* @__PURE__ */ jsxs4("button", { type: "button", "data-bcms-field": field, "data-bcms-kind": kind, style, className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className), children: [
888
+ icon && /* @__PURE__ */ jsx6("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
627
889
  text,
628
- arrow === true && /* @__PURE__ */ jsx5("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
890
+ arrow === true && /* @__PURE__ */ jsx6("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
629
891
  ] });
630
892
  }
631
- return /* @__PURE__ */ jsxs3(
893
+ return /* @__PURE__ */ jsxs4(
632
894
  "a",
633
895
  {
634
896
  href: typeof href === "string" && isScriptHref(href) ? "#" : href,
@@ -639,24 +901,24 @@ function Button({
639
901
  style,
640
902
  className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className),
641
903
  children: [
642
- icon && /* @__PURE__ */ jsx5("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
904
+ icon && /* @__PURE__ */ jsx6("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
643
905
  text,
644
- arrow === true && /* @__PURE__ */ jsx5("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
906
+ arrow === true && /* @__PURE__ */ jsx6("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
645
907
  ]
646
908
  }
647
909
  );
648
910
  }
649
911
 
650
912
  // src/blocks/Spacer.tsx
651
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
913
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
652
914
  function Spacer({ height = 32, className = "" }) {
653
- return /* @__PURE__ */ jsx6(
915
+ return /* @__PURE__ */ jsx7(
654
916
  "div",
655
917
  {
656
918
  className: `w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`,
657
919
  style: { height: `${height}px` },
658
920
  "aria-hidden": "true",
659
- children: /* @__PURE__ */ jsxs4("span", { className: "text-xs text-muted-foreground/40 select-none", children: [
921
+ children: /* @__PURE__ */ jsxs5("span", { className: "text-xs text-muted-foreground/40 select-none", children: [
660
922
  height,
661
923
  "px"
662
924
  ] })
@@ -666,14 +928,14 @@ function Spacer({ height = 32, className = "" }) {
666
928
 
667
929
  // src/blocks/Columns.tsx
668
930
  import "react";
669
- import { jsx as jsx7 } from "react/jsx-runtime";
931
+ import { jsx as jsx8 } from "react/jsx-runtime";
670
932
  function Columns({
671
933
  columns,
672
934
  gap = 16,
673
935
  className = "",
674
936
  renderBlock
675
937
  }) {
676
- return /* @__PURE__ */ jsx7(
938
+ return /* @__PURE__ */ jsx8(
677
939
  "div",
678
940
  {
679
941
  className: cn("grid w-full", className),
@@ -681,14 +943,14 @@ function Columns({
681
943
  gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,
682
944
  gap: `${gap}px`
683
945
  },
684
- children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */ jsx7("div", { className: "flex flex-col gap-4 min-w-0", children: columnBlocks.map((block) => /* @__PURE__ */ jsx7("span", { children: renderBlock ? renderBlock(block) : null }, block.id)) }, colIndex))
946
+ children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-4 min-w-0", children: columnBlocks.map((block) => /* @__PURE__ */ jsx8("span", { children: renderBlock ? renderBlock(block) : null }, block.id)) }, colIndex))
685
947
  }
686
948
  );
687
949
  }
688
950
 
689
951
  // src/blocks/Video.tsx
690
952
  import { useState } from "react";
691
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
953
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
692
954
  function getEmbedUrl(url) {
693
955
  const ytMatch = url.match(
694
956
  /(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
@@ -718,7 +980,7 @@ function Video({
718
980
  const [isLoaded, setIsLoaded] = useState(false);
719
981
  const { embedUrl, provider } = getEmbedUrl(url);
720
982
  if (provider === "unknown") {
721
- return /* @__PURE__ */ jsx8(
983
+ return /* @__PURE__ */ jsx9(
722
984
  "video",
723
985
  {
724
986
  src: url,
@@ -731,9 +993,9 @@ function Video({
731
993
  }
732
994
  );
733
995
  }
734
- return /* @__PURE__ */ jsxs5("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
996
+ return /* @__PURE__ */ jsxs6("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
735
997
  !isLoaded && poster && // eslint-disable-next-line @next/next/no-img-element
736
- /* @__PURE__ */ jsx8(
998
+ /* @__PURE__ */ jsx9(
737
999
  "img",
738
1000
  {
739
1001
  src: poster,
@@ -741,7 +1003,7 @@ function Video({
741
1003
  className: "w-full h-full object-cover absolute inset-0"
742
1004
  }
743
1005
  ),
744
- /* @__PURE__ */ jsx8(
1006
+ /* @__PURE__ */ jsx9(
745
1007
  "iframe",
746
1008
  {
747
1009
  src: `${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`,
@@ -757,7 +1019,7 @@ function Video({
757
1019
 
758
1020
  // src/blocks/Section.tsx
759
1021
  import "react";
760
- import { jsx as jsx9 } from "react/jsx-runtime";
1022
+ import { jsx as jsx10 } from "react/jsx-runtime";
761
1023
  function Section({
762
1024
  children,
763
1025
  background,
@@ -766,24 +1028,24 @@ function Section({
766
1028
  className = "",
767
1029
  renderBlock
768
1030
  }) {
769
- const inner = (children ?? []).map((block) => /* @__PURE__ */ jsx9("span", { children: renderBlock ? renderBlock(block) : null }, block.id));
770
- return /* @__PURE__ */ jsx9(
1031
+ const inner = (children ?? []).map((block) => /* @__PURE__ */ jsx10("span", { children: renderBlock ? renderBlock(block) : null }, block.id));
1032
+ return /* @__PURE__ */ jsx10(
771
1033
  "section",
772
1034
  {
773
1035
  className: cn("w-full", className),
774
1036
  style: { background, paddingTop: paddingY, paddingBottom: paddingY },
775
- children: maxWidth ? /* @__PURE__ */ jsx9("div", { style: { maxWidth, marginInline: "auto" }, children: inner }) : inner
1037
+ children: maxWidth ? /* @__PURE__ */ jsx10("div", { style: { maxWidth, marginInline: "auto" }, children: inner }) : inner
776
1038
  }
777
1039
  );
778
1040
  }
779
1041
 
780
1042
  // src/blocks/Navbar.tsx
781
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1043
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
782
1044
  function Navbar({ logo, links, cta, className = "" }) {
783
- return /* @__PURE__ */ jsxs6("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
784
- 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,
785
- /* @__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)) }),
786
- cta ? /* @__PURE__ */ jsx10(
1045
+ return /* @__PURE__ */ jsxs7("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
1046
+ logo?.src ? /* @__PURE__ */ jsx11("a", { href: logo.href ?? "/", className: "shrink-0", children: /* @__PURE__ */ jsx11("img", { src: logo.src, alt: logo.alt, className: "h-8 w-auto" }) }) : null,
1047
+ /* @__PURE__ */ jsx11("div", { className: "flex flex-1 gap-5", children: (links ?? []).map((l, i) => /* @__PURE__ */ jsx11("a", { href: l.href, className: "text-inherit no-underline", children: l.label }, i)) }),
1048
+ cta ? /* @__PURE__ */ jsx11(
787
1049
  "a",
788
1050
  {
789
1051
  href: cta.href,
@@ -795,40 +1057,40 @@ function Navbar({ logo, links, cta, className = "" }) {
795
1057
  }
796
1058
 
797
1059
  // src/blocks/Footer.tsx
798
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1060
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
799
1061
  function Footer({ columns, copyright, className = "" }) {
800
- return /* @__PURE__ */ jsxs7("footer", { className: cn("mt-12 border-t pt-8", className), children: [
801
- /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5", children: [
802
- col.heading ? /* @__PURE__ */ jsx11("h4", { className: "mb-2 font-semibold", children: col.heading }) : null,
803
- (col.links ?? []).map((l, j) => /* @__PURE__ */ jsx11("a", { href: l.href, className: "text-neutral-500 no-underline", children: l.label }, j))
1062
+ return /* @__PURE__ */ jsxs8("footer", { className: cn("mt-12 border-t pt-8", className), children: [
1063
+ /* @__PURE__ */ jsx12("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
1064
+ col.heading ? /* @__PURE__ */ jsx12("h4", { className: "mb-2 font-semibold", children: col.heading }) : null,
1065
+ (col.links ?? []).map((l, j) => /* @__PURE__ */ jsx12("a", { href: l.href, className: "text-neutral-500 no-underline", children: l.label }, j))
804
1066
  ] }, i)) }),
805
- copyright ? /* @__PURE__ */ jsx11("p", { className: "mt-6 text-sm text-neutral-500", children: copyright }) : null
1067
+ copyright ? /* @__PURE__ */ jsx12("p", { className: "mt-6 text-sm text-neutral-500", children: copyright }) : null
806
1068
  ] });
807
1069
  }
808
1070
 
809
1071
  // src/blocks/Slider.tsx
810
1072
  import { useState as useState2 } from "react";
811
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1073
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
812
1074
  function Slider({ slides, className = "", renderBlock }) {
813
1075
  const [i, setI] = useState2(0);
814
1076
  const list = slides ?? [];
815
1077
  const go = (n) => setI((n + list.length) % (list.length || 1));
816
1078
  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";
817
- return /* @__PURE__ */ jsxs8("div", { className: cn("relative w-full", className), children: [
818
- 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)),
819
- /* @__PURE__ */ jsx12("button", { type: "button", "aria-label": "Previous", onClick: () => go(i - 1), className: cn(btn, "left-2"), children: "\u2039" }),
820
- /* @__PURE__ */ jsx12("button", { type: "button", "aria-label": "Next", onClick: () => go(i + 1), className: cn(btn, "right-2"), children: "\u203A" })
1079
+ return /* @__PURE__ */ jsxs9("div", { className: cn("relative w-full", className), children: [
1080
+ list.map((s, idx) => /* @__PURE__ */ jsx13("div", { hidden: idx !== i, className: "flex flex-col gap-4", children: (s.children ?? []).map((b) => /* @__PURE__ */ jsx13("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, s.id)),
1081
+ /* @__PURE__ */ jsx13("button", { type: "button", "aria-label": "Previous", onClick: () => go(i - 1), className: cn(btn, "left-2"), children: "\u2039" }),
1082
+ /* @__PURE__ */ jsx13("button", { type: "button", "aria-label": "Next", onClick: () => go(i + 1), className: cn(btn, "right-2"), children: "\u203A" })
821
1083
  ] });
822
1084
  }
823
1085
 
824
1086
  // src/blocks/Tabs.tsx
825
1087
  import { useState as useState3 } from "react";
826
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1088
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
827
1089
  function Tabs({ tabs, className = "", renderBlock }) {
828
1090
  const [active, setActive] = useState3(0);
829
1091
  const list = tabs ?? [];
830
- return /* @__PURE__ */ jsxs9("div", { className: cn("w-full", className), children: [
831
- /* @__PURE__ */ jsx13("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx13(
1092
+ return /* @__PURE__ */ jsxs10("div", { className: cn("w-full", className), children: [
1093
+ /* @__PURE__ */ jsx14("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx14(
832
1094
  "button",
833
1095
  {
834
1096
  role: "tab",
@@ -843,38 +1105,38 @@ function Tabs({ tabs, className = "", renderBlock }) {
843
1105
  },
844
1106
  t.id
845
1107
  )) }),
846
- list.map((t, idx) => /* @__PURE__ */ jsx13("div", { role: "tabpanel", hidden: idx !== active, className: "flex flex-col gap-4", children: (t.children ?? []).map((b) => /* @__PURE__ */ jsx13("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, t.id))
1108
+ list.map((t, idx) => /* @__PURE__ */ jsx14("div", { role: "tabpanel", hidden: idx !== active, className: "flex flex-col gap-4", children: (t.children ?? []).map((b) => /* @__PURE__ */ jsx14("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, t.id))
847
1109
  ] });
848
1110
  }
849
1111
 
850
1112
  // src/blocks/Placeholders.tsx
851
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1113
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
852
1114
  var box = "rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500";
853
1115
  function ComponentInstance({
854
1116
  componentId,
855
1117
  className = ""
856
1118
  }) {
857
- return /* @__PURE__ */ jsxs10("div", { className: cn(box, className), children: [
1119
+ return /* @__PURE__ */ jsxs11("div", { className: cn(box, className), children: [
858
1120
  "Component instance: ",
859
- /* @__PURE__ */ jsx14("code", { children: componentId })
1121
+ /* @__PURE__ */ jsx15("code", { children: componentId })
860
1122
  ] });
861
1123
  }
862
1124
  function FormPlaceholder({ formId, className = "" }) {
863
- return /* @__PURE__ */ jsxs10("div", { className: cn(box, className), children: [
1125
+ return /* @__PURE__ */ jsxs11("div", { className: cn(box, className), children: [
864
1126
  "Form: ",
865
- /* @__PURE__ */ jsx14("code", { children: formId })
1127
+ /* @__PURE__ */ jsx15("code", { children: formId })
866
1128
  ] });
867
1129
  }
868
1130
  function CollectionPlaceholder({
869
1131
  cardComponentId,
870
1132
  className = ""
871
1133
  }) {
872
- return /* @__PURE__ */ jsxs10("div", { className: cn(box, className), children: [
1134
+ return /* @__PURE__ */ jsxs11("div", { className: cn(box, className), children: [
873
1135
  "Collection: entries of this page",
874
- cardComponentId ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
1136
+ cardComponentId ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
875
1137
  " as ",
876
- /* @__PURE__ */ jsx14("code", { children: cardComponentId })
877
- ] }) : /* @__PURE__ */ jsx14(Fragment2, { children: " (default card)" })
1138
+ /* @__PURE__ */ jsx15("code", { children: cardComponentId })
1139
+ ] }) : /* @__PURE__ */ jsx15(Fragment2, { children: " (default card)" })
878
1140
  ] });
879
1141
  }
880
1142