svelte-streamdown 3.0.0 → 3.1.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.
- package/README.md +260 -139
- package/dist/Elements/FootnoteRef.svelte +1 -1
- package/dist/Elements/Mermaid.svelte +1 -1
- package/dist/Streamdown.svelte +17 -4
- package/dist/context.svelte.d.ts +5 -1
- package/dist/marked/index.d.ts +15 -1
- package/dist/marked/index.js +123 -10
- package/dist/marked/marked-alert.js +12 -3
- package/dist/marked/marked-align.js +9 -5
- package/dist/marked/marked-citations.js +4 -3
- package/dist/marked/marked-dl.js +8 -3
- package/dist/marked/marked-footnotes.js +7 -6
- package/dist/marked/marked-hr.js +4 -3
- package/dist/marked/marked-list.js +55 -18
- package/dist/marked/marked-math.js +12 -14
- package/dist/marked/marked-table.js +14 -34
- package/dist/utils/parse-incomplete-markdown.js +127 -66
- package/dist/utils/url.js +6 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -73,7 +73,6 @@ Full support for
|
|
|
73
73
|
> [!NOTE]
|
|
74
74
|
> 🧠 **AI Prompting Tip:** For best results, use our [comprehensive prompt](/prompting) covering all supported markdown features.
|
|
75
75
|
|
|
76
|
-
|
|
77
76
|
### 💻 Interactive Code Blocks
|
|
78
77
|
|
|
79
78
|
- Syntax highlighting powered by Shiki
|
|
@@ -82,16 +81,28 @@ Full support for
|
|
|
82
81
|
|
|
83
82
|
### 🔢 Mathematical Expressions
|
|
84
83
|
|
|
85
|
-
LaTeX math support through KaTeX:
|
|
84
|
+
LaTeX math support through KaTeX. Use single dollars for **inline** math and double dollars for **block** (display) math:
|
|
86
85
|
|
|
87
|
-
-
|
|
88
|
-
- Inline math: $E = mc^2$
|
|
86
|
+
- Inline math: `$E = mc^2$` renders inline as $E = mc^2$
|
|
89
87
|
- Block math:
|
|
90
88
|
|
|
91
89
|
$$
|
|
92
|
-
|
|
90
|
+
f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2}
|
|
93
91
|
$$
|
|
94
92
|
|
|
93
|
+
KaTeX is an **opt-in** heavy component, so you must import the `Math` component and pass it via the `components` prop. Without it, math is rendered as raw text:
|
|
94
|
+
|
|
95
|
+
```svelte
|
|
96
|
+
<script>
|
|
97
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
98
|
+
import Math from 'svelte-streamdown/math'; // KaTeX math rendering
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<Streamdown {content} components={{ math: Math }} />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Pass KaTeX options through the [`katexConfig`](#-props-api) prop (e.g. to set `throwOnError` or macros). See [Bundle Optimization](#-bundle-optimization) for details on enabling heavy components.
|
|
105
|
+
|
|
95
106
|
### 🧜♀️ Mermaid Diagrams
|
|
96
107
|
|
|
97
108
|
- Render Mermaid diagrams from code blocks
|
|
@@ -99,7 +110,7 @@ $$
|
|
|
99
110
|
- Pan and Zoom
|
|
100
111
|
- Full screen mode
|
|
101
112
|
|
|
102
|
-
**Example:**
|
|
113
|
+
# **Example:**
|
|
103
114
|
|
|
104
115
|
```mermaid
|
|
105
116
|
graph TD
|
|
@@ -139,11 +150,11 @@ pie title Project Time Allocation
|
|
|
139
150
|
|
|
140
151
|
| H1 | H2 | H3 |
|
|
141
152
|
| ------------------------- | --- | --- |
|
|
142
|
-
| This cell spans 3 columns
|
|
153
|
+
| This cell spans 3 columns | | |
|
|
143
154
|
|
|
144
155
|
| Header 1 | Header 2 | Header 3 |
|
|
145
156
|
| ------------------------- | -------- | -------- |
|
|
146
|
-
| This cell spans 2 columns
|
|
157
|
+
| This cell spans 2 columns | | Normal |
|
|
147
158
|
| Normal | Normal | Normal |
|
|
148
159
|
|
|
149
160
|
#### Rowspan
|
|
@@ -159,7 +170,7 @@ pie title Project Time Allocation
|
|
|
159
170
|
| --------------- | -------- |
|
|
160
171
|
| Cell B | Cell A |
|
|
161
172
|
| --------------- | -------- |
|
|
162
|
-
| Footer
|
|
173
|
+
| Footer | |
|
|
163
174
|
|
|
164
175
|
#### Column alignment
|
|
165
176
|
|
|
@@ -254,23 +265,23 @@ To enable inline citations, pass a `sources` object as a prop to the `Streamdown
|
|
|
254
265
|
|
|
255
266
|
```svelte
|
|
256
267
|
<script>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
268
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
269
|
+
|
|
270
|
+
let content = `According to [smith2023], AI is advancing rapidly. See also [nested.subsection] for related work.`;
|
|
271
|
+
|
|
272
|
+
let sources = {
|
|
273
|
+
smith2023: {
|
|
274
|
+
title: 'AI Research Paper',
|
|
275
|
+
url: 'https://example.com/paper',
|
|
276
|
+
content: 'Detailed content of the citation...'
|
|
277
|
+
},
|
|
278
|
+
nested: {
|
|
279
|
+
subsection: {
|
|
280
|
+
title: 'Nested Citation',
|
|
281
|
+
url: 'https://example.com/nested'
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
274
285
|
</script>
|
|
275
286
|
|
|
276
287
|
<Streamdown {content} {sources} />
|
|
@@ -284,7 +295,6 @@ Citations work with objects containing these properties:
|
|
|
284
295
|
- `url (or href, url, link or source)`: Link to the source
|
|
285
296
|
- `content (or text, summary or excerpt)`: Rich content to display in carousel mode
|
|
286
297
|
|
|
287
|
-
|
|
288
298
|
#### Display Modes
|
|
289
299
|
|
|
290
300
|
Streamdown offers two ways to display citations:
|
|
@@ -305,6 +315,7 @@ You can control the display mode using the `inlineCitationsMode` prop:
|
|
|
305
315
|
#### Citation Popovers
|
|
306
316
|
|
|
307
317
|
Citations appear as clickable buttons that open popovers when clicked. The popover shows:
|
|
318
|
+
|
|
308
319
|
- Source title and URL (when available)
|
|
309
320
|
- Favicon from the source domain
|
|
310
321
|
- Rich content (in carousel mode)
|
|
@@ -316,22 +327,23 @@ If your citation data structure doesn't match the default format, you can custom
|
|
|
316
327
|
|
|
317
328
|
```svelte
|
|
318
329
|
<Streamdown {content} {sources}>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
{#snippet inlineCitationPreview({ token })}
|
|
331
|
+
<!-- Customize the clickable citation button -->
|
|
332
|
+
{token.keys[0]}
|
|
333
|
+
{/snippet}
|
|
334
|
+
|
|
335
|
+
{#snippet inlineCitationContent({ source, key, token })}
|
|
336
|
+
<!-- Customize content displayed in popover -->
|
|
337
|
+
<div class="custom-content">
|
|
338
|
+
<h4>{source.customTitle || key}</h4>
|
|
339
|
+
<p>{source.customDescription}</p>
|
|
340
|
+
</div>
|
|
341
|
+
{/snippet}
|
|
331
342
|
</Streamdown>
|
|
332
343
|
```
|
|
333
344
|
|
|
334
345
|
These snippets allow you to:
|
|
346
|
+
|
|
335
347
|
- **`inlineCitationPreview`**: Customize the content of the clickable button that appears in the text
|
|
336
348
|
- **`inlineCitationContent`**: Customize how individual citation content is displayed within popovers
|
|
337
349
|
- **`inlineCitationPopover`**: Completely customize the list of citations
|
|
@@ -340,15 +352,16 @@ These snippets allow you to:
|
|
|
340
352
|
|
|
341
353
|
This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
|
|
342
354
|
|
|
343
|
-
| Aspect | Original (React) | Svelte Port
|
|
344
|
-
| ----------------- | ------------------------ |
|
|
345
|
-
| **Framework** | React | Svelte 5
|
|
346
|
-
| **Component API** | JSX Components | Svelte Snippets
|
|
347
|
-
| **Styling** | Tailwind CSS | Tailwind CSS (compatible)
|
|
348
|
-
| **Context** | React Context | Svelte Context
|
|
349
|
-
| **Build System** | Vite/React | Vite/SvelteKit
|
|
350
|
-
| **TypeScript** | Full TS support | Full TS support
|
|
351
|
-
| **Engine** | Remark / Rehype + marked | marked only
|
|
355
|
+
| Aspect | Original (React) | Svelte Port |
|
|
356
|
+
| ----------------- | ------------------------ | ---------------------------------------- |
|
|
357
|
+
| **Framework** | React | Svelte 5 |
|
|
358
|
+
| **Component API** | JSX Components | Svelte Snippets |
|
|
359
|
+
| **Styling** | Tailwind CSS | Tailwind CSS (compatible) |
|
|
360
|
+
| **Context** | React Context | Svelte Context |
|
|
361
|
+
| **Build System** | Vite/React | Vite/SvelteKit |
|
|
362
|
+
| **TypeScript** | Full TS support | Full TS support |
|
|
363
|
+
| **Engine** | Remark / Rehype + marked | marked only |
|
|
364
|
+
| **Memoization** | Memoized `Block` (LRU) | Svelte reactivity (per-block `$derived`) |
|
|
352
365
|
|
|
353
366
|
### Tailwind CSS Setup
|
|
354
367
|
|
|
@@ -364,6 +377,32 @@ This Svelte port maintains feature parity with the original [Streamdown](https:/
|
|
|
364
377
|
@source "../node_modules/svelte-streamdown/**/*";
|
|
365
378
|
```
|
|
366
379
|
|
|
380
|
+
> [!IMPORTANT]
|
|
381
|
+
> The `@source` path is **relative to the stylesheet file** that contains the directive, so adjust the number of `../` segments to match where your stylesheet lives. The example above assumes `src/app.css`. If your global stylesheet lives one level deeper (e.g. `src/routes/+layout.css`, the default in newer SvelteKit projects), add one more `../` so the glob still resolves to your project's `node_modules`:
|
|
382
|
+
|
|
383
|
+
```css
|
|
384
|
+
@import 'tailwindcss';
|
|
385
|
+
/* Add Streamdown styles to your Tailwind build (stylesheet inside src/routes) */
|
|
386
|
+
@source "../../node_modules/svelte-streamdown/**/*";
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## ⚡ Streaming Performance & Memoization
|
|
390
|
+
|
|
391
|
+
Like the original [Streamdown](https://streamdown.ai/), `svelte-streamdown` avoids re-parsing the whole document on every streaming update — but it achieves this through Svelte 5's fine-grained reactivity rather than an explicit parse cache.
|
|
392
|
+
|
|
393
|
+
Here is how it works on each content update:
|
|
394
|
+
|
|
395
|
+
1. **Block splitting**: the incoming `content` is split into top-level markdown blocks with `parseBlocks`. This is a lightweight lexer pass that only computes each block's raw string.
|
|
396
|
+
2. **Keyed rendering**: blocks are rendered with a keyed `{#each}`, so existing block components are preserved across updates instead of being torn down and recreated.
|
|
397
|
+
3. **Per-block memoized lexing**: each block component derives its tokens from its own raw string (`const tokens = $derived(lex(...))`). A Svelte `$derived` only recomputes when its inputs change, so a block is only re-lexed (the expensive inline tokenization step) when **its own** raw string changes.
|
|
398
|
+
|
|
399
|
+
During streaming, newly received text almost always only changes the **last** block (and occasionally starts a new one). Every earlier block keeps an identical raw string, so Svelte skips its `lex()` call entirely — this is the equivalent of the memoized `Block` component in the React version. The block-splitting pass itself runs on every update, but it is the cheap pass; the costly inline parsing is what gets reused.
|
|
400
|
+
|
|
401
|
+
Code highlighting is incremental as well: a code block is only re-highlighted when its text changes, and the Shiki highlighter caches the languages and themes it has already loaded, so a streaming code block does not reload its grammar on every chunk.
|
|
402
|
+
|
|
403
|
+
> [!NOTE]
|
|
404
|
+
> There is intentionally no separate block-level parse cache (e.g. an LRU keyed by block content). For the common append-only streaming case the reactivity-based approach above already avoids redundant work, and a standalone cache would add memory usage and invalidation complexity without a measurable benefit. If you have a workload where this matters, please [open an issue](https://github.com/beynar/svelte-streamdown/issues) with a repro — we're happy to revisit.
|
|
405
|
+
|
|
367
406
|
## 🎭 Animation System
|
|
368
407
|
|
|
369
408
|
Streamdown includes an animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.
|
|
@@ -462,6 +501,19 @@ This heading will use a custom component!`;
|
|
|
462
501
|
/>
|
|
463
502
|
```
|
|
464
503
|
|
|
504
|
+
Prefixes can also be **protocol-only**, which allows any URL using that protocol. For example, `'https://'` allows every HTTPS link while still blocking insecure `http://` links, and `'mailto:'` / `'tel:'` allow email and phone links:
|
|
505
|
+
|
|
506
|
+
```svelte
|
|
507
|
+
<Streamdown
|
|
508
|
+
{content}
|
|
509
|
+
allowedLinkPrefixes={['https://', 'mailto:']}
|
|
510
|
+
allowedImagePrefixes={['https://']}
|
|
511
|
+
/>
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
> [!NOTE]
|
|
515
|
+
> `'*'` allows all `http://` and `https://` URLs. A protocol-only prefix only allows that exact protocol, so list each one you want to permit. Only add a protocol you trust — e.g. do not add `'javascript:'`.
|
|
516
|
+
|
|
465
517
|
## 📦 Bundle Optimization
|
|
466
518
|
|
|
467
519
|
Streamdown is optimized for minimal bundle size by making heavy components **opt-in**. By default, Code blocks, Mermaid diagrams, and Math expressions render as lightweight fallbacks (plain text). To enable full functionality, import and pass the components you need:
|
|
@@ -472,24 +524,21 @@ Streamdown is optimized for minimal bundle size by making heavy components **opt
|
|
|
472
524
|
<script>
|
|
473
525
|
import { Streamdown } from 'svelte-streamdown';
|
|
474
526
|
// Import only the components you need
|
|
475
|
-
import Code from 'svelte-streamdown/code';
|
|
527
|
+
import Code from 'svelte-streamdown/code'; // Shiki syntax highlighting
|
|
476
528
|
import Mermaid from 'svelte-streamdown/mermaid'; // Mermaid diagrams
|
|
477
|
-
import Math from 'svelte-streamdown/math';
|
|
529
|
+
import Math from 'svelte-streamdown/math'; // KaTeX math rendering
|
|
478
530
|
</script>
|
|
479
531
|
|
|
480
|
-
<Streamdown
|
|
481
|
-
{content}
|
|
482
|
-
components={{ code: Code, mermaid: Mermaid, math: Math }}
|
|
483
|
-
/>
|
|
532
|
+
<Streamdown {content} components={{ code: Code, mermaid: Mermaid, math: Math }} />
|
|
484
533
|
```
|
|
485
534
|
|
|
486
535
|
### Component Dependencies
|
|
487
536
|
|
|
488
|
-
| Component | Import Path
|
|
489
|
-
|
|
490
|
-
| `Code`
|
|
491
|
-
| `Mermaid` | `svelte-streamdown/mermaid` | Mermaid.js | ~1.5MB
|
|
492
|
-
| `Math`
|
|
537
|
+
| Component | Import Path | Dependency | Size Impact |
|
|
538
|
+
| --------- | --------------------------- | ---------- | ------------------------- |
|
|
539
|
+
| `Code` | `svelte-streamdown/code` | Shiki | ~2MB (languages + themes) |
|
|
540
|
+
| `Mermaid` | `svelte-streamdown/mermaid` | Mermaid.js | ~1.5MB |
|
|
541
|
+
| `Math` | `svelte-streamdown/math` | KaTeX | ~300KB |
|
|
493
542
|
|
|
494
543
|
> [!TIP]
|
|
495
544
|
> Only import the components your application actually uses. If your content doesn't include code blocks, mermaid diagrams, or math expressions, you can skip those imports entirely for a much smaller bundle.
|
|
@@ -497,43 +546,111 @@ Streamdown is optimized for minimal bundle size by making heavy components **opt
|
|
|
497
546
|
### Fallback Behavior
|
|
498
547
|
|
|
499
548
|
When a heavy component is not provided:
|
|
549
|
+
|
|
500
550
|
- **Code blocks**: Render as plain `<pre><code>` without syntax highlighting
|
|
501
551
|
- **Mermaid**: Renders the mermaid source as a code block
|
|
502
552
|
- **Math**: Renders the raw LaTeX/math text
|
|
503
553
|
|
|
554
|
+
### Shiki Themes
|
|
555
|
+
|
|
556
|
+
The `Code` component bundles two themes out of the box: **`github-dark`** and **`github-light`**. By default `shikiTheme` follows the active color scheme (`github-dark` in dark mode, `github-light` otherwise), so basic light/dark theming works with no extra configuration.
|
|
557
|
+
|
|
558
|
+
To use any other Shiki theme (e.g. `vesper`, `github-dark-default`, `github-light-default`), import it from `@shikijs/themes/<name>` and register it via the `shikiThemes` prop. The **key** you register it under is the value you pass to `shikiTheme`:
|
|
559
|
+
|
|
560
|
+
```svelte
|
|
561
|
+
<script lang="ts">
|
|
562
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
563
|
+
import Code from 'svelte-streamdown/code'; // enables Shiki highlighting
|
|
564
|
+
import vesper from '@shikijs/themes/vesper';
|
|
565
|
+
|
|
566
|
+
let { content } = $props();
|
|
567
|
+
</script>
|
|
568
|
+
|
|
569
|
+
<Streamdown {content} components={{ code: Code }} shikiThemes={{ vesper }} shikiTheme="vesper" />
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
> [!IMPORTANT]
|
|
573
|
+
> A theme passed to `shikiTheme` must be one of the two built-in themes **or** registered via `shikiThemes`. If it is neither, the code block stays in its loading (skeleton) state and is never highlighted — this is the most common cause of "my theme stopped working".
|
|
574
|
+
|
|
575
|
+
#### Dynamic (light/dark) theme switching
|
|
576
|
+
|
|
577
|
+
Register every theme you intend to switch between in `shikiThemes`, then drive `shikiTheme` from your color-scheme store. Switching is fully reactive — code blocks re-highlight when `shikiTheme` changes:
|
|
578
|
+
|
|
579
|
+
```svelte
|
|
580
|
+
<script lang="ts">
|
|
581
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
582
|
+
import Code from 'svelte-streamdown/code';
|
|
583
|
+
import { mode } from 'mode-watcher';
|
|
584
|
+
import githubDarkDefault from '@shikijs/themes/github-dark-default';
|
|
585
|
+
import githubLightDefault from '@shikijs/themes/github-light-default';
|
|
586
|
+
|
|
587
|
+
let { content } = $props();
|
|
588
|
+
|
|
589
|
+
const shikiTheme = $derived(
|
|
590
|
+
mode.current === 'dark' ? 'github-dark-default' : 'github-light-default'
|
|
591
|
+
);
|
|
592
|
+
</script>
|
|
593
|
+
|
|
594
|
+
<Streamdown
|
|
595
|
+
{content}
|
|
596
|
+
components={{ code: Code }}
|
|
597
|
+
baseTheme="shadcn"
|
|
598
|
+
shikiThemes={{
|
|
599
|
+
'github-dark-default': githubDarkDefault,
|
|
600
|
+
'github-light-default': githubLightDefault
|
|
601
|
+
}}
|
|
602
|
+
{shikiTheme}
|
|
603
|
+
/>
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
> [!NOTE]
|
|
607
|
+
> The built-in `github-dark` / `github-light` themes can be switched dynamically with just `shikiTheme` (no `shikiThemes` registration needed), since both are always loaded.
|
|
608
|
+
|
|
609
|
+
#### Migrating from v2 (`shikiPreloadThemes`)
|
|
610
|
+
|
|
611
|
+
The v2 `shikiPreloadThemes` prop has been **removed**. Themes are no longer referenced by bundled name; instead you import the theme objects yourself and register them with `shikiThemes`. Registered themes are loaded together with the highlighter (i.e. effectively preloaded), so there is no separate preload step:
|
|
612
|
+
|
|
613
|
+
```diff
|
|
614
|
+
- shikiPreloadThemes={['github-dark-default', 'github-light-default']}
|
|
615
|
+
+ shikiThemes={{ 'github-dark-default': githubDarkDefault, 'github-light-default': githubLightDefault }}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
This also keeps the default bundle small: only the themes you actually import are included.
|
|
619
|
+
|
|
504
620
|
## 📋 Props API
|
|
505
621
|
|
|
506
|
-
| Prop | Type
|
|
507
|
-
| -------------------------- |
|
|
508
|
-
| `content` | `string`
|
|
509
|
-
| `sources` | `Record<string, any>`
|
|
510
|
-
| `class` | `string`
|
|
511
|
-
| `parseIncompleteMarkdown` | `boolean`
|
|
512
|
-
| `defaultOrigin` | `string`
|
|
513
|
-
| `allowedLinkPrefixes` | `string[]`
|
|
514
|
-
| `allowedImagePrefixes` | `string[]`
|
|
515
|
-
| `skipHtml` | `boolean`
|
|
516
|
-
| `unwrapDisallowed` | `boolean`
|
|
517
|
-
| `urlTransform` | `UrlTransform \| null`
|
|
518
|
-
| `theme` | `DeepPartial<Theme>`
|
|
519
|
-
| `baseTheme` | `'tailwind' \| 'shadcn'`
|
|
520
|
-
| `mergeTheme` | `boolean`
|
|
521
|
-
| `shikiTheme` | `string`
|
|
522
|
-
| `shikiThemes` | `Record<string, ThemeRegistration>`
|
|
523
|
-
| `shikiLanguages` | `LanguageInfo[]`
|
|
524
|
-
| `mermaidConfig` | `MermaidConfig`
|
|
525
|
-
| `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)`
|
|
526
|
-
| `animation` | `AnimationConfig`
|
|
527
|
-
| `animation.enabled` | `boolean`
|
|
528
|
-
| `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'`
|
|
529
|
-
| `animation.duration` | `number`
|
|
530
|
-
| `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'`
|
|
531
|
-
| `animation.tokenize` | `'word' \| 'char'`
|
|
532
|
-
| `animation.animateOnMount` | `boolean`
|
|
533
|
-
| `extensions` | `Array<Extension>`
|
|
534
|
-
| `mdxComponents` | `Record<string, Component>`
|
|
535
|
-
| `components` | `{ code?, mermaid?, math? }`
|
|
536
|
-
| `
|
|
622
|
+
| Prop | Type | Default | Description |
|
|
623
|
+
| -------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
624
|
+
| `content` | `string` | - | **Required.** The markdown content to render |
|
|
625
|
+
| `sources` | `Record<string, any>` | - | Citation data object for inline citations |
|
|
626
|
+
| `class` | `string` | - | CSS class names for the wrapper element |
|
|
627
|
+
| `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
|
|
628
|
+
| `defaultOrigin` | `string` | - | Default origin for relative URLs |
|
|
629
|
+
| `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
|
|
630
|
+
| `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
|
|
631
|
+
| `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
|
|
632
|
+
| `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
|
|
633
|
+
| `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
|
|
634
|
+
| `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
|
|
635
|
+
| `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
|
|
636
|
+
| `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
|
|
637
|
+
| `shikiTheme` | `string` | auto (dark-mode aware) | Code highlighting theme. Defaults to `github-dark` in dark mode / `github-light` otherwise. Any other value must be a key registered via `shikiThemes`. See [Shiki Themes](#shiki-themes). |
|
|
638
|
+
| `shikiThemes` | `Record<string, ThemeRegistration>` | - | Register additional pre-imported themes (e.g. `{ vesper }`) so they can be selected via `shikiTheme`, including dynamic light/dark switching. Replaces the v2 `shikiPreloadThemes` prop. |
|
|
639
|
+
| `shikiLanguages` | `LanguageInfo[]` | - | Additional syntax highlighting languages (merged with defaults) |
|
|
640
|
+
| `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
|
|
641
|
+
| `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
|
|
642
|
+
| `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
|
|
643
|
+
| `animation.enabled` | `boolean` | `false` | Enable/disable animations |
|
|
644
|
+
| `animation.type` | `'fade' \| 'blur' \| 'typewriter' \| 'slideUp' \| 'slideDown'` | `'blur'` | Animation style for text appearance |
|
|
645
|
+
| `animation.duration` | `number` | `500` | Animation duration in milliseconds |
|
|
646
|
+
| `animation.timingFunction` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'` | `'ease-in'` | CSS timing function for animations |
|
|
647
|
+
| `animation.tokenize` | `'word' \| 'char'` | `'word'` | Tokenization method for text animations |
|
|
648
|
+
| `animation.animateOnMount` | `boolean` | `false` | Run the token animation on mount or not, useful if you render the Streamdown component in the same time as the first token is receive from the LLM |
|
|
649
|
+
| `extensions` | `Array<Extension>` | `[]` | Custom marked tokenizers to render special markdown blocks or inline tokens |
|
|
650
|
+
| `mdxComponents` | `Record<string, Component>` | `{}` | Map of MDX component names to Svelte components (e.g., `{ Card, Button }`) |
|
|
651
|
+
| `components` | `{ code?, mermaid?, math? }` | - | Optional heavy components for syntax highlighting, diagrams, and math rendering |
|
|
652
|
+
| `controls` | `{ code?: boolean, mermaid?: boolean \| { enabled?: boolean, mouseWheelZoom?: boolean }, table?: boolean }` | all `true` | Toggle the action toolbars for code blocks, mermaid diagrams, and tables. For mermaid, pass an object to disable only mouse-wheel zoom while keeping pan and the zoom buttons, e.g. `{ mermaid: { mouseWheelZoom: false } }` |
|
|
653
|
+
| `children` | `Snippet<[{token:GenericToken, streamdown: StreamdownContext, children: Snippet` | `undefined` | Snippet used to render elements not supported by Streamdown, custom extensions, and MDX components |
|
|
537
654
|
|
|
538
655
|
#### All Available Customizable Elements:
|
|
539
656
|
|
|
@@ -666,9 +783,9 @@ Streamdown supports MDX-style JSX components, allowing you to embed custom Svelt
|
|
|
666
783
|
|
|
667
784
|
```svelte
|
|
668
785
|
<script>
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
786
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
787
|
+
|
|
788
|
+
let content = `
|
|
672
789
|
# Using MDX Components
|
|
673
790
|
|
|
674
791
|
<Card title="Hello" count={42}>
|
|
@@ -680,23 +797,23 @@ This is **markdown content** inside a component!
|
|
|
680
797
|
</script>
|
|
681
798
|
|
|
682
799
|
<Streamdown {content}>
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
800
|
+
{#snippet mdx({ token, props, children })}
|
|
801
|
+
{#if token.tagName === 'Card'}
|
|
802
|
+
<div class="rounded-lg border border-gray-200 p-4 shadow-sm">
|
|
803
|
+
<h3 class="text-xl font-bold">{props.title}</h3>
|
|
804
|
+
<p class="text-gray-600">Count: {props.count}</p>
|
|
805
|
+
<div class="mt-2">
|
|
806
|
+
{@render children()}
|
|
807
|
+
</div>
|
|
808
|
+
</div>
|
|
809
|
+
{:else if token.tagName === 'Button'}
|
|
810
|
+
<button class="rounded px-4 py-2 {props.active ? 'bg-blue-500 text-white' : 'bg-gray-200'}">
|
|
811
|
+
{props.label}
|
|
812
|
+
</button>
|
|
813
|
+
{:else}
|
|
814
|
+
{@render children()}
|
|
815
|
+
{/if}
|
|
816
|
+
{/snippet}
|
|
700
817
|
</Streamdown>
|
|
701
818
|
```
|
|
702
819
|
|
|
@@ -706,11 +823,11 @@ Instead of using the `mdx` snippet with conditional logic, you can pass Svelte c
|
|
|
706
823
|
|
|
707
824
|
```svelte
|
|
708
825
|
<script>
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
826
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
827
|
+
import Card from './Card.svelte';
|
|
828
|
+
import Button from './Button.svelte';
|
|
829
|
+
|
|
830
|
+
let content = `
|
|
714
831
|
# Using MDX Components
|
|
715
832
|
|
|
716
833
|
<Card title="Hello" count={42}>
|
|
@@ -729,26 +846,26 @@ This is **markdown content** inside a component!
|
|
|
729
846
|
```svelte
|
|
730
847
|
<!-- Card.svelte -->
|
|
731
848
|
<script>
|
|
732
|
-
|
|
849
|
+
let { title, count, children } = $props();
|
|
733
850
|
</script>
|
|
734
851
|
|
|
735
852
|
<div class="rounded-lg border border-gray-200 p-4 shadow-sm">
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
853
|
+
<h3 class="text-xl font-bold">{title}</h3>
|
|
854
|
+
<p class="text-gray-600">Count: {count}</p>
|
|
855
|
+
<div class="mt-2">
|
|
856
|
+
{@render children()}
|
|
857
|
+
</div>
|
|
741
858
|
</div>
|
|
742
859
|
```
|
|
743
860
|
|
|
744
861
|
```svelte
|
|
745
862
|
<!-- Button.svelte -->
|
|
746
863
|
<script>
|
|
747
|
-
|
|
864
|
+
let { label, active } = $props();
|
|
748
865
|
</script>
|
|
749
866
|
|
|
750
867
|
<button class="rounded px-4 py-2 {active ? 'bg-blue-500 text-white' : 'bg-gray-200'}">
|
|
751
|
-
|
|
868
|
+
{label}
|
|
752
869
|
</button>
|
|
753
870
|
```
|
|
754
871
|
|
|
@@ -757,11 +874,13 @@ This approach is cleaner when you have standalone component files, while the `md
|
|
|
757
874
|
### Supported Syntax
|
|
758
875
|
|
|
759
876
|
**Self-closing components:**
|
|
877
|
+
|
|
760
878
|
```markdown
|
|
761
879
|
<Component attr="value" count={42} enabled={true} />
|
|
762
880
|
```
|
|
763
881
|
|
|
764
882
|
**Components with markdown children:**
|
|
883
|
+
|
|
765
884
|
```markdown
|
|
766
885
|
<Component title="Hello">
|
|
767
886
|
# This is a heading
|
|
@@ -797,31 +916,33 @@ This ensures your UI remains stable even when receiving partial markdown from st
|
|
|
797
916
|
### Component Props
|
|
798
917
|
|
|
799
918
|
The `mdx` snippet receives three parameters:
|
|
919
|
+
|
|
800
920
|
- `token`: The full MdxToken with `tagName`, `attributes`, `selfClosing`, etc.
|
|
801
921
|
- `props`: Object containing all parsed attributes (e.g., `props.title`, `props.count`)
|
|
802
922
|
- `children`: Snippet containing parsed markdown content
|
|
803
923
|
|
|
804
924
|
Use `token.tagName` to determine which component is being rendered:
|
|
925
|
+
<Card title="Hello" count={5}>Content</Card>
|
|
805
926
|
|
|
806
927
|
```svelte
|
|
807
928
|
<!-- Markdown: <Card title="Hello" count={5}>Content</Card> -->
|
|
808
929
|
<Streamdown {content}>
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
930
|
+
{#snippet mdx({ token, props, children })}
|
|
931
|
+
{#if token.tagName === 'Card'}
|
|
932
|
+
<div>
|
|
933
|
+
<h3>{props.title}</h3>
|
|
934
|
+
<span>Count: {props.count}</span>
|
|
935
|
+
{@render children()}
|
|
936
|
+
</div>
|
|
937
|
+
{:else if token.tagName === 'Alert'}
|
|
938
|
+
<div class="alert alert-{props.type}">
|
|
939
|
+
{@render children()}
|
|
940
|
+
</div>
|
|
941
|
+
{:else}
|
|
942
|
+
<!-- Fallback for unknown components -->
|
|
943
|
+
{@render children()}
|
|
944
|
+
{/if}
|
|
945
|
+
{/snippet}
|
|
825
946
|
</Streamdown>
|
|
826
947
|
```
|
|
827
948
|
|