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 +21 -0
- package/README.md +511 -31
- package/dist/Elements/Code.svelte +27 -17
- package/dist/Elements/Element.svelte +11 -3
- package/dist/Elements/Image.svelte +2 -7
- package/dist/Elements/Math.svelte +29 -47
- package/dist/Elements/Mermaid.svelte +207 -5
- package/dist/Elements/Table.svelte +2 -2
- package/dist/Elements/element.d.ts +1 -1
- package/dist/Streamdown.d.ts +8 -35
- package/dist/Streamdown.svelte +22 -3
- package/dist/hightlighter.svelte.d.ts +18 -12
- package/dist/hightlighter.svelte.js +91 -65
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/theme.d.ts +127 -14
- package/dist/theme.js +139 -22
- package/dist/utils/panzoom.svelte.d.ts +36 -0
- package/dist/utils/panzoom.svelte.js +483 -0
- package/package.json +3 -2
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
|
|
1
|
+
# Svelte Streamdown
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/svelte-streamdown)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
7
|
+
## 📦 Installation
|
|
8
8
|
|
|
9
|
-
|
|
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 = `
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
#
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
+
Use the right approach for the right elements:
|
|
22
362
|
|
|
23
|
-
|
|
24
|
-
npm run dev
|
|
363
|
+
**Individual Props** (for supported Streamdown elements):
|
|
25
364
|
|
|
26
|
-
|
|
27
|
-
|
|
365
|
+
```svelte
|
|
366
|
+
<Streamdown {content} h1={customH1} p={customP} code={customCode} />
|
|
28
367
|
```
|
|
29
368
|
|
|
30
|
-
|
|
369
|
+
**CustomElements Record** (for unsupported HTML elements):
|
|
31
370
|
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
406
|
+
### Using Custom Themes
|
|
407
|
+
|
|
408
|
+
```svelte
|
|
409
|
+
<script>
|
|
410
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
41
411
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
+
**Other (`hr`, `sup`, `sub`)**: `base`
|
|
49
480
|
|
|
50
|
-
|
|
481
|
+
### Theme Merging
|
|
51
482
|
|
|
52
|
-
|
|
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
|
-
|
|
485
|
+
## 🛠️ Development
|
|
55
486
|
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
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=
|
|
61
|
-
<div
|
|
62
|
-
{#
|
|
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
|
-
|
|
74
|
+
theme,
|
|
69
75
|
streamdown.theme.code.pre
|
|
70
76
|
)}
|
|
71
77
|
{@html code}
|
|
72
|
-
{
|
|
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
|
-
|
|
81
|
-
<!-- --><code
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|