@bettercms-ai/ui 0.20.1 → 0.23.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/renderer.js CHANGED
@@ -67,6 +67,24 @@ var PT_TYPES = {
67
67
  pullQuote: "pullQuote",
68
68
  /** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt="takeaways">`. */
69
69
  keyTakeaways: "keyTakeaways",
70
+ /**
71
+ * A third-party embed (X, CodePen, Figma, Maps, Spotify, Podcasts, audio, a link card) — see
72
+ * `PortableTextEmbed` and `embed.ts`. The V2 kit's `<figure data-rt="embed">`. Gated by the
73
+ * `embed` feature; YouTube and Vimeo links still resolve here too, through the video resolver.
74
+ */
75
+ embed: "embed",
76
+ /** A gallery of images, a grid or a carousel — see `PortableTextGallery`. The V2 kit's `<figure data-rt="gallery">`. */
77
+ gallery: "gallery",
78
+ /** A table of contents built from the headings — see `PortableTextTableOfContents`. The V2 kit's `<nav data-rt="toc">`. */
79
+ tableOfContents: "tableOfContents",
80
+ /** A footnote, an INLINE object in body text — see `PortableTextFootnote`. The V2 kit's `<sup data-rt="fn">`. */
81
+ footnote: "footnote",
82
+ /** Columns of text — see `PortableTextColumns`. The V2 kit's `<div data-rt="columns">`; the legacy `builtin:columns` reads the same way. */
83
+ columns: "columns",
84
+ /** An author bio — see `PortableTextAuthorBio`. The V2 kit's `<aside data-rt="author">`. */
85
+ authorBio: "authorBio",
86
+ /** Related posts — see `PortableTextRelatedPosts`. The V2 kit's `<aside data-rt="related">`. */
87
+ relatedPosts: "relatedPosts",
70
88
  /**
71
89
  * A table. Spelled `table`, unprefixed, because that is what every other Portable Text
72
90
  * producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
@@ -93,7 +111,13 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
93
111
  PT_TYPES.video,
94
112
  PT_TYPES.file,
95
113
  PT_TYPES.pullQuote,
96
- PT_TYPES.keyTakeaways
114
+ PT_TYPES.keyTakeaways,
115
+ PT_TYPES.embed,
116
+ PT_TYPES.gallery,
117
+ PT_TYPES.tableOfContents,
118
+ PT_TYPES.columns,
119
+ PT_TYPES.authorBio,
120
+ PT_TYPES.relatedPosts
97
121
  ]);
98
122
  var EMBEDDED_COMPONENT_KEY = "component";
99
123
 
@@ -568,16 +592,109 @@ function keyTakeawaysView(value) {
568
592
  return { title, points };
569
593
  }
570
594
 
571
- // ../richtext/src/component-copy.ts
595
+ // ../richtext/src/image-layout.ts
596
+ import {
597
+ focalInCrop,
598
+ formatImageRect,
599
+ imageValueUrl,
600
+ normalizeImageCrop,
601
+ normalizeImageFocal,
602
+ objectPosition,
603
+ parseImageRect
604
+ } from "@bettercms-ai/image-url";
605
+ function richTextImageFrame(src, crop, focal) {
606
+ const c = normalizeImageCrop(crop);
607
+ const f = normalizeImageFocal(focal);
608
+ if (!c && !f) return { src, crop: null, focal: null, position: null, data: [] };
609
+ const drawn = imageValueUrl({ url: src, crop: c, focal: f }) ?? src;
610
+ const data = [];
611
+ if (drawn !== src) data.push(["data-src", src]);
612
+ if (c) data.push(["data-crop", formatImageRect(c)]);
613
+ if (f) data.push(["data-focal", `${f.x},${f.y}`]);
614
+ return { src: drawn, crop: c, focal: f, position: f ? objectPosition(focalInCrop(f, c)) : null, data };
615
+ }
616
+
617
+ // ../richtext/src/gallery.ts
572
618
  var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
573
- var fieldsOf = (value) => isRecord3(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord3(value.fields) ? value.fields : null;
619
+ var galleryMediaSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
620
+ var GALLERY_STYLES = {
621
+ grid: {
622
+ ul: "display:grid;grid-template-columns:repeat(auto-fill,minmax(12rem,1fr));gap:1rem;list-style:none;margin:0;padding:0",
623
+ li: "margin:0",
624
+ figure: "margin:0",
625
+ img: "display:block;width:100%;height:auto"
626
+ },
627
+ carousel: {
628
+ ul: "display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;list-style:none;margin:0;padding:0",
629
+ li: "flex:0 0 100%;scroll-snap-align:start;margin:0",
630
+ figure: "margin:0",
631
+ img: "display:block;width:100%;height:auto"
632
+ }
633
+ };
634
+ function galleryView(value) {
635
+ const v = isRecord3(value) ? value : {};
636
+ const layout = v.layout === "carousel" ? "carousel" : "grid";
637
+ const images = [];
638
+ (Array.isArray(v.images) ? v.images : []).forEach((item, i) => {
639
+ if (!isRecord3(item) || !isRecord3(item.image)) return;
640
+ const src = galleryMediaSrc(typeof item.image.src === "string" ? item.image.src.trim() : "");
641
+ if (!src) return;
642
+ const frame = richTextImageFrame(src, item.image.crop, item.image.focal);
643
+ images.push({
644
+ key: typeof item._key === "string" && item._key ? item._key : `i${i}`,
645
+ src: frame.src,
646
+ position: frame.position,
647
+ data: frame.data,
648
+ alt: typeof item.image.alt === "string" ? item.image.alt : "",
649
+ caption: typeof item.caption === "string" ? item.caption.trim() : ""
650
+ });
651
+ });
652
+ return { layout, images };
653
+ }
654
+
655
+ // ../richtext/src/columns.ts
656
+ var PT_COLUMNS_LIMITS = { min: 2, max: 4 };
657
+ var BUILTIN_COLUMNS_KEY = "builtin:columns";
658
+ var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
659
+ var COLUMNS_STYLES = {
660
+ s: { row: "display:flex;flex-wrap:wrap;gap:0.75rem", col: "flex:1 1 14rem;min-width:0" },
661
+ m: { row: "display:flex;flex-wrap:wrap;gap:1.25rem", col: "flex:1 1 14rem;min-width:0" },
662
+ l: { row: "display:flex;flex-wrap:wrap;gap:2rem", col: "flex:1 1 14rem;min-width:0" }
663
+ };
664
+ var legacyContent = (text, key) => text.split(/\n{2,}/).filter((p) => p.trim() !== "").map((p, i) => ({ _type: "block", _key: `${key}p${i}`, style: "normal", markDefs: [], children: [{ _type: "span", _key: `${key}s${i}`, text: p, marks: [] }] }));
665
+ function columnsView(node) {
666
+ if (!isRecord4(node)) return null;
667
+ if (node._type === PT_TYPES.columns) {
668
+ const gap = node.gap === "s" || node.gap === "l" ? node.gap : "m";
669
+ const columns = (Array.isArray(node.columns) ? node.columns : []).map((c, i) => ({
670
+ key: isRecord4(c) && typeof c._key === "string" && c._key ? c._key : `c${i}`,
671
+ content: isRecord4(c) && Array.isArray(c.content) ? c.content : []
672
+ }));
673
+ return { gap, columns };
674
+ }
675
+ if (node._type === PT_TYPES.block && node.schemaKey === BUILTIN_COLUMNS_KEY) {
676
+ const f = isRecord4(node.fields) ? node.fields : {};
677
+ if (!Array.isArray(f.columns) || !f.columns.every((c) => typeof c === "string")) return null;
678
+ const gap = f.gap === "small" ? "s" : f.gap === "large" ? "l" : "m";
679
+ const columns = (Array.isArray(f.columns) ? f.columns : []).slice(0, PT_COLUMNS_LIMITS.max).map((c, i) => ({
680
+ key: `c${i}`,
681
+ content: legacyContent(typeof c === "string" ? c : "", `c${i}`)
682
+ }));
683
+ return { gap, columns };
684
+ }
685
+ return null;
686
+ }
687
+
688
+ // ../richtext/src/component-copy.ts
689
+ var isRecord5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
690
+ var fieldsOf = (value) => isRecord5(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord5(value.fields) ? value.fields : null;
574
691
  function detachedComponentCopy(value) {
575
692
  const f = fieldsOf(value);
576
693
  if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
577
694
  return {
578
- id: isRecord3(value) && typeof value._key === "string" ? value._key : "",
695
+ id: isRecord5(value) && typeof value._key === "string" ? value._key : "",
579
696
  componentId: typeof f.componentId === "string" ? f.componentId : "",
580
- props: isRecord3(f.props) ? f.props : {},
697
+ props: isRecord5(f.props) ? f.props : {},
581
698
  blocks: f.blocks,
582
699
  ...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
583
700
  ...typeof f.variant === "string" ? { variant: f.variant } : {}
@@ -590,7 +707,7 @@ function componentCopyLines(copy) {
590
707
  const t = v.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
591
708
  return t || null;
592
709
  }
593
- if (isRecord3(v)) return line(v.label ?? v.text ?? v.title);
710
+ if (isRecord5(v)) return line(v.label ?? v.text ?? v.title);
594
711
  return null;
595
712
  };
596
713
  return Object.values(copy.props).map(line).filter((s) => !!s);
@@ -617,6 +734,18 @@ function portableTextToPlainText(value) {
617
734
  lines.push(...title ? [title] : [], ...points);
618
735
  continue;
619
736
  }
737
+ if (node._type === PT_TYPES.gallery) {
738
+ lines.push(...galleryView(node).images.map((x) => x.caption).filter(Boolean));
739
+ continue;
740
+ }
741
+ const cols = columnsView(node);
742
+ if (cols) {
743
+ for (const c of cols.columns) {
744
+ const text = portableTextToPlainText(c.content);
745
+ if (text) lines.push(text);
746
+ }
747
+ continue;
748
+ }
620
749
  const table = migrateTable(node);
621
750
  if (table) {
622
751
  lines.push(...tableLines(table));
@@ -655,20 +784,121 @@ var pullQuoteLines = (v) => {
655
784
  return [text(v.quote), ...by ? [`\u2014 ${by}`] : []].filter(Boolean);
656
785
  };
657
786
 
787
+ // ../richtext/src/toc.ts
788
+ import { slugify } from "@bettercms-ai/types";
789
+ var isRecord6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
790
+ var isHeadingAnchor = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_:.-]{0,127}$/.test(v);
791
+ var headingText = (node) => (Array.isArray(node.children) ? node.children : []).map((c) => isRecord6(c) && typeof c.text === "string" ? c.text : "").join("").replace(/​/g, "").trim();
792
+ function outlineOf(value) {
793
+ const heads = [];
794
+ const used = /* @__PURE__ */ new Set();
795
+ for (const node of Array.isArray(value) ? value : []) {
796
+ if (!isRecord6(node) || node._type !== "block" || node.listItem || node.style !== "h2" && node.style !== "h3") continue;
797
+ const text = headingText(node);
798
+ if (!text) continue;
799
+ const anchor = isHeadingAnchor(node.anchor) && !used.has(node.anchor) ? node.anchor : null;
800
+ if (anchor) used.add(anchor);
801
+ heads.push({ node, text, anchor });
802
+ }
803
+ const items = [];
804
+ const nodes = /* @__PURE__ */ new WeakMap();
805
+ for (const { node, text, anchor } of heads) {
806
+ let id = anchor;
807
+ if (!id) {
808
+ const base = slugify(text);
809
+ id = base;
810
+ for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
811
+ used.add(id);
812
+ }
813
+ nodes.set(node, id);
814
+ items.push({ id, text, depth: node.style === "h2" ? 2 : 3 });
815
+ }
816
+ return { items, nodes };
817
+ }
818
+ function tocTree(items, depth) {
819
+ const out = [];
820
+ for (const it of items) {
821
+ if (it.depth === 3 && depth === "2") continue;
822
+ const last = out[out.length - 1];
823
+ if (it.depth === 3 && last) last.children.push(it);
824
+ else out.push({ ...it, children: [] });
825
+ }
826
+ return out;
827
+ }
828
+ function tocView(value) {
829
+ const v = isRecord6(value) ? value : {};
830
+ const title = typeof v.title === "string" ? v.title.trim() : "";
831
+ return { title, label: title || "Table of contents", depth: v.depth === "2" ? "2" : "3" };
832
+ }
833
+
834
+ // ../richtext/src/footnote.ts
835
+ var isRecord7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
836
+ function footnoteOrder(value) {
837
+ const notes = [];
838
+ const nodes = /* @__PURE__ */ new WeakMap();
839
+ for (const node of Array.isArray(value) ? value : []) {
840
+ if (!isRecord7(node) || node._type !== "block" || !Array.isArray(node.children)) continue;
841
+ for (const child of node.children) {
842
+ if (!isRecord7(child) || child._type !== PT_TYPES.footnote) continue;
843
+ nodes.set(child, notes.push(typeof child.note === "string" ? child.note : ""));
844
+ }
845
+ }
846
+ return { notes, nodes };
847
+ }
848
+
849
+ // ../richtext/src/references.ts
850
+ var PT_RELATED_LIMITS = { titleChars: 40, minCount: 1, maxCount: 6, posts: 6 };
851
+ var isRecord8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
852
+ var refOf = (v) => isRecord8(v) && typeof v._ref === "string" ? v._ref : "";
853
+ var imageSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
854
+ function relatedPostsView(value, resolved = {}) {
855
+ const v = isRecord8(value) ? value : {};
856
+ const title = typeof v.title === "string" ? v.title.trim() : "";
857
+ const mode = v.mode === "manual" || v.mode === "newest" ? v.mode : "topic";
858
+ const n = typeof v.count === "number" && Number.isInteger(v.count) ? v.count : 3;
859
+ const count = Math.min(PT_RELATED_LIMITS.maxCount, Math.max(PT_RELATED_LIMITS.minCount, n));
860
+ const picked = (Array.isArray(v.posts) ? v.posts : []).map(refOf).filter(Boolean);
861
+ const key = typeof v._key === "string" ? v._key : "";
862
+ const ids = mode === "manual" ? picked : (resolved.related?.[key] ?? []).slice(0, count);
863
+ const items = [];
864
+ for (const id of ids) {
865
+ const post = resolved.posts?.[id];
866
+ const url = post && typeof post.url === "string" ? safeHref(post.url) : null;
867
+ if (post && typeof post.title === "string" && url) items.push({ ...post, id, url });
868
+ }
869
+ return { key, title, mode, count, picked, items };
870
+ }
871
+ function authorBioView(value, resolved = {}) {
872
+ const key = isRecord8(value) && typeof value._key === "string" ? value._key : "";
873
+ const id = refOf(isRecord8(value) ? value.author : void 0);
874
+ const a = id ? resolved.authors?.[id] : void 0;
875
+ if (!a || typeof a.name !== "string") return { key, id, author: null };
876
+ const avatar = typeof a.avatar === "string" ? imageSrc(a.avatar.trim()) : null;
877
+ return {
878
+ key,
879
+ id,
880
+ author: {
881
+ name: a.name,
882
+ ...avatar ? { avatar } : {},
883
+ ...typeof a.bio === "string" && a.bio.trim() ? { bio: a.bio.trim() } : {}
884
+ }
885
+ };
886
+ }
887
+
658
888
  // ../richtext/src/read.ts
