svelte-streamdown 0.0.1 → 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Arnaud Derbey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,58 +1,538 @@
1
- # Svelte library
1
+ # Svelte Streamdown
2
2
 
3
- Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
3
+ [![npm version](https://badge.fury.io/js/svelte-streamdown.svg)](https://badge.fury.io/js/svelte-streamdown)
4
4
 
5
- Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
5
+ A **Svelte port** of [Streamdown](https://streamdown.ai/) by Vercel - an all markdown renderer, designed specifically for AI-powered streaming applications.
6
6
 
7
- ## Creating a project
7
+ ## 📦 Installation
8
8
 
9
- If you're seeing this, you've probably already done this step. Congrats!
9
+ ```bash
10
+ npm install svelte-streamdown
11
+ # or
12
+ pnpm add svelte-streamdown
13
+ # or
14
+ yarn add svelte-streamdown
15
+ ```
16
+
17
+ ## 🚀 Overview
18
+
19
+ Streamdown makes formatting Markdown easy, but when you tokenize and stream it from AI models, new challenges arise. This Svelte port brings all the power of the original React Streamdown component to the Svelte ecosystem.
20
+
21
+ Perfect for AI-powered applications that need to stream and render markdown content safely and beautifully, with support for incomplete markdown blocks, security hardening, and rich features like code highlighting, math expressions, and interactive diagrams.
22
+
23
+ ## ✨ Main Features
24
+
25
+ ### 🎨 Built-in Typography Styles
26
+
27
+ Beautiful, responsive typography with **built-in Tailwind CSS classes** for headings, lists, code blocks, and more. Comes with a complete default theme that works out of the box.
28
+
29
+ ### 📝 GitHub Flavored Markdown
30
+
31
+ Full support for GitHub Flavored Markdown including:
32
+
33
+ - Task lists
34
+ - Tables
35
+ - Strikethrough text
36
+
37
+ ### 💻 Interactive Code Blocks
38
+
39
+ - Syntax highlighting powered by Shiki
40
+ - Copy-to-clipboard functionality
41
+ - Support any Shiki themes
42
+
43
+ ### 🔢 Mathematical Expressions
44
+
45
+ LaTeX math support through KaTeX:
46
+
47
+ - Perfect rendering for scientific content
48
+ - Inline math: $E = mc^2$
49
+ - Block math:
50
+
51
+ $$
52
+ \\sum_{i=1}^n x_i
53
+ $$
54
+
55
+ ### 🧜‍♀️ Mermaid Diagrams
56
+
57
+ - Render Mermaid diagrams from code blocks
58
+ - **Incremental rendering** during streaming content
59
+ - Pan and Zoom
60
+ - Full screen mode
61
+
62
+ **Example:**
63
+
64
+ ```mermaid
65
+ graph TD
66
+ A[Start] --> B{Is it working?}
67
+ B -->|Yes| C[Great!]
68
+ B -->|No| D[Debug]
69
+ D --> B
70
+ C --> E[End]
71
+ ```
72
+
73
+ ```mermaid
74
+ sequenceDiagram
75
+ participant User
76
+ participant Frontend
77
+ participant API
78
+ participant Database
79
+
80
+ User->>Frontend: Submit form
81
+ Frontend->>API: POST /api/data
82
+ API->>Database: INSERT query
83
+ Database-->>API: Success
84
+ API-->>Frontend: 200 OK
85
+ Frontend-->>User: Show success message
86
+ ```
87
+
88
+ ```mermaid
89
+ pie title Project Time Allocation
90
+ "Development" : 45
91
+ "Testing" : 25
92
+ "Documentation" : 15
93
+ "Meetings" : 15
94
+ ```
95
+
96
+ ### Alert Support
97
+
98
+ > [!IMPORTANT]
99
+ > Native support for Github style Alert
100
+
101
+ ### 🔄 Streaming-Optimized
102
+
103
+ - **Incomplete Markdown Parsing**: Handles unterminated blocks gracefully
104
+ - **Progressive Rendering**: Perfect for streaming AI responses
105
+ - **Real-time Updates**: Optimized for dynamic content
106
+
107
+ ### 🔒 Security Hardening
108
+
109
+ - **Image Origin Control**: Whitelist allowed image sources
110
+ - **Link Safety**: Control link destinations
111
+ - **HTML Sanitization**: Prevent XSS attacks
112
+
113
+ ### 🎯 Fully Customizable Components & Theming
114
+
115
+ - **Every component customizable** with Svelte snippets
116
+ - **Granular theming system** - customize every part of every component
117
+ - Override default styling and behavior for any markdown element
118
+ - Full control over rendering with type-safe props
119
+ - Seamless integration with your design system
120
+
121
+ ## 🔄 Differences from Original React Version
122
+
123
+ This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
124
+
125
+ | Aspect | Original (React) | Svelte Port |
126
+ | ----------------- | ---------------- | ------------------------- |
127
+ | **Framework** | React | Svelte 5 |
128
+ | **Component API** | JSX Components | Svelte Snippets |
129
+ | **Styling** | Tailwind CSS | Tailwind CSS (compatible) |
130
+ | **Context** | React Context | Svelte Context |
131
+ | **Build System** | Vite/React | Vite/SvelteKit |
132
+ | **TypeScript** | Full TS support | Full TS support |
133
+
134
+ ### Tailwind CSS Setup
135
+
136
+ > [!NOTE]
137
+ > Streamdown comes with **built-in Tailwind CSS classes** for beautiful default styling. To ensure all styles are included in your build, add the following to your `app.css` or main CSS file:
138
+
139
+ ```css
140
+ @import 'tailwindcss';
141
+ /* Add Streamdown styles to your Tailwind build */
142
+ @source "../node_modules/svelte-streamdown/**/*";
143
+ ```
144
+
145
+ This ensures that all Streamdown's default styling is included in your Tailwind build process.
146
+
147
+ > **Note:** This setup is primarily necessary if you're using Tailwind CSS v4's new `@source` directive or if you have aggressive purging enabled in older versions. If you're using standard Tailwind CSS v3+ with default purging, Streamdown's styles should be automatically included when the component is imported and used in your application.
148
+
149
+ ## 🚀 Quick Start
150
+
151
+ ### Basic Usage
152
+
153
+ ```svelte
154
+ <script>
155
+ import { Streamdown } from 'svelte-streamdown';
156
+
157
+ let content = `# Hello World
158
+
159
+ This is a **bold** text and this is *italic*.
160
+
161
+ \`\`\`javascript
162
+ console.log('Hello from Streamdown!');
163
+ \`\`\`
164
+ `;
165
+ </script>
166
+
167
+ <Streamdown {content} />
168
+ ```
169
+
170
+ ### Advanced Usage with Custom Components
171
+
172
+ ```svelte
173
+ <script>
174
+ import { Streamdown } from 'svelte-streamdown';
175
+
176
+ let content = `# Custom Components Example
177
+
178
+ This heading will use a custom component!`;
179
+
180
+ // Custom heading component
181
+ </script>
182
+
183
+ {#snippet customH1({ children, props })}
184
+ <h1 class="mb-4 text-4xl font-bold text-blue-600" {...props}>
185
+ {children}
186
+ </h1>
187
+ {/snippet}
188
+
189
+ <Streamdown {content} h1={customH1} />
190
+ ```
191
+
192
+ ### Security Configuration
193
+
194
+ ```svelte
195
+ <script>
196
+ import { Streamdown } from 'svelte-streamdown';
197
+
198
+ let markdown = `![Safe Image](https://trusted-domain.com/image.jpg)
199
+ [Safe Link](https://trusted-domain.com/page)`;
200
+ </script>
201
+
202
+ <Streamdown
203
+ {content}
204
+ allowedImagePrefixes={['https://trusted-domain.com']}
205
+ allowedLinkPrefixes={['https://trusted-domain.com']}
206
+ />
207
+ ```
208
+
209
+ ## 📋 Props API
210
+
211
+ | Prop | Type | Default | Description |
212
+ | ------------------------- | ----------------------------------------------------- | ---------------- | ---------------------------------------------- |
213
+ | `content` | `string` | - | **Required.** The markdown content to render |
214
+ | `class` | `string` | - | CSS class names for the wrapper element |
215
+ | `parseIncompleteMarkdown` | `boolean` | `true` | Parse and fix incomplete markdown syntax |
216
+ | `defaultOrigin` | `string` | - | Default origin for relative URLs |
217
+ | `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
218
+ | `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
219
+ | `allowElement` | `AllowElement \| null` | - | Custom element filtering function |
220
+ | `allowedElements` | `readonly string[] \| null` | - | Whitelist of allowed HTML elements |
221
+ | `disallowedElements` | `readonly string[] \| null` | - | Blacklist of disallowed HTML elements |
222
+ | `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
223
+ | `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
224
+ | `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
225
+ | `theme` | `Partial<Theme>` | - | Custom theme overrides |
226
+ | `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
227
+ | `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
228
+ | `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
229
+ | `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
230
+ | `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
231
+ | `remarkPlugins` | `PluggableList` | - | Additional remark plugins |
232
+ | `rehypePlugins` | `PluggableList` | - | Additional rehype plugins |
233
+ | `remarkRehypeOptions` | `RemarkRehypeOptions` | - | Remark-rehype conversion options |
234
+ | `customElements` | `Record<string, Snippet<[ElementProps]>>` | - | Custom snippets for not handled nodes |
235
+
236
+ ### Custom Component Props
237
+
238
+ **Every single markdown element** can be customized with Svelte snippets, giving you complete control over styling and behavior:
239
+
240
+ ```svelte
241
+ <script>
242
+ import { Streamdown } from 'svelte-streamdown';
243
+
244
+ let content = `# Fully Customizable
245
+
246
+ This heading uses a custom component with your design system!`;
247
+ </script>
248
+
249
+ {#snippet customH1({ children, ...props })}
250
+ <h1
251
+ class="text-gradient mb-6 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-4xl font-bold text-transparent"
252
+ {...props}
253
+ >
254
+ {@render children()}
255
+ </h1>
256
+ {/snippet}
10
257
 
11
- ```sh
12
- # create a new project in the current directory
13
- npx sv create
258
+ {#snippet customCode({ children, ...props })}
259
+ <code class="rounded bg-gray-100 px-2 py-1 font-mono text-sm dark:bg-gray-800" {...props}>
260
+ {@render children()}
261
+ </code>
262
+ {/snippet}
14
263
 
15
- # create a new project in my-app
16
- npx sv create my-app
264
+ {#snippet customBlockquote({ children, ...props })}
265
+ <blockquote
266
+ class="border-l-4 border-blue-500 pl-4 text-gray-600 italic dark:text-gray-300"
267
+ {...props}
268
+ >
269
+ {@render children()}
270
+ </blockquote>
271
+ {/snippet}
272
+
273
+ <Streamdown {content} h1={customH1} code={customCode} blockquote={customBlockquote} />
274
+ ```
275
+
276
+ #### All Available Customizable Elements:
277
+
278
+ **Text Elements**: `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `p`, `strong`, `em`, `del`
279
+
280
+ **Links & Media**: `a`, `img`
281
+
282
+ **Lists**: `ul`, `ol`, `li`
283
+
284
+ **Code**: `code`, `inlineCode`, `pre`
285
+
286
+ **Tables**: `table`, `thead`, `tbody`, `tr`, `th`, `td`
287
+
288
+ **Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `inlineMath`
289
+
290
+ **Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system. Use `customElements` only for HTML elements **not in this list** (like `div`, `span`, `section`, `article`, etc.).
291
+
292
+ Each snippet receives `{ children, ...props }` where `props` contains all element attributes and classes.
293
+
294
+ ### Using `customElements` Record
295
+
296
+ The `customElements` prop is specifically for HTML elements that are **not handled by the library by default**. For elements already supported by Streamdown (like `h1`, `p`, `code`, etc.), use individual props or the theme system instead.
297
+
298
+ ```svelte
299
+ <script>
300
+ import { Streamdown } from 'svelte-streamdown';
301
+
302
+ let content = `# Custom Elements Example
303
+
304
+ This content contains HTML elements not handled by Streamdown by default:
305
+
306
+ <div class="special">This is a custom div element</div>
307
+
308
+ <span class="highlight">This is a custom span element</span>
309
+
310
+ <section class="wrapper">
311
+ <article>This is a custom article inside a section</article>
312
+ </section>`;
313
+
314
+ // Define custom components for unsupported HTML elements
315
+ </script>
316
+
317
+ {#snippet customDiv({ children, props, className, node })}
318
+ <div class="rounded-lg border-2 border-blue-500 p-4 {className}" {...props}>
319
+ {@render children()}
320
+ </div>
321
+ {/snippet}
322
+
323
+ {#snippet customSpan({ children, props, className, node })}
324
+ <span class="rounded bg-yellow-200 px-2 py-1 {className}" {...props}>
325
+ {@render children()}
326
+ </span>
327
+ {/snippet}
328
+
329
+ {#snippet customSection({ children, props, className, node })}
330
+ <section class="my-8 rounded-xl bg-gray-50 p-6 {className}" {...props}>
331
+ {@render children()}
332
+ </section>
333
+ {/snippet}
334
+
335
+ {#snippet customArticle({ children, props, className, node })}
336
+ <article class="prose max-w-none {className}" {...props}>
337
+ {@render children()}
338
+ </article>
339
+ {/snippet}
340
+
341
+ <Streamdown
342
+ {content}
343
+ customElements={{
344
+ div: customDiv,
345
+ span: customSpan,
346
+ section: customSection,
347
+ article: customArticle
348
+ }}
349
+ />
17
350
  ```
18
351
 
19
- ## Developing
352
+ ### Benefits of `customElements`
353
+
354
+ - **Handle Unsupported Elements**: Define components for HTML elements not built into Streamdown
355
+ - **Semantic HTML Support**: Use elements like `<section>`, `<article>`, `<aside>`, `<nav>`, etc.
356
+ - **Fallback Support**: Automatically handles unknown elements that would otherwise be ignored
357
+ - **Type Safety**: Full TypeScript support with `ElementProps` interface
358
+
359
+ ### `customElements` vs Individual Props
20
360
 
21
- Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
361
+ Use the right approach for the right elements:
22
362
 
23
- ```sh
24
- npm run dev
363
+ **Individual Props** (for supported Streamdown elements):
25
364
 
26
- # or start the server and open the app in a new browser tab
27
- npm run dev -- --open
365
+ ```svelte
366
+ <Streamdown {content} h1={customH1} p={customP} code={customCode} />
28
367
  ```
29
368
 
30
- Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
369
+ **CustomElements Record** (for unsupported HTML elements):
31
370
 
32
- ## Building
371
+ ```svelte
372
+ <Streamdown
373
+ {content}
374
+ customElements={{ div: customDiv, span: customSpan, section: customSection }}
375
+ />
376
+ ```
377
+
378
+ **Note**: For supported elements (h1, p, code, etc.), use individual props or the theme system. For unsupported elements (div, span, section, etc.), use `customElements`.
379
+
380
+ ## 🎨 Advanced Theming System
381
+
382
+ ### Built-in Themes
383
+
384
+ Streamdown comes with two built-in themes:
33
385
 
34
- To build your library:
386
+ - **Default Theme**: The standard theme with gray-based colors
387
+ - **Shadcn Theme**: A theme that uses shadcn/ui design tokens for seamless integration with shadcn-based projects
35
388
 
36
- ```sh
37
- npm pack
389
+ Beyond custom snippets, Streamdown provides a **granular theming system** that lets you customize every part of every component without writing custom snippets. You can use the built-in themes (default and shadcn) or create completely custom themes using the `mergeTheme` utility.
390
+
391
+ ### Theme Structure
392
+
393
+ Every component has multiple themeable parts. For example, the `code` component has:
394
+
395
+ ```typescript
396
+ code: {
397
+ base: 'bg-gray-100 rounded p-2 font-mono text-sm', // Main code block
398
+ container: 'my-4 w-full overflow-hidden rounded-xl border', // Wrapper container
399
+ header: 'flex items-center justify-between bg-gray-100/80', // Header with language
400
+ button: 'cursor-pointer p-1 text-gray-600 transition-all', // Copy button
401
+ language: 'ml-1 font-mono lowercase', // Language label
402
+ pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40' // Pre element
403
+ }
38
404
  ```
39
405
 
40
- To create a production version of your showcase app:
406
+ ### Using Custom Themes
407
+
408
+ ```svelte
409
+ <script>
410
+ import { Streamdown } from 'svelte-streamdown';
41
411
 
42
- ```sh
43
- npm run build
412
+ let content = `# Custom Theme Example
413
+
414
+ \`\`\`javascript
415
+ console.log('Beautiful code blocks!');
416
+ \`\`\`
417
+
418
+ > This blockquote is also themed
419
+
420
+ | Header 1 | Header 2 |
421
+ |----------|----------|
422
+ | Cell 1 | Cell 2 |
423
+ `;
424
+
425
+ // Custom theme overrides
426
+ let customTheme = {
427
+ code: {
428
+ container: 'my-6 rounded-2xl border-2 border-purple-200 shadow-lg',
429
+ header: 'bg-purple-50 text-purple-700 font-medium',
430
+ button: 'text-purple-600 hover:text-purple-800 hover:bg-purple-100'
431
+ },
432
+ blockquote: {
433
+ base: 'border-l-8 border-purple-400 bg-purple-50 p-4 italic text-purple-800'
434
+ },
435
+ table: {
436
+ base: 'border-purple-200 shadow-md',
437
+ container: 'my-6 rounded-lg overflow-hidden'
438
+ },
439
+ th: {
440
+ base: 'bg-purple-100 px-6 py-3 text-purple-900 font-bold'
441
+ },
442
+ td: {
443
+ base: 'px-6 py-3 border-purple-100'
444
+ }
445
+ };
446
+ </script>
447
+
448
+ <Streamdown {content} theme={customTheme} />
44
449
  ```
45
450
 
46
- You can preview the production build with `npm run preview`.
451
+ ### All Themeable Components
452
+
453
+ Each component supports multiple themeable parts:
454
+
455
+ **Headings (`h1`-`h6`)**: `base`
456
+
457
+ **Text Elements (`p`, `strong`, `em`, `del`)**: `base`
458
+
459
+ **Lists (`ul`, `ol`, `li`)**: `base`
460
+
461
+ **Links (`a`)**: `base`, `blocked` (for blocked/unsafe links)
462
+
463
+ **Code (`code`)**: `base`, `container`, `header`, `button`, `language`, `skeleton`, `pre`
464
+
465
+ **Inline Code (`inlineCode`)**: `base`
466
+
467
+ **Images (`img`)**: `container`, `base`, `downloadButton`
468
+
469
+ **Tables (`table`, `thead`, `tbody`, `tr`, `th`, `td`)**: `base`, `container` (table only)
470
+
471
+ **Blockquotes (`blockquote`)**: `base`
472
+
473
+ **Alerts (`alert`)**: `base`, `title`, `icon`, plus type-specific styles (`note`, `tip`, `warning`, `caution`, `important`)
474
+
475
+ **Mermaid (`mermaid`)**: `base`, `downloadButton`
476
+
477
+ **Math (`math`, `inlineMath`)**: `base`
47
478
 
48
- > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
479
+ **Other (`hr`, `sup`, `sub`)**: `base`
49
480
 
50
- ## Publishing
481
+ ### Theme Merging
51
482
 
52
- Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
483
+ Themes are intelligently merged using Tailwind's class merging utility, so you only need to override the specific parts you want to customize while keeping the default styling for everything else.
53
484
 
54
- To publish your library to [npm](https://www.npmjs.com):
485
+ ## 🛠️ Development
55
486
 
56
- ```sh
57
- npm publish
487
+ ### Setup
488
+
489
+ ```bash
490
+ # Clone the repository
491
+ git clone <repository-url>
492
+ cd svelte-streamdown
493
+
494
+ # Install dependencies
495
+ pnpm install
496
+
497
+ # Start development server
498
+ pnpm dev
499
+
500
+ # Run tests
501
+ pnpm test
502
+
503
+ # Build for production
504
+ pnpm build
505
+ ```
506
+
507
+ ### Building
508
+
509
+ ```bash
510
+ # Build the library
511
+ pnpm build
512
+
513
+ # Preview the showcase app
514
+ pnpm preview
58
515
  ```
516
+
517
+ ## 🤝 Contributing
518
+
519
+ Contributions are welcome! This is a port of the original Streamdown project, so please:
520
+
521
+ 1. Check the [original Streamdown repository](https://github.com/vercel/streamdown) for upstream changes
522
+ 2. Ensure compatibility with the original API
523
+ 3. Maintain feature parity where possible
524
+ 4. Add tests for new features if you want
525
+
526
+ ## 📄 License
527
+
528
+ MIT
529
+
530
+ ## 🙏 Acknowledgments
531
+
532
+ - **Original Streamdown**: [Vercel](https://vercel.com) for creating the original React component
533
+ - **Svelte Community**: For the amazing framework that made this port possible
534
+ - **All Contributors**: For helping improve and maintain this project
535
+
536
+ ---
537
+
538
+ Made with ❤️ and 🤖
@@ -12,7 +12,7 @@
12
12
  let { node, className, props }: ElementProps = $props();
13
13
 
14
14
  const streamdown = useStreamdown();
15
- const themes = $derived(streamdown.shikiTheme);
15
+ const theme = $derived(streamdown.shikiTheme);
16
16
  let codeContent = $derived((node.children[0] as any).value);
17
17
  const language = $derived(node.properties.language as string);
18
18
  const copy = useCopy({
@@ -36,9 +36,17 @@
36
36
  console.error('Failed to download file:', error);
37
37
  }
38
38
  };
39
+
40
+ $effect(() => {
41
+ void highlighter.isReady(theme, language as any);
42
+ });
39
43
  </script>
40
44
 
41
- <div {...props} class={clsx(streamdown.theme.code.container, className)} data-language={language}>
45
+ <div
46
+ {...props}
47
+ class={clsx(streamdown.theme.code.base, streamdown.theme.code.container, className)}
48
+ data-language={language}
49
+ >
42
50
  <div class={clsx(streamdown.theme.code.header)} data-code-block-header data-language={language}>
43
51
  <span class={streamdown.theme.code.language}>{language}</span>
44
52
  <div class="flex items-center gap-2">
@@ -57,34 +65,36 @@
57
65
  </button>
58
66
  </div>
59
67
  </div>
60
- <div style="height: fit-content; width: 100%;" class="relative overflow-visible">
61
- <div class={streamdown.theme.code.base}>
62
- {#await highlighter.isReady(themes, language as any)}
63
- {@render Skeleton()}
64
- {:then}
68
+ <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
69
+ <div>
70
+ {#if highlighter.isLoaded(theme, language as any)}
65
71
  {@const code = highlighter.highlightCode(
66
72
  codeContent,
67
73
  language as any,
68
- themes,
74
+ theme,
69
75
  streamdown.theme.code.pre
70
76
  )}
71
77
  {@html code}
72
- {/await}
78
+ {:else}
79
+ {@render Skeleton()}
80
+ {/if}
73
81
  </div>
74
82
  </div>
75
83
  </div>
76
84
 
85
+ <!-- Need to improve this -->
77
86
  {#snippet Skeleton()}
78
87
  {@const lines = codeContent.split('\n')}
79
88
  <!-- -->
80
- <pre class={streamdown.theme.code.pre}><!-- -->
81
- <!-- --><code
82
- ><!-- -->{#each lines as line}<!-- --><span class={streamdown.theme.code.skeleton}
83
- >{line}</span
84
- ><!-- -->
85
- <!-- -->{/each}<!-- --></code
86
- ><!-- -->
87
- </pre>
89
+
90
+ <!-- --><code
91
+ class={streamdown.theme.code.pre}
92
+ style="height: fit-content; width: 100%; display: flex; flex-direction: column;"
93
+ ><!-- -->{#each lines as line}<!-- --><span class={streamdown.theme.code.skeleton}
94
+ >{line}</span
95
+ ><!-- -->
96
+ <!-- -->{/each}<!-- --></code
97
+ ><!-- -->
88
98
  {/snippet}
89
99
  {#snippet copyIcon()}
90
100
  <svg