svelte-streamdown 2.6.1 → 3.0.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 CHANGED
@@ -462,6 +462,45 @@ This heading will use a custom component!`;
462
462
  />
463
463
  ```
464
464
 
465
+ ## 📦 Bundle Optimization
466
+
467
+ 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:
468
+
469
+ ### Enabling Heavy Components
470
+
471
+ ```svelte
472
+ <script>
473
+ import { Streamdown } from 'svelte-streamdown';
474
+ // Import only the components you need
475
+ import Code from 'svelte-streamdown/code'; // Shiki syntax highlighting
476
+ import Mermaid from 'svelte-streamdown/mermaid'; // Mermaid diagrams
477
+ import Math from 'svelte-streamdown/math'; // KaTeX math rendering
478
+ </script>
479
+
480
+ <Streamdown
481
+ {content}
482
+ components={{ code: Code, mermaid: Mermaid, math: Math }}
483
+ />
484
+ ```
485
+
486
+ ### Component Dependencies
487
+
488
+ | Component | Import Path | Dependency | Size Impact |
489
+ |-----------|-------------|------------|-------------|
490
+ | `Code` | `svelte-streamdown/code` | Shiki | ~2MB (languages + themes) |
491
+ | `Mermaid` | `svelte-streamdown/mermaid` | Mermaid.js | ~1.5MB |
492
+ | `Math` | `svelte-streamdown/math` | KaTeX | ~300KB |
493
+
494
+ > [!TIP]
495
+ > 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.
496
+
497
+ ### Fallback Behavior
498
+
499
+ When a heavy component is not provided:
500
+ - **Code blocks**: Render as plain `<pre><code>` without syntax highlighting
501
+ - **Mermaid**: Renders the mermaid source as a code block
502
+ - **Math**: Renders the raw LaTeX/math text
503
+
465
504
  ## 📋 Props API
466
505
 
467
506
  | Prop | Type | Default | Description |
@@ -479,7 +518,9 @@ This heading will use a custom component!`;
479
518
  | `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
480
519
  | `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
481
520
  | `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
482
- | `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
521
+ | `shikiTheme` | `string` | `'github-light'` | Code highlighting theme (`github-dark` or `github-light` by default, or custom theme key) |
522
+ | `shikiThemes` | `Record<string, ThemeRegistration>` | - | Additional themes as pre-imported objects (e.g., `{ nord: nordTheme }`) |
523
+ | `shikiLanguages` | `LanguageInfo[]` | - | Additional syntax highlighting languages (merged with defaults) |
483
524
  | `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
484
525
  | `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
485
526
  | `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
@@ -491,6 +532,7 @@ This heading will use a custom component!`;
491
532
  | `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 |
492
533
  | `extensions` | `Array<Extension>` | `[]` | Custom marked tokenizers to render special markdown blocks or inline tokens |
493
534
  | `mdxComponents` | `Record<string, Component>` | `{}` | Map of MDX component names to Svelte components (e.g., `{ Card, Button }`) |
535
+ | `components` | `{ code?, mermaid?, math? }` | - | Optional heavy components for syntax highlighting, diagrams, and math rendering |
494
536
  | `children` | `Snippet<[{token:GenericToken, streamdown: StreamdownContext, children: Snippet` | `undefined` | Snippet used to render elements not supported by Streamdown, custom extensions, and MDX components |
495
537
 
496
538
  #### All Available Customizable Elements:
package/dist/Block.svelte CHANGED
@@ -7,13 +7,17 @@
7
7
  import { getContext } from 'svelte';
8
8
 
9
9
  let {
10
- block
10
+ block,
11
+ static: isStatic = false
11
12
  }: {
12
13
  block: string;
14
+ static?: boolean;
13
15
  } = $props();
14
16
 
15
17
  const streamdown = useStreamdown();
16
- const tokens = $derived(lex(parseIncompleteMarkdown(block.trim()), streamdown.extensions));
18
+ const tokens = $derived(
19
+ lex(isStatic ? block : parseIncompleteMarkdown(block.trim()), streamdown.extensions)
20
+ );
17
21
  const insidePopover = getContext('POPOVER');
