@bettercms-ai/ui 0.18.0 → 0.20.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/dist/blocks.js CHANGED
@@ -585,6 +585,7 @@ function BlocksRenderer({
585
585
  }
586
586
 
587
587
  // ../richtext/src/types.ts
588
+ var PT_TABLE_COLUMN_TYPES = ["text", "number", "money", "boolean", "rating", "link"];
588
589
  function pullQuoteHref(link) {
589
590
  if (typeof link === "string") return link.trim();
590
591
  if (link && typeof link === "object" && typeof link.href === "string") {
@@ -817,7 +818,12 @@ function normalizeCell(raw, at, mint, read) {
817
818
  ...rowSpan === void 0 ? {} : { rowSpan },
818
819
  ...typeof raw.group === "string" ? { group: raw.group } : {},
819
820
  ...raw.highlight === true ? { highlight: true } : {},
820
- ...raw.sortable === true ? { sortable: true } : {}
821
+ ...raw.sortable === true ? { sortable: true } : {},
822
+ // The V2 table's typed cells: the column (first-row cell), the per-cell type, the typed value.
823
+ ...columnOf(raw.column) ? { column: columnOf(raw.column) } : {},
824
+ ...isColumnType(raw.type) ? { type: raw.type } : {},
825
+ ...typeof raw.value === "boolean" || typeof raw.value === "number" && Number.isFinite(raw.value) ? { value: raw.value } : {},
826
+ ...typeof raw.note === "string" && raw.note ? { note: raw.note } : {}
821
827
  };
822
828
  }
823
829
  function normalizeRow(raw, at, mint, read) {
@@ -829,7 +835,8 @@ function normalizeRow(raw, at, mint, read) {
829
835
  ...isRecord(raw) && typeof raw.group === "string" ? { group: raw.group } : {},
830
836
  // The V2 table's two row flags ("Totals row", "Highlight this row"); see `PortableTextTableRow`.
831
837
  ...isRecord(raw) && raw.footer === true ? { footer: true } : {},
832
- ...isRecord(raw) && raw.highlight === true ? { highlight: true } : {}
838
+ ...isRecord(raw) && raw.highlight === true ? { highlight: true } : {},
839
+ ...isRecord(raw) && isColumnType(raw.type) ? { type: raw.type } : {}
833
840
  };
834
841
  }
835
842
  function builtInTableToTable(node, key = randomKey) {
@@ -889,9 +896,18 @@ function tableExtras(node) {
889
896
  ...columnGroups ? { columnGroups } : {},
890
897
  ...node.filterable === true ? { filterable: true } : {},
891
898
  ...jsonLdOf(node.jsonLd) ? { jsonLd: jsonLdOf(node.jsonLd) } : {},
892
- ...designOf(node.design) ? { design: designOf(node.design) } : {}
899
+ ...designOf(node.design) ? { design: designOf(node.design) } : {},
900
+ ...footnotesOf(node.footnotes) ? { footnotes: footnotesOf(node.footnotes) } : {}
893
901
  };
894
902
  }
903
+ function footnotesOf(v) {
904
+ if (!Array.isArray(v)) return void 0;
905
+ const out = v.filter((f) => isRecord(f) && typeof f._key === "string" && f._key !== "").map((f) => ({ _key: f._key, text: typeof f.text === "string" ? f.text : "" }));
906
+ return out.length ? out : void 0;
907
+ }
908
+ function footnoteNumbers(table) {
909
+ return new Map((footnotesOf(table.footnotes) ?? []).map((f, i) => [f._key, i + 1]));
910
+ }
895
911
  function designOf(v) {
896
912
  if (!isRecord(v)) return void 0;
897
913
  const d = {
@@ -964,6 +980,133 @@ function tableGroupLayout(table) {
964
980
  width
965
981
  };
966
982
  }
983
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["number", "money", "rating"]);
984
+ var isColumnType = (v) => PT_TABLE_COLUMN_TYPES.includes(v);
985
+ function columnOf(v) {
986
+ if (!isRecord(v)) return void 0;
987
+ const str = (x) => typeof x === "string" && x !== "" ? x : void 0;
988
+ const int = (x, lo, hi) => Number.isInteger(x) && x >= lo && x <= hi ? x : void 0;
989
+ const c = {
990
+ ...isColumnType(v.type) ? { type: v.type } : {},
991
+ ...str(v.description) ? { description: str(v.description) } : {},
992
+ ...str(v.unit) ? { unit: str(v.unit) } : {},
993
+ ...typeof v.currency === "string" && /^[A-Z]{3}$/.test(v.currency) ? { currency: v.currency } : {},
994
+ ...int(v.decimals, 0, 10) !== void 0 ? { decimals: int(v.decimals, 0, 10) } : {},
995
+ ...v.align === "start" || v.align === "center" || v.align === "end" ? { align: v.align } : {},
996
+ ...str(v.trueLabel) ? { trueLabel: str(v.trueLabel) } : {},
997
+ ...str(v.falseLabel) ? { falseLabel: str(v.falseLabel) } : {},
998
+ ...int(v.max, 1, 10) !== void 0 ? { max: int(v.max, 1, 10) } : {}
999
+ };
1000
+ return Object.keys(c).length ? c : void 0;
1001
+ }
1002
+ function tableColumns(table) {
1003
+ const out = [];
1004
+ const first = Array.isArray(table.rows) ? table.rows[0] : void 0;
1005
+ for (const cell of isRecord(first) && Array.isArray(first.cells) ? first.cells : []) {
1006
+ const w = isRecord(cell) && typeof cell.span === "number" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;
1007
+ const col = isRecord(cell) ? columnOf(cell.column) : void 0;
1008
+ for (let i = 0; i < w; i += 1) out.push(col);
1009
+ }
1010
+ return out;
1011
+ }
1012
+ function isTypedTable(table) {
1013
+ if (tableColumns(table).some(Boolean)) return true;
1014
+ return (Array.isArray(table.rows) ? table.rows : []).some(
1015
+ (r) => isRecord(r) && (r.type !== void 0 || Array.isArray(r.cells) && r.cells.some((c) => isRecord(c) && (c.type !== void 0 || c.value !== void 0)))
1016
+ );
1017
+ }
1018
+ var cellTypeOf = (col, row, cell) => (isRecord(cell) && isColumnType(cell.type) ? cell.type : void 0) ?? (isRecord(row) && isColumnType(row.type) ? row.type : void 0) ?? col?.type ?? "text";
1019
+ var columnAlign = (col, type) => col?.align ?? (NUMERIC_TYPES.has(type) ? "end" : "start");
1020
+ var TRUE_WORDS = /* @__PURE__ */ new Set(["\u2713", "\u2714", "\u2714\uFE0F", "\u2611", "\u2705", "yes", "y", "true", "included", "available", "supported", "x \u2713", "check", "\u25CF"]);
1021
+ var FALSE_WORDS = /* @__PURE__ */ new Set(["\u2717", "\u2715", "\u2716", "\u274C", "\xD7", "x", "no", "n", "false", "not included", "unavailable", "not available", "unsupported", "\u2014", "\u2013", "-", "\u25CB"]);
1022
+ function toBool(v) {
1023
+ if (typeof v === "boolean") return v;
1024
+ if (typeof v !== "string") return null;
1025
+ const s = v.trim().toLowerCase();
1026
+ return TRUE_WORDS.has(s) ? true : FALSE_WORDS.has(s) ? false : null;
1027
+ }
1028
+ function toNumber(v) {
1029
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
1030
+ if (typeof v !== "string") return null;
1031
+ const s = v.replace(/[,\s]/g, "").replace(/[^\d.+-]/g, "");
1032
+ if (!s || !/^[+-]?\d*\.?\d+$/.test(s)) return null;
1033
+ return Number(s);
1034
+ }
1035
+ var formatNumber = (n, decimals) => new Intl.NumberFormat("en-US", decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { maximumFractionDigits: 6 }).format(n);
1036
+ function formatMoney(n, currency = "USD", decimals) {
1037
+ try {
1038
+ const whole = Number.isInteger(n) && decimals == null;
1039
+ return new Intl.NumberFormat("en-US", { style: "currency", currency, minimumFractionDigits: whole ? 0 : decimals ?? 2, maximumFractionDigits: whole ? 0 : decimals ?? 2 }).format(n);
1040
+ } catch {
1041
+ return `${formatNumber(n, decimals)} ${currency}`;
1042
+ }
1043
+ }
1044
+ var boolLabels = (col) => ({ yes: col?.trueLabel || "Included", no: col?.falseLabel || "Not included" });
1045
+ function typedCellText(col, type, v) {
1046
+ if (v == null || v === "") return "";
1047
+ const words = String(v).replace(/\s+/g, " ").trim();
1048
+ switch (type) {
1049
+ case "boolean": {
1050
+ const b = toBool(v);
1051
+ return b == null ? words : b ? boolLabels(col).yes : boolLabels(col).no;
1052
+ }
1053
+ case "number": {
1054
+ const n = toNumber(v);
1055
+ return n == null ? words : `${formatNumber(n, col?.decimals)}${col?.unit ? /^[%°]/.test(col.unit) ? col.unit : ` ${col.unit}` : ""}`;
1056
+ }
1057
+ case "money": {
1058
+ const n = toNumber(v);
1059
+ return n == null ? words : formatMoney(n, col?.currency || "USD", col?.decimals);
1060
+ }
1061
+ case "rating": {
1062
+ const n = toNumber(v);
1063
+ return n == null ? words : `${formatNumber(n, 1).replace(/\.0$/, "")} out of ${col?.max ?? 5}`;
1064
+ }
1065
+ default:
1066
+ return words;
1067
+ }
1068
+ }
1069
+ var escHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1070
+ function typedCellView(column, row, cell, inHead) {
1071
+ if (inHead) {
1072
+ const align2 = columnAlign(column, column?.type ?? "text");
1073
+ return { lead: align2 !== "start" ? [["data-align", align2]] : [], tail: column?.description ? [["title", column.description]] : [] };
1074
+ }
1075
+ const type = cellTypeOf(column, row, cell);
1076
+ const words = portableTextWords(cell.content);
1077
+ const v = typeof cell.value === "number" || typeof cell.value === "boolean" ? cell.value : words;
1078
+ const tail = isColumnType(cell.type) ? [["data-type", cell.type]] : [];
1079
+ let inner;
1080
+ if (v === "") {
1081
+ inner = cell.header === true ? "" : '<span aria-hidden="true" class="bt-dash">\u2014</span><span class="bt-sr">No value</span>';
1082
+ tail.push(["data-empty", ""]);
1083
+ } else if (type === "boolean") {
1084
+ const b = toBool(v);
1085
+ if (b == null) inner = escHtml(words);
1086
+ else {
1087
+ const { yes, no } = boolLabels(column);
1088
+ inner = `<span aria-hidden="true" class="bt-bool" data-bool="${b}">${b ? "\u2713" : "\u2013"}</span><span class="bt-sr">${escHtml(b ? yes : no)}</span>`;
1089
+ tail.push(["data-value", String(b)]);
1090
+ }
1091
+ } else if (type === "rating") {
1092
+ const n = toNumber(v);
1093
+ if (n == null) inner = escHtml(words);
1094
+ else {
1095
+ inner = `<span aria-hidden="true" class="bt-star">\u2605</span> ${escHtml(formatNumber(n, 1).replace(/\.0$/, ""))}<span class="bt-dim"> / ${column?.max ?? 5}</span>`;
1096
+ tail.push(["data-value", String(n)]);
1097
+ }
1098
+ } else if (type === "number" || type === "money") {
1099
+ const n = toNumber(v);
1100
+ inner = escHtml(typedCellText(column, type, v));
1101
+ if (n != null) tail.push(["data-value", String(n)]);
1102
+ }
1103
+ const align = columnAlign(column, type);
1104
+ if (align !== "start") tail.push(["data-align", align]);
1105
+ return { lead: [], tail, ...inner === void 0 ? {} : { inner } };
1106
+ }
1107
+ function portableTextWords(content) {
1108
+ return (Array.isArray(content) ? content : []).map((b) => isRecord(b) && Array.isArray(b.children) ? b.children.map((c) => isRecord(c) && typeof c.text === "string" ? c.text : "").join("") : "").join(" ").replace(/\s+/g, " ").trim();
1109
+ }
967
1110
 
968
1111
  // ../richtext/src/key-takeaways.ts
969
1112
  var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
@@ -1047,9 +1190,12 @@ var tableLines = (table) => {
1047
1190
  layout.columns.map((r) => one(r.label)).filter(Boolean).join(" "),
1048
1191
  ...head.map(rowLine),
1049
1192
  ...layout.rows.flatMap((run) => [...run.label === void 0 ? [] : [one(run.label)], ...run.rows.map(rowLine)]),
1050
- one(table.notes)
1193
+ one(table.notes),
1194
+ // footnotes, numbered as the html numbers them (V2 tableSearchText reads them too)
1195
+ ...(Array.isArray(table.footnotes) ? table.footnotes : []).map((f, i) => one(`[${i + 1}] ${typeof f?.text === "string" ? f.text : ""}`))
1051
1196
  ];
1052
- const extras = /* @__PURE__ */ new Set([0, 1, lines.length - 1]);
1197
+ const fnCount = Array.isArray(table.footnotes) ? table.footnotes.length : 0;
1198
+ const extras = /* @__PURE__ */ new Set([0, 1, lines.length - 1 - fnCount]);
1053
1199
  return lines.filter((l, i) => l !== "" || !extras.has(i));
1054
1200
  };
1055
1201
  var pullQuoteLines = (v) => {
@@ -1134,6 +1280,13 @@ function Table({ value }) {
1134
1280
  at += w;
1135
1281
  }
1136
1282
  }
1283
+ const columns = tableColumns(value);
1284
+ const typed = isTypedTable(value);
1285
+ const headSet = new Set(tableSections(value).head);
1286
+ const props = (list) => Object.fromEntries(list);
1287
+ const fnNumber = footnoteNumbers(value);
1288
+ const footnotes = footnotesOf(value.footnotes) ?? [];
1289
+ const tableId = typeof value._key === "string" && value._key ? value._key : "tbl";
1137
1290
  const cells = (row) => {
1138
1291
  let col = 0;
1139
1292
  return (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {
@@ -1142,19 +1295,27 @@ function Table({ value }) {
1142
1295
  const at = col;
1143
1296
  col += span2 ?? 1;
1144
1297
  const rowSpan = typeof cell?.rowSpan === "number" && cell.rowSpan > 1 ? cell.rowSpan : void 0;
1298
+ const n = typeof cell?.note === "string" ? fnNumber.get(cell.note) : void 0;
1145
1299
  const content = Array.isArray(cell?.content) ? cell.content : [];
1146
1300
  const lone = content.length === 1 && content[0]._type === "block" && content[0].style === "normal" && !content[0].listItem;
1147
- return /* @__PURE__ */ jsx14(Tag, { ...Tag === "td" ? role("cell") : {}, ...span2 ? { colSpan: span2 } : {}, ...rowSpan ? { rowSpan } : {}, ...stacked && Tag === "td" ? { "data-label": labels[at] || `Column ${at + 1}` } : {}, ...marked.has(at) ? { "data-highlight": "true" } : {}, children: /* @__PURE__ */ jsx14(PT, { value: content, components: lone ? inline : components }) }, cell?._key);
1301
+ const view = typed ? typedCellView(columns[at], row, cell ?? {}, headSet.has(row)) : void 0;
1302
+ const noteAttr = n ? [["data-note", cell.note]] : [];
1303
+ const tail = [...(view?.tail ?? []).filter(([k]) => k !== "data-align"), ...noteAttr, ...(view?.tail ?? []).filter(([k]) => k === "data-align")];
1304
+ const supHtml = n ? `<sup><a href="#${tableId}-fn-${n}" aria-label="Note ${n}">${n}</a></sup>` : "";
1305
+ return /* @__PURE__ */ jsx14(Tag, { ...Tag === "td" ? role("cell") : {}, ...span2 ? { colSpan: span2 } : {}, ...rowSpan ? { rowSpan } : {}, ...props(view?.lead ?? []), ...stacked && Tag === "td" ? { "data-label": labels[at] || `Column ${at + 1}` } : {}, ...marked.has(at) ? { "data-highlight": "true" } : {}, ...props(tail), ...view?.inner !== void 0 ? { dangerouslySetInnerHTML: { __html: view.inner + supHtml } } : {}, children: view?.inner !== void 0 ? void 0 : /* @__PURE__ */ jsxs11(Fragment2, { children: [
1306
+ /* @__PURE__ */ jsx14(PT, { value: content, components: lone ? inline : components }),
1307
+ n ? /* @__PURE__ */ jsx14("sup", { children: /* @__PURE__ */ jsx14("a", { href: `#${tableId}-fn-${n}`, "aria-label": `Note ${n}`, children: n }) }) : null
1308
+ ] }) }, cell?._key);
1148
1309
  });
1149
1310
  };
1150
- const rowEl = (row) => /* @__PURE__ */ jsx14("tr", { ...role("row"), ...row?.highlight === true ? { "data-highlight": "true" } : {}, children: cells(row) }, row?._key);
1311
+ const rowEl = (row) => /* @__PURE__ */ jsx14("tr", { ...role("row"), ...typeof row?.type === "string" ? { "data-type": row.type } : {}, ...row?.highlight === true ? { "data-highlight": "true" } : {}, children: cells(row) }, row?._key);
1151
1312
  const sections = tableSections(value);
1152
1313
  const head = sections.head.length;
1153
1314
  const foot = sections.foot;
1154
1315
  const layout = tableGroupLayout(value);
1155
1316
  const caption = typeof value.caption === "string" ? value.caption.trim() : "";
1156
1317
  const notes = typeof value.notes === "string" ? value.notes.trim() : "";
1157
- const notesId = notes && typeof value._key === "string" && value._key ? `${value._key}-notes` : void 0;
1318
+ const notesId = (notes || footnotes.length) && typeof value._key === "string" && value._key ? `${value._key}-notes` : void 0;
1158
1319
  const span = (n) => n > 1 ? n : void 0;
1159
1320
  const groupRow = layout.columns.length ? /* @__PURE__ */ jsx14("tr", { ...role("row"), children: layout.columns.flatMap(
1160
1321
  (run, i) => run.label === void 0 ? Array.from({ length: run.width }, (_, k) => /* @__PURE__ */ jsx14("td", { ...role("cell") }, `${i}-${k}`)) : [/* @__PURE__ */ jsx14("th", { ...role("columnheader"), scope: "colgroup", colSpan: span(run.width), "data-group": run.key, children: run.label }, i)]
@@ -1163,11 +1324,26 @@ function Table({ value }) {
1163
1324
  run.label !== void 0 && /* @__PURE__ */ jsx14("tr", { ...role("row"), children: /* @__PURE__ */ jsx14("th", { ...role("rowheader"), scope: "rowgroup", colSpan: span(layout.width), children: run.label }) }),
1164
1325
  run.rows.map(rowEl)
1165
1326
  ] }, i)) : sections.body.length > 0 && /* @__PURE__ */ jsx14("tbody", { ...role("rowgroup"), children: sections.body.map(rowEl) });
1327
+ const colProps = (i) => {
1328
+ const c = columns[i];
1329
+ return {
1330
+ ...c ? { "data-type": c.type ?? "text" } : {},
1331
+ ...c?.unit ? { "data-unit": c.unit } : {},
1332
+ ...c?.currency ? { "data-currency": c.currency } : {},
1333
+ ...c?.decimals != null ? { "data-decimals": String(c.decimals) } : {},
1334
+ ...c?.align ? { "data-align": c.align } : {},
1335
+ ...sortable.has(i) ? { "data-sortable": "true" } : {},
1336
+ ...c?.trueLabel ? { "data-true": c.trueLabel } : {},
1337
+ ...c?.falseLabel ? { "data-false": c.falseLabel } : {},
1338
+ ...c?.max != null ? { "data-max": String(c.max) } : {},
1339
+ ...c?.description ? { "data-description": c.description } : {}
1340
+ };
1341
+ };
1166
1342
  const table = /* @__PURE__ */ jsxs11("table", { ...role("table"), ...value.legacy === true ? { className: "bcms-table" } : {}, ...notesId ? { "aria-describedby": notesId } : {}, children: [
1167
1343
  caption && /* @__PURE__ */ jsx14("caption", { children: caption }),
1168
- sortable.size ? (layout.columns.length ? layout.columns.map((run) => run.width) : [layout.width]).map((w, i, all) => {
1344
+ sortable.size || columns.some(Boolean) ? (layout.columns.length ? layout.columns.map((run) => run.width) : [layout.width]).map((w, i, all) => {
1169
1345
  const from = all.slice(0, i).reduce((n, x) => n + x, 0);
1170
- return /* @__PURE__ */ jsx14("colgroup", { children: Array.from({ length: w }, (_, k) => /* @__PURE__ */ jsx14("col", { ...sortable.has(from + k) ? { "data-sortable": "true" } : {} }, k)) }, i);
1346
+ return /* @__PURE__ */ jsx14("colgroup", { children: Array.from({ length: w }, (_, k) => /* @__PURE__ */ jsx14("col", { ...colProps(from + k) }, k)) }, i);
1171
1347
  }) : layout.columns.map((run, i) => /* @__PURE__ */ jsx14("colgroup", { span: span(run.width) }, i)),
1172
1348
  (groupRow || head > 0) && /* @__PURE__ */ jsxs11("thead", { ...role("rowgroup"), children: [
1173
1349
  groupRow,
@@ -1180,9 +1356,12 @@ function Table({ value }) {
1180
1356
  const ld = jsonLdOf(value.jsonLd);
1181
1357
  const ldAttrs = ld ? { "data-jsonld": ld.type, ...ld.axis ? { "data-jsonld-axis": ld.axis } : {}, ...ld.priceRow ? { "data-jsonld-price": ld.priceRow } : {} } : {};
1182
1358
  const designAttrs = design ? { ...design.responsive ? { "data-responsive": design.responsive } : {}, ...design.sticky ? { "data-sticky": "true" } : {} } : {};
1183
- return notes || filter || ld || design ? /* @__PURE__ */ jsxs11("figure", { "data-rt": "table", ...designAttrs, ...filter ? { "data-filter": "true" } : {}, ...ldAttrs, children: [
1359
+ return notes || filter || ld || design || footnotes.length ? /* @__PURE__ */ jsxs11("figure", { "data-rt": "table", ...designAttrs, ...filter ? { "data-filter": "true" } : {}, ...ldAttrs, children: [
1184
1360
  table,
1185
- notes ? /* @__PURE__ */ jsx14("p", { "data-notes": "", id: notesId, children: notes }) : null
1361
+ footnotes.length ? /* @__PURE__ */ jsxs11("div", { className: "bt-notes", id: `${tableId}-notes`, children: [
1362
+ notes ? /* @__PURE__ */ jsx14("p", { "data-notes": "", children: notes }) : null,
1363
+ /* @__PURE__ */ jsx14("ol", { "data-footnotes": "", children: footnotes.map((f, i) => /* @__PURE__ */ jsx14("li", { id: `${tableId}-fn-${i + 1}`, "data-key": f._key, children: f.text }, f._key)) })
1364
+ ] }) : notes ? /* @__PURE__ */ jsx14("p", { "data-notes": "", id: notesId, children: notes }) : null
1186
1365
  ] }) : table;
1187
1366
  }
1188
1367
  function buildComponents(renderBlock) {
@@ -1205,6 +1384,8 @@ function buildComponents(renderBlock) {
1205
1384
  [PT_TYPES.block]: ({ value }) => {
1206
1385
  const rendered = renderBlock?.(value);
1207
1386
  if (rendered !== void 0) return /* @__PURE__ */ jsx14(Fragment2, { children: rendered });
1387
+ const builtIn = calloutOrToggle(value);
1388
+ if (builtIn) return builtIn;
1208
1389
  const copy = detachedComponentCopy(value);
1209
1390
  if (copy) {
1210
1391
  return /* @__PURE__ */ jsx14("div", { className: "bcms-block bcms-block--component", "data-bcms-component-copy": copy.componentId, "data-bcms-instance": copy.id, children: /* @__PURE__ */ jsx14(BlocksRenderer, { blocks: copy.blocks }) });
@@ -1321,6 +1502,35 @@ function runsOf(blocks) {
1321
1502
  }
1322
1503
  return runs;
1323
1504
  }
1505
+ function paragraphs(text) {
1506
+ return text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean).map((p, i) => /* @__PURE__ */ jsx14("p", { children: p.split("\n").map((line, j) => j === 0 ? line : [/* @__PURE__ */ jsx14("br", {}, j), line]) }, i));
1507
+ }
1508
+ var CALLOUT_TONES = /* @__PURE__ */ new Set(["info", "tip", "warning"]);
1509
+ function calloutOrToggle(value) {
1510
+ const v = value ?? {};
1511
+ const f = v.fields && typeof v.fields === "object" ? v.fields : {};
1512
+ const text = (x) => typeof x === "string" ? x.trim() : "";
1513
+ if (v.schemaKey === "builtin:callout") {
1514
+ const tone = CALLOUT_TONES.has(text(f.tone)) ? text(f.tone) : "info";
1515
+ const title = text(f.title);
1516
+ const body = paragraphs(typeof f.message === "string" ? f.message : "");
1517
+ if (!title && !body.length) return /* @__PURE__ */ jsx14(Fragment2, {});
1518
+ return /* @__PURE__ */ jsxs11("div", { className: `bcms-callout bcms-callout--${tone}`, "data-rt": "callout", "data-callout": tone, role: "note", children: [
1519
+ title ? /* @__PURE__ */ jsx14("p", { className: "bcms-callout__title", "data-title": "", children: title }) : null,
1520
+ /* @__PURE__ */ jsx14("div", { className: "bcms-callout__body", "data-body": "", children: body })
1521
+ ] });
1522
+ }
1523
+ if (v.schemaKey === "builtin:toggle") {
1524
+ const title = text(f.title);
1525
+ const body = paragraphs(typeof f.body === "string" ? f.body : "");
1526
+ if (!title && !body.length) return /* @__PURE__ */ jsx14(Fragment2, {});
1527
+ return /* @__PURE__ */ jsxs11("details", { className: "bcms-toggle", "data-rt": "toggle", children: [
1528
+ /* @__PURE__ */ jsx14("summary", { className: "bcms-toggle__title", children: title }),
1529
+ /* @__PURE__ */ jsx14("div", { className: "bcms-toggle__body", "data-body": "", children: body })
1530
+ ] });
1531
+ }
1532
+ return null;
1533
+ }
1324
1534
  function PortableText({ value, bindingBase, renderBlock }) {
1325
1535
  const blocks = React6.useMemo(() => normalizePortableText(readValue(value)), [value]);
1326
1536
  const components = React6.useMemo(() => buildComponents(renderBlock), [renderBlock]);