@bettercms-ai/ui 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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","../src/registry.ts","../src/blockStyle.ts","../src/renderer.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","import type { ContentBlock, BlockType } from \"@bettercms-ai/types\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Text } from \"./blocks/Text\";\nimport { Image } from \"./blocks/Image\";\nimport { Button } from \"./blocks/Button\";\nimport { Spacer } from \"./blocks/Spacer\";\nimport { Columns } from \"./blocks/Columns\";\nimport { Video } from \"./blocks/Video\";\nimport { Section } from \"./blocks/Section\";\nimport { Navbar } from \"./blocks/Navbar\";\nimport { Footer } from \"./blocks/Footer\";\nimport { Slider } from \"./blocks/Slider\";\nimport { Tabs } from \"./blocks/Tabs\";\nimport { ComponentInstance, FormPlaceholder, CollectionPlaceholder } from \"./blocks/Placeholders\";\n\n/**\n * Block registry entry — maps a block type to its React component\n * and metadata for the block library UI.\n */\nexport interface BlockRegistryEntry {\n component: React.ComponentType<Record<string, unknown>>;\n label: string;\n icon: string;\n}\n\nexport const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry> = {\n // The entries of the page this block sits on. No preview here by design: the list is\n // fetched per request by the site renderer, so the builder has nothing to show.\n collection: {\n component: CollectionPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Collection\",\n icon: \"list\",\n },\n heading: {\n component: Heading as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Heading\",\n icon: \"type\",\n },\n text: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Text\",\n icon: \"align-left\",\n },\n // Same payload, same renderer — deliberately. `richtext` differs from `text` only in how\n // an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML\n // string, so duplicating the component would be two things to keep in sync for no gain.\n richtext: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Rich text\",\n icon: \"pilcrow\",\n },\n image: {\n component: Image as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Image\",\n icon: \"image\",\n },\n button: {\n component: Button as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Button\",\n icon: \"square\",\n },\n spacer: {\n component: Spacer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Spacer\",\n icon: \"minus\",\n },\n columns: {\n component: Columns as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Columns\",\n icon: \"columns\",\n },\n video: {\n component: Video as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Video\",\n icon: \"play\",\n },\n section: {\n component: Section as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Section\",\n icon: \"layout\",\n },\n navbar: {\n component: Navbar as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Navbar\",\n icon: \"menu\",\n },\n footer: {\n component: Footer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Footer\",\n icon: \"panel-bottom\",\n },\n slider: {\n component: Slider as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Slider\",\n icon: \"gallery-horizontal\",\n },\n tabs: {\n component: Tabs as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Tabs\",\n icon: \"folder\",\n },\n component: {\n component: ComponentInstance as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Component\",\n icon: \"component\",\n },\n form: {\n component: FormPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Form\",\n icon: \"clipboard-list\",\n },\n} as const;\n\nexport const BLOCK_TYPES = Object.keys(BLOCK_REGISTRY) as BlockType[];\n\nexport type { ContentBlock, BlockType };\n","import type { CSSProperties } from \"react\";\nimport { blockStyleToCss, type BlockStyle } from \"@bettercms-ai/types\";\n\n/**\n * FLO-264 — React-typed wrapper over the shared, framework-agnostic\n * `blockStyleToCss` in @bettercms-ai/types. One mapping, three consumers (this UI\n * renderer, the @bettercms-ai/next adapter, and — in string form — the hosted\n * render-page.ts).\n */\nexport function blockStyleToReactStyle(style: BlockStyle | undefined): CSSProperties | undefined {\n return blockStyleToCss(style) as CSSProperties | undefined;\n}\n","import type { ContentBlock } from \"@bettercms-ai/types\";\nimport { BLOCK_REGISTRY, type BlockRegistryEntry } from \"./registry\";\nimport { blockStyleToReactStyle } from \"./blockStyle\";\nimport type { BcmsBindingProps } from \"./binding\";\nimport React from \"react\";\n\n// Container blocks hold nested blocks and need the `renderBlock` callback threaded\n// so their children render recursively.\nconst CONTAINER_TYPES = new Set([\"columns\", \"section\", \"slider\", \"tabs\"]);\n\ninterface BlockRendererProps {\n block: ContentBlock;\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n /** Emit visual-editor field bindings. Default-off keeps published consumers clean. */\n annotate?: boolean;\n}\n\nfunction bindingProps(block: ContentBlock, annotate: boolean): BcmsBindingProps {\n if (!annotate || !block.id) return {};\n switch (block.type) {\n case \"heading\":\n case \"button\":\n return { \"data-bcms-field\": `${block.id}__text`, \"data-bcms-kind\": \"text\" };\n case \"text\":\n case \"richtext\":\n return { \"data-bcms-field\": `${block.id}__html`, \"data-bcms-kind\": \"richtext\" };\n case \"image\":\n return { \"data-bcms-field\": `${block.id}__src`, \"data-bcms-kind\": \"image\" };\n default:\n return {};\n }\n}\n\nfunction resolveBlock(block: ContentBlock, className?: string, annotate = false): React.ReactNode {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? \"\"}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n const Component = entry.component;\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = <Component {...block.props} className={className} {...bindingProps(block, annotate)} />;\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\nexport function BlockRenderer({\n block,\n className = \"\",\n renderBlock,\n annotate = false,\n}: BlockRendererProps) {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n // Container blocks (columns/section/slider/tabs) hold nested blocks, so thread the\n // renderBlock callback. Standalone usage (no BlocksRenderer above) falls back to\n // resolveBlock so one level of nesting still renders.\n const Component = entry.component;\n const containerProps = CONTAINER_TYPES.has(block.type)\n ? { renderBlock: renderBlock ?? ((child: ContentBlock) => resolveBlock(child, undefined, annotate)) }\n : {};\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = (\n <Component\n {...block.props}\n className={className}\n {...containerProps}\n {...bindingProps(block, annotate)}\n />\n );\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\ninterface BlocksRendererProps {\n blocks: ContentBlock[];\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n annotate?: boolean;\n}\n\nexport function BlocksRenderer({\n blocks,\n className = \"\",\n renderBlock,\n annotate = false,\n}: BlocksRendererProps) {\n if (blocks.length === 0) return null;\n\n return (\n <div className={className}>\n {blocks.map((block) => (\n <BlockRenderer\n key={block.id}\n block={block}\n renderBlock={renderBlock}\n annotate={annotate}\n />\n ))}\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","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/blocks/Heading.tsx","../src/blocks/portableText.tsx","../../richtext/src/types.ts","../../richtext/src/styled.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","../src/registry.ts","../src/blockStyle.ts","../src/renderer.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 isStyledTag,\n styledAttrEntries,\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\n/**\n * `style=\"color:red\"` → `{ color: \"red\" }`, because React refuses the string form outright\n * (\"The `style` prop expects a mapping from style properties to values, not a string\") — it\n * THROWS rather than degrading, so a styled run carrying a style attribute would blank the\n * page it is on. The HTML serialiser writes the same declaration back out verbatim; this is\n * only the shape React demands to write it.\n *\n * Custom properties keep their `--` spelling (React sets those through `setProperty`);\n * everything else is camelised, which is the only name React recognises.\n */\nfunction styleObject(css: string): React.CSSProperties {\n const out: Record<string, string> = {};\n for (const declaration of css.split(\";\")) {\n const at = declaration.indexOf(\":\");\n if (at < 0) continue;\n const name = declaration.slice(0, at).trim();\n const value = declaration.slice(at + 1).trim();\n if (!name || !value) continue;\n out[name.startsWith(\"--\") ? name : name.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())] = value;\n }\n return out as React.CSSProperties;\n}\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 /**\n * The site's own inline markup, rendered as itself — the React half of\n * `portableTextToHtml`'s `styled` mark, and it has to stay the same shape: Astro and the\n * editor canvas render through that serialiser while a Next site renders through this\n * one, and two renderers of one value that disagree is the whole bug class.\n *\n * The tag is re-checked against `STYLED_TAGS` and the attribute bag re-filtered, because\n * a markDef is DATA and arrives from imports, the Management API and AI writes. React\n * escapes the values it writes; the allowlist is what keeps a `href` or an `onClick` out\n * of the props entirely. An unrecognised tag renders its CHILDREN — the words are the\n * author's, the wrapper is only the design.\n */\n [PT_TYPES.styled]: ({ value, children }) => {\n const v = (value ?? {}) as { tag?: unknown; className?: unknown; attrs?: unknown };\n const tag = typeof v.tag === \"string\" ? v.tag.toLowerCase() : \"\";\n if (!isStyledTag(tag)) return <>{children}</>;\n const Tag = tag as \"span\";\n const className = typeof v.className === \"string\" && v.className ? v.className : undefined;\n const attrs = styledAttrEntries(v.attrs);\n const style = attrs.find(([name]) => name === \"style\")?.[1];\n return (\n <Tag\n className={className}\n {...Object.fromEntries(attrs.filter(([name]) => name !== \"style\"))}\n {...(style ? { style: styleObject(style) } : {})}\n >\n {children}\n </Tag>\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 /**\n * The annotation carrying a site's OWN inline markup — `<span class>`, `<small>`, `<time\n * datetime>`. Unprefixed, like `table`, because it describes HTML rather than a BetterCMS\n * concept. See `styled.ts` for what it may hold and why the list is that short.\n */\n styled: \"styled\",\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 `styled` annotation — a site's own inline markup, carried through Portable Text.\n *\n * WHY IT EXISTS. An imported site writes `<h1>Notes from a <span class=\"serif italic\">web\n * studio</span></h1>`. Portable Text models `strong`/`em`/`underline`/`strike`/`code`/\n * `highlight` and `link`, and NOTHING ELSE inline — so that span had no node to become,\n * `unknownMark` rendered its children, and the class was gone the first time an author saved.\n * That loss is why `derive-schema` deliberately refused to bind any heading containing a\n * `<span class>`: a field that destroys the design on save is worse than no field at all.\n *\n * So this is NOT an authoring mark. There is no toolbar button and no editing UI: it is the\n * site's design, recorded so the round trip is lossless, and it rides along with whatever the\n * author actually types inside it.\n *\n * ── The attribute allowlist IS the threat model ──────────────────────────────────────────\n * A markDef is data, and data reaches this platform from an import, the Management API, MCP\n * and AI writes. Rendering an arbitrary attribute bag would be an XSS primitive wearing a\n * design system's clothes, so exactly four names plus `data-*` are admitted, and the render\n * escapes every value. `id` is refused because it is an addressing surface (a duplicate id\n * breaks anchors and label association), `data-bcms-*` because those attributes ARE the Visual\n * Editor's binding addresses and content must not be able to mint one. `style` is admitted but\n * NOT passed through — its value is a language of its own, bounded by `safeStyle` below.\n */\n\n/** Inline tags a `styled` annotation may wear. Nothing outside this list is ever rendered. */\nexport const STYLED_TAGS = [\"span\", \"small\", \"sup\", \"sub\", \"kbd\", \"abbr\", \"time\"] as const;\n\nexport type StyledTag = (typeof STYLED_TAGS)[number];\n\nconst TAG_SET: ReadonlySet<string> = new Set<string>(STYLED_TAGS);\n\nexport const isStyledTag = (tag: unknown): tag is StyledTag =>\n typeof tag === \"string\" && TAG_SET.has(tag);\n\n/**\n * Attribute names a `styled` annotation may carry.\n *\n * Presentational or descriptive only. Never `href`/`src` (a navigation or fetch surface),\n * never `id` (an addressing surface), never `on*` (script), and never `data-bcms-*` — those\n * are binding addresses the editor reads, and content that could write one could point an\n * author's edit at somebody else's field.\n */\nconst ATTR_NAMES: ReadonlySet<string> = new Set([\"title\", \"datetime\", \"style\", \"aria-label\"]);\n\nconst DATA_ATTR = /^data-[a-z][a-z0-9-]*$/;\n\nexport const isStyledAttr = (name: string): boolean =>\n ATTR_NAMES.has(name) || (DATA_ATTR.test(name) && !name.startsWith(\"data-bcms-\"));\n\n/**\n * CSS an inline run may carry: how the words LOOK inside their own box, and nothing else.\n *\n * `style` is the one admitted attribute whose value is itself a language, so the tag allowlist\n * buys nothing here — a bare passthrough lets any markDef write\n * `position:fixed;inset:0;z-index:9999`, which is a full-viewport overlay over the published\n * page (clickjacking, defacement) minted by anyone who can write content, and\n * `background:url(https://…)` which fetches a third-party URL on every page view. Neither is a\n * design an import needs, so neither is on the list. Properties that only paint glyphs are.\n *\n * Values are checked as well as names, because `url()`, a CSS escape (`\\75 rl(`), a comment and\n * `expression()` are all ways to smuggle a fetch past a name check.\n */\nconst STYLE_PROPS: ReadonlySet<string> = new Set([\n \"color\", \"background-color\", \"opacity\",\n \"font\", \"font-family\", \"font-size\", \"font-style\", \"font-weight\", \"font-variant\",\n \"font-feature-settings\", \"font-variation-settings\",\n \"letter-spacing\", \"line-height\", \"word-spacing\", \"white-space\", \"word-break\",\n \"text-align\", \"text-decoration\", \"text-decoration-color\", \"text-decoration-line\",\n \"text-decoration-style\", \"text-transform\", \"text-underline-offset\", \"vertical-align\",\n]);\n\nconst STYLE_VALUE_BAN = /url\\(|expression\\(|@import|\\\\|\\/\\*|[<>]/i;\n\n/**\n * The declarations of a `style` attribute that survive, rejoined — or undefined when none do.\n * Custom properties (`--brand`) are kept: they paint nothing on their own, and a site's own CSS\n * is what decides whether they mean anything.\n */\nexport function safeStyle(css: string): string | undefined {\n const kept: string[] = [];\n for (const declaration of css.split(\";\")) {\n const at = declaration.indexOf(\":\");\n if (at < 0) continue;\n const name = declaration.slice(0, at).trim().toLowerCase();\n const value = declaration.slice(at + 1).trim();\n if (!name || !value) continue;\n if (!name.startsWith(\"--\") && !STYLE_PROPS.has(name)) continue;\n if (STYLE_VALUE_BAN.test(value)) continue;\n kept.push(`${name}:${value}`);\n }\n return kept.length ? kept.join(\";\") : undefined;\n}\n\n/** The annotation's payload, as stored. `tag` is validated again at render time. */\nexport interface StyledMarkDef {\n _type: \"styled\";\n _key: string;\n tag: StyledTag;\n className?: string;\n attrs?: Record<string, string>;\n}\n\n/**\n * The admitted attributes of an element, in source order, or undefined when there are none.\n *\n * `class` is deliberately absent — it lives on the markDef as `className`, because it is the\n * one attribute every consumer of this annotation needs to reach without parsing a bag.\n */\nexport function styledAttrsOf(el: Element): Record<string, string> | undefined {\n const out: Record<string, string> = {};\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name === \"class\" || !isStyledAttr(name)) continue;\n const value = name === \"style\" ? safeStyle(attr.value) : attr.value;\n if (value === undefined) continue;\n out[name] = value;\n }\n return Object.keys(out).length ? out : undefined;\n}\n\n/**\n * The same filter applied to a STORED bag, which may have arrived from anywhere.\n *\n * Both ends, and this is the end that matters: a markDef reaches storage from an import, the\n * Management API, MCP and AI writes, so the ingest filter is a courtesy and this one is the\n * control. Every renderer of the annotation goes through here.\n */\nexport function styledAttrEntries(attrs: unknown): [string, string][] {\n if (!attrs || typeof attrs !== \"object\" || Array.isArray(attrs)) return [];\n const out: [string, string][] = [];\n for (const [name, raw] of Object.entries(attrs as Record<string, unknown>)) {\n if (!isStyledAttr(name) || typeof raw !== \"string\") continue;\n const value = name === \"style\" ? safeStyle(raw) : raw;\n if (value === undefined) continue;\n out.push([name, value]);\n }\n return out;\n}\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","import type { ContentBlock, BlockType } from \"@bettercms-ai/types\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Text } from \"./blocks/Text\";\nimport { Image } from \"./blocks/Image\";\nimport { Button } from \"./blocks/Button\";\nimport { Spacer } from \"./blocks/Spacer\";\nimport { Columns } from \"./blocks/Columns\";\nimport { Video } from \"./blocks/Video\";\nimport { Section } from \"./blocks/Section\";\nimport { Navbar } from \"./blocks/Navbar\";\nimport { Footer } from \"./blocks/Footer\";\nimport { Slider } from \"./blocks/Slider\";\nimport { Tabs } from \"./blocks/Tabs\";\nimport { ComponentInstance, FormPlaceholder, CollectionPlaceholder } from \"./blocks/Placeholders\";\n\n/**\n * Block registry entry — maps a block type to its React component\n * and metadata for the block library UI.\n */\nexport interface BlockRegistryEntry {\n component: React.ComponentType<Record<string, unknown>>;\n label: string;\n icon: string;\n}\n\nexport const BLOCK_REGISTRY: Record<BlockType, BlockRegistryEntry> = {\n // The entries of the page this block sits on. No preview here by design: the list is\n // fetched per request by the site renderer, so the builder has nothing to show.\n collection: {\n component: CollectionPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Collection\",\n icon: \"list\",\n },\n heading: {\n component: Heading as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Heading\",\n icon: \"type\",\n },\n text: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Text\",\n icon: \"align-left\",\n },\n // Same payload, same renderer — deliberately. `richtext` differs from `text` only in how\n // an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML\n // string, so duplicating the component would be two things to keep in sync for no gain.\n richtext: {\n component: Text as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Rich text\",\n icon: \"pilcrow\",\n },\n image: {\n component: Image as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Image\",\n icon: \"image\",\n },\n button: {\n component: Button as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Button\",\n icon: \"square\",\n },\n spacer: {\n component: Spacer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Spacer\",\n icon: \"minus\",\n },\n columns: {\n component: Columns as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Columns\",\n icon: \"columns\",\n },\n video: {\n component: Video as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Video\",\n icon: \"play\",\n },\n section: {\n component: Section as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Section\",\n icon: \"layout\",\n },\n navbar: {\n component: Navbar as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Navbar\",\n icon: \"menu\",\n },\n footer: {\n component: Footer as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Footer\",\n icon: \"panel-bottom\",\n },\n slider: {\n component: Slider as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Slider\",\n icon: \"gallery-horizontal\",\n },\n tabs: {\n component: Tabs as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Tabs\",\n icon: \"folder\",\n },\n component: {\n component: ComponentInstance as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Component\",\n icon: \"component\",\n },\n form: {\n component: FormPlaceholder as unknown as React.ComponentType<Record<string, unknown>>,\n label: \"Form\",\n icon: \"clipboard-list\",\n },\n} as const;\n\nexport const BLOCK_TYPES = Object.keys(BLOCK_REGISTRY) as BlockType[];\n\nexport type { ContentBlock, BlockType };\n","import type { CSSProperties } from \"react\";\nimport { blockStyleToCss, type BlockStyle } from \"@bettercms-ai/types\";\n\n/**\n * FLO-264 — React-typed wrapper over the shared, framework-agnostic\n * `blockStyleToCss` in @bettercms-ai/types. One mapping, three consumers (this UI\n * renderer, the @bettercms-ai/next adapter, and — in string form — the hosted\n * render-page.ts).\n */\nexport function blockStyleToReactStyle(style: BlockStyle | undefined): CSSProperties | undefined {\n return blockStyleToCss(style) as CSSProperties | undefined;\n}\n","import type { ContentBlock } from \"@bettercms-ai/types\";\nimport { BLOCK_REGISTRY, type BlockRegistryEntry } from \"./registry\";\nimport { blockStyleToReactStyle } from \"./blockStyle\";\nimport type { BcmsBindingProps } from \"./binding\";\nimport React from \"react\";\n\n// Container blocks hold nested blocks and need the `renderBlock` callback threaded\n// so their children render recursively.\nconst CONTAINER_TYPES = new Set([\"columns\", \"section\", \"slider\", \"tabs\"]);\n\ninterface BlockRendererProps {\n block: ContentBlock;\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n /** Emit visual-editor field bindings. Default-off keeps published consumers clean. */\n annotate?: boolean;\n}\n\n/**\n * Direct scalar props of the four prose block types, and nothing else. `component`, `form` and\n * `collection` render as placeholders here (see blocks/Placeholders.tsx): this lane has no\n * definition lookup and no entry fetch, so it never emits the `<instanceId>__overrides.<key>`\n * addresses the delivery renderers do — resolution is theirs, through @bettercms-ai/ui/resolve.\n */\nfunction bindingProps(block: ContentBlock, annotate: boolean): BcmsBindingProps {\n if (!annotate || !block.id) return {};\n switch (block.type) {\n case \"heading\":\n case \"button\":\n return { \"data-bcms-field\": `${block.id}__text`, \"data-bcms-kind\": \"text\" };\n case \"text\":\n case \"richtext\":\n return { \"data-bcms-field\": `${block.id}__html`, \"data-bcms-kind\": \"richtext\" };\n case \"image\":\n return { \"data-bcms-field\": `${block.id}__src`, \"data-bcms-kind\": \"image\" };\n default:\n return {};\n }\n}\n\nfunction resolveBlock(block: ContentBlock, className?: string, annotate = false): React.ReactNode {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? \"\"}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n const Component = entry.component;\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = <Component {...block.props} className={className} {...bindingProps(block, annotate)} />;\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\nexport function BlockRenderer({\n block,\n className = \"\",\n renderBlock,\n annotate = false,\n}: BlockRendererProps) {\n const entry: BlockRegistryEntry | undefined = BLOCK_REGISTRY[block.type];\n\n if (!entry) {\n return (\n <div\n className={`w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`}\n >\n Unknown block type: <code>{block.type}</code>\n </div>\n );\n }\n\n // Container blocks (columns/section/slider/tabs) hold nested blocks, so thread the\n // renderBlock callback. Standalone usage (no BlocksRenderer above) falls back to\n // resolveBlock so one level of nesting still renders.\n const Component = entry.component;\n const containerProps = CONTAINER_TYPES.has(block.type)\n ? { renderBlock: renderBlock ?? ((child: ContentBlock) => resolveBlock(child, undefined, annotate)) }\n : {};\n const blockStyle = blockStyleToReactStyle(block.style);\n const el = (\n <Component\n {...block.props}\n className={className}\n {...containerProps}\n {...bindingProps(block, annotate)}\n />\n );\n return blockStyle ? <div style={blockStyle}>{el}</div> : el;\n}\n\ninterface BlocksRendererProps {\n blocks: ContentBlock[];\n className?: string;\n renderBlock?: (block: ContentBlock) => React.ReactNode;\n annotate?: boolean;\n}\n\nexport function BlocksRenderer({\n blocks,\n className = \"\",\n renderBlock,\n annotate = false,\n}: BlocksRendererProps) {\n if (blocks.length === 0) return null;\n\n return (\n <div className={className}>\n {blocks.map((block) => (\n <BlockRenderer\n key={block.id}\n block={block}\n renderBlock={renderBlock}\n annotate={annotate}\n />\n ))}\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","jsx","jsxs"]}
package/dist/resolve.d.ts CHANGED
@@ -15,13 +15,30 @@ type ResolverBlock = {
15
15
  id?: string;
16
16
  props?: unknown;
17
17
  };
