UNPKG

@humanspeak/svelte-markdown

Version:

Markdown and HTML renderer for Svelte 5 — built for rendering streaming AI agent output from Claude Code, ChatGPT, and agentic workflows. XSS-safe defaults, streaming-aware sanitization, token caching, TypeScript types, and Svelte 5 runes.

289 lines (288 loc) 9.26 kB
/** * Type definitions for the Svelte Markdown component. * * This module provides TypeScript type definitions for the core functionality * of the Svelte Markdown parser and renderer. It defines the primary interface * for component props and integrates with the marked library's token system. * * Typical usage example: * ```typescript * import type { SvelteMarkdownProps } from './types'; * * const markdownProps: SvelteMarkdownProps = { * source: "# Hello World", * isInline: false * }; * ``` * * @packageDocumentation */ import type { MarkedExtension, Token, TokensList } from 'marked'; import type { Snippet } from 'svelte'; import type { MarkedOptions, Renderers } from './utils/markdown-parser.js'; import type { HtmlKey } from './utils/rendererKeys.js'; import type { SanitizeAttributesFn, SanitizeUrlFn } from './utils/sanitize.js'; export interface ParagraphSnippetProps { raw?: string; text?: string; children?: Snippet; } export interface HeadingSnippetProps { depth: number; raw: string; text: string; options: SvelteMarkdownOptions; slug: (val: string) => string; children?: Snippet; } export interface LinkSnippetProps { href?: string; title?: string; raw?: string; text?: string; children?: Snippet; } export interface ImageSnippetProps { href?: string; title?: string; text?: string; raw?: string; } export interface CodeSnippetProps { lang: string; text: string; codeBlockStyle?: 'indented'; } export interface CodespanSnippetProps { raw: string; text?: string; } export interface BlockquoteSnippetProps { raw?: string; text?: string; children?: Snippet; } export interface ListSnippetProps { ordered?: boolean; start?: number; loose?: boolean; children?: Snippet; } export interface ListItemSnippetProps { text?: string; task?: boolean; checked?: boolean; loose?: boolean; children?: Snippet; listItemIndex?: number; } export interface TableSnippetProps { children?: Snippet; } export interface TableHeadSnippetProps { children?: Snippet; } export interface TableBodySnippetProps { children?: Snippet; } export interface TableRowSnippetProps { children?: Snippet; } export interface TableCellSnippetProps { header: boolean; align: 'left' | 'center' | 'right' | null; children?: Snippet; } export interface EmSnippetProps { raw?: string; text?: string; children?: Snippet; } export interface StrongSnippetProps { raw?: string; text?: string; children?: Snippet; } export interface DelSnippetProps { raw?: string; text?: string; children?: Snippet; } export type HrSnippetProps = Record<string, never>; export type BrSnippetProps = Record<string, never>; export interface TextSnippetProps { raw?: string; text?: string; children?: Snippet; } export interface RawTextSnippetProps { text: string; } export interface EscapeSnippetProps { text: string; raw?: string; } export type SnippetOverrides = { paragraph?: Snippet<[ParagraphSnippetProps]>; heading?: Snippet<[HeadingSnippetProps]>; link?: Snippet<[LinkSnippetProps]>; image?: Snippet<[ImageSnippetProps]>; code?: Snippet<[CodeSnippetProps]>; codespan?: Snippet<[CodespanSnippetProps]>; blockquote?: Snippet<[BlockquoteSnippetProps]>; list?: Snippet<[ListSnippetProps]>; listitem?: Snippet<[ListItemSnippetProps]>; orderedlistitem?: Snippet<[ListItemSnippetProps]>; unorderedlistitem?: Snippet<[ListItemSnippetProps]>; table?: Snippet<[TableSnippetProps]>; tablehead?: Snippet<[TableHeadSnippetProps]>; tablebody?: Snippet<[TableBodySnippetProps]>; tablerow?: Snippet<[TableRowSnippetProps]>; tablecell?: Snippet<[TableCellSnippetProps]>; em?: Snippet<[EmSnippetProps]>; strong?: Snippet<[StrongSnippetProps]>; del?: Snippet<[DelSnippetProps]>; hr?: Snippet<[HrSnippetProps]>; br?: Snippet<[BrSnippetProps]>; text?: Snippet<[TextSnippetProps]>; rawtext?: Snippet<[RawTextSnippetProps]>; escape?: Snippet<[EscapeSnippetProps]>; }; /** * Props passed to HTML snippet overrides. * * **Security note:** By default, `attributes` are sanitized in the Parser before * reaching any renderer or snippet — event handlers (`on*`) are stripped and * URL attributes are validated against a protocol allowlist. To customize this * behavior, pass a `sanitizeUrl` prop to `SvelteMarkdown`. */ export interface HtmlSnippetProps { attributes?: Record<string, string | number | boolean | undefined>; children?: Snippet; } export type HtmlSnippetOverrides = { [K in HtmlKey as `html_${K}`]?: Snippet<[HtmlSnippetProps]>; }; export interface StreamingOffsetChunk { value: string; offset: number; } export type StreamingChunk = string | StreamingOffsetChunk; export type SvelteMarkdownProps<T extends Renderers = Renderers> = { /** * Markdown content to render. * * Pass a `string` for raw markdown that will be parsed, or a pre-parsed * `Token[]` array to skip parsing entirely. An empty string renders nothing. */ source: Token[] | string; /** * Partial map of renderer overrides merged with the built-in defaults. * * Only the keys you provide are replaced — all others keep their default * component. Use the `allowRenderersOnly` / `excludeRenderersOnly` * helpers to disable entire categories of renderers. */ renderers?: Partial<T>; /** * Options forwarded to the `marked` lexer, extended with header-ID * generation settings. Merged with {@link defaultOptions}. */ options?: Partial<SvelteMarkdownOptions>; /** * Array of marked extensions to apply when parsing. * * Internally creates a scoped `Marked` instance, extracts its resolved * defaults, and merges them into the parser options so the lexer * recognises any custom token types the extensions define. * * Extension token names are also used to collect snippet overrides, * enabling both component-renderer and snippet-based rendering of * custom tokens. * * @defaultValue `[]` * * @example * ```svelte * <script lang="ts"> * import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions' * </script> * * <SvelteMarkdown * source={markdown} * extensions={[markedKatex()]} * renderers={{ inlineKatex: KatexRenderer, blockKatex: KatexRenderer }} * /> * ``` */ extensions?: MarkedExtension[]; /** * When `true`, the source is parsed with `Lexer.lexInline()` instead of * `Lexer.lex()`, producing only inline tokens (no block elements). * * @defaultValue `false` */ isInline?: boolean; /** * Enables optimized rendering for LLM streaming scenarios. * * When `true`, the component performs a full re-parse on each source * update but diffs the resulting tokens against the previous parse. * Only changed or appended tokens trigger DOM updates, keeping render * cost proportional to the change rather than the full document size. * * Use this when appending tokens to `source` in a streaming fashion * (e.g., ChatGPT/Claude SSE responses). * * @defaultValue `false` */ streaming?: boolean; /** * Callback invoked after the source has been parsed into tokens. * * Receives the full token array before rendering begins. Useful for * inspecting or transforming the parsed AST. * * @param tokens - The parsed token array or `TokensList`. */ parsed?: (tokens: Token[] | TokensList) => void; /** * Custom URL sanitizer applied in the Parser before tokens reach any * renderer component or snippet. Receives a URL string and must return * a sanitized URL (or `''` to strip it). * * The default allowlists `http:`, `https:`, `mailto:`, `tel:`, and * relative URLs. Override to implement a custom policy. * * @defaultValue {@link defaultSanitizeUrl} */ sanitizeUrl?: SanitizeUrlFn; /** * Custom HTML attribute sanitizer applied in the Parser before tokens * reach any renderer component or snippet. Receives the attribute map, * a {@link SanitizeContext} with token type and HTML tag name, and the * active `sanitizeUrl` function. * * The default strips all `on*` event handlers and runs URL-bearing * attributes (`href`, `src`, `action`, etc.) through `sanitizeUrl`. * Override to add custom attribute rules per tag. * * @defaultValue {@link defaultSanitizeAttributes} */ sanitizeAttributes?: SanitizeAttributesFn; } & Partial<SnippetOverrides> & Partial<HtmlSnippetOverrides>; export interface SvelteMarkdownOptions extends MarkedOptions { /** * When `true`, an `id` attribute is generated for every heading element * using `github-slugger`. * * @defaultValue `true` */ headerIds?: boolean; /** * String prepended to every generated heading `id`. * * @defaultValue `''` */ headerPrefix?: string; }