18
22
  </script>
19
23
 
@@ -24,8 +28,8 @@
24
28
  {@const isTextOnlyNode = children.length === 0}
25
29
  <Element {token}>
26
30
  {#if isTextOnlyNode}
27
- {#if streamdown.animation.enabled && !insidePopover}
28
- <AnimatedText text={'text' in token ? token.text : ''} />
31
+ {#if streamdown.animation.enabled && !insidePopover && !isStatic}
32
+ <AnimatedText text={'text' in token ? token.text || '' : ''} />
29
33
  {:else}
30
34
  {'text' in token ? token.text : ''}
31
35
  {/if}
@@ -1,5 +1,6 @@
1
1
  type $$ComponentProps = {
2
2
  block: string;
3
+ static?: boolean;
3
4
  };
4
5
  declare const Block: import("svelte").Component<$$ComponentProps, {}, "">;
5
6
  type Block = ReturnType<typeof Block>;
@@ -3,6 +3,7 @@
3
3
  import { save } from '../utils/save.js';
4
4
  import { useCopy } from '../utils/copy.svelte.js';
5
5
  import { HighlighterManager, languageExtensionMap } from '../utils/hightlighter.svelte.js';
6
+ import { bundledLanguagesInfo } from '../utils/bundledLanguages.js';
6
7
  import type { Tokens } from 'marked';
7
8
  import { type ThemedToken } from 'shiki';
8
9
  import { untrack } from 'svelte';
@@ -17,7 +18,11 @@
17
18
  } = $props();
18
19
 
19
20
  const streamdown = useStreamdown();
20
- const highlighter = HighlighterManager.create(streamdown.shikiPreloadThemes);
21
+ const highlighter = HighlighterManager.create(
22
+ bundledLanguagesInfo,
23
+ streamdown.shikiThemes,
24
+ streamdown.shikiLanguages
25
+ );
21
26
 
22
27
  const copy = useCopy({
23
28
  get content() {
@@ -1,10 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import Link from './Link.svelte';
4
- import Code from './Code.svelte';
5
4
  import Image from './Image.svelte';
6
- import Mermaid from './Mermaid.svelte';
7
- import Math from './Math.svelte';
8
5
  import Alert from './Alert.svelte';
9
6
  import type { StreamdownToken } from '../marked/index.js';
10
7
  import Slot from './Slot.svelte';
@@ -12,9 +9,16 @@
12
9
  import FootnoteRef from './FootnoteRef.svelte';
13
10
  import Citation from './Citation.svelte';
14
11
  import TableDownload from './TableDownload.svelte';
12
+ // Import fallback components
13
+ import { CodeFallback, MermaidFallback, MathFallback } from './fallbacks/index.js';
15
14
  let { token, children }: { token: StreamdownToken; children: Snippet } = $props();
16
15
  const streamdown = useStreamdown();
17
16
 
17
+ // Use provided components or fallback to lightweight versions
18
+ const CodeComponent = $derived(streamdown.components?.code ?? CodeFallback);
19
+ const MermaidComponent = $derived(streamdown.components?.mermaid ?? MermaidFallback);
20
+ const MathComponent = $derived(streamdown.components?.math ?? MathFallback);
21
+
18
22
  // Only apply animation on block level elements. Leaves text elements to be animated by their text children.
19
23
  const style = $derived(streamdown.isMounted ? streamdown.animationBlockStyle : '');
20
24
  const id = $props.id();
@@ -68,11 +72,11 @@
68
72
  </Slot>
69
73
  {:else if token.type === 'code' && token.lang === 'mermaid'}
70
74
  <Slot props={{ children, token }} render={streamdown.snippets.code}>
71
- <Mermaid {id} {token} />
75
+ <MermaidComponent {id} {token} />
72
76
  </Slot>
73
77
  {:else if token.type === 'code'}
74
78
  <Slot props={{ children, token }} render={streamdown.snippets.code}>
75
- <Code {id} {token} />
79
+ <CodeComponent {id} {token} />
76
80
  </Slot>
77
81
  {:else if token.type === 'codespan'}
78
82
  <Slot props={{ children, token }} render={streamdown.snippets.codespan}>
@@ -240,7 +244,7 @@
240
244
  }}
241
245
  render={streamdown.snippets.math}
242
246
  >
243
- <Math {id} {token} />
247
+ <MathComponent {id} {token} />
244
248
  </Slot>
245
249
  {:else if token.type === 'alert'}
246
250
  <Alert {id} {token} {children} />
@@ -6,6 +6,7 @@
6
6
  import { on } from 'svelte/events';
7
7
  import { usePanzoom } from '../utils/panzoom.svelte';
8
8
  import { fitViewIcon, fullscreenIcon, zoomInIcon, zoomOutIcon } from './icons.js';
9
+ import MermaidDownload from './MermaidDownload.svelte';
9
10
 
10
11
  const streamdown = useStreamdown();
11
12
 
@@ -254,6 +255,7 @@
254
255
  >
255
256
  {@render (streamdown.icons?.fullscreen || fullscreenIcon)()}
256
257
  </button>
258
+ <MermaidDownload {id} />
257
259
  </div>
258
260
  {/if}
259
261
  <svg {@attach panzoom.attach} data-mermaid-svg></svg>
@@ -0,0 +1,196 @@
1
+ <script lang="ts">
2
+ import { useStreamdown } from '../context.svelte.js';
3
+ import { scale } from 'svelte/transition';
4
+ import { downloadIcon } from './icons.js';
5
+ import { Popover } from './popover.svelte.js';
6
+ import { useClickOutside } from '../utils/useClickOutside.svelte.js';
7
+ import { useKeyDown } from '../utils/useKeyDown.svelte.js';
8
+ import { save } from '../utils/save.js';
9
+
10
+ let {
11
+ id
12
+ }: {
13
+ id: string;
14
+ } = $props();
15
+
16
+ const streamdown = useStreamdown();
17
+ const popover = new Popover();
18
+
19
+ useKeyDown({
20
+ keys: ['Escape'],
21
+ get isActive() {
22
+ return popover.isOpen;
23
+ },
24
+ callback: () => {
25
+ popover.isOpen = false;
26
+ }
27
+ });
28
+
29
+ const clickOutside = useClickOutside({
30
+ get isActive() {
31
+ return popover.isOpen;
32
+ },
33
+ callback: () => {
34
+ popover.isOpen = false;
35
+ }
36
+ });
37
+
38
+ const getSvgElement = (): SVGSVGElement | null => {
39
+ const container = document.querySelector(`[data-streamdown-mermaid="${id}"]`);
40
+ if (!container) return null;
41
+
42
+ const svgContainer = container.querySelector('[data-mermaid-svg]');
43
+ if (!svgContainer) return null;
44
+
45
+ // The actual SVG is rendered inside the data-mermaid-svg container
46
+ const svg = svgContainer.querySelector('svg');
47
+ return svg;
48
+ };
49
+
50
+ const downloadSvg = () => {
51
+ const svg = getSvgElement();
52
+ if (!svg) return;
53
+
54
+ // Clone the SVG to avoid modifying the original
55
+ const clonedSvg = svg.cloneNode(true) as SVGSVGElement;
56
+
57
+ // Ensure the SVG has proper xmlns
58
+ clonedSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
59
+ clonedSvg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
60
+
61
+ // Get computed styles and inline them for standalone SVG
62
+ const styles = getComputedStyle(svg);
63
+ if (!clonedSvg.getAttribute('width')) {
64
+ clonedSvg.setAttribute('width', styles.width);
65
+ }
66
+ if (!clonedSvg.getAttribute('height')) {
67
+ clonedSvg.setAttribute('height', styles.height);
68
+ }
69
+
70
+ const svgString = new XMLSerializer().serializeToString(clonedSvg);
71
+ save('mermaid-diagram.svg', svgString, 'image/svg+xml');
72
+ popover.isOpen = false;
73
+ };
74
+
75
+ const downloadPng = async () => {
76
+ const svg = getSvgElement();
77
+ if (!svg) return;
78
+
79
+ // Clone the SVG to avoid modifying the original
80
+ const clonedSvg = svg.cloneNode(true) as SVGSVGElement;
81
+
82
+ // Ensure the SVG has proper xmlns
83
+ clonedSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
84
+ clonedSvg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
85
+
86
+ // Get dimensions
87
+ const bbox = svg.getBBox();
88
+ const styles = getComputedStyle(svg);
89
+ const width = parseFloat(styles.width) || bbox.width || 800;
90
+ const height = parseFloat(styles.height) || bbox.height || 600;
91
+
92
+ // Set explicit dimensions on the cloned SVG
93
+ clonedSvg.setAttribute('width', String(width));
94
+ clonedSvg.setAttribute('height', String(height));
95
+
96
+ // Serialize SVG to string
97
+ const svgString = new XMLSerializer().serializeToString(clonedSvg);
98
+
99
+ // Use data URL instead of blob URL to avoid tainted canvas issues
100
+ const svgDataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));
101
+
102
+ // Create an image to load the SVG
103
+ const img = new Image();
104
+
105
+ img.onload = () => {
106
+ // Create a canvas with 2x scale for better quality
107
+ const scale = 2;
108
+ const canvas = document.createElement('canvas');
109
+ canvas.width = width * scale;
110
+ canvas.height = height * scale;
111
+
112
+ const ctx = canvas.getContext('2d');
113
+ if (!ctx) {
114
+ return;
115
+ }
116
+
117
+ // Fill with white background (optional, remove for transparent)
118
+ ctx.fillStyle = '#ffffff';
119
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
120
+
121
+ // Scale and draw the image
122
+ ctx.scale(scale, scale);
123
+ ctx.drawImage(img, 0, 0);
124
+
125
+ // Convert to PNG and download
126
+ canvas.toBlob((blob) => {
127
+ if (blob) {
128
+ const url = URL.createObjectURL(blob);
129
+ const link = document.createElement('a');
130
+ link.href = url;
131
+ link.download = 'mermaid-diagram.png';
132
+ document.body.appendChild(link);
133
+ link.click();
134
+ document.body.removeChild(link);
135
+ URL.revokeObjectURL(url);
136
+ }
137
+ }, 'image/png');
138
+ };
139
+
140
+ img.onerror = () => {
141
+ console.error('Failed to load SVG for PNG conversion');
142
+ };
143
+
144
+ img.src = svgDataUrl;
145
+ popover.isOpen = false;
146
+ };
147
+
148
+ const download = (type: 'SVG' | 'PNG') => {
149
+ if (type === 'SVG') {
150
+ downloadSvg();
151
+ } else {
152
+ downloadPng();
153
+ }
154
+ };
155
+ </script>
156
+
157
+ {#if popover.isOpen}
158
+ <dialog
159
+ id={'mermaid-download-popover'}
160
+ aria-modal="false"
161
+ transition:scale|global={{ start: 0.95, duration: 100 }}
162
+ {@attach clickOutside.attachment}
163
+ {@attach popover.popoverAttachment}
164
+ open
165
+ style:width="fit-content !important"
166
+ style:min-width="fit-content !important"
167
+ class={streamdown.theme.components.popover}
168
+ >
169
+ {#each ['PNG', 'SVG'] as type}
170
+ <button
171
+ style="width: 100%; text-align: left; justify-content: flex-start; padding: 1rem 1rem; margin: 0.2rem 0;"
172
+ onclick={() => download(type as 'SVG' | 'PNG')}
173
+ class={streamdown.theme.components.button}
174
+ >
175
+ {type}
176
+ </button>
177
+ {/each}
178
+ </dialog>
179
+ {/if}
180
+
181
+ <button
182
+ class={streamdown.theme.components.button}
183
+ onclick={(e: MouseEvent) => {
184
+ if (popover.isOpen) {
185
+ popover.isOpen = false;
186
+ return;
187
+ }
188
+ popover.reference = e.target as HTMLButtonElement;
189
+ popover.isOpen = true;
190
+ }}
191
+ {@attach clickOutside.attachment}
192
+ title="Download diagram"
193
+ data-panzoom-ignore
194
+ >
195
+ {@render (streamdown.icons?.download || downloadIcon)()}
196
+ </button>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ id: string;
3
+ };
4
+ declare const MermaidDownload: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type MermaidDownload = ReturnType<typeof MermaidDownload>;
6
+ export default MermaidDownload;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import { useStreamdown } from '../../context.svelte.js';
3
+ import type { Tokens } from 'marked';
4
+
5
+ const {
6
+ token,
7
+ id
8
+ }: {
9
+ token: Tokens.Code;
10
+ id: string;
11
+ } = $props();
12
+
13
+ const streamdown = useStreamdown();
14
+ </script>
15
+
16
+ <div
17
+ data-streamdown-code={id}
18
+ style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
19
+ class={streamdown.theme.code.base}
20
+ >
21
+ <div class={streamdown.theme.code.header}>
22
+ <span class={streamdown.theme.code.language}>{token.lang}</span>
23
+ </div>
24
+ <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
25
+ <pre class={streamdown.theme.code.pre}><code
26
+ >{#each token.text.split('\n') as line}<span class={streamdown.theme.code.line}
27
+ ><span style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
28
+ >{line.trim().length > 0 ? line : '\u200B'}</span
29
+ ></span
30
+ >{/each}</code
31
+ ></pre>
32
+ </div>
33
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { Tokens } from 'marked';
2
+ type $$ComponentProps = {
3
+ token: Tokens.Code;
4
+ id: string;
5
+ };
6
+ declare const CodeFallback: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type CodeFallback = ReturnType<typeof CodeFallback>;
8
+ export default CodeFallback;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { useStreamdown } from '../../context.svelte.js';
3
+ import type { MathToken } from '../../marked/index.js';
4
+
5
+ const {
6
+ token,
7
+ id
8
+ }: {
9
+ token: MathToken;
10
+ id: string;
11
+ } = $props();
12
+
13
+ const streamdown = useStreamdown();
14
+ </script>
15
+
16
+ {#if token.isInline}
17
+ <span
18
+ data-streamdown-inline-math={id}
19
+ style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
20
+ class={streamdown.theme.math.inline}
21
+ >
22
+ <code>{token.text}</code>
23
+ </span>
24
+ {:else}
25
+ <div
26
+ data-streamdown-block-math={id}
27
+ style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
28
+ style:height="fit-content"
29
+ style:width="100%"
30
+ >
31
+ <div class="overflow-x-auto">
32
+ <pre class={streamdown.theme.math.block}><code>{token.text}</code></pre>
33
+ </div>
34
+ </div>
35
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { MathToken } from '../../marked/index.js';
2
+ type $$ComponentProps = {
3
+ token: MathToken;
4
+ id: string;
5
+ };
6
+ declare const MathFallback: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type MathFallback = ReturnType<typeof MathFallback>;
8
+ export default MathFallback;
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { useStreamdown } from '../../context.svelte.js';
3
+ import type { Tokens } from 'marked';
4
+
5
+ const {
6
+ token,
7
+ id
8
+ }: {
9
+ token: Tokens.Code;
10
+ id: string;
11
+ } = $props();
12
+
13
+ const streamdown = useStreamdown();
14
+ </script>
15
+
16
+ <div data-streamdown-mermaid={id}>
17
+ <div
18
+ style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
19
+ class={streamdown.theme.code.base}
20
+ >
21
+ <div class={streamdown.theme.code.header}>
22
+ <span class={streamdown.theme.code.language}>mermaid</span>
23
+ </div>
24
+ <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
25
+ <pre class={streamdown.theme.code.pre}><code
26
+ >{#each token.text.split('\n') as line}<span class={streamdown.theme.code.line}
27
+ ><span style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
28
+ >{line.trim().length > 0 ? line : '\u200B'}</span
29
+ ></span
30
+ >{/each}</code
31
+ ></pre>
32
+ </div>
33
+ </div>
34
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { Tokens } from 'marked';
2
+ type $$ComponentProps = {
3
+ token: Tokens.Code;
4
+ id: string;
5
+ };
6
+ declare const MermaidFallback: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type MermaidFallback = ReturnType<typeof MermaidFallback>;
8
+ export default MermaidFallback;
@@ -0,0 +1,3 @@
1
+ export { default as CodeFallback } from './CodeFallback.svelte';
2
+ export { default as MermaidFallback } from './MermaidFallback.svelte';
3
+ export { default as MathFallback } from './MathFallback.svelte';
@@ -0,0 +1,3 @@
1
+ export { default as CodeFallback } from './CodeFallback.svelte';
2
+ export { default as MermaidFallback } from './MermaidFallback.svelte';
3
+ export { default as MathFallback } from './MathFallback.svelte';
@@ -8,13 +8,14 @@
8
8
  content = '',
9
9
  class: className,
10
10
  shikiTheme,
11
- shikiPreloadThemes,
11
+ shikiLanguages,
12
+ shikiThemes,
12
13
  parseIncompleteMarkdown,
13
14
  defaultOrigin,
14
15
  allowedLinkPrefixes = ['*'],
15
16
  allowedImagePrefixes = ['*'],
16
17
  theme,
17
- mermaidConfig,
18
+ mermaidConfig = {},
18
19
  katexConfig,
19
20
  translations,
20
21
  baseTheme,
@@ -30,8 +31,21 @@
30
31
  sources,
31
32
  inlineCitationsMode = 'carousel',
32
33
  mdxComponents,
34
+ components,
35
+ static: isStatic,
33
36
  ...snippets
34
37
  }: StreamdownProps<Source> = $props();
38
+ import { useDarkMode } from './utils/darkMode.svelte.js';
39
+
40
+ const darkMode = useDarkMode();
41
+
42
+ const shikiThemedTheme = $derived(
43
+ shikiThemes ? Object.keys(shikiThemes)[0] || 'github-light' : darkMode.current ? 'github-dark' : 'github-light'
44
+ );
45
+
46
+ const mermaidThemedTheme = $derived(
47
+ mermaidConfig?.theme ? mermaidConfig.theme : darkMode.current ? 'dark' : 'default'
48
+ );
35
49
 
36
50
  streamdown = new StreamdownContext({
37
51
  get element() {
@@ -53,7 +67,7 @@
53
67
  return allowedImagePrefixes;
54
68
  },
55
69
  get shikiTheme() {
56
- return shikiTheme || 'github-light';
70
+ return shikiTheme || shikiThemedTheme;
57
71
  },
58
72
  get snippets() {
59
73
  return snippets;
@@ -67,7 +81,10 @@
67
81
  return baseTheme;
68
82
  },
69
83
  get mermaidConfig() {
70
- return mermaidConfig;
84
+ return {
85
+ theme: mermaidThemedTheme,
86
+ ...mermaidConfig
87
+ };
71
88
  },
72
89
  get katexConfig() {
73
90
  return katexConfig;
@@ -78,8 +95,11 @@
78
95
  get translations() {
79
96
  return translations;
80
97
  },
81
- get shikiPreloadThemes() {
82
- return shikiPreloadThemes;
98
+ get shikiLanguages() {
99
+ return shikiLanguages;
100
+ },
101
+ get shikiThemes() {
102
+ return shikiThemes;
83
103
  },
84
104
  get sources() {
85
105
  return sources;
@@ -122,18 +142,25 @@
122
142
  },
123
143
  get mdxComponents() {
124
144
  return mdxComponents;
145
+ },
146
+ get components() {
147
+ return components;
125
148
  }
126
149
  });
127
150
 
128
151
  const id = $props.id();
129
152
 
130
- const blocks = $derived(parseBlocks(content, streamdown.extensions));
153
+ const blocks = $derived(isStatic ? content : parseBlocks(content, streamdown.extensions));
131
154
  </script>
132
155
 
133
156
  <div bind:this={element} class={className}>
134
- {#each blocks as block, index (`${id}-block-${index}`)}
135
- <Block {block} />
136
- {/each}
157
+ {#if isStatic}
158
+ <Block static={isStatic} block={content} />
159
+ {:else}
160
+ {#each blocks as block, index (`${id}-block-${index}`)}
161
+ <Block static={isStatic} {block} />
162
+ {/each}
163
+ {/if}
137
164
  </div>
138
165
 
139
166
  <style global>