18
- /** Minimal prop-def shape — the real ComponentPropDef is structurally wider. */
18
+ /** Minimal prop-def shape — the real ComponentPropDef is structurally wider.
19
+ *
20
+ * `type` is carried for callers that need it (the backfill reads it to decide which values are
21
+ * rich text). The resolver itself branches on the TARGET alone. @see writeRichTextOverride */
19
22
  type ResolverPropDef = {
20
23
  key: string;
24
+ type?: string;
21
25
  target: {
22
26
  blockId: string;
23
27
  path: string;
24
28
  };
29
+ /** A `table`/`group` prop's row shape — the ORDERED columns a row holds. @see expandRows */
30
+ config?: {
31
+ fields?: ResolverSubField[];
32
+ } & Record<string, unknown>;
33
+ };
34
+ /** One column of a `table` row. `fields` (or its `config.fields` mirror) makes it a nested table. */
35
+ type ResolverSubField = {
36
+ key: string;
37
+ type?: string;
38
+ fields?: ResolverSubField[];
39
+ config?: {
40
+ fields?: ResolverSubField[];
41
+ } & Record<string, unknown>;
25
42
  };
26
43
  /** The child block arrays a container holds, one entry per nested slot (all container types). */
27
44
  declare function childArrays<B extends ResolverBlock>(block: B): B[][];
