@bettercms-ai/ui 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/portableText.tsx","../src/blocks/Text.tsx","../src/lib/utils.ts","../src/blocks/Image.tsx","../src/blocks/Button.tsx","../src/blocks/Spacer.tsx","../src/blocks/Columns.tsx","../src/blocks/Video.tsx","../src/blocks/Section.tsx","../src/blocks/Navbar.tsx","../src/blocks/Footer.tsx","../src/blocks/Slider.tsx","../src/blocks/Tabs.tsx","../src/blocks/Placeholders.tsx"],"sourcesContent":["import type { HeadingProps } from \"@bettercms-ai/types\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface HeadingBlockProps extends HeadingProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Heading({\n text,\n level = 2,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: HeadingBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;\n\n switch (level) {\n case 1:\n return <h1 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h1>;\n case 2:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n case 3:\n return <h3 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h3>;\n default:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n }\n}\n","import React from \"react\";\nimport type { PortableTextDocument, PortableTextNode } from \"@bettercms-ai/types\";\n\n/**\n * Portable Text → React.\n *\n * React rather than an HTML string, deliberately: React escapes text children by default, so\n * author data cannot become markup here at all. The `dangerouslySetInnerHTML` path that the\n * `html` cache still uses has to be trusted; this one does not.\n *\n * Unknown marks and unknown styles are DROPPED, never passed through. A colour mark carries a\n * token NAME (`color:brand-accent`) which becomes a class the consuming site styles — so it is\n * theme-aware and survives a rebrand, which a baked hex does not.\n */\n\nconst MARK_TAGS: Record<string, keyof React.JSX.IntrinsicElements> = {\n strong: \"strong\",\n em: \"em\",\n underline: \"u\",\n \"strike-through\": \"s\",\n code: \"code\",\n};\n\nconst BLOCK_TAGS: Record<string, keyof React.JSX.IntrinsicElements> = {\n normal: \"p\",\n h1: \"h1\",\n h2: \"h2\",\n h3: \"h3\",\n h4: \"h4\",\n h5: \"h5\",\n h6: \"h6\",\n blockquote: \"blockquote\",\n};\n\nfunction Span({ node, index }: { node: PortableTextNode; index: number }) {\n const marks = node.marks ?? [];\n const colour = marks.find((m) => m.startsWith(\"color:\"))?.slice(\"color:\".length);\n\n // Text is a React child, so it is escaped by construction.\n let out: React.ReactNode = node.text ?? \"\";\n for (const m of marks) {\n const Tag = MARK_TAGS[m];\n if (Tag) out = <Tag>{out}</Tag>;\n }\n if (colour && /^[a-z0-9-]+$/i.test(colour)) {\n out = <span className={`bcms-color-${colour}`}>{out}</span>;\n }\n return <React.Fragment key={node._key ?? index}>{out}</React.Fragment>;\n}\n\nexport function PortableText({ doc }: { doc: PortableTextDocument }) {\n return (\n <>\n {doc.content.map((block, i) => {\n const Tag = BLOCK_TAGS[block.style ?? \"normal\"] ?? \"p\";\n return (\n <Tag key={block._key ?? i}>\n {(block.children ?? []).map((c, j) => (\n <Span key={c._key ?? j} node={c} index={j} />\n ))}\n </Tag>\n );\n })}\n </>\n );\n}\n","import type { PortableTextDocument, TextProps } from \"@bettercms-ai/types\";\nimport { PortableText } from \"./portableText\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface TextBlockProps extends TextProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** Portable Text source. WINS over `html`, which is the derived cache. */\n content?: PortableTextDocument;\n}\n\nexport function Text({\n html,\n content,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: TextBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;\n\n // `content` wins when present — it is the source of truth and renders through React, which\n // escapes by construction. `html` remains the fallback so every block authored before\n // Portable Text existed renders exactly as it always did.\n if (content && content.content.length > 0) {\n return (\n <div className={cls}>\n <PortableText doc={content} />\n </div>\n );\n }\n\n return (\n <div\n className={cls}\n data-bcms-field={field}\n data-bcms-kind={kind}\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { ImageProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ImageBlockProps extends ImageProps, BcmsBindingProps {\n className?: string;\n sizes?: string;\n}\n\nexport function Image({\n src,\n alt,\n width,\n height,\n caption,\n className = \"\",\n sizes,\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ImageBlockProps) {\n return (\n <figure className={cn(\"w-full\", className)}>\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n data-bcms-field={field}\n data-bcms-kind={kind}\n className=\"w-full h-auto rounded-md object-cover\"\n loading=\"lazy\"\n />\n {caption && (\n <figcaption className=\"mt-2 text-sm text-muted-foreground text-center italic\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n","import type { ButtonProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ButtonBlockProps extends ButtonProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Button({\n text,\n href,\n variant = \"primary\",\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ButtonBlockProps) {\n const baseStyles =\n \"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\";\n\n const variantStyles = {\n primary:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n };\n\n const alignClass = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n }[align];\n\n return (\n <a\n href={href}\n data-bcms-field={field}\n data-bcms-kind={kind}\n className={cn(baseStyles, variantStyles[variant], alignClass, \"px-4 py-2\", className)}\n >\n {text}\n </a>\n );\n}\n","import type { SpacerProps } from \"@bettercms-ai/types\";\n\ninterface SpacerBlockProps extends SpacerProps {\n className?: string;\n}\n\nexport function Spacer({ height = 32, className = \"\" }: SpacerBlockProps) {\n return (\n <div\n className={`w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`}\n style={{ height: `${height}px` }}\n aria-hidden=\"true\"\n >\n <span className=\"text-xs text-muted-foreground/40 select-none\">\n {height}px\n </span>\n </div>\n );\n}\n","import type { ColumnsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface ColumnsBlockProps extends ColumnsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Columns({\n columns,\n gap = 16,\n className = \"\",\n renderBlock,\n}: ColumnsBlockProps) {\n return (\n <div\n className={cn(\"grid w-full\", className)}\n style={{\n gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,\n gap: `${gap}px`,\n }}\n >\n {columns.map((columnBlocks, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-4 min-w-0\">\n {columnBlocks.map((block) => (\n <span key={block.id}>\n {renderBlock ? renderBlock(block) : null}\n </span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { VideoProps } from \"@bettercms-ai/types\";\nimport { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ninterface VideoBlockProps extends VideoProps {\n className?: string;\n}\n\nfunction getEmbedUrl(url: string): { embedUrl: string; provider: \"youtube\" | \"vimeo\" | \"unknown\" } {\n // YouTube\n const ytMatch = url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/\n );\n if (ytMatch) {\n return {\n embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,\n provider: \"youtube\",\n };\n }\n\n // Vimeo\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) {\n return {\n embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,\n provider: \"vimeo\",\n };\n }\n\n return { embedUrl: url, provider: \"unknown\" };\n}\n\nexport function Video({\n url,\n poster,\n autoplay = false,\n loop = false,\n className = \"\",\n}: VideoBlockProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const { embedUrl, provider } = getEmbedUrl(url);\n\n // If not a recognized embed, render a native video tag\n if (provider === \"unknown\") {\n return (\n <video\n src={url}\n poster={poster}\n autoPlay={autoplay}\n loop={loop}\n controls\n className={className}\n playsInline\n />\n );\n }\n\n return (\n <div className={cn(\"w-full overflow-hidden rounded-md bg-black aspect-video\", className)}>\n {!isLoaded && poster && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={poster}\n alt=\"Video thumbnail\"\n className=\"w-full h-full object-cover absolute inset-0\"\n />\n )}\n <iframe\n src={`${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n loading=\"lazy\"\n onLoad={() => setIsLoaded(true)}\n className=\"w-full h-full\"\n />\n </div>\n );\n}\n","import type { SectionProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface SectionBlockProps extends SectionProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Section({\n children,\n background,\n paddingY,\n maxWidth,\n className = \"\",\n renderBlock,\n}: SectionBlockProps) {\n const inner = (children ?? []).map((block) => (\n <span key={block.id}>{renderBlock ? renderBlock(block) : null}</span>\n ));\n return (\n <section\n className={cn(\"w-full\", className)}\n style={{ background, paddingTop: paddingY, paddingBottom: paddingY }}\n >\n {maxWidth ? <div style={{ maxWidth, marginInline: \"auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n","import type { NavbarProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface NavbarBlockProps extends NavbarProps {\n className?: string;\n}\n\nexport function Navbar({ logo, links, cta, className = \"\" }: NavbarBlockProps) {\n return (\n <nav className={cn(\"flex items-center gap-6 py-4\", className)}>\n {logo?.src ? (\n <a href={logo.href ?? \"/\"} className=\"shrink-0\">\n <img src={logo.src} alt={logo.alt} className=\"h-8 w-auto\" />\n </a>\n ) : null}\n <div className=\"flex flex-1 gap-5\">\n {(links ?? []).map((l, i) => (\n <a key={i} href={l.href} className=\"text-inherit no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n {cta ? (\n <a\n href={cta.href}\n className=\"rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline\"\n >\n {cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n","import type { FooterProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface FooterBlockProps extends FooterProps {\n className?: string;\n}\n\nexport function Footer({ columns, copyright, className = \"\" }: FooterBlockProps) {\n return (\n <footer className={cn(\"mt-12 border-t pt-8\", className)}>\n <div className=\"flex flex-wrap gap-12\">\n {(columns ?? []).map((col, i) => (\n <div key={i} className=\"flex flex-col gap-1.5\">\n {col.heading ? <h4 className=\"mb-2 font-semibold\">{col.heading}</h4> : null}\n {(col.links ?? []).map((l, j) => (\n <a key={j} href={l.href} className=\"text-neutral-500 no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n ))}\n </div>\n {copyright ? <p className=\"mt-6 text-sm text-neutral-500\">{copyright}</p> : null}\n </footer>\n );\n}\n","\"use client\";\n\nimport type { SliderProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface SliderBlockProps extends SliderProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Slider({ slides, className = \"\", renderBlock }: SliderBlockProps) {\n const [i, setI] = useState(0);\n const list = slides ?? [];\n const go = (n: number) => setI((n + list.length) % (list.length || 1));\n const btn =\n \"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\";\n return (\n <div className={cn(\"relative w-full\", className)}>\n {list.map((s, idx) => (\n <div key={s.id} hidden={idx !== i} className=\"flex flex-col gap-4\">\n {(s.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n <button type=\"button\" aria-label=\"Previous\" onClick={() => go(i - 1)} className={cn(btn, \"left-2\")}>\n &#8249;\n </button>\n <button type=\"button\" aria-label=\"Next\" onClick={() => go(i + 1)} className={cn(btn, \"right-2\")}>\n &#8250;\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport type { TabsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface TabsBlockProps extends TabsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Tabs({ tabs, className = \"\", renderBlock }: TabsBlockProps) {\n const [active, setActive] = useState(0);\n const list = tabs ?? [];\n return (\n <div className={cn(\"w-full\", className)}>\n <div role=\"tablist\" className=\"mb-4 flex gap-1 border-b\">\n {list.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n className={cn(\n \"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2\",\n idx === active && \"border-black font-semibold\",\n )}\n >\n {t.label}\n </button>\n ))}\n </div>\n {list.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" hidden={idx !== active} className=\"flex flex-col gap-4\">\n {(t.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { ComponentProps as BcmsComponentProps, FormProps, CollectionProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\nconst box =\n \"rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500\";\n\n/**\n * Builder-preview placeholder for a `component` instance. Live instance resolution\n * (definition lookup + override merge) happens server-side in the delivery renderer\n * and in the page-builder, which pass a resolver — the standalone library preview\n * just shows what's referenced.\n */\nexport function ComponentInstance({\n componentId,\n className = \"\",\n}: BcmsComponentProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Component instance: <code>{componentId}</code>\n </div>\n );\n}\n\n/** Builder-preview placeholder for a `form` block (rendered live by @bettercms-ai/next). */\nexport function FormPlaceholder({ formId, className = \"\" }: FormProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Form: <code>{formId}</code>\n </div>\n );\n}\n\n/**\n * Builder-preview placeholder for a `collection` block.\n *\n * The builder canvas has no entries to list — the list is fetched per request by the site\n * renderer, deliberately, so that a published snapshot can never bake in a stale set of\n * posts. A placeholder is therefore the honest render here, not a limitation.\n */\nexport function CollectionPlaceholder({\n cardComponentId,\n className = \"\",\n}: CollectionProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Collection: entries of this page\n {cardComponentId ? <> as <code>{cardComponentId}</code></> : <> (default card)</>}\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsx","jsx","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","Fragment","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/portableText.tsx","../../richtext/src/types.ts","../../richtext/src/href.ts","../../richtext/src/key.ts","../../richtext/src/table.ts","../../richtext/src/read.ts","../src/blocks/Text.tsx","../src/lib/utils.ts","../src/blocks/Image.tsx","../src/blocks/Button.tsx","../src/blocks/Spacer.tsx","../src/blocks/Columns.tsx","../src/blocks/Video.tsx","../src/blocks/Section.tsx","../src/blocks/Navbar.tsx","../src/blocks/Footer.tsx","../src/blocks/Slider.tsx","../src/blocks/Tabs.tsx","../src/blocks/Placeholders.tsx"],"sourcesContent":["import type { HeadingProps } from \"@bettercms-ai/types\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface HeadingBlockProps extends HeadingProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Heading({\n text,\n level = 2,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: HeadingBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const classNames = `font-heading font-bold tracking-tight text-foreground ${alignClass} ${className}`;\n\n switch (level) {\n case 1:\n return <h1 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h1>;\n case 2:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n case 3:\n return <h3 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h3>;\n default:\n return <h2 className={classNames} data-bcms-field={field} data-bcms-kind={kind}>{text}</h2>;\n }\n}\n","import React from \"react\";\nimport { PortableText as PT, type PortableTextComponents } from \"@portabletext/react\";\nimport {\n PT_TYPES,\n safeHref,\n normalizePortableText,\n type PortableText as PtValue,\n type PortableTextTable,\n type PortableTextTableRow,\n} from \"@bettercms-ai/richtext\";\n\n/**\n * Portable Text → React, on the official renderer.\n *\n * REPLACES A 66-LINE HAND-ROLLED WALKER that could not express a hyperlink. It had no notion\n * of `markDefs`, so a link had nowhere to store its href; it also DROPPED unknown marks and\n * unknown styles silently, meaning content written by a newer editor disappeared here with no\n * error. `@portabletext/react` handles both: annotations resolve through `markDefs`, and\n * `unknownMark`/`unknownType` render through instead of swallowing.\n *\n * ── Why v6 and not v8 ────────────────────────────────────────────────────────────────────\n * `@portabletext/react@6.2.0` peers `react ^18.2 || ^19`. v7 and v8 require `react ^19`, and\n * this package peers `react >=18` — adopting them would force a breaking peer bump on every\n * customer of `@bettercms-ai/ui` and `@bettercms-ai/next` for no rendering benefit.\n *\n * ── The binding, which is the point of the whole migration ───────────────────────────────\n * Every top-level node carries `data-bcms-field=\"<blockId>__<prop>#<_key>\"`. That suffix is\n * what makes ONE PARAGRAPH inside a rich-text field addressable, instead of the field being a\n * single opaque string with one binding on the outside. `_key` and not an index, because a\n * list is edited continuously and an index-addressed binding silently retargets a different\n * paragraph the moment someone inserts one above it.\n */\n\nconst TOKEN = /^[a-z0-9-]+$/i;\n\nexport interface PortableTextProps {\n value: unknown;\n /** `<blockId>__<prop>`. Present ⇒ stamp a per-block address. Absent ⇒ published output. */\n bindingBase?: string;\n}\n\n/** Per-block wrapper: the element the editor selects when someone clicks this paragraph. */\nfunction Bound({\n base,\n nodeKey,\n children,\n}: {\n base: string | undefined;\n nodeKey: string | undefined;\n children: React.ReactNode;\n}) {\n if (!base || !nodeKey) return <>{children}</>;\n return (\n <div data-bcms-field={`${base}#${nodeKey}`} data-bcms-kind=\"richtext\">\n {children}\n </div>\n );\n}\n\n/**\n * A table, drawn from the value rather than from a marker.\n *\n * A cell holds BLOCKS, so it renders through this same component set — a link or a bolded word\n * inside a cell is content, and a cell that rendered only text would drop it. `thead` is the\n * LEADING RUN of `head` rows and `th` follows the cell's own `header`, matching\n * `portableTextToHtml` exactly: two renderers of one value that disagree is the whole bug class\n * this package exists to close.\n */\nfunction Table({ value }: { value: PortableTextTable }) {\n const components = React.useMemo(buildComponents, []);\n /**\n * A LONE paragraph in a cell is unwrapped, and only a lone one.\n *\n * `<td>Pro</td>` is what a cell of one line looks like in every table ever authored, and\n * `portableTextToHtml` unwraps it for exactly that reason — the html it emits is compared\n * against stored markup. Two renderers of one value that disagree about a `<p>` is the drift\n * this package exists to end, so the rule is spelled the same way here. A cell holding two\n * blocks keeps both paragraphs, in both renderers.\n */\n const inline = React.useMemo(\n () => ({ ...components, block: { normal: ({ children }: { children?: React.ReactNode }) => <>{children}</> } }),\n [components],\n );\n const rows = Array.isArray(value?.rows) ? value.rows : [];\n if (rows.length === 0) return null;\n\n const cells = (row: PortableTextTableRow) =>\n (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {\n const Tag = cell?.header === true ? \"th\" : \"td\";\n const span = typeof cell?.span === \"number\" && cell.span > 1 ? cell.span : undefined;\n const rowSpan = typeof cell?.rowSpan === \"number\" && cell.rowSpan > 1 ? cell.rowSpan : undefined;\n const content = Array.isArray(cell?.content) ? cell.content : [];\n const lone =\n content.length === 1\n && (content[0] as { _type?: string; style?: string; listItem?: string })._type === \"block\"\n && (content[0] as { style?: string }).style === \"normal\"\n && !(content[0] as { listItem?: string }).listItem;\n return (\n <Tag key={cell?._key} {...(span ? { colSpan: span } : {})} {...(rowSpan ? { rowSpan } : {})}>\n <PT value={content as never} components={lone ? inline : components} />\n </Tag>\n );\n });\n const rendered = rows.map((row) => <tr key={row?._key}>{cells(row)}</tr>);\n const headCount = rows.findIndex((r) => r?.head !== true);\n const head = headCount === -1 ? rows.length : headCount;\n\n // `bcms-table` on migrated values only, exactly as `portableTextToHtml` writes it — see\n // `PortableTextTable[\"legacy\"]`. The two renderers agree about the class or they agree about\n // nothing.\n return (\n <table {...(value.legacy === true ? { className: \"bcms-table\" } : {})}>\n {head > 0 && <thead>{rendered.slice(0, head)}</thead>}\n {head < rows.length && <tbody>{rendered.slice(head)}</tbody>}\n </table>\n );\n}\n\nfunction buildComponents(): PortableTextComponents {\n return {\n types: {\n [PT_TYPES.divider]: () => <hr />,\n [PT_TYPES.table]: ({ value }) => <Table value={value as PortableTextTable} />,\n [PT_TYPES.code]: ({ value }) => {\n const v = value as { code?: unknown; language?: unknown };\n const lang = typeof v.language === \"string\" && TOKEN.test(v.language) ? v.language : undefined;\n return (\n <pre>\n <code className={lang ? `language-${lang}` : undefined}>\n {typeof v.code === \"string\" ? v.code : \"\"}\n </code>\n </pre>\n );\n },\n /**\n * A generic embedded block renders as a marker, not as content: its payload references\n * something (a component, a media id) that has to be RESOLVED against the project, and\n * this renderer has no project. A marker keeps the node visible and addressable rather\n * than rendering nothing at all, which is how embedded content used to vanish.\n */\n [PT_TYPES.block]: ({ value }) => {\n const v = value as { schemaKey?: unknown };\n const key = typeof v.schemaKey === \"string\" ? v.schemaKey : \"block\";\n return <div className=\"bcms-block\" data-bcms-schema-key={key} />;\n },\n [PT_TYPES.mention]: ({ value }) => {\n const v = value as { label?: unknown };\n return <span className=\"bcms-mention\">{typeof v.label === \"string\" ? v.label : \"\"}</span>;\n },\n },\n marks: {\n /**\n * The only attribute this renderer writes from author data. An unsafe href renders the\n * LABEL rather than deleting it — blocking a link must not lose the words.\n */\n [PT_TYPES.link]: ({ value, children }) => {\n const v = (value ?? {}) as { href?: unknown; target?: unknown };\n const href = typeof v.href === \"string\" ? safeHref(v.href) : null;\n if (!href) return <>{children}</>;\n const blank = v.target === \"_blank\";\n return (\n <a href={href} {...(blank ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}>\n {children}\n </a>\n );\n },\n highlight: ({ children }) => <mark>{children}</mark>,\n \"strike-through\": ({ children }) => <s>{children}</s>,\n /**\n * A colour mark carries a TOKEN name (`color:brand-accent`), never a baked hex, so the\n * consuming site styles it and a rebrand does not orphan the content.\n */\n color: ({ children, markType }) => {\n const token = markType.startsWith(\"color:\") ? markType.slice(6) : \"\";\n if (!TOKEN.test(token)) return <>{children}</>;\n return <span className={`bcms-color-${token}`}>{children}</span>;\n },\n },\n /** Unknown marks and types render their children. Never swallow content. */\n unknownMark: ({ children }) => <>{children}</>,\n unknownType: ({ children }) => <>{children ?? null}</>,\n unknownBlockStyle: ({ children }) => <p>{children}</p>,\n };\n}\n\nexport function PortableText({ value, bindingBase }: PortableTextProps) {\n const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);\n const components = React.useMemo(buildComponents, []);\n if (blocks.length === 0) return null;\n\n // Binding wraps at the TOP LEVEL rather than inside `components.block`, because a block\n // component only sees text blocks — a divider or an embedded image would get no address,\n // and those are exactly the nodes an editor most wants to click.\n if (!bindingBase) return <PT value={blocks as never} components={components} />;\n\n return (\n <>\n {blocks.map((node) => (\n <Bound key={node._key} base={bindingBase} nodeKey={node._key}>\n <PT value={[node] as never} components={components} />\n </Bound>\n ))}\n </>\n );\n}\n\n/** Accept the array, the `{format,value}` envelope, and the legacy `{version,content}` tree. */\nfunction readValue(value: unknown): PtValue {\n if (Array.isArray(value)) return value as PtValue;\n if (value && typeof value === \"object\") {\n const o = value as Record<string, unknown>;\n if (Array.isArray(o.value)) return o.value as PtValue;\n if (Array.isArray(o.content)) return o.content as PtValue;\n }\n return [];\n}\n","/**\n * Portable Text, the real spec — https://portabletext.org\n *\n * BetterCMS shipped four hand-rolled \"Portable Text\" trees before this file, and all four\n * diverged from the spec in the same expensive way: no `markDefs`. Without markDefs a link\n * has nowhere to live, so every one of them could render bold text and none of them could\n * render a hyperlink. Two also dropped unknown marks silently. This file is the spec, so the\n * official `@portabletext/*` packages can read and write our content without a shim.\n *\n * THE SHAPE, and why each part earns its place:\n *\n * block ─ one paragraph/heading/quote. `style` says which. `listItem` + `level` make\n * it a list item, because Portable Text keeps lists FLAT and reconstructs the\n * nesting at render time; there is no `<ul>` node to lose.\n * span ─ a run of text inside a block. `marks` are strings, and a mark is either a\n * DECORATOR (a bare name like \"strong\") or an ANNOTATION (the `_key` of an\n * entry in the block's `markDefs`). That one indirection is the whole reason\n * links, and any other data-carrying mark, are expressible.\n * markDefs ─ the data behind annotation marks. A link's `href` lives here, not on the span.\n * blockObject─ anything that is not text: an image, an embed, a table, a placed component.\n *\n * `_key` is not decoration. It is the stable identity of a block, and it is what lets the\n * Visual Editor address ONE paragraph inside a rich-text field instead of treating the whole\n * field as a single opaque string. Everything the editor gained rests on it, so `_key` is\n * minted on ingest and never trusted from foreign content.\n */\n\n/** Every Portable Text node carries a type and a stable key. */\nexport interface PortableTextNode {\n _type: string;\n _key: string;\n}\n\n/**\n * A run of text. `marks` mixes decorator NAMES and markDef `_key`s; a consumer resolves the\n * difference by looking each mark up in the parent block's `markDefs`. A mark that resolves to\n * nothing is a decorator.\n */\nexport interface PortableTextSpan extends PortableTextNode {\n _type: \"span\";\n text: string;\n marks: string[];\n}\n\n/** The data behind an annotation mark. `_key` is what a span's `marks` entry points at. */\nexport interface PortableTextMarkDef extends PortableTextNode {\n [field: string]: unknown;\n}\n\n/** A text block. Lists are FLAT: `listItem` + `level`, never a nested container. */\nexport interface PortableTextBlock extends PortableTextNode {\n _type: \"block\";\n style: string;\n markDefs: PortableTextMarkDef[];\n children: (PortableTextSpan | PortableTextNode)[];\n listItem?: string;\n level?: number;\n /** Only meaningful when `listItem` is \"check\". A checklist item's state is content, not style. */\n checked?: boolean;\n}\n\n/** Anything that is not text — an image, an embed, a table, a placed component. */\nexport interface PortableTextObject extends PortableTextNode {\n [field: string]: unknown;\n}\n\n/** The value of a rich-text field: a flat array of blocks and block objects. */\nexport type PortableText = (PortableTextBlock | PortableTextObject)[];\n\n/**\n * ── THE TABLE ────────────────────────────────────────────────────────────────────────────\n *\n * A cell holds BLOCKS, not a string. The predecessor stored a string matrix, and the price was\n * paid on every edge of it: a link, a bolded word or a second paragraph inside a cell was\n * flattened to text on the way in and could never be authored on the way out. A cell is a small\n * rich-text field, so it is spelled as one, and every mark, annotation and list the rest of the\n * document can express works inside it for free.\n *\n * `header` is per CELL because a row-header table is a COLUMN of `<th>`; `head` is per ROW\n * because `<thead>` is a section of the table and a browser does not invent one. Both exist so\n * the four spellings of a header (`thead`+`th`, `thead`+`td`, bare `th`, none) survive a round\n * trip instead of being normalised into whichever one we happened to prefer — the html cache is\n * compared against the stored markup, so an invented `<thead>` reads as content drift.\n */\nexport interface PortableTextTableCell {\n _key: string;\n /** The cell's own blocks. Usually one paragraph; a cell may hold several, or none. */\n content: PortableText;\n /** `<th>` rather than `<td>`. */\n header?: boolean;\n /**\n * The COLUMN span (`colspan`), RECORDED but not honoured: the grid keeps exactly one cell per\n * `<td>`, and this is what the renderer writes back out. Honouring it would mean a ragged\n * model every consumer has to re-square, for a layout the editor cannot author anyway.\n */\n span?: number;\n /**\n * The ROW span (`rowspan`), recorded on the same terms.\n *\n * It is here because DROPPING it is lossy in a way the eye catches immediately: a spanning\n * cell that comes back spanning one row leaves a hole in the grid, and the round-trip audit\n * would refuse the whole table over an attribute we simply forgot to carry.\n */\n rowSpan?: number;\n}\n\nexport interface PortableTextTableRow {\n _key: string;\n cells: PortableTextTableCell[];\n /** The row sat in `<thead>`. Absent means `<tbody>`, which is what a parser assumes. */\n head?: boolean;\n}\n\n/** A table. The ONE table BetterCMS has — see `PT_TYPES.table`. */\nexport interface PortableTextTable extends PortableTextObject {\n _type: \"table\";\n rows: PortableTextTableRow[];\n /**\n * Render with the platform's own `class=\"bcms-table\"`, and with every row squared off.\n *\n * 🔴 THIS IS A COMPATIBILITY FLAG, and it is the whole of what a pre-P1c table's rendition\n * needs. The renderer this block object replaces emitted that class on every table and padded\n * short rows to the widest one, so a value migrated from `builtin:table` MUST keep both or it\n * renders differently than the `html` sitting beside it in the same row — which is drift, and\n * the parity gate is right to refuse it.\n *\n * Set on every migrated value, and recognised on ingest (`class=\"bcms-table\"` and nothing\n * else), because that class only ever appears in markup that renderer produced. A table\n * authored or pasted since P1c carries neither the flag nor the class.\n */\n legacy?: boolean;\n}\n\nexport const isPortableTextBlock = (n: unknown): n is PortableTextBlock =>\n !!n && typeof n === \"object\" && (n as PortableTextNode)._type === \"block\";\n\nexport const isPortableTextSpan = (n: unknown): n is PortableTextSpan =>\n !!n && typeof n === \"object\" && (n as PortableTextNode)._type === \"span\";\n\n/**\n * The stored envelope.\n *\n * `value` is the source of truth; `html` is a DERIVED CACHE and is never trusted from a client\n * when `value` is present. The envelope survives rather than storing a bare array because\n * customer sites that shipped months ago read `.html` directly and cannot be recompiled —\n * `packages/sdk/src/richtext.ts` `isRichText()` even returns false for an array, so a bare\n * array would make every deployed `rich()` call return \"\" and blank the page at 200 OK.\n */\nexport const PORTABLE_TEXT_FORMAT = \"portable-text-1\" as const;\n\nexport interface RichTextEnvelope {\n format: typeof PORTABLE_TEXT_FORMAT;\n value: PortableText;\n html: string;\n}\n\n/**\n * Our block-object and annotation type names, spelled once.\n *\n * Two repos serialise these and four renderers read them. A typo in any one of them is a node\n * that renders as nothing, with no error — the failure mode this whole migration exists to\n * stop — so the strings live here and nowhere else.\n */\nexport const PT_TYPES = {\n /** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */\n block: \"bcmsBlock\",\n /** An inline mention. A PT inline object is natively void, which is what Lexical's `token` mode was faking. */\n mention: \"bcmsMention\",\n divider: \"bcmsDivider\",\n code: \"bcmsCode\",\n /**\n * A table. Spelled `table`, unprefixed, because that is what every other Portable Text\n * producer calls one — the Sanity-shaped `{_type:\"table\", rows}` this platform used to refuse\n * as foreign content now lands on OUR type and renders, rather than being data nothing draws.\n */\n table: \"table\",\n /** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */\n link: \"link\",\n} as const;\n\n/**\n * The `schemaKey` a table stored under BEFORE it had a type of its own.\n *\n * Every table written up to P1c is a generic `bcmsBlock` carrying a string matrix under this\n * key. Those rows are still in the database and nothing rewrites them, so this stays READABLE\n * forever: `migrateTable` (table.ts) turns one into a `table` on the way out, which is why the\n * reader, the renderer and the plain-text pass all still recognise it.\n *\n * Nothing WRITES it any more.\n */\nexport const BUILTIN_TABLE_KEY = \"builtin:table\" as const;\n\n/**\n * Every `_type` that may appear at the TOP LEVEL of a stored value.\n *\n * Closed on purpose, and short: a text block, the table, the two standalone block objects, and\n * the generic `bcmsBlock` envelope that carries an image, an embed or columns behind its\n * `schemaKey` (see `ptSchemaFor`). Anything else is content from another system and has no\n * renderer here.\n *\n * `table` joined the set in P1c, and it is the one member tested by SHAPE as well as by name —\n * see `isStorableTable`. The Sanity-shaped `{_type:\"table\", rows:[{cells}]}` that prompted this\n * list is the reason: refusing it was right while nothing could draw a table, and became wrong\n * the moment something could. `migrateTable` adopts that shape on read, so the write it used to\n * refuse now renders every cell.\n *\n * `bcmsMention` is included although it is an INLINE object: a mention that has somehow been\n * hoisted to the top level is ours, harmless, and refusing an author's save over it would be\n * the cure being worse than the disease.\n */\nexport const STORABLE_BLOCK_TYPES: ReadonlySet<string> = new Set<string>([\n \"block\",\n PT_TYPES.block,\n PT_TYPES.table,\n PT_TYPES.code,\n PT_TYPES.divider,\n PT_TYPES.mention,\n]);\n\n/**\n * Top-level nodes whose `_type` this platform cannot store or render, in document order.\n *\n * WHY THIS IS A WRITE-TIME CHECK AND NOT A RENDER-TIME SHRUG. An unrecognised block survives\n * the JSON round trip perfectly — it is data, and every reader here hands it back untouched —\n * but `portableTextToHtml` has no component for it, so it renders as NOTHING. The `html` on\n * the envelope is a cache that every delivery surface reads, so the node silently disappears\n * from the live site while the stored value still looks complete. That is the \"renders as\n * nothing at 200 OK\" failure this codebase keeps paying for, and the only place to catch it\n * cheaply is the write, where the author is still present to be told.\n *\n * Returns the offenders rather than a boolean so the refusal can NAME them — `_type` and\n * `_key` are what let an author find the block in a body of eighty.\n */\nexport function foreignBlocks(value: unknown): { _type: string; _key: string }[] {\n if (!Array.isArray(value)) return [];\n const out: { _type: string; _key: string }[] = [];\n for (const node of value) {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) continue;\n const type = (node as PortableTextNode)._type;\n if (typeof type !== \"string\") continue;\n // A table is admitted by SHAPE, never by its `_type` alone — see `isStorableTable`.\n const storable = type === PT_TYPES.table ? isStorableTable(node) : STORABLE_BLOCK_TYPES.has(type);\n if (storable) continue;\n const key = (node as PortableTextNode)._key;\n out.push({ _type: type, _key: typeof key === \"string\" ? key : \"\" });\n }\n return out;\n}\n\n/**\n * Is this `_type:\"table\"` a table we can actually RENDER?\n *\n * 🔴 THE TYPE NAME IS NOT A CONTRACT. `table` is what every Portable Text producer calls a\n * table, and they do not agree on what is inside one. Admitting the name alone would let any\n * foreign `rows` through the write guard, and the reader — permissive by design, because a\n * reader that throws is a field that opens blank — would then coerce whatever it did not\n * recognise into EMPTY cells. That is the silent-emptying failure the guard exists to stop,\n * wearing our own type name.\n *\n * So exactly two shapes pass, tested cell by cell:\n * · ours — `cells: [{ content: PortableText }]`\n * · Sanity's — `cells: [\"a\", \"b\"]`, a row of strings\n *\n * Everything else is refused at the write, where the author is present to be told, and stays\n * readable through `migrateTable` for anything already stored.\n */\nexport function isStorableTable(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n const rows = (node as { rows?: unknown }).rows;\n if (!Array.isArray(rows)) return false;\n return rows.every((row) => {\n if (!row || typeof row !== \"object\" || Array.isArray(row)) return false;\n const cells = (row as { cells?: unknown }).cells;\n if (!Array.isArray(cells)) return false;\n return cells.every((cell) => {\n if (typeof cell === \"string\") return true;\n if (!cell || typeof cell !== \"object\" || Array.isArray(cell)) return false;\n const content = (cell as { content?: unknown }).content;\n return Array.isArray(content)\n && content.every((n) => !!n && typeof n === \"object\" && typeof (n as PortableTextNode)._type === \"string\");\n });\n });\n}\n\n/**\n * @deprecated The marker a table-accepting field used to carry INSTEAD of a table.\n *\n * It existed because a table stored as a generic block object, so `ptSchemaFor` had nothing\n * table-shaped to gate the HTML ingest on. `PT_TYPES.table` is that thing now, and it is a real\n * block object rather than a marker, so the ingest asks for it by name and nothing emits this.\n *\n * Kept exported for one release: the dashboard filters it out of the schema it hands\n * `defineSchema` (`src/lib/richtext/ptSchema.ts`), and removing the export would fail that\n * import rather than make the filter a harmless no-op. Delete it once that filter is gone.\n */\nexport const TABLE_CAPABILITY = \"bcmsTableCapability\" as const;\n","/**\n * The URL allowlist, lifted verbatim from `src/lib/richtext/notion-doc.ts:232-243`.\n *\n * It moves here rather than being re-derived because Portable Text turns every hyperlink into\n * a `link` markDef, and markDefs are written by four ingest paths (paste, the Management API,\n * MCP, markdown). One shared check is the difference between \"javascript: is blocked\" and\n * \"javascript: is blocked on the path we remembered to guard\".\n */\nconst SAFE_HREF_SCHEMES = [\"http:\", \"https:\", \"mailto:\", \"tel:\"];\n\n/** The href if it is safe to emit, otherwise null. Callers render the label as plain text. */\nexport function safeHref(raw: string): string | null {\n const trimmed = raw.trim();\n // A site-relative path has no scheme to check and cannot navigate off-origin.\n if (trimmed.startsWith(\"/\") && !trimmed.startsWith(\"//\")) return trimmed;\n try {\n return SAFE_HREF_SCHEMES.includes(new URL(trimmed).protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * `_key` minting.\n *\n * THE CHARSET IS LOAD-BEARING, not cosmetic. A `_key` ends up inside a Visual Editor address\n * (`<blockId>__<prop>#<key>`), and that address is parsed by splitting on `__`, `.`, `[`, `]`\n * and `#`. A key containing any of those characters would silently retarget an edit to the\n * wrong node, so we mint from `[A-Za-z0-9-]` only — no underscore, no dot, no separator.\n *\n * Foreign keys are RE-MINTED, never trusted: imported content and pasted HTML can carry\n * anything, and the address grammar is not a place to find out.\n *\n * Determinism matters at migration time. Converting a field twice — once for `data`, once for\n * `published_data` — with random keys makes two semantically identical copies compare unequal,\n * which lights up the change feed and the parity check on every entry in the project. The\n * backfill converts once and writes the same object to both columns; `deterministicKeys()`\n * exists for the cases that cannot.\n */\nconst ALPHABET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\nexport const KEY_PATTERN = /^[A-Za-z0-9-]{1,32}$/;\n\nexport const isValidKey = (k: unknown): k is string =>\n typeof k === \"string\" && KEY_PATTERN.test(k);\n\n/** A fresh random key. 12 chars of this alphabet is ~71 bits; collisions are not a concern. */\nexport function randomKey(length = 12): string {\n let out = \"\";\n for (let i = 0; i < length; i += 1) {\n out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];\n }\n return out;\n}\n\n/**\n * A counter-based generator, for conversions that must be byte-reproducible.\n *\n * Same input, same keys, every run — so a value converted for the draft column and the\n * published column produces identical output and `jsonbEqual` still says they match.\n */\nexport function deterministicKeys(prefix = \"k\"): () => string {\n let n = 0;\n return () => {\n n += 1;\n return `${prefix}${n}`;\n };\n}\n\nexport type KeyGenerator = () => string;\n","import {\n BUILTIN_TABLE_KEY,\n PT_TYPES,\n type PortableText,\n type PortableTextTable,\n type PortableTextTableCell,\n type PortableTextTableRow,\n} from \"./types\";\nimport { deterministicKeys, isValidKey, randomKey, type KeyGenerator } from \"./key\";\n\n/**\n * The one table, and every older spelling of it.\n *\n * THREE SHAPES REACH THIS FILE, and only one of them is written any more:\n *\n * `{_type:\"table\", rows:[{cells:[{content}]}]}` ours, since P1c\n * `bcmsBlock` + `schemaKey:\"builtin:table\"` every table stored BEFORE P1c: a string\n * matrix under `fields.rows`, plus `hasHeader`\n * `{_type:\"table\", rows:[{cells:[\"a\",\"b\"]}]}` content from another system (Sanity's is the\n * one found in this corpus): string cells\n *\n * They converge HERE rather than at each of the four readers, because the alternative is what\n * this package was created to end — four places deciding independently what a stored value means,\n * and a table rendering on one surface and vanishing on another. Nothing rewrites the database:\n * a legacy row is migrated on the way OUT, every time it is read.\n *\n * A string cell becomes one paragraph holding one span. That is lossless in the direction that\n * matters — the matrix never held marks to lose — and it means the rest of the pipeline has\n * exactly one cell shape to render, not two.\n *\n * ── 🔴 KEYS ARE DERIVED, NEVER MINTED AT RANDOM ──────────────────────────────────────────\n * This runs on EVERY READ of a value nothing rewrites, so a random `_key` here is a different\n * key each time the same row is read. Three things break at once: React remounts every cell on\n * every render because its `key` changed; a Visual Editor address minted against one read points\n * at nothing on the next; and the migration backfill converting `data` and `published_data`\n * produces two values that compare unequal, lighting up the change feed for every entry in the\n * project — the exact trap `deterministicKeys` was added for.\n *\n * So a migrated table's keys come from the table's OWN `_key` plus the row and cell INDEX, and\n * any key the value already carries is kept and reserved so a derived one cannot collide with it.\n * Same value in, same keys out, on every read, in every process.\n */\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\nexport const isPortableTextTable = (n: unknown): n is PortableTextTable =>\n isRecord(n) && n._type === PT_TYPES.table && Array.isArray(n.rows);\n\n/** A table as it was stored before it had a type: the generic block object under its schemaKey. */\nexport const isBuiltInTableBlock = (n: unknown): boolean =>\n isRecord(n) && n._type === PT_TYPES.block && n.schemaKey === BUILTIN_TABLE_KEY;\n\n/**\n * How a cell's `content` is read back.\n *\n * Injected rather than imported so this file stays free of `read.ts` — the reader already\n * depends on the table, and a cycle between the two is the kind of thing that resolves at type\n * level and explodes at runtime. The reader passes `normalizePortableText`; a caller with no\n * reader (the renderer, the plain-text pass) gets the identity, which is all a legacy string\n * matrix ever needs.\n *\n * It takes the cell's OWN key generator, so a block inside a cell that arrives without a `_key`\n * is keyed from the cell rather than from the document's random minter.\n */\nexport type CellContentReader = (value: unknown, key: KeyGenerator) => PortableText;\n\nconst asIs: CellContentReader = (v) => (Array.isArray(v) ? (v as PortableText) : []);\n\n/**\n * `<table key>-r0c1` — deterministic, unique among siblings, and inside `KEY_PATTERN`.\n *\n * The base is TRUNCATED because a `_key` is capped at 32 characters and an address is parsed by\n * splitting on the separators; 16 leaves room for `-r99c99-9`, which is longer than any table a\n * person writes. A collision with a key the value already carries is resolved by suffixing,\n * never by falling back to randomness.\n */\nfunction keyMinter(base: string, reserved: Set<string>) {\n const prefix = (base.match(/[A-Za-z0-9-]/g) ?? []).join(\"\").slice(0, 16) || \"t\";\n return (...parts: (string | number)[]): string => {\n const candidate = `${prefix}-${parts.join(\"\")}`.slice(0, 30);\n let key = candidate;\n for (let n = 1; reserved.has(key); n += 1) key = `${candidate}-${n}`;\n reserved.add(key);\n return key;\n };\n}\n\n/** Every `_key` the stored value already carries, so a derived key cannot land on one. */\nfunction reservedKeys(rows: unknown[]): Set<string> {\n const out = new Set<string>();\n for (const row of rows) {\n if (!isRecord(row)) continue;\n if (isValidKey(row._key)) out.add(row._key);\n for (const cell of Array.isArray(row.cells) ? row.cells : []) {\n if (isRecord(cell) && isValidKey(cell._key)) out.add(cell._key);\n }\n }\n return out;\n}\n\n/** One line of text as the single block a cell holds. Empty text holds NO block, not an empty one. */\nconst textContent = (text: string, key: KeyGenerator): PortableText =>\n text === \"\"\n ? []\n : [{\n _type: \"block\",\n _key: key(),\n style: \"normal\",\n markDefs: [],\n children: [{ _type: \"span\", _key: key(), text, marks: [] }],\n }];\n\n/** A positive integer span, or nothing. A `colspan=\"0\"` or `\"two\"` is not a span. */\nconst spanOf = (value: unknown): number | undefined => {\n const n = typeof value === \"number\" ? value : Number(value);\n return Number.isInteger(n) && n > 1 ? n : undefined;\n};\n\ntype Mint = (...parts: (string | number)[]) => string;\n\nfunction normalizeCell(raw: unknown, at: [number, number], mint: Mint, read: CellContentReader): PortableTextTableCell {\n const [row, col] = at;\n const key = isRecord(raw) && isValidKey(raw._key) ? raw._key : mint(\"r\", row, \"c\", col);\n // The cell's own generator: a block inside it that has no key is keyed from the CELL, so the\n // whole subtree is reproducible from the stored value alone.\n const inner = deterministicKeys(`${key}-`);\n\n if (typeof raw === \"string\" || typeof raw === \"number\" || typeof raw === \"boolean\") {\n return { _key: key, content: textContent(String(raw), inner) };\n }\n if (!isRecord(raw)) return { _key: key, content: [] };\n\n const span = spanOf(raw.span ?? raw.colSpan ?? raw.colspan);\n const rowSpan = spanOf(raw.rowSpan ?? raw.rowspan);\n return {\n _key: key,\n // `text` is the spelling a string matrix uses once its cells have been given objects; it is\n // read here so a half-migrated value cannot lose its words.\n content: Array.isArray(raw.content)\n ? read(raw.content, inner)\n : textContent(typeof raw.text === \"string\" ? raw.text : \"\", inner),\n ...(raw.header === true ? { header: true } : {}),\n ...(span === undefined ? {} : { span }),\n ...(rowSpan === undefined ? {} : { rowSpan }),\n };\n}\n\nfunction normalizeRow(raw: unknown, at: number, mint: Mint, read: CellContentReader): PortableTextTableRow {\n // A bare array IS a row: that is how the legacy matrix spells one, and how a hand-written\n // value most often arrives.\n const cells = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.cells) ? raw.cells : [];\n return {\n _key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint(\"r\", at),\n cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),\n ...(isRecord(raw) && raw.head === true ? { head: true } : {}),\n };\n}\n\n/**\n * A pre-P1c `builtin:table` block → the table.\n *\n * `hasHeader` defaults TRUE when absent, exactly as the renderer it replaces did, so a value\n * written before that flag existed keeps the header row it has always displayed. It is a WHOLE\n * ROW of headers, which is the only header the string matrix could express.\n */\nexport function builtInTableToTable(node: unknown, key: KeyGenerator = randomKey): PortableTextTable | null {\n if (!isBuiltInTableBlock(node)) return null;\n const fields = (node as { fields?: unknown }).fields;\n const raw = isRecord(fields) && Array.isArray(fields.rows) ? fields.rows : [];\n const hasHeader = isRecord(fields) ? fields.hasHeader !== false : true;\n const own = (node as { _key?: unknown })._key;\n const tableKey = isValidKey(own) ? own : key();\n const mint = keyMinter(tableKey, reservedKeys(raw));\n\n const rows = raw.map((row, i) => {\n const normalized = normalizeRow(row, i, mint, asIs);\n if (!(hasHeader && i === 0)) return normalized;\n return {\n ...normalized,\n head: true,\n cells: normalized.cells.map((c) => ({ ...c, header: true })),\n };\n });\n\n // 🔴 SQUARE THE GRID. The renderer this replaces padded every row to the widest one, so a\n // ragged matrix has always DISPLAYED as a full rectangle. Migrating without the padding drops\n // those cells from the page — a value that rendered five columns rendering four.\n const width = rows.reduce((max, r) => Math.max(max, r.cells.length), 0);\n for (const [i, row] of rows.entries()) {\n while (row.cells.length < width) {\n row.cells.push({\n _key: mint(\"r\", i, \"c\", row.cells.length),\n content: [],\n ...(row.head === true ? { header: true } : {}),\n });\n }\n }\n return { _type: PT_TYPES.table, _key: tableKey, rows, legacy: true };\n}\n\n/**\n * Any stored table shape → the one table, or `null` when the node is not a table at all.\n *\n * Returning `null` rather than throwing or coercing is what lets the reader call this on EVERY\n * node cheaply: a non-table answers no, and the caller carries on.\n *\n * PERMISSIVE ON READ, by design: a shape it does not recognise reads as far as it can rather\n * than throwing, because a reader that throws is a field that opens blank. The strict test lives\n * on the WRITE, where the author is present to be told — see `isStorableTable` in `types.ts`.\n */\nexport function migrateTable(\n node: unknown,\n key: KeyGenerator = randomKey,\n read: CellContentReader = asIs,\n): PortableTextTable | null {\n const builtIn = builtInTableToTable(node, key);\n if (builtIn) return builtIn;\n if (!isPortableTextTable(node)) return null;\n const rows = node.rows as unknown[];\n const tableKey = isValidKey(node._key) ? node._key : key();\n const mint = keyMinter(tableKey, reservedKeys(rows));\n return {\n _type: PT_TYPES.table,\n _key: tableKey,\n rows: rows.map((r, i) => normalizeRow(r, i, mint, read)),\n // Carried through: a migrated table that an author saves back is still the same table, and\n // dropping the flag on that save would change the page the next time it renders.\n ...(node.legacy === true ? { legacy: true } : {}),\n };\n}\n","import { PORTABLE_TEXT_FORMAT, PT_TYPES, type PortableText } from \"./types\";\nimport { lexicalToPortableText } from \"./from-lexical\";\nimport { migrateTable } from \"./table\";\nimport { randomKey, type KeyGenerator } from \"./key\";\n\n/**\n * THE reader. One function, every stored shape, and it never throws.\n *\n * ── What this replaces ───────────────────────────────────────────────────────────────────\n * Five readers, each guessing at the same six shapes, each maintained by hand, each with its\n * own idea of what an unrecognised value meant:\n *\n * `RichTextField.toEditorContent` (dashboard, the dock)\n * `pageDocAdapter.richtextToEditorString`(dashboard, the canvas)\n * the `LexicalEditor` import path (dashboard, parse-then-fallback)\n * `lexicalJsonToText` (dashboard, plain text)\n * `renderRichtextValue` (dashboard, reflection)\n *\n * A comment in the codebase says they \"must agree on every shape or a field loads on one\n * surface and not the other\". They did not agree, five separate times, and the symptom each\n * time was a field that opened blank and then autosaved the blank over real content. Keeping\n * five copies in sync by hand was the bug; there is now one copy, and it lives in a package\n * both repos import.\n *\n * ── Never throws, by contract ────────────────────────────────────────────────────────────\n * This runs on every read during the dual-read window. A throw here is a blank field, and a\n * blank field is one autosave away from being permanent. Unrecognised input returns `[]` and\n * is reported through `onUnreadable`, never raised.\n */\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\nconst looksLikeHtml = (s: string): boolean => /<[a-z!/][\\s\\S]*>/i.test(s);\n\nexport interface ReadOptions {\n keyGenerator?: KeyGenerator;\n /** HTML ingest, injected: this module must stay free of any DOM dependency. */\n fromHtml?: (html: string) => PortableText;\n /** Called instead of throwing. The migration turns this into a quarantine report. */\n onUnreadable?: (reason: string, value: unknown) => void;\n}\n\n/**\n * Make a Portable Text array OURS: a `_key` on every node, `markDefs` on every block.\n *\n * Values reach this from two places that predate the spec — a bare array persisted before\n * `_key` was minted, and the hand-rolled `{version:1, content}` tree the `ui` package's old\n * renderer emitted. Both are structurally Portable Text and neither carries markDefs, so\n * `@portabletext/react` would treat a block without them as malformed. Filling them in is\n * cheaper than a migration and makes every historical value renderable today.\n *\n * A TABLE is migrated here too, and that is the whole of the P1c read path: every table stored\n * before P1c is a generic `bcmsBlock` holding a string matrix, and every one of them comes back\n * out as a `table`. Nothing rewrites the database — the shape a reader sees is settled on the\n * way out, so a value converted years ago and a value pasted this morning are the same thing to\n * everything downstream.\n */\nexport function normalizePortableText(value: unknown, key: KeyGenerator = randomKey): PortableText {\n if (!Array.isArray(value)) return [];\n return value.flatMap((raw) => {\n if (!isRecord(raw) || typeof raw._type !== \"string\") return [];\n // Recursing through this same function is what gives a cell everything a block has: keys,\n // markDefs, and a nested table in the rare cell that holds one.\n const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));\n if (table) return [table as never];\n const node: Record<string, unknown> = { ...raw, _key: isRecord(raw) && typeof raw._key === \"string\" && raw._key ? raw._key : key() };\n if (node._type === \"block\") {\n if (!Array.isArray(node.markDefs)) node.markDefs = [];\n if (typeof node.style !== \"string\") node.style = \"normal\";\n node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {\n if (!isRecord(c)) return c;\n const child: Record<string, unknown> = { ...c, _key: typeof c._key === \"string\" && c._key ? c._key : key() };\n if (child._type === \"span\" && !Array.isArray(child.marks)) child.marks = [];\n return child;\n });\n }\n return [node as never];\n });\n}\n\nfunction plainBlocks(text: string, key: KeyGenerator): PortableText {\n if (text === \"\") return [];\n return text.split(\"\\n\").map((line) => ({\n _type: \"block\" as const,\n _key: key(),\n style: \"normal\",\n markDefs: [],\n children: [{ _type: \"span\" as const, _key: key(), text: line, marks: [] }],\n }));\n}\n\nfunction fromString(raw: string, key: KeyGenerator, opts: ReadOptions): PortableText {\n const trimmed = raw.trim();\n if (trimmed === \"\") return [];\n\n // A serialized Lexical state arrives as a JSON STRING, which is why this is tried before\n // the HTML sniff — `{\"root\":…}` contains no angle brackets but is not plain text either.\n if (trimmed.startsWith(\"{\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (isRecord(parsed) && isRecord(parsed.root)) {\n return lexicalToPortableText(parsed, { keyGenerator: key }).value;\n }\n } catch {\n // Not JSON after all. Fall through: it is text that happens to start with a brace.\n }\n }\n\n if (looksLikeHtml(trimmed) && opts.fromHtml) return opts.fromHtml(raw);\n\n // A bare string is LITERAL. It is NOT run through a markdown parser, because that is how\n // a line beginning \"#\" silently became a heading in copy the author never marked up.\n return plainBlocks(raw, key);\n}\n\n/**\n * Read any stored rich-text value as Portable Text.\n *\n * Shapes handled, in the order they are tested:\n * 1. `null` / `\"\"` → []\n * 2. `PortableText[]` → as-is\n * 3. `{format:\"portable-text-1\", value}` → `.value`\n * 4. `{format:\"lexical-…\", value:{root}}` → converted\n * 5. `{root:…}` bare Lexical state → converted\n * 6. `{format:\"bettercms-notion-doc\", blocks}` → walked, recursing per block\n * 7. `{html:\"…\"}` pre-envelope seed → ingested\n * 8. `\"{\\\"root\\\":…}\"` Lexical JSON string → parsed, converted\n * 9. `\"<p>…</p>\"` bare HTML → ingested\n * 10. `\"plain text\"` → one block per line, literal\n */\nexport function toPortableText(value: unknown, opts: ReadOptions = {}): PortableText {\n const key = opts.keyGenerator ?? randomKey;\n\n if (value == null || value === \"\") return [];\n if (Array.isArray(value)) return normalizePortableText(value, key);\n if (typeof value === \"string\") return fromString(value, key, opts);\n\n if (!isRecord(value)) {\n opts.onUnreadable?.(`unsupported ${typeof value}`, value);\n return [];\n }\n\n if (value.format === PORTABLE_TEXT_FORMAT && Array.isArray(value.value)) {\n return normalizePortableText(value.value, key);\n }\n\n // The hand-rolled `{version:1, content}` tree the old `ui` renderer emitted. Structurally\n // Portable Text, missing only `_key` and `markDefs`, so normalising is the whole conversion.\n if (value.version === 1 && Array.isArray(value.content)) {\n return normalizePortableText(value.content, key);\n }\n\n if (Array.isArray(value.blocks)) return fromNotionDoc(value.blocks, key, opts);\n\n // A Lexical envelope carries the state under `value`; a bare state IS the state.\n if (isRecord(value.value) && isRecord((value.value as Record<string, unknown>).root)) {\n return lexicalToPortableText(value.value, { keyGenerator: key }).value;\n }\n if (isRecord(value.root)) return lexicalToPortableText(value, { keyGenerator: key }).value;\n\n // The pre-envelope seed. Last, so a real envelope is never mistaken for one.\n if (typeof value.html === \"string\") return fromString(value.html, key, opts);\n\n opts.onUnreadable?.(\"unrecognised object shape\", value);\n return [];\n}\n\n/**\n * The `document` / Body format — `{format:\"bettercms-notion-doc\", version:1, blocks}`.\n *\n * Its blocks are heterogeneous and one of them (`richtext`) nests a whole rich-text value of\n * its own, so this recurses through `toPortableText` rather than duplicating the ladder. That\n * recursion is the point: a Body containing a rich-text block containing a link now produces\n * real markDefs, which the old renderer could not express at any depth.\n */\nfunction fromNotionDoc(blocks: unknown[], key: KeyGenerator, opts: ReadOptions): PortableText {\n const out: PortableText = [];\n for (const raw of blocks) {\n if (!isRecord(raw)) continue;\n const type = typeof raw.type === \"string\" ? raw.type : \"\";\n\n if (type === \"paragraph\") {\n out.push(...plainBlocks(typeof raw.text === \"string\" ? raw.text : \"\", key));\n continue;\n }\n if (type === \"richtext\") {\n out.push(...toPortableText(raw.value, { ...opts, keyGenerator: key }));\n continue;\n }\n // `field`, `component` and `reference` blocks reference something that has to be RESOLVED\n // against the project before it renders. They keep their whole payload in a declared\n // block object so the resolution can happen at publish time, where the project is known.\n out.push({\n _type: PT_TYPES.block,\n _key: key(),\n schemaKey: type || \"block\",\n schemaLabel: typeof raw.label === \"string\" ? raw.label : \"\",\n fields: {\n ...(raw.value !== undefined ? { value: raw.value } : {}),\n ...(typeof raw.fieldType === \"string\" ? { fieldType: raw.fieldType } : {}),\n },\n });\n }\n return out;\n}\n\n/**\n * The structured value on a block's props, or null when there is none.\n *\n * Every delivery lane asks this same question — \"is there structured content, or do I fall\n * back to the html cache?\" — and each one answering it locally is how the lanes drifted in the\n * first place: the hosted renderer preferred `content`, the shared React renderer preferred\n * `content`, and Next and Astro did not look for it at all. One implementation, four callers.\n *\n * Accepts every spelling in the wild: a bare array, `{value}` (the new envelope), and\n * `{content}` (the legacy hand-rolled tree). EMPTY is treated as absent on purpose — an empty\n * array must fall back to `html` rather than render a blank region.\n */\nexport function readStructuredContent(props: unknown): PortableText | null {\n if (!isRecord(props)) return null;\n const candidate = props.content ?? props.value;\n const arr = Array.isArray(candidate)\n ? candidate\n : isRecord(candidate)\n ? Array.isArray(candidate.content)\n ? candidate.content\n : Array.isArray(candidate.value)\n ? candidate.value\n : null\n : null;\n return arr && arr.length > 0 ? (arr as PortableText) : null;\n}\n","import type { PortableTextDocument, TextProps } from \"@bettercms-ai/types\";\nimport { PortableText } from \"./portableText\";\nimport { readStructuredContent } from \"@bettercms-ai/richtext\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface TextBlockProps extends TextProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n /** Portable Text source. WINS over `html`, which is the derived cache. */\n content?: PortableTextDocument | unknown[];\n}\n\nexport function Text({\n html,\n content,\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: TextBlockProps) {\n const alignClass = {\n left: \"text-left\",\n center: \"text-center\",\n right: \"text-right\",\n }[align];\n\n const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;\n\n /**\n * 🔴 THE BINDING USED TO BE DROPPED HERE, and that made Portable Text uneditable.\n *\n * This was two mutually exclusive `return`s: the structured branch rendered `<PortableText>`\n * in a plain `<div>`, and `data-bcms-field` / `data-bcms-kind` were spread ONLY on the HTML\n * fallback below it. So the moment a block carried structured content it lost every editable\n * address — and `block-bindings.ts` suppressed `html` in the same case, leaving the block\n * with none at all. Structured content, the thing that exists to make rich text MORE\n * editable, made it completely uneditable.\n *\n * One container now, always carrying the binding; only the CHILDREN switch. And when\n * structured content is present the binding gets richer rather than poorer: `PortableText`\n * stamps a per-block address off each `_key`, so a click lands on one paragraph instead of\n * the whole field.\n */\n const structured = readStructuredContent({ content });\n\n return (\n <div\n className={cls}\n {...(structured ? {} : { \"data-bcms-field\": field, \"data-bcms-kind\": kind })}\n >\n {structured ? (\n <PortableText value={structured} bindingBase={field} />\n ) : (\n // eslint-disable-next-line react/no-danger\n <span dangerouslySetInnerHTML={{ __html: html }} />\n )}\n </div>\n );\n}\n\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { ImageProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ImageBlockProps extends ImageProps, BcmsBindingProps {\n className?: string;\n sizes?: string;\n}\n\nexport function Image({\n src,\n alt,\n width,\n height,\n caption,\n className = \"\",\n sizes,\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ImageBlockProps) {\n return (\n <figure className={cn(\"w-full\", className)}>\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n data-bcms-field={field}\n data-bcms-kind={kind}\n className=\"w-full h-auto rounded-md object-cover\"\n loading=\"lazy\"\n />\n {caption && (\n <figcaption className=\"mt-2 text-sm text-muted-foreground text-center italic\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n","import type { ButtonProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport type { BcmsBindingProps } from \"../binding\";\n\ninterface ButtonBlockProps extends ButtonProps, BcmsBindingProps {\n className?: string;\n align?: \"left\" | \"center\" | \"right\";\n}\n\nexport function Button({\n text,\n href,\n variant = \"primary\",\n align = \"left\",\n className = \"\",\n \"data-bcms-field\": field,\n \"data-bcms-kind\": kind,\n}: ButtonBlockProps) {\n const baseStyles =\n \"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\";\n\n const variantStyles = {\n primary:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n };\n\n const alignClass = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n }[align];\n\n return (\n <a\n href={href}\n data-bcms-field={field}\n data-bcms-kind={kind}\n className={cn(baseStyles, variantStyles[variant], alignClass, \"px-4 py-2\", className)}\n >\n {text}\n </a>\n );\n}\n","import type { SpacerProps } from \"@bettercms-ai/types\";\n\ninterface SpacerBlockProps extends SpacerProps {\n className?: string;\n}\n\nexport function Spacer({ height = 32, className = \"\" }: SpacerBlockProps) {\n return (\n <div\n className={`w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`}\n style={{ height: `${height}px` }}\n aria-hidden=\"true\"\n >\n <span className=\"text-xs text-muted-foreground/40 select-none\">\n {height}px\n </span>\n </div>\n );\n}\n","import type { ColumnsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface ColumnsBlockProps extends ColumnsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Columns({\n columns,\n gap = 16,\n className = \"\",\n renderBlock,\n}: ColumnsBlockProps) {\n return (\n <div\n className={cn(\"grid w-full\", className)}\n style={{\n gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,\n gap: `${gap}px`,\n }}\n >\n {columns.map((columnBlocks, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-4 min-w-0\">\n {columnBlocks.map((block) => (\n <span key={block.id}>\n {renderBlock ? renderBlock(block) : null}\n </span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { VideoProps } from \"@bettercms-ai/types\";\nimport { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\ninterface VideoBlockProps extends VideoProps {\n className?: string;\n}\n\nfunction getEmbedUrl(url: string): { embedUrl: string; provider: \"youtube\" | \"vimeo\" | \"unknown\" } {\n // YouTube\n const ytMatch = url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/\n );\n if (ytMatch) {\n return {\n embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,\n provider: \"youtube\",\n };\n }\n\n // Vimeo\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) {\n return {\n embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,\n provider: \"vimeo\",\n };\n }\n\n return { embedUrl: url, provider: \"unknown\" };\n}\n\nexport function Video({\n url,\n poster,\n autoplay = false,\n loop = false,\n className = \"\",\n}: VideoBlockProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const { embedUrl, provider } = getEmbedUrl(url);\n\n // If not a recognized embed, render a native video tag\n if (provider === \"unknown\") {\n return (\n <video\n src={url}\n poster={poster}\n autoPlay={autoplay}\n loop={loop}\n controls\n className={className}\n playsInline\n />\n );\n }\n\n return (\n <div className={cn(\"w-full overflow-hidden rounded-md bg-black aspect-video\", className)}>\n {!isLoaded && poster && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={poster}\n alt=\"Video thumbnail\"\n className=\"w-full h-full object-cover absolute inset-0\"\n />\n )}\n <iframe\n src={`${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n loading=\"lazy\"\n onLoad={() => setIsLoaded(true)}\n className=\"w-full h-full\"\n />\n </div>\n );\n}\n","import type { SectionProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React from \"react\";\n\ninterface SectionBlockProps extends SectionProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Section({\n children,\n background,\n paddingY,\n maxWidth,\n className = \"\",\n renderBlock,\n}: SectionBlockProps) {\n const inner = (children ?? []).map((block) => (\n <span key={block.id}>{renderBlock ? renderBlock(block) : null}</span>\n ));\n return (\n <section\n className={cn(\"w-full\", className)}\n style={{ background, paddingTop: paddingY, paddingBottom: paddingY }}\n >\n {maxWidth ? <div style={{ maxWidth, marginInline: \"auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n","import type { NavbarProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface NavbarBlockProps extends NavbarProps {\n className?: string;\n}\n\nexport function Navbar({ logo, links, cta, className = \"\" }: NavbarBlockProps) {\n return (\n <nav className={cn(\"flex items-center gap-6 py-4\", className)}>\n {logo?.src ? (\n <a href={logo.href ?? \"/\"} className=\"shrink-0\">\n <img src={logo.src} alt={logo.alt} className=\"h-8 w-auto\" />\n </a>\n ) : null}\n <div className=\"flex flex-1 gap-5\">\n {(links ?? []).map((l, i) => (\n <a key={i} href={l.href} className=\"text-inherit no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n {cta ? (\n <a\n href={cta.href}\n className=\"rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline\"\n >\n {cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n","import type { FooterProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\ninterface FooterBlockProps extends FooterProps {\n className?: string;\n}\n\nexport function Footer({ columns, copyright, className = \"\" }: FooterBlockProps) {\n return (\n <footer className={cn(\"mt-12 border-t pt-8\", className)}>\n <div className=\"flex flex-wrap gap-12\">\n {(columns ?? []).map((col, i) => (\n <div key={i} className=\"flex flex-col gap-1.5\">\n {col.heading ? <h4 className=\"mb-2 font-semibold\">{col.heading}</h4> : null}\n {(col.links ?? []).map((l, j) => (\n <a key={j} href={l.href} className=\"text-neutral-500 no-underline\">\n {l.label}\n </a>\n ))}\n </div>\n ))}\n </div>\n {copyright ? <p className=\"mt-6 text-sm text-neutral-500\">{copyright}</p> : null}\n </footer>\n );\n}\n","\"use client\";\n\nimport type { SliderProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface SliderBlockProps extends SliderProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Slider({ slides, className = \"\", renderBlock }: SliderBlockProps) {\n const [i, setI] = useState(0);\n const list = slides ?? [];\n const go = (n: number) => setI((n + list.length) % (list.length || 1));\n const btn =\n \"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\";\n return (\n <div className={cn(\"relative w-full\", className)}>\n {list.map((s, idx) => (\n <div key={s.id} hidden={idx !== i} className=\"flex flex-col gap-4\">\n {(s.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n <button type=\"button\" aria-label=\"Previous\" onClick={() => go(i - 1)} className={cn(btn, \"left-2\")}>\n &#8249;\n </button>\n <button type=\"button\" aria-label=\"Next\" onClick={() => go(i + 1)} className={cn(btn, \"right-2\")}>\n &#8250;\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport type { TabsProps, ContentBlock } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\nimport React, { useState } from \"react\";\n\ninterface TabsBlockProps extends TabsProps {\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n}\n\nexport function Tabs({ tabs, className = \"\", renderBlock }: TabsBlockProps) {\n const [active, setActive] = useState(0);\n const list = tabs ?? [];\n return (\n <div className={cn(\"w-full\", className)}>\n <div role=\"tablist\" className=\"mb-4 flex gap-1 border-b\">\n {list.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n className={cn(\n \"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2\",\n idx === active && \"border-black font-semibold\",\n )}\n >\n {t.label}\n </button>\n ))}\n </div>\n {list.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" hidden={idx !== active} className=\"flex flex-col gap-4\">\n {(t.children ?? []).map((b) => (\n <span key={b.id}>{renderBlock ? renderBlock(b) : null}</span>\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import type { ComponentProps as BcmsComponentProps, FormProps, CollectionProps } from \"@bettercms-ai/types\";\nimport { cn } from \"../lib/utils\";\n\nconst box =\n \"rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500\";\n\n/**\n * Builder-preview placeholder for a `component` instance. Live instance resolution\n * (definition lookup + override merge) happens server-side in the delivery renderer\n * and in the page-builder, which pass a resolver — the standalone library preview\n * just shows what's referenced.\n */\nexport function ComponentInstance({\n componentId,\n className = \"\",\n}: BcmsComponentProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Component instance: <code>{componentId}</code>\n </div>\n );\n}\n\n/** Builder-preview placeholder for a `form` block (rendered live by @bettercms-ai/next). */\nexport function FormPlaceholder({ formId, className = \"\" }: FormProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Form: <code>{formId}</code>\n </div>\n );\n}\n\n/**\n * Builder-preview placeholder for a `collection` block.\n *\n * The builder canvas has no entries to list — the list is fetched per request by the site\n * renderer, deliberately, so that a published snapshot can never bake in a stale set of\n * posts. A placeholder is therefore the honest render here, not a limitation.\n */\nexport function CollectionPlaceholder({\n cardComponentId,\n className = \"\",\n}: CollectionProps & { className?: string }) {\n return (\n <div className={cn(box, className)}>\n Collection: entries of this page\n {cardComponentId ? <> as <code>{cardComponentId}</code></> : <> (default card)</>}\n </div>\n );\n}\n"],"mappings":"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","names":["isRecord","isRecord","isRecord","jsx","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsx","jsxs","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","Fragment","jsx","jsxs"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { B as BcmsBindingProps, a as Button, C as Columns, H as Heading, I as Image, S as Spacer, T as Text, V as Video } from './Video-DRC_fPjV.js';
1
+ export { B as BcmsBindingProps, a as Button, C as Columns, H as Heading, I as Image, S as Spacer, T as Text, V as Video } from './Video-CZkWQ3Hm.js';
2
2
  export { BLOCK_REGISTRY, BLOCK_TYPES, BlockRegistryEntry } from './registry.js';
3
3
  export { BlockRenderer, BlocksRenderer } from './renderer.js';
4
4
  import { ClassValue } from 'clsx';