{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"internals-markdown","type":"registry:lib","title":"Shared internals (markdown)","description":"The streaming-safe markdown renderer and its styles.","registryDependencies":["https://fancy-ui.rama.app/r/internals.json","https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/internals/markdown/Markdown.tsx","content":"\"use client\";\n\n/*\n * The two renderer components sit one directory below the parser they consume,\n * and that nesting is load-bearing.\n *\n * `Markdown.tsx` and `markdown.ts` differ only in case. Emitted side by side\n * into one flat output directory they are the same path on a case-insensitive\n * filesystem (macOS, Windows), and the declaration build — `tsc -p\n * tsconfig.build.json`, which compiles all of `src`, not just the entry graph —\n * wrote a single `dist/internals/markdown.d.ts` holding the parser's types with\n * this module's `Markdown` / `MarkdownProps` silently gone. The bundle half\n * (`preserveModules`, `entryFileNames: \"[name].js\"`) would collide the same way\n * the moment a component imported this file.\n *\n * Nesting keeps the two emitted paths distinct; the file names themselves are\n * unchanged, and `../markdown.js` names the parser unambiguously from here.\n */\nimport { Fragment, type ReactNode } from \"react\";\nimport { cn } from \"../../utils\";\nimport { parseMarkdown, type BlockToken } from \"../markdown\";\nimport { MarkdownInline } from \"./MarkdownInline\";\nimport \"./markdown.css\";\n\nexport interface MarkdownProps {\n\t/** Markdown source. Safe to update on every streamed chunk. */\n\ttext?: string;\n\tclassName?: string;\n\t/**\n\t * Already-parsed blocks, used when the component renders itself for a\n\t * blockquote. Consumers pass `text`.\n\t */\n\tblocks?: BlockToken[];\n\t/**\n\t * Rendered inline at the very end of the document — a streaming caret, say.\n\t * Every block here is block-level, so a caret placed after the component\n\t * would sit on its own line instead of trailing the last character; this\n\t * puts it inside the last paragraph, heading or list item instead. A\n\t * document ending in code, a table, a rule, or nothing at all has no inline\n\t * tail to hold it, and drops it.\n\t */\n\ttrailingCursor?: ReactNode;\n}\n\n/**\n * One block token. `cursor` is only ever passed for the last block of the\n * document; every other block receives `undefined` and renders no tail.\n */\nfunction renderBlock(block: BlockToken, cursor: ReactNode): ReactNode {\n\tswitch (block.type) {\n\t\tcase \"paragraph\":\n\t\t\treturn (\n\t\t\t\t<p className=\"ft-md-p\">\n\t\t\t\t\t<MarkdownInline tokens={block.children} />\n\t\t\t\t\t{cursor}\n\t\t\t\t</p>\n\t\t\t);\n\t\tcase \"heading\": {\n\t\t\tconst Heading = `h${block.depth}` as const;\n\t\t\treturn (\n\t\t\t\t<Heading className=\"ft-md-h\" data-depth={block.depth}>\n\t\t\t\t\t<MarkdownInline tokens={block.children} />\n\t\t\t\t\t{cursor}\n\t\t\t\t</Heading>\n\t\t\t);\n\t\t}\n\t\tcase \"code\":\n\t\t\treturn (\n\t\t\t\t<pre className=\"ft-md-pre\" data-lang={block.lang || undefined}>\n\t\t\t\t\t<code>{block.text}</code>\n\t\t\t\t</pre>\n\t\t\t);\n\t\tcase \"list\": {\n\t\t\tconst items = block.items.map((item, itemIndex) => (\n\t\t\t\t<li key={itemIndex}>\n\t\t\t\t\t<MarkdownInline tokens={item} />\n\t\t\t\t\t{itemIndex === block.items.length - 1 ? cursor : undefined}\n\t\t\t\t</li>\n\t\t\t));\n\t\t\treturn block.ordered ? (\n\t\t\t\t<ol className=\"ft-md-ol\" start={block.start}>\n\t\t\t\t\t{items}\n\t\t\t\t</ol>\n\t\t\t) : (\n\t\t\t\t<ul className=\"ft-md-ul\">{items}</ul>\n\t\t\t);\n\t\t}\n\t\tcase \"blockquote\":\n\t\t\treturn (\n\t\t\t\t<blockquote className=\"ft-md-quote\">\n\t\t\t\t\t<Markdown blocks={block.children} trailingCursor={cursor} />\n\t\t\t\t</blockquote>\n\t\t\t);\n\t\tcase \"table\":\n\t\t\treturn (\n\t\t\t\t<div className=\"ft-md-table-scroll\">\n\t\t\t\t\t<table className=\"ft-md-table\">\n\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t{block.header.map((cell, i) => (\n\t\t\t\t\t\t\t\t\t<th key={i} style={{ textAlign: block.align[i] ?? undefined }}>\n\t\t\t\t\t\t\t\t\t\t<MarkdownInline tokens={cell} />\n\t\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t{block.rows.map((row, rowIndex) => (\n\t\t\t\t\t\t\t\t<tr key={rowIndex}>\n\t\t\t\t\t\t\t\t\t{row.map((cell, i) => (\n\t\t\t\t\t\t\t\t\t\t<td key={i} style={{ textAlign: block.align[i] ?? undefined }}>\n\t\t\t\t\t\t\t\t\t\t\t<MarkdownInline tokens={cell} />\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</tbody>\n\t\t\t\t\t</table>\n\t\t\t\t</div>\n\t\t\t);\n\t\tcase \"hr\":\n\t\t\treturn <hr className=\"ft-md-hr\" />;\n\t}\n}\n\nexport function Markdown({ text = \"\", className, blocks, trailingCursor }: MarkdownProps) {\n\tconst nodes = blocks ?? parseMarkdown(text);\n\tconst lastIndex = nodes.length - 1;\n\n\treturn (\n\t\t<div className={cn(\"ft-md\", className)}>\n\t\t\t{nodes.map((block, index) => (\n\t\t\t\t// Blocks have no identity beyond their position; a streamed document\n\t\t\t\t// is re-parsed and re-rendered whole on every chunk.\n\t\t\t\t<Fragment key={index}>\n\t\t\t\t\t{renderBlock(block, index === lastIndex ? trailingCursor : undefined)}\n\t\t\t\t</Fragment>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n","type":"registry:lib","target":"@ui/fancy/internals/markdown/Markdown.tsx"},{"path":"react/src/internals/markdown/MarkdownInline.tsx","content":"\"use client\";\n\nimport { Fragment, type ReactNode } from \"react\";\nimport type { InlineToken } from \"../markdown\";\nimport \"./markdown.css\";\n\nexport interface MarkdownInlineProps {\n\ttokens: InlineToken[];\n}\n\n/**\n * One inline token. Never builds an HTML string — the token tree is walked and\n * turned into elements directly, which is what keeps `sanitizeHref`\n * load-bearing and anything unrecognised a plain text node.\n */\nfunction renderToken(token: InlineToken): ReactNode {\n\tswitch (token.type) {\n\t\tcase \"text\":\n\t\t\treturn token.text;\n\t\tcase \"code\":\n\t\t\treturn <code className=\"ft-md-code\">{token.text}</code>;\n\t\tcase \"strong\":\n\t\t\treturn (\n\t\t\t\t<strong>\n\t\t\t\t\t<MarkdownInline tokens={token.children} />\n\t\t\t\t</strong>\n\t\t\t);\n\t\tcase \"em\":\n\t\t\treturn (\n\t\t\t\t<em>\n\t\t\t\t\t<MarkdownInline tokens={token.children} />\n\t\t\t\t</em>\n\t\t\t);\n\t\tcase \"del\":\n\t\t\treturn (\n\t\t\t\t<del>\n\t\t\t\t\t<MarkdownInline tokens={token.children} />\n\t\t\t\t</del>\n\t\t\t);\n\t\tcase \"link\":\n\t\t\t// A rejected destination keeps the label and loses the anchor.\n\t\t\treturn token.href !== null ? (\n\t\t\t\t<a\n\t\t\t\t\tclassName=\"ft-md-link\"\n\t\t\t\t\thref={token.href}\n\t\t\t\t\trel=\"noopener noreferrer nofollow ugc\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t>\n\t\t\t\t\t<MarkdownInline tokens={token.children} />\n\t\t\t\t</a>\n\t\t\t) : (\n\t\t\t\t<MarkdownInline tokens={token.children} />\n\t\t\t);\n\t}\n}\n\nexport function MarkdownInline({ tokens }: MarkdownInlineProps) {\n\treturn (\n\t\t<>\n\t\t\t{tokens.map((token, index) => (\n\t\t\t\t// A token list has no stable identity of its own; the index is the\n\t\t\t\t// only key available, and the tree is re-rendered wholesale on every\n\t\t\t\t// streamed chunk anyway.\n\t\t\t\t<Fragment key={index}>{renderToken(token)}</Fragment>\n\t\t\t))}\n\t\t</>\n\t);\n}\n","type":"registry:lib","target":"@ui/fancy/internals/markdown/MarkdownInline.tsx"},{"path":"react/src/internals/markdown/markdown.css","content":"/*\n * Typographic normalization, not a theme. Rendered markdown has to stay\n * legible in a host app whose reset has stripped list markers and heading\n * scale, so these defaults restore structure and nothing else — no palette,\n * no font stack, no spacing opinions beyond the flow rhythm. Every value the\n * host is likely to disagree with reads from a custom property first, and\n * colours are mixed from `currentColor` so a dark surface needs no override.\n *\n * The Svelte source scoped these rules through the compiler; as plain CSS they\n * are global, so every rule is anchored on the component's own `.ft-md*`\n * classes — the same names the source uses, which are already specific enough\n * to need no port-added anchor. The blockquote branch renders the component\n * recursively and a recursive instance carries the same `.ft-md` root class,\n * so the block rules reach quoted content exactly as they did before.\n *\n * Both halves live here: `Markdown.tsx` owns the block marks, `MarkdownInline.tsx`\n * the inline ones (code spans, links), and the contract ships one stylesheet.\n * It stays colocated with the two components that import it, which is why it\n * moved down with them into `internals/markdown/` — see the header of\n * `Markdown.tsx` for why that directory exists.\n */\n\n.ft-md {\n\tline-height: 1.6;\n}\n\n/* One rhythm for every block, whatever margins the UA or reset supplies. */\n.ft-md > * {\n\tmargin: 0;\n}\n\n.ft-md > * + * {\n\tmargin-block-start: 0.75em;\n}\n\n.ft-md > * + .ft-md-h {\n\tmargin-block-start: 1.25em;\n}\n\n.ft-md > * + .ft-md-hr,\n.ft-md > .ft-md-hr + * {\n\tmargin-block-start: 1em;\n}\n\n.ft-md-h {\n\tfont-weight: 600;\n\tline-height: 1.25;\n}\n\n.ft-md-h[data-depth=\"1\"] {\n\tfont-size: 1.5em;\n}\n\n.ft-md-h[data-depth=\"2\"] {\n\tfont-size: 1.3em;\n}\n\n.ft-md-h[data-depth=\"3\"] {\n\tfont-size: 1.15em;\n}\n\n.ft-md-h[data-depth=\"4\"] {\n\tfont-size: 1.05em;\n}\n\n.ft-md-h[data-depth=\"5\"] {\n\tfont-size: 1em;\n}\n\n.ft-md-h[data-depth=\"6\"] {\n\tfont-size: 0.95em;\n}\n\n.ft-md-ul {\n\tlist-style: disc;\n\tpadding-inline-start: 1.5em;\n}\n\n.ft-md-ol {\n\tlist-style: decimal;\n\tpadding-inline-start: 1.5em;\n}\n\n.ft-md-quote {\n\tborder-inline-start: 3px solid\n\t\tvar(--ft-md-quote-border, color-mix(in oklab, currentColor 25%, transparent));\n\tpadding-inline-start: 0.75em;\n\tcolor: var(--ft-md-quote-color, color-mix(in oklab, currentColor 75%, transparent));\n}\n\n.ft-md-pre {\n\tbackground: var(--ft-md-code-bg, color-mix(in oklab, currentColor 8%, transparent));\n\tborder-radius: 6px;\n\tpadding: 0.75em 1em;\n\toverflow-x: auto;\n\tfont-size: 0.9em;\n\tline-height: 1.5;\n}\n\n/* The wrapper scrolls so a wide table never widens the whole message. */\n.ft-md-table-scroll {\n\toverflow-x: auto;\n}\n\n.ft-md-table {\n\tborder-collapse: collapse;\n}\n\n.ft-md-table th,\n.ft-md-table td {\n\tborder: 1px solid var(--ft-md-border, color-mix(in oklab, currentColor 15%, transparent));\n\tpadding: 0.35em 0.6em;\n}\n\n.ft-md-table th {\n\tfont-weight: 600;\n}\n\n.ft-md-hr {\n\tborder: 0;\n\tborder-block-start: 1px solid\n\t\tvar(--ft-md-border, color-mix(in oklab, currentColor 15%, transparent));\n}\n\n/*\n * The inline half of the markdown defaults. Both marks below go invisible under\n * a reset that strips link underlines and code backgrounds, which is what makes\n * them worth shipping. Colours mix from `currentColor` so they follow the\n * surface, and both read from a custom property first so a host can restyle them.\n */\n\n.ft-md-code {\n\tbackground: var(--ft-md-code-bg, color-mix(in oklab, currentColor 8%, transparent));\n\tborder-radius: 4px;\n\tpadding: 0.1em 0.35em;\n\tfont-size: 0.9em;\n}\n\n.ft-md-link {\n\tcolor: var(--ft-md-link, inherit);\n\ttext-decoration: underline;\n\ttext-underline-offset: 2px;\n}\n\n.ft-md-link:hover {\n\topacity: 0.8;\n}\n","type":"registry:lib","target":"@ui/fancy/internals/markdown/markdown.css"}]}