@@ -29,11 +46,45 @@ declare function childArrays<B extends ResolverBlock>(block: B): B[][];
29
46
  declare function findBlockById<B extends ResolverBlock>(blocks: B[], id: string): B | null;
30
47
  /** Write `value` at a dotted path (e.g. "props.text"), creating objects as needed. */
31
48
  declare function setPath(obj: Record<string, unknown>, path: string, value: unknown): void;
49
+ /** `props.src` — the target that holds an IMAGE, whose canonical value carries its own alt. */
50
+ declare const IMAGE_PATH = "props.src";
51
+ /**
52
+ * `props.rows` — the one target path whose value is a LIST, and therefore REPEATS.
53
+ *
54
+ * 🔴 WHY THIS EXISTS. A repeater becomes one `table` prop pointing at a container block whose
55
+ * children are ONE ROW's blocks — the row TEMPLATE. `setPath` assigned that array to
56
+ * `props.rows`, and no renderer reads `props.rows`: `Section` renders `props.children`. So an
57
+ * FAQ with nine questions rendered the single seeded row, on the canvas, in delivery and in the
58
+ * SSG output alike, and the other eight were invisible everywhere except the dock that edited
59
+ * them.
60
+ */
61
+ declare const ROWS_PATH = "props.rows";
62
+ /**
63
+ * Can this container's ROW TEMPLATE be repeated for these columns?
64
+ *
65
+ * 🔴 THE ONE STATEMENT OF WHAT `expandRows` NEEDS, exported because the componentize lane has to
66
+ * ask the SAME question before it points a placement at an existing component. Keys and types
67
+ * matching is not enough: a component whose FAQ template holds two children for a one-column row
68
+ * (or a child nothing writes into) passes every prop check and then silently fails to expand —
69
+ * rows do not render, while the page fields that hold their copy are marked hidden. Two
70
+ * implementations of "does this template fit" would be two answers, and the pair that disagreed
71
+ * is exactly that bug.
72
+ *
73
+ * ONE template child per declared column, in order; a `table` column's child is a container of
74
+ * its own and recurses; every other child must be a block type a row cell can be written into.
75
+ */
76
+ declare function rowTemplateFits(target: ResolverBlock, fields: ResolverSubField[] | null): boolean;
32
77
  /**
33
78
  * Apply an instance's `overrides` onto a clone of a component's blockJson, honoring the
34
79
  * declared `props[]` allowlist (key → target block + path). Returns the original array
35
80
  * untouched (referentially stable) when there's nothing to override; returns [] for a
36
81
  * non-array input.
82
+ *
83
+ * Every prop but three is written verbatim at its `target.path`. The exceptions are the paths
84
+ * whose value is not a scalar the renderer can read where it lands: `props.html` is written as a
85
+ * `{ html, content }` PAIR, `props.src` as a `src` + `alt` pair, and `props.rows` REPEATS the
86
+ * target's row template once per row.
87
+ * @see writeRichTextOverride · @see writeImageOverride · @see expandRows
37
88
  */