659
- var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
889
+ var isRecord9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
660
890
  function normalizePortableText(value, key = randomKey) {
661
891
  if (!Array.isArray(value)) return [];
662
892
  return value.flatMap((raw) => {
663
- if (!isRecord4(raw) || typeof raw._type !== "string") return [];
893
+ if (!isRecord9(raw) || typeof raw._type !== "string") return [];
664
894
  const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
665
895
  if (table) return [table];
666
- const node = { ...raw, _key: isRecord4(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
896
+ const node = { ...raw, _key: isRecord9(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
667
897
  if (node._type === "block") {
668
898
  if (!Array.isArray(node.markDefs)) node.markDefs = [];
669
899
  if (typeof node.style !== "string") node.style = "normal";
670
900
  node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
671
- if (!isRecord4(c)) return c;
901
+ if (!isRecord9(c)) return c;
672
902
  const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
673
903
  if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
674
904
  return child;
@@ -678,12 +908,219 @@ function normalizePortableText(value, key = randomKey) {
678
908
  });
679
909
  }
680
910
  function readStructuredContent(props) {
681
- if (!isRecord4(props)) return null;
911
+ if (!isRecord9(props)) return null;
682
912
  const candidate = props.content ?? props.value;
683
- const arr = Array.isArray(candidate) ? candidate : isRecord4(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
913
+ const arr = Array.isArray(candidate) ? candidate : isRecord9(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
684
914
  return arr && arr.length > 0 ? arr : null;
685
915
  }
686
916
 
917
+ // ../richtext/src/video.ts
918
+ var videoId = (value) => value && /^[a-zA-Z0-9_-]{6,32}$/.test(value) ? value : null;
919
+ function resolveVideoSource(value) {
920
+ if (typeof value !== "string" || !value.trim()) return null;
921
+ let parsed;
922
+ try {
923
+ parsed = new URL(value.trim());
924
+ } catch {
925
+ return null;
926
+ }
927
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
928
+ const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
929
+ let id = null;
930
+ if (host === "youtu.be") id = videoId(parsed.pathname.split("/").filter(Boolean)[0]);
931
+ if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") {
932
+ id = videoId(
933
+ parsed.searchParams.get("v") ?? parsed.pathname.match(/^\/(?:embed|shorts)\/([^/?#]+)/)?.[1]
934
+ );
935
+ }
936
+ if (id) {
937
+ return {
938
+ kind: "iframe",
939
+ src: `https://www.youtube-nocookie.com/embed/${id}`,
940
+ provider: "YouTube"
941
+ };
942
+ }
943
+ if (host === "vimeo.com" || host === "player.vimeo.com") {
944
+ const segments = parsed.pathname.split("/").filter(Boolean);
945
+ let vimeoId = null;
946
+ for (let i = segments.length - 1; i >= 0; i -= 1) {
947
+ if (/^\d+$/.test(segments[i])) {
948
+ vimeoId = segments[i];
949
+ break;
950
+ }
951
+ }
952
+ if (vimeoId) {
953
+ return {
954
+ kind: "iframe",
955
+ src: `https://player.vimeo.com/video/${vimeoId}`,
956
+ provider: "Vimeo"
957
+ };
958
+ }
959
+ }
960
+ if (/\.(?:m4v|mp4|og[gv]|webm)$/i.test(parsed.pathname)) {
961
+ return { kind: "video", src: parsed.toString(), provider: "Video file" };
962
+ }
963
+ return null;
964
+ }
965
+
966
+ // ../richtext/src/embed.ts
967
+ var EMBED_ASPECTS = ["16:9", "4:3", "1:1", "9:16"];
968
+ var isEmbedAspect = (v) => EMBED_ASPECTS.includes(v);
969
+ function detectEmbed(url) {
970
+ const u = url.trim();
971
+ let host = "";
972
+ try {
973
+ host = new URL(u).hostname.replace(/^www\./, "");
974
+ } catch {
975
+ host = "";
976
+ }
977
+ const is = (re) => re.test(u);
978
+ if (is(/(?:youtube\.com\/(?:watch\?|embed\/|shorts\/|live\/)|youtu\.be\/)/i)) return { provider: "youtube", label: "YouTube", host };
979
+ if (is(/vimeo\.com\/(?:video\/)?\d+/i)) return { provider: "vimeo", label: "Vimeo", host };
980
+ if (is(/(?:twitter|x)\.com\/[^/]+\/status\/\d+/i)) return { provider: "x", label: "X post", host };
981
+ if (is(/codepen\.io\/[^/]+\/(?:pen|embed)\//i)) return { provider: "codepen", label: "CodePen", host };
982
+ if (is(/figma\.com\/(?:file|design|proto|board)\//i)) return { provider: "figma", label: "Figma", host };
983
+ if (is(/(?:google\.[a-z.]+\/maps|maps\.app\.goo\.gl|goo\.gl\/maps)/i)) return { provider: "maps", label: "Google Maps", host };
984
+ if (is(/open\.spotify\.com\/(?:episode|track|show|playlist|album)\//i)) return { provider: "spotify", label: "Spotify", host };
985
+ if (is(/podcasts\.apple\.com\//i)) return { provider: "podcast", label: "Apple Podcasts", host };
986
+ if (is(/\.(?:mp3|m4a|wav|ogg|aac)(?:\?|$)/i)) return { provider: "audio", label: "Audio", host };
987
+ return { provider: "link", label: host || "Link", host };
988
+ }
989
+ function detectMore(url) {
990
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
991
+ const path = url.pathname;
992
+ if (host === "youtube-nocookie.com" && path.startsWith("/embed/")) return { provider: "youtube", label: "YouTube" };
993
+ if (host === "open.spotify.com" && /^\/embed\/(?:episode|track|show|playlist|album)\//.test(path)) return { provider: "spotify", label: "Spotify" };
994
+ if (host === "calendly.com") return { provider: "calendly", label: "Calendly" };
995
+ if (host.endsWith(".typeform.com") && /^\/to\//.test(path)) return { provider: "typeform", label: "Typeform" };
996
+ if (host === "loom.com" && /^\/(?:share|embed)\//.test(path)) return { provider: "loom", label: "Loom" };
997
+ if (host === "airtable.com") return { provider: "airtable", label: "Airtable" };
998
+ if (host === "docs.google.com") {
999
+ const kind = path.split("/")[1];
1000
+ const label = { forms: "Google Forms", document: "Google Docs", spreadsheets: "Google Sheets", presentation: "Google Slides" }[kind ?? ""];
1001
+ if (label) return { provider: "google", label };
1002
+ }
1003
+ return null;
1004
+ }
1005
+ var FRAME_SIZE = {
1006
+ "16:9": ["640", "360"],
1007
+ "4:3": ["640", "480"],
1008
+ "1:1": ["640", "640"],
1009
+ "9:16": ["360", "640"]
1010
+ };
1011
+ var ID = /^[A-Za-z0-9_-]{1,64}$/;
1012
+ var FULL_WIDTH_HEIGHT = { calendly: "700", typeform: "500", airtable: "533" };
1013
+ function frameOf(provider, url) {
1014
+ const parts = url.pathname.split("/").filter(Boolean);
1015
+ switch (provider) {
1016
+ case "youtube":
1017
+ case "vimeo": {
1018
+ const video = resolveVideoSource(url.href);
1019
+ return video?.kind === "iframe" ? { src: video.src } : null;
1020
+ }
1021
+ case "codepen": {
1022
+ const [user, mode, id] = parts;
1023
+ return user && ID.test(user) && (mode === "pen" || mode === "embed") && id && ID.test(id) ? { src: `https://codepen.io/${user}/embed/${id}?default-tab=result` } : null;
1024
+ }
1025
+ case "figma": {
1026
+ const [mode, key] = parts;
1027
+ if (!mode || !["file", "design", "proto", "board"].includes(mode) || !key || !ID.test(key)) return null;
1028
+ const frame = new URL(`https://embed.figma.com/${mode}/${key}`);
1029
+ const node = url.searchParams.get("node-id");
1030
+ if (node && /^[0-9A-Za-z:_-]{1,64}$/.test(node)) frame.searchParams.set("node-id", node);
1031
+ frame.searchParams.set("embed-host", "bettercms");
1032
+ return { src: frame.href };
1033
+ }
1034
+ case "maps": {
1035
+ if (!/^(?:www\.)?google\.[a-z.]+$/i.test(url.hostname)) return null;
1036
+ const pb = url.searchParams.get("pb");
1037
+ if (parts[0] === "maps" && parts[1] === "embed" && pb && /^[!A-Za-z0-9._:%+-]{1,2000}$/.test(pb)) {
1038
+ return { src: `https://www.google.com/maps/embed?pb=${encodeURIComponent(pb)}` };
1039
+ }
1040
+ const at = /@(-?\d{1,3}(?:\.\d+)?),(-?\d{1,3}(?:\.\d+)?)/.exec(url.pathname);
1041
+ const place = parts[0] === "maps" && parts[1] === "place" && parts[2] ? decodeURIComponent(parts[2]).replace(/\+/g, " ") : null;
1042
+ const q = at ? `${at[1]},${at[2]}` : url.searchParams.get("q") ?? place;
1043
+ if (!q || q.length > 200) return null;
1044
+ return { src: `https://maps.google.com/maps?q=${encodeURIComponent(q)}&output=embed` };
1045
+ }
1046
+ case "spotify": {
1047
+ const [type, id] = parts[0] === "embed" ? parts.slice(1) : parts;
1048
+ if (!type || !["episode", "track", "show", "playlist", "album"].includes(type) || !id || !ID.test(id)) return null;
1049
+ return { src: `https://open.spotify.com/embed/${type}/${id}`, height: type === "track" || type === "episode" ? "152" : "352" };
1050
+ }
1051
+ case "podcast":
1052
+ return (url.hostname === "podcasts.apple.com" || url.hostname === "embed.podcasts.apple.com") && parts.length >= 2 ? { src: `https://embed.podcasts.apple.com${url.pathname}${url.search}`, height: url.searchParams.has("i") ? "175" : "450" } : null;
1053
+ case "calendly":
1054
+ return parts.length >= 1 && parts.length <= 2 && parts.every((p) => ID.test(p)) ? { src: `https://calendly.com/${parts.join("/")}?embed_type=Inline&hide_gdpr_banner=1` } : null;
1055
+ case "typeform": {
1056
+ const id = parts[0] === "to" ? parts[1] : void 0;
1057
+ return id && /^[A-Za-z0-9]{4,32}$/.test(id) ? { src: `https://form.typeform.com/to/${id}` } : null;
1058
+ }
1059
+ case "loom": {
1060
+ const id = parts[1];
1061
+ return (parts[0] === "share" || parts[0] === "embed") && id && /^[0-9a-f]{32}$/.test(id) ? { src: `https://www.loom.com/embed/${id}` } : null;
1062
+ }
1063
+ case "airtable": {
1064
+ const ids = parts[0] === "embed" ? parts.slice(1) : parts;
1065
+ return ids.length >= 1 && ids.length <= 3 && ids.every((p) => /^(?:app|shr|tbl|viw)[A-Za-z0-9]{14}$/.test(p)) && ids.some((p) => p.startsWith("shr")) ? { src: `https://airtable.com/embed/${ids.join("/")}` } : null;
1066
+ }
1067
+ case "google": {
1068
+ const [kind, d, maybeE, maybeId] = parts;
1069
+ if (d !== "d") return null;
1070
+ const published = maybeE === "e";
1071
+ const id = published ? maybeId : maybeE;
1072
+ if (!id || !/^[A-Za-z0-9_-]{10,128}$/.test(id)) return null;
1073
+ const base = `https://docs.google.com/${kind}/d/${published ? `e/${id}` : id}`;
1074
+ if (kind === "forms") return { src: `${base}/viewform?embedded=true`, height: "800" };
1075
+ if (kind === "document") return { src: published ? `${base}/pub?embedded=true` : `${base}/preview`, height: "600" };
1076
+ if (kind === "spreadsheets") return { src: published ? `${base}/pubhtml?widget=true&headers=false` : `${base}/preview`, height: "600" };
1077
+ if (kind === "presentation") return { src: published ? `${base}/embed` : `${base}/preview` };
1078
+ return null;
1079
+ }
1080
+ default:
1081
+ return null;
1082
+ }
1083
+ }
1084
+ var X_STATUS = /^\/([A-Za-z0-9_]{1,15})\/status\/(\d{1,25})(?:\/|$)/;
1085
+ var X_HOSTS = /^(?:www\.|mobile\.)?(?:twitter|x)\.com$/i;
1086
+ function canonicalEmbedUrl(raw) {
1087
+ const url = typeof raw === "string" ? raw.trim() : "";
1088
+ const safe = url ? safeHref(url) : null;
1089
+ if (!safe || !/^https?:\/\//i.test(safe)) return null;
1090
+ let parsed;
1091
+ try {
1092
+ parsed = new URL(safe);
1093
+ } catch {
1094
+ return null;
1095
+ }
1096
+ const status = X_HOSTS.test(parsed.hostname) ? X_STATUS.exec(parsed.pathname) : null;
1097
+ if (status) return `https://x.com/${status[1].toLowerCase()}/status/${status[2]}`;
1098
+ parsed.hash = "";
1099
+ return parsed.href;
1100
+ }
1101
+ function resolveEmbed(rawUrl, aspect = "16:9") {
1102
+ const url = typeof rawUrl === "string" ? rawUrl.trim() : "";
1103
+ const v2 = detectEmbed(url);
1104
+ const safe = url ? safeHref(url) : null;
1105
+ let parsed = null;
1106
+ try {
1107
+ parsed = safe && /^https?:\/\//i.test(safe) ? new URL(safe) : null;
1108
+ } catch {
1109
+ parsed = null;
1110
+ }
1111
+ const { provider, label } = v2.provider === "link" && parsed ? detectMore(parsed) ?? v2 : v2;
1112
+ const link = { kind: "link", provider, label, url: safe };
1113
+ if (!parsed) return link;
1114
+ if (provider === "x") return { kind: "card", provider: "x", label, url: canonicalEmbedUrl(parsed.href) ?? parsed.href };
1115
+ if (provider === "audio") return parsed.protocol === "https:" ? { kind: "audio", provider: "audio", label, src: parsed.href } : link;
1116
+ const frame = frameOf(provider, parsed);
1117
+ if (!frame) return link;
1118
+ const height = frame.height ?? FULL_WIDTH_HEIGHT[provider];
1119
+ if (height) return { kind: "frame", provider, label, src: frame.src, width: "100%", height };
1120
+ const [width, h] = FRAME_SIZE[isEmbedAspect(aspect) ? aspect : "16:9"];
1121
+ return { kind: "frame", provider, label, src: frame.src, width, height: h };
1122
+ }
1123
+
687
1124
  // src/blocks/portableText.tsx
688
1125
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
689
1126
  var TOKEN = /^[a-z0-9-]+$/i;
@@ -815,8 +1252,119 @@ function Table({ value }) {
815
1252
  ] }) : notes ? /* @__PURE__ */ jsx2("p", { "data-notes": "", id: notesId, children: notes }) : null
816
1253
  ] }) : table;
817
1254
  }
818
- function buildComponents(renderBlock) {
819
- return {
1255
+ function CheckItem({ checked, address, children }) {
1256
+ return /* @__PURE__ */ jsxs("li", { ...address, "data-checked": String(checked), children: [
1257
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", disabled: true, defaultChecked: checked }),
1258
+ children
1259
+ ] });
1260
+ }
1261
+ function XCard({ url, card }) {
1262
+ return /* @__PURE__ */ jsxs("blockquote", { className: "twitter-tweet bcms-embed-card", cite: url, children: [
1263
+ /* @__PURE__ */ jsx2("p", { children: card.text }),
1264
+ "\u2014 ",
1265
+ card.authorName,
1266
+ " ",
1267
+ /* @__PURE__ */ jsx2("a", { href: url, children: card.date || "View on X" })
1268
+ ] });
1269
+ }
1270
+ function RelatedPosts({ value, resolved }) {
1271
+ const { key, title, mode, count, picked, items } = relatedPostsView(value, resolved);
1272
+ if (!resolved) {
1273
+ return /* @__PURE__ */ jsx2("aside", { "data-rt": "related", "data-key": key, "data-mode": mode, "data-count": count, "data-posts": picked.join(","), "data-title": title || void 0 });
1274
+ }
1275
+ if (!items.length) return null;
1276
+ return /* @__PURE__ */ jsxs("aside", { "data-rt": "related", "data-mode": mode, "data-count": count, "data-posts": picked.join(","), children: [
1277
+ title ? /* @__PURE__ */ jsx2("p", { "data-title": "", children: title }) : null,
1278
+ items.length ? /* @__PURE__ */ jsx2("ul", { children: items.map((p) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2("a", { href: p.url, children: p.title }) }, p.id)) }) : null
1279
+ ] });
1280
+ }
1281
+ function AuthorBio({ value, resolved }) {
1282
+ const { key, id, author } = authorBioView(value, resolved);
1283
+ if (!resolved) return /* @__PURE__ */ jsx2("aside", { "data-rt": "author", "data-key": key, "data-author": id });
1284
+ if (!author) return null;
1285
+ return /* @__PURE__ */ jsxs("aside", { "data-rt": "author", "data-author": id, children: [
1286
+ author?.avatar ? /* @__PURE__ */ jsx2("img", { src: author.avatar, alt: "" }) : null,
1287
+ author ? /* @__PURE__ */ jsx2("p", { "data-name": "", children: author.name }) : null,
1288
+ author?.bio ? /* @__PURE__ */ jsx2("p", { "data-bio": "", children: author.bio }) : null
1289
+ ] });
1290
+ }
1291
+ function Columns({ value, components }) {
1292
+ const view = columnsView(value);
1293
+ if (!view) return null;
1294
+ const s = COLUMNS_STYLES[view.gap];
1295
+ return /* @__PURE__ */ jsx2("div", { "data-rt": "columns", "data-gap": view.gap, "data-cols": view.columns.length, style: styleObject(s.row), children: view.columns.map((c) => /* @__PURE__ */ jsx2("div", { "data-col": "true", style: styleObject(s.col), children: c.content.length ? /* @__PURE__ */ jsx2(PT, { value: c.content, components }) : null }, c.key)) });
1296
+ }
1297
+ function FootnoteMark({ value, footnotes }) {
1298
+ const note = typeof value.note === "string" ? value.note : "";
1299
+ const n = footnotes.nodes.get(value);
1300
+ if (!n) return /* @__PURE__ */ jsx2("sup", { "data-rt": "fn", "data-note": note });
1301
+ return /* @__PURE__ */ jsx2("sup", { "data-rt": "fn", id: `fnref-${n}`, "data-note": note, children: /* @__PURE__ */ jsx2("a", { href: `#fn-${n}`, "aria-label": `Footnote ${n}`, children: n }) });
1302
+ }
1303
+ function FootnoteList({ notes }) {
1304
+ if (!notes.length) return null;
1305
+ return /* @__PURE__ */ jsx2("ol", { className: "cs-footnotes", "data-rt": "footnotes", "aria-label": "Footnotes", children: notes.map((note, i) => /* @__PURE__ */ jsxs("li", { id: `fn-${i + 1}`, children: [
1306
+ note,
1307
+ " ",
1308
+ /* @__PURE__ */ jsx2("a", { href: `#fnref-${i + 1}`, "aria-label": `Back to reference ${i + 1}`, children: "\u21A9" })
1309
+ ] }, i)) });
1310
+ }
1311
+ function Toc({ value, outline }) {
1312
+ const { title, label, depth } = tocView(value);
1313
+ const link = (o) => /* @__PURE__ */ jsx2("a", { href: `#${o.id}`, children: o.text });
1314
+ return /* @__PURE__ */ jsxs("nav", { "data-rt": "toc", "data-depth": depth, "aria-label": label, children: [
1315
+ title ? /* @__PURE__ */ jsx2("p", { "data-title": "", children: title }) : null,
1316
+ /* @__PURE__ */ jsx2("ol", { children: tocTree(outline.items, depth).map((e) => /* @__PURE__ */ jsxs("li", { children: [
1317
+ link(e),
1318
+ e.children.length ? /* @__PURE__ */ jsx2("ol", { children: e.children.map((c) => /* @__PURE__ */ jsx2("li", { children: link(c) }, c.id)) }) : null
1319
+ ] }, e.id)) })
1320
+ ] });
1321
+ }
1322
+ function Gallery({ value }) {
1323
+ const { layout, images } = galleryView(value);
1324
+ const s = GALLERY_STYLES[layout];
1325
+ return /* @__PURE__ */ jsx2("figure", { "data-rt": "gallery", "data-layout": layout, children: /* @__PURE__ */ jsx2("ul", { style: styleObject(s.ul), tabIndex: layout === "carousel" ? 0 : void 0, children: images.map((x) => /* @__PURE__ */ jsx2("li", { style: styleObject(s.li), children: /* @__PURE__ */ jsxs("figure", { style: styleObject(s.figure), children: [
1326
+ /* @__PURE__ */ jsx2(
1327
+ "img",
1328
+ {
1329
+ alt: x.alt,
1330
+ style: styleObject(x.position ? `${s.img};object-position:${x.position}` : s.img),
1331
+ loading: "lazy",
1332
+ ...Object.fromEntries(x.data),
1333
+ src: x.src
1334
+ }
1335
+ ),
1336
+ x.caption ? /* @__PURE__ */ jsx2("figcaption", { children: x.caption }) : null
1337
+ ] }) }, x.key)) }) });
1338
+ }
1339
+ function Embed({ value, embeds }) {
1340
+ const v = value ?? {};
1341
+ const url = typeof v.url === "string" ? v.url.trim() : "";
1342
+ const aspect = isEmbedAspect(v.aspect) ? v.aspect : "16:9";
1343
+ const title = typeof v.title === "string" ? v.title.trim() : "";
1344
+ const r = resolveEmbed(url, aspect);
1345
+ const label = title || r.label;
1346
+ const card = r.kind === "card" ? embeds.cards?.[r.url] : void 0;
1347
+ let body = null;
1348
+ if (r.kind === "frame") {
1349
+ body = /* @__PURE__ */ jsx2("iframe", { src: r.src, title: label, width: r.width, height: r.height, loading: "lazy", referrerPolicy: "strict-origin-when-cross-origin", allowFullScreen: true });
1350
+ } else if (r.kind === "audio") {
1351
+ body = /* @__PURE__ */ jsx2("audio", { controls: true, preload: "none", src: r.src, title: label });
1352
+ } else if (r.kind === "card" && card) {
1353
+ body = /* @__PURE__ */ jsx2(XCard, { url: r.url, card });
1354
+ } else if (r.url) {
1355
+ body = /* @__PURE__ */ jsx2("a", { href: r.url, children: label });
1356
+ }
1357
+ return /* @__PURE__ */ jsx2("figure", { "data-rt": "embed", "data-url": url, "data-provider": r.provider, "data-aspect": aspect, "aria-label": title, children: body });
1358
+ }
1359
+ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes: /* @__PURE__ */ new WeakMap() }, footnotes = { notes: [], nodes: /* @__PURE__ */ new WeakMap() }, resolved) {
1360
+ const components = {
1361
+ // RT5: every top-level h2 and h3 carries its outline id, as html.ts writes it.
1362
+ block: {
1363
+ h2: ({ value, children }) => /* @__PURE__ */ jsx2("h2", { id: outline.nodes.get(value), children }),
1364
+ h3: ({ value, children }) => /* @__PURE__ */ jsx2("h3", { id: outline.nodes.get(value), children })
1365
+ },
1366
+ list: { check: ({ children }) => /* @__PURE__ */ jsx2("ul", { "data-task": "true", children }) },
1367
+ listItem: { check: ({ value, children }) => /* @__PURE__ */ jsx2(CheckItem, { checked: value.checked === true, children }) },
820
1368
  types: {
821
1369
  [PT_TYPES.divider]: () => /* @__PURE__ */ jsx2("hr", {}),
822
1370
  [PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx2(Table, { value }),
@@ -837,6 +1385,7 @@ function buildComponents(renderBlock) {
837
1385
  if (rendered !== void 0) return /* @__PURE__ */ jsx2(Fragment, { children: rendered });
838
1386
  const builtIn = calloutOrToggle(value);
839
1387
  if (builtIn) return builtIn;
1388
+ if (columnsView(value)) return /* @__PURE__ */ jsx2(Columns, { value, components });
840
1389
  const copy = detachedComponentCopy(value);
841
1390
  if (copy) {
842
1391
  return /* @__PURE__ */ jsx2("div", { className: "bcms-block bcms-block--component", "data-bcms-component-copy": copy.componentId, "data-bcms-instance": copy.id, children: /* @__PURE__ */ jsx2(BlocksRenderer, { blocks: copy.blocks }) });
@@ -866,6 +1415,12 @@ function buildComponents(renderBlock) {
866
1415
  ] }) : null
867
1416
  ] });
868
1417
  },
1418
+ [PT_TYPES.embed]: ({ value }) => /* @__PURE__ */ jsx2(Embed, { value, embeds }),
1419
+ [PT_TYPES.gallery]: ({ value }) => /* @__PURE__ */ jsx2(Gallery, { value }),
1420
+ [PT_TYPES.tableOfContents]: ({ value }) => /* @__PURE__ */ jsx2(Toc, { value, outline }),
1421
+ [PT_TYPES.columns]: ({ value }) => /* @__PURE__ */ jsx2(Columns, { value, components }),
1422
+ [PT_TYPES.relatedPosts]: ({ value }) => /* @__PURE__ */ jsx2(RelatedPosts, { value, resolved }),
1423
+ [PT_TYPES.authorBio]: ({ value }) => /* @__PURE__ */ jsx2(AuthorBio, { value, resolved }),
869
1424
  /**
870
1425
  * Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
871
1426
  * `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
@@ -878,6 +1433,7 @@ function buildComponents(renderBlock) {
878
1433
  /* @__PURE__ */ jsx2("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx2("li", { children: p }, i)) })
879
1434
  ] });
880
1435
  },
1436
+ [PT_TYPES.footnote]: ({ value }) => /* @__PURE__ */ jsx2(FootnoteMark, { value, footnotes }),
881
1437
  [PT_TYPES.mention]: ({ value }) => {
882
1438
  const v = value;
883
1439
  return /* @__PURE__ */ jsx2("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
@@ -893,7 +1449,7 @@ function buildComponents(renderBlock) {
893
1449
  const href = typeof v.href === "string" ? safeHref(v.href) : null;
894
1450
  if (!href) return /* @__PURE__ */ jsx2(Fragment, { children });
895
1451
  const blank = v.target === "_blank";
896
- return /* @__PURE__ */ jsx2("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
1452
+ return /* @__PURE__ */ jsx2("a", { ...v.button === true ? { "data-button": "true" } : {}, href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
897
1453
  },
898
1454
  /**
899
1455
  * The site's own inline markup, rendered as itself — the React half of
@@ -942,6 +1498,7 @@ function buildComponents(renderBlock) {
942
1498
  unknownType: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children: children ?? null }),
943
1499
  unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx2("p", { children })
944
1500
  };
1501
+ return components;
945
1502
  }
946
1503
  var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
947
1504
  function runsOf(blocks) {
@@ -982,22 +1539,42 @@ function calloutOrToggle(value) {
982
1539
  }
983
1540
  return null;
984
1541
  }
985
- function PortableText({ value, bindingBase, renderBlock }) {
1542
+ function PortableText({ value, bindingBase, renderBlock, embeds, resolved }) {
986
1543
  const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);
987
- const components = React.useMemo(() => buildComponents(renderBlock), [renderBlock]);
1544
+ const outline = React.useMemo(() => outlineOf(blocks), [blocks]);
1545
+ const footnotes = React.useMemo(() => footnoteOrder(blocks), [blocks]);
1546
+ const components = React.useMemo(
1547
+ () => buildComponents(renderBlock, embeds, outline, footnotes, resolved),
1548
+ [renderBlock, embeds, outline, footnotes, resolved]
1549
+ );
988
1550
  const listComponents = React.useMemo(() => {
989
1551
  if (!bindingBase) return null;
990
1552
  const keys = new Set(blocks.map((b) => b._key));
991
1553
  return {
992
1554
  ...components,
993
- 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 })
1555
+ listItem: ({ value: item, children }) => {
1556
+ const address = keys.has(item._key) ? { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext" } : void 0;
1557
+ if (item.listItem === "check") {
1558
+ return /* @__PURE__ */ jsx2(CheckItem, { checked: item.checked === true, address, children });
1559
+ }
1560
+ return /* @__PURE__ */ jsx2("li", { ...address, children });
1561
+ }
994
1562
  };
995
1563
  }, [bindingBase, blocks, components]);
996
1564
  if (blocks.length === 0) return null;
997
- if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx2(PT, { value: blocks, components });
998
- return /* @__PURE__ */ jsx2(Fragment, { children: runsOf(blocks).map(
999
- (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)
1000
- ) });
1565
+ const notes = /* @__PURE__ */ jsx2(FootnoteList, { notes: footnotes.notes });
1566
+ if (!bindingBase || !listComponents) {
1567
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1568
+ /* @__PURE__ */ jsx2(PT, { value: blocks, components }),
1569
+ notes
1570
+ ] });
1571
+ }
1572
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1573
+ runsOf(blocks).map(
1574
+ (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)
1575
+ ),
1576
+ notes
1577
+ ] });
1001
1578
  }
1002
1579
  function readValue(value) {
1003
1580
  if (Array.isArray(value)) return value;
@@ -1171,7 +1748,7 @@ function Spacer({ height = 32, className = "" }) {
1171
1748
  // src/blocks/Columns.tsx
1172
1749
  import "react";
1173
1750
  import { jsx as jsx7 } from "react/jsx-runtime";
1174
- function Columns({
1751
+ function Columns2({
1175
1752
  columns,
1176
1753
  gap = 16,
1177
1754
  className = "",
@@ -1425,7 +2002,7 @@ var BLOCK_REGISTRY = {
1425
2002
  icon: "minus"
1426
2003
  },
1427
2004
  columns: {
1428
- component: Columns,
2005
+ component: Columns2,
1429
2006
  label: "Columns",
1430
2007
  icon: "columns"
1431
2008
  },