svelte-streamdown 0.0.1 → 1.0.1

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