38
89
  declare function applyOverrides<B extends ResolverBlock>(blockJson: unknown, propDefs: ResolverPropDef[], overrides: Record<string, unknown> | undefined): B[];
39
90
  /**
@@ -70,4 +121,4 @@ type ResolverComponentDef = {
70
121
  */
71
122
  declare function resolveDeep<B extends ResolverBlock>(blockJson: unknown, propDefs: ResolverPropDef[], overrides: Record<string, unknown> | undefined, lookup: (componentId: string) => ResolverComponentDef | undefined, seen?: readonly string[], depth?: number): B[];
72
123
 
73
- export { MAX_COMPONENT_DEPTH, type ResolverBlock, type ResolverComponentDef, type ResolverPropDef, applyOverrides, childArrays, findBlockById, resolveDeep, setPath };
124
+ export { IMAGE_PATH, MAX_COMPONENT_DEPTH, ROWS_PATH, type ResolverBlock, type ResolverComponentDef, type ResolverPropDef, type ResolverSubField, applyOverrides, childArrays, findBlockById, resolveDeep, rowTemplateFits, setPath };
package/dist/resolve.js CHANGED
@@ -36,6 +36,105 @@ function setPath(obj, path, value) {
36
36
  }
37
37
  cur[keys[keys.length - 1]] = value;
38
38
  }
39
+ var MARKUP_PATH = "props.html";
40
+ function writeRichTextOverride(target, value) {
41
+ const block = target;
42
+ if (typeof block.props !== "object" || block.props === null) block.props = {};
43
+ const props = block.props;
44
+ const envelope = value && typeof value === "object" && !Array.isArray(value) ? value : null;
45
+ if (envelope && typeof envelope.html === "string") {
46
+ props.html = envelope.html;
47
+ if (envelope.content === void 0) delete props.content;
48
+ else props.content = envelope.content;
49
+ return;
50
+ }
51
+ props.html = value;
52
+ delete props.content;
53
+ }
54
+ var writesMarkupSlot = (def) => def.target.path === MARKUP_PATH;
55
+ var IMAGE_PATH = "props.src";
56
+ function writeImageOverride(target, value) {
57
+ const block = target;
58
+ if (typeof block.props !== "object" || block.props === null) block.props = {};
59
+ const props = block.props;
60
+ if (value && typeof value === "object" && !Array.isArray(value)) {
61
+ const envelope = value;
62
+ const url = typeof envelope.url === "string" ? envelope.url : envelope.src;
63
+ if (typeof url === "string") props.src = url;
64
+ const alt = typeof envelope.altText === "string" ? envelope.altText : envelope.alt;
65
+ if (typeof alt === "string") props.alt = alt;
66
+ return;
67
+ }
68
+ props.src = value;
69
+ }
70
+ var writesImageSlot = (def) => def.target.path === IMAGE_PATH;
71
+ var ROWS_PATH = "props.rows";
72
+ var CELL_TYPES = {
73
+ text: ["heading", "text", "richtext"],
74
+ richtext: ["text", "richtext"],
75
+ url: ["button"],
76
+ image: ["image", "video"]
77
+ };
78
+ var ROW_SLOTS = {
79
+ heading: "props.text",
80
+ text: "props.html",
81
+ richtext: "props.html",
82
+ image: "props.src",
83
+ button: "props.href",
84
+ video: "props.src"
85
+ };
86
+ var subFieldsOf = (node) => node.fields ?? node.config?.fields ?? null;
87
+ function rowTemplateFits(target, fields) {
88
+ if (!fields) return false;
89
+ const props = target.props;
90
+ const template = Array.isArray(props?.children) ? props.children : null;
91
+ if (!template || template.length !== fields.length) return false;
92
+ return fields.every((field, column) => {
93
+ const child = template[column];
94
+ const nested = subFieldsOf(field);
95
+ if (field.type === "table" || nested) return rowTemplateFits(child, nested);
96
+ if (ROW_SLOTS[child.type] === void 0) return false;
97
+ const allowed = field.type === void 0 ? void 0 : CELL_TYPES[field.type];
98
+ return allowed === void 0 || allowed.includes(child.type);
99
+ });
100
+ }
101
+ function expandRows(target, value, fields) {
102
+ if (!Array.isArray(value) || !fields) return false;
103
+ if (!rowTemplateFits(target, fields)) return false;
104
+ const props = target.props;
105
+ const template = props.children;
106
+ const children = [];
107
+ value.forEach((row, index) => {
108
+ const cells = row ?? {};
109
+ template.forEach((block, column) => {
110
+ const copy = structuredClone(block);
111
+ suffixIds(copy, `#${index}`);
112
+ const field = fields[column];
113
+ const cell = cells[field.key];
114
+ const nested = subFieldsOf(field);
115
+ if (nested && Array.isArray(cell)) {
116
+ if (!expandRows(copy, cell, nested)) writeCell(copy, cell);
117
+ } else if (cell !== void 0) {
118
+ writeCell(copy, cell);
119
+ }
120
+ children.push(copy);
121
+ });
122
+ });
123
+ props.children = children;
124
+ props.rows = value;
125
+ return true;
126
+ }
127
+ function writeCell(block, cell) {
128
+ const path = ROW_SLOTS[block.type];
129
+ if (!path) return;
130
+ if (path === MARKUP_PATH) writeRichTextOverride(block, cell);
131
+ else if (path === IMAGE_PATH) writeImageOverride(block, cell);
132
+ else setPath(block, path, cell);
133
+ }
134
+ function suffixIds(block, suffix) {
135
+ if (typeof block.id === "string") block.id = `${block.id}${suffix}`;
136
+ for (const children of childArrays(block)) for (const child of children) suffixIds(child, suffix);
137
+ }
39
138
  function applyOverrides(blockJson, propDefs, overrides) {
40
139
  if (!Array.isArray(blockJson)) return [];
41
140
  if (!propDefs.length || !overrides || Object.keys(overrides).length === 0) {
@@ -45,7 +144,11 @@ function applyOverrides(blockJson, propDefs, overrides) {
45
144
  for (const def of propDefs) {
46
145
  if (!(def.key in overrides)) continue;
47
146
  const target = findBlockById(clone, def.target.blockId);
48
- if (target) setPath(target, def.target.path, overrides[def.key]);
147
+ if (!target) continue;
148
+ if (writesMarkupSlot(def)) writeRichTextOverride(target, overrides[def.key]);
149
+ else if (writesImageSlot(def)) writeImageOverride(target, overrides[def.key]);
150
+ else if (def.target.path === ROWS_PATH && expandRows(target, overrides[def.key], subFieldsOf(def))) {
151
+ } else setPath(target, def.target.path, overrides[def.key]);
49
152
  }
50
153
  return clone;
51
154
  }
@@ -113,11 +216,14 @@ function resolveDeep(blockJson, propDefs, overrides, lookup, seen = [], depth =
113
216
  return out;
114
217
  }
115
218
  export {
219
+ IMAGE_PATH,
116
220
  MAX_COMPONENT_DEPTH,
221
+ ROWS_PATH,
117
222
  applyOverrides,
118
223
  childArrays,
119
224
  findBlockById,
120
225
  resolveDeep,
226
+ rowTemplateFits,
121
227
  setPath
122
228
  };
123
229
  //# sourceMappingURL=resolve.js.map