--- ## StreamingMarkdown Streaming-safe markdown renderer for LLM output. Parses a growing `content` string incrementally (settled blocks are never re-rendered) and renders to a real component tree — never through `{@html}`, so raw HTML in the source stays inert text. Links and images pass a strict-by-default URL policy (external images blocked unless allowlisted). Citation markers like `[1]` resolve to CitationChip when a matching entry exists in `sources`; code fences render through CodeBlock. Per-node-type `renderers` snippets hook in syntax highlighting, custom links, images, or citations without adding dependencies. **Import:** `import { StreamingMarkdown } from '@urbicon-ui/blocks';` ### Examples ```svelte ``` ```svelte ``` ### Variants - size: md, sm (default: md) ### Api | Prop | Type | Required | Default | Description | | --- | --- | :---: | --- | --- | | content | `string` | yes | | Markdown source. Append-only growth streams incrementally; any other change re-parses. | | ...HTMLAttributes | `HTMLAttributes` | no | | HTML attributes (excluding: 'children') | | ...StreamingMarkdownVariants | `VariantProps` | no | | Styling variants from StreamingMarkdownVariants | | autolink | `boolean` | no | false | Recognize bare `https://…` autolinks (GFM-style) | | class | `string` | no | | Additional CSS classes to apply to the StreamingMarkdown component | | headingLevelStart | `1 | 2 | 3 | 4 | 5 | 6` | no | 1 | DOM heading level that markdown `#` maps to (deeper levels shift along, clamped at h6). Visual sizing keeps following the author's level. Set to 3 in chat surfaces so message headings stay out of the page outline. | | linkTarget | `'_blank' | '_self'` | no | '_blank' | Target for rendered links; `rel="noopener noreferrer"` is added for `_blank` | | preset | `string` | no | | Apply a named preset registered via ``. Prefer this over `class` overrides for reusable custom looks. | | renderers | `MarkdownRenderers` | no | | Per-node-type snippet overrides (code highlighting, router links, lightboxes, …) | | size | `'sm' | 'md'` | no | 'md' | Type scale: `md` inherits the surrounding font size, `sm` is compact | | slotClasses | `Partial>` | no | | Per-slot class overrides. Slots: base | paragraph | heading1–6 | inlineCode | link | linkBlocked | image | imageBlocked | listUnordered | listOrdered | listItem | taskItem | taskCheckbox | blockquote | codeBlock | tableWrapper | table | tableRow | tableHeadCell | tableCell | hr | cursor | | sources | `CitationSource[]` | no | | Citation sources. Ids activate `[id]` / `【id】` markers in the content, rendering them as CitationChip (1-based index follows array order). Markers without a matching id stay plain text. | | streaming | `boolean` | no | false | Shows a pulsing cursor after the last block while the answer streams | | tableRegionLabel | `string` | no | 'Table' | aria-label for the focusable scrollable region around tables | | unstyled | `boolean` | no | | Remove default styles | | urlPolicy | `MarkdownUrlPolicy` | no | | URL policy for links and images. Strict by default: links limited to http/https/mailto/tel + relative, every external image blocked. Keep the object referentially stable — a new reference re-parses the content. | Inherited from: - StreamingMarkdownVariants (external) - Omit, 'children'> (omit-pattern) ### Types ```ts interface CitationSource { /** Stable id — matches the `[id]` marker in the streamed markdown. */ id: string; /** Human-readable source title, always shown in the popover (and as the chip label under `citationStyle="label"`). */ title: string; /** Source URL. Rendered as an outbound link only when it passes the `urlPolicy`; a blocked or absent URL shows title/snippet with no link. */ url?: string; /** Short excerpt shown under the title, clamped to ~3 lines. */ snippet?: string; } ``` ```ts interface MarkdownUrlPolicy { /** * Allowed link protocols (with trailing colon). Relative URLs are always * allowed for links. @default ['http:', 'https:', 'mailto:', 'tel:'] */ allowedLinkProtocols?: string[]; /** * Prefix allowlist for image sources, matched against the normalized * absolute URL. Empty (the default) blocks every external image; relative * sources are allowed. Prefer deep, specific prefixes — broad ones are * open-redirect bait. `data:` images are blocked unless a `data:` prefix * is listed explicitly. * @default [] */ allowedImagePrefixes?: string[]; /** Observability hook — fires once per blocked URL occurrence. */ onBlocked?: (kind: 'link' | 'image', url: string) => void; } ``` ```ts interface MarkdownRenderers { codeBlock?: Snippet<[{ code: string; lang?: string; open?: boolean }]>; /** `href` is already policy-checked: empty string when `blocked` is true. */ link?: Snippet<[{ href: string; title?: string; children: InlineNode[]; blocked: boolean }]>; /** `src` is already policy-checked: empty string when `blocked` is true. */ image?: Snippet<[{ src: string; alt: string; title?: string; blocked: boolean }]>; /** `source.url` is already policy-checked: stripped (undefined) when blocked. */ citation?: Snippet<[{ id: string; source?: CitationSource; index?: number }]>; } ``` ```ts type InlineNode = | { kind: 'text'; text: string } | { kind: 'strong'; children: InlineNode[] } | { kind: 'em'; children: InlineNode[] } | { kind: 'strike'; children: InlineNode[] } | { kind: 'code'; text: string } | { kind: 'link'; /** Empty string when `blocked` is true — the original URL never reaches the DOM. */ href: string; title?: string; children: InlineNode[]; /** Set when the URL failed the `MarkdownUrlPolicy`; render as inert text. */ blocked?: boolean; } | { kind: 'image'; /** Empty string when `blocked` is true. */ src: string; alt: string; title?: string; blocked?: boolean; } | { /** `[id]` / `【id】` citation marker; emitted only for ids in `MarkdownParseOptions.citationIds`. */ kind: 'citation'; id: string; } | { kind: 'break' } ``` ```ts type SlotNames = keyof ReturnType & string ``` ```ts type VariantProps = Omit< Exclude[0], undefined>, 'class' > ``` ### Slots (slotClasses keys) `base`, `paragraph`, `heading1`, `heading2`, `heading3`, `heading4`, `heading5`, `heading6`, `inlineCode`, `link`, `linkBlocked`, `image`, `imageBlocked`, `listUnordered`, `listOrdered`, `listItem`, `taskItem`, `taskCheckbox`, `blockquote`, `codeBlock`, `tableWrapper`, `table`, `tableRow`, `tableHeadCell`, `tableCell`, `hr`, `cursor`