svelte-streamdown 1.0.9 → 2.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.
Files changed (37) hide show
  1. package/README.md +144 -135
  2. package/dist/Block.svelte +14 -54
  3. package/dist/Elements/Alert.svelte +1 -2
  4. package/dist/Elements/Code.svelte +2 -4
  5. package/dist/Elements/Code.svelte.d.ts +0 -2
  6. package/dist/Elements/Element.svelte +70 -25
  7. package/dist/Elements/FootnoteRef.svelte +19 -6
  8. package/dist/Elements/Image.svelte +1 -1
  9. package/dist/Elements/Link.svelte +1 -1
  10. package/dist/Elements/Math.svelte +13 -25
  11. package/dist/Elements/Math.svelte.d.ts +0 -2
  12. package/dist/Elements/Mermaid.svelte +105 -115
  13. package/dist/Elements/Mermaid.svelte.d.ts +0 -2
  14. package/dist/Elements/Table.svelte +1 -1
  15. package/dist/Streamdown.d.ts +52 -10
  16. package/dist/Streamdown.js +19 -1
  17. package/dist/Streamdown.svelte +3 -41
  18. package/dist/Streamdown.svelte.d.ts +1 -20
  19. package/dist/marked/index.d.ts +3 -20
  20. package/dist/marked/index.js +27 -19
  21. package/dist/marked/marked-alert.d.ts +2 -2
  22. package/dist/marked/marked-alert.js +6 -19
  23. package/dist/marked/marked-footnotes.js +1 -2
  24. package/dist/marked/marked-list.d.ts +2 -2
  25. package/dist/marked/marked-list.js +28 -11
  26. package/dist/marked/marked-math.js +1 -1
  27. package/dist/marked/marked-subsup.d.ts +1 -1
  28. package/dist/marked/marked-subsup.js +2 -1
  29. package/dist/marked/marked-table.d.ts +59 -0
  30. package/dist/marked/marked-table.js +495 -0
  31. package/dist/theme.d.ts +24 -6
  32. package/dist/theme.js +22 -10
  33. package/dist/utils/panzoom.svelte.d.ts +17 -18
  34. package/dist/utils/panzoom.svelte.js +0 -2
  35. package/dist/utils/useClickOutside.svelte.js +0 -1
  36. package/dist/utils/useKeyDown.svelte.js +4 -1
  37. package/package.json +1 -2
package/README.md CHANGED
@@ -20,6 +20,27 @@ Perfect for AI-powered applications that need to stream and render markdown cont
20
20
 
21
21
  ## ✨ Main Features
22
22
 
23
+
24
+ ### 🔄 Streaming-Optimized
25
+
26
+ - **Incomplete Markdown Parsing**: Handles unterminated blocks gracefully
27
+ - **Progressive Rendering**: Perfect for streaming AI responses
28
+ - **Real-time Updates**: Optimized for dynamic content
29
+
30
+ ### 🔒 Security Hardening
31
+
32
+ - **Image Origin Control**: Whitelist allowed image sources
33
+ - **Link Safety**: Control link destinations
34
+ - **HTML Sanitization**: Prevent XSS attacks
35
+
36
+ ### 🎯 Fully Customizable Components & Theming
37
+
38
+ - **Every component customizable** with Svelte snippets
39
+ - **Granular theming system** - customize every part of every component
40
+ - Override default styling and behavior for any markdown element
41
+ - Full control over rendering with type-safe props
42
+ - Seamless integration with your design system
43
+
23
44
  ### 🎨 Built-in Typography Styles
24
45
 
25
46
  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,7 +49,6 @@ Beautiful, responsive typography with **built-in Tailwind CSS classes** for head
28
49
 
29
50
  Full support for GitHub Flavored Markdown including:
30
51
 
31
- - Task lists
32
52
  - Tables
33
53
  - ~~Strikethrough~~
34
54
  - Subscript (H~2~O)
@@ -99,43 +119,124 @@ pie title Project Time Allocation
99
119
  "Meetings" : 15
100
120
  ```
101
121
 
102
- ### Alert Support
122
+ ### Complex table support
103
123
 
104
- > [!IMPORTANT]
105
- > Native support for Github style Alert
124
+ #### Colspan
106
125
 
107
- ### 🔄 Streaming-Optimized
126
+ | H1 | H2 | H3 |
127
+ | ------------------------- | --- | --- |
128
+ | This cell spans 3 columns |||
108
129
 
109
- - **Incomplete Markdown Parsing**: Handles unterminated blocks gracefully
110
- - **Progressive Rendering**: Perfect for streaming AI responses
111
- - **Real-time Updates**: Optimized for dynamic content
130
+ | Header 1 | Header 2 | Header 3 |
131
+ | ------------------------- | -------- | -------- |
132
+ | This cell spans 2 columns || Normal
133
+ | Normal | Normal | Normal |
112
134
 
113
- ### 🔒 Security Hardening
135
+ #### Rowspan
136
+ | Header 1 | Header 2 |
137
+ | --------------- | -------- |
138
+ | This cell spans | Cell A |
139
+ | two rows ^ | Cell B |
140
+ #### Footer
141
+ | Header 1 | Header 2 |
142
+ | --------------- | -------- |
143
+ | Cell B | Cell A |
144
+ | --------------- | -------- |
145
+ | Footer ||
114
146
 
115
- - **Image Origin Control**: Whitelist allowed image sources
116
- - **Link Safety**: Control link destinations
117
- - **HTML Sanitization**: Prevent XSS attacks
147
+ #### Column alignment
118
148
 
119
- ### 🎯 Fully Customizable Components & Theming
149
+ | Left | Center | Right |
150
+ | :--- | :----: | ----: |
151
+ | A | B | C |
152
+
153
+ #### Multiple headers and very complex layout
154
+
155
+
156
+ | Product Category ||| Sales Data Q1-Q4 2024 ||||
157
+ | Product | Region || Q1 | Q2 | Q3 | Q4 |
158
+ | Name | Type | Area | Revenue | Revenue | Revenue | Revenue |
159
+ |-------------|---------|------------|---------|---------|---------|---------|
160
+ | Laptop Pro | Electronics | North America || $45,000 | $52,000 | $48,000 |
161
+ | Laptop Pro ^ | ^ | Europe | $32,000 | $38,000 | $41,000 | $44,000 |
162
+ | Laptop Pro ^ | ^ | Asia || $28,000 | $35,000 | $42,000 |
163
+ | Office Chair | Furniture | North America | $15,000 | $18,000 | $16,000 | $17,000 |
164
+ | Office Chair ^ | ^ | Europe | $12,000 | $14,000 | $15,000 | $16,000 |
165
+ | Wireless Mouse | Electronics | Global ||| $25,000 | $28,000 |
166
+ |-------------|---------|------------|---------|---------|---------|---------|
167
+ | **Total Revenue** ||| **$152,000** | **$185,000** | **$187,000** | **$205,000** |
168
+
169
+ ### Complex list support
170
+
171
+ #### decimal
172
+
173
+ 1. First item
174
+ 2. Second item
175
+ 3. Third item
176
+
177
+ #### lower-alpha
178
+
179
+ a. First item
180
+ b. Second item
181
+ c. Third item
182
+
183
+ #### upper-alpha
184
+
185
+ A. First item
186
+ B. Second item
187
+ C. Third item
188
+
189
+ #### lower-roman
190
+
191
+ i. First item
192
+ ii. Second item
193
+ iii. Third item
194
+
195
+ #### upper-roman
196
+
197
+ I. First item
198
+ II. Second item
199
+ III. Third item
200
+
201
+ #### Nested Lists
202
+
203
+ 1. First level (numeric)
204
+ a. Second level (lowercase alpha)
205
+ i. Third level (lowercase roman)
206
+ - Fourth level (bullet)
207
+ I. Fifth level (uppercase roman)
208
+ A. Sixth level (uppercase alpha)
209
+
210
+ 2. Back to the first level
211
+
212
+ #### Task List
213
+
214
+ - [ ] Uncompleted task
215
+ - [x] Completed task
216
+ - [ ] Another uncompleted task
217
+ - [ ] Nested uncompleted subtask
218
+ - [x] Nested completed subtask
219
+
220
+
221
+ ### Alert Support
222
+
223
+ > [!IMPORTANT]
224
+ > Native support for Github style Alert
120
225
 
121
- - **Every component customizable** with Svelte snippets
122
- - **Granular theming system** - customize every part of every component
123
- - Override default styling and behavior for any markdown element
124
- - Full control over rendering with type-safe props
125
- - Seamless integration with your design system
126
226
 
127
227
  ## 🔄 Differences from Original React Version
128
228
 
129
229
  This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
130
230
 
131
- | Aspect | Original (React) | Svelte Port |
132
- | ----------------- | ---------------- | ------------------------- |
133
- | **Framework** | React | Svelte 5 |
134
- | **Component API** | JSX Components | Svelte Snippets |
135
- | **Styling** | Tailwind CSS | Tailwind CSS (compatible) |
136
- | **Context** | React Context | Svelte Context |
137
- | **Build System** | Vite/React | Vite/SvelteKit |
138
- | **TypeScript** | Full TS support | Full TS support |
231
+ | Aspect | Original (React) | Svelte Port |
232
+ | ----------------- | ------------------------ | ------------------------- |
233
+ | **Framework** | React | Svelte 5 |
234
+ | **Component API** | JSX Components | Svelte Snippets |
235
+ | **Styling** | Tailwind CSS | Tailwind CSS (compatible) |
236
+ | **Context** | React Context | Svelte Context |
237
+ | **Build System** | Vite/React | Vite/SvelteKit |
238
+ | **TypeScript** | Full TS support | Full TS support |
239
+ | **Engine** | Remark / Rehype + marked | marked only |
139
240
 
140
241
  ### Tailwind CSS Setup
141
242
 
@@ -184,13 +285,13 @@ This heading will use a custom component!`;
184
285
  // Custom heading component
185
286
  </script>
186
287
 
187
- {#snippet customH1({ children, props })}
188
- <h1 class="mb-4 text-4xl font-bold text-blue-600" {...props}>
189
- {children}
288
+ {#snippet customHeading({ children, token })}
289
+ <h1 class="mb-4 text-4xl font-bold text-blue-600" {...token.props}>
290
+ {@render children()}
190
291
  </h1>
191
292
  {/snippet}
192
293
 
193
- <Streamdown {content} h1={customH1} />
294
+ <Streamdown {content} heading={customHeading} />
194
295
  ```
195
296
 
196
297
  ### Security Configuration
@@ -220,9 +321,6 @@ This heading will use a custom component!`;
220
321
  | `defaultOrigin` | `string` | - | Default origin for relative URLs |
221
322
  | `allowedLinkPrefixes` | `string[]` | `['*']` | Allowed URL prefixes for links |
222
323
  | `allowedImagePrefixes` | `string[]` | `['*']` | Allowed URL prefixes for images |
223
- | `allowElement` | `AllowElement \| null` | - | Custom element filtering function |
224
- | `allowedElements` | `readonly string[] \| null` | - | Whitelist of allowed HTML elements |
225
- | `disallowedElements` | `readonly string[] \| null` | - | Blacklist of disallowed HTML elements |
226
324
  | `skipHtml` | `boolean` | - | Skip HTML parsing entirely |
227
325
  | `unwrapDisallowed` | `boolean` | - | Unwrap instead of removing disallowed elements |
228
326
  | `urlTransform` | `UrlTransform \| null` | - | Custom URL transformation function |
@@ -232,14 +330,13 @@ This heading will use a custom component!`;
232
330
  | `shikiTheme` | `BundledTheme` | `'github-light'` | Code highlighting theme |
233
331
  | `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
234
332
  | `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
235
- | `remarkPlugins` | `PluggableList` | - | Additional remark plugins |
236
- | `rehypePlugins` | `PluggableList` | - | Additional rehype plugins |
237
- | `remarkRehypeOptions` | `RemarkRehypeOptions` | - | Remark-rehype conversion options |
238
- | `customElements` | `Record<string, Snippet<[ElementProps]>>` | - | Custom snippets for not handled nodes |
239
333
 
240
334
  ### Custom Component Props
241
335
 
242
- **Every single markdown element** can be customized with Svelte snippets, giving you complete control over styling and behavior:
336
+ **Every single markdown element** can be customized with Svelte snippets, giving you
337
+ complete control over styling and behavior:
338
+
339
+ Each snippet receives `{ children, token }` where `token` is a typed token object containing the parsed markdown token with its properties and children is a snippet to be rendered.
243
340
 
244
341
  ```svelte
245
342
  <script>
@@ -250,36 +347,36 @@ This heading will use a custom component!`;
250
347
  This heading uses a custom component with your design system!`;
251
348
  </script>
252
349
 
253
- {#snippet customH1({ children, ...props })}
350
+ {#snippet customHeading({ children, token })}
254
351
  <h1
255
352
  class="text-gradient mb-6 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-4xl font-bold text-transparent"
256
- {...props}
353
+ {...token.props}
257
354
  >
258
355
  {@render children()}
259
356
  </h1>
260
357
  {/snippet}
261
358
 
262
- {#snippet customCode({ children, ...props })}
263
- <code class="rounded bg-gray-100 px-2 py-1 font-mono text-sm dark:bg-gray-800" {...props}>
359
+ {#snippet customCode({ children, token })}
360
+ <code class="rounded bg-gray-100 px-2 py-1 font-mono text-sm dark:bg-gray-800" {...token.props}>
264
361
  {@render children()}
265
362
  </code>
266
363
  {/snippet}
267
364
 
268
- {#snippet customBlockquote({ children, ...props })}
365
+ {#snippet customBlockquote({ children, token })}
269
366
  <blockquote
270
367
  class="border-l-4 border-blue-500 pl-4 text-gray-600 italic dark:text-gray-300"
271
- {...props}
368
+ {...token.props}
272
369
  >
273
370
  {@render children()}
274
371
  </blockquote>
275
372
  {/snippet}
276
373
 
277
- <Streamdown {content} h1={customH1} code={customCode} blockquote={customBlockquote} />
374
+ <Streamdown {content} heading={customHeading} code={customCode} blockquote={customBlockquote} />
278
375
  ```
279
376
 
280
377
  #### All Available Customizable Elements:
281
378
 
282
- **Text Elements**: `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `p`, `strong`, `em`, `del`
379
+ **Text Elements**: `heading`, `p`, `strong`, `em`, `del`
283
380
 
284
381
  **Links & Media**: `a`, `img`
285
382
 
@@ -291,95 +388,7 @@ This heading uses a custom component with your design system!`;
291
388
 
292
389
  **Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `inlineMath`
293
390
 
294
- **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.).
295
-
296
- Each snippet receives `{ children, ...props }` where `props` contains all element attributes and classes.
297
-
298
- ### Using `customElements` Record
299
-
300
- 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.
301
-
302
- ```svelte
303
- <script>
304
- import { Streamdown } from 'svelte-streamdown';
305
-
306
- let content = `# Custom Elements Example
307
-
308
- This content contains HTML elements not handled by Streamdown by default:
309
-
310
- <div class="special">This is a custom div element</div>
311
-
312
- <span class="highlight">This is a custom span element</span>
313
-
314
- <section class="wrapper">
315
- <article>This is a custom article inside a section</article>
316
- </section>`;
317
-
318
- // Define custom components for unsupported HTML elements
319
- </script>
320
-
321
- {#snippet customDiv({ children, props, className, node })}
322
- <div class="rounded-lg border-2 border-blue-500 p-4 {className}" {...props}>
323
- {@render children()}
324
- </div>
325
- {/snippet}
326
-
327
- {#snippet customSpan({ children, props, className, node })}
328
- <span class="rounded bg-yellow-200 px-2 py-1 {className}" {...props}>
329
- {@render children()}
330
- </span>
331
- {/snippet}
332
-
333
- {#snippet customSection({ children, props, className, node })}
334
- <section class="my-8 rounded-xl bg-gray-50 p-6 {className}" {...props}>
335
- {@render children()}
336
- </section>
337
- {/snippet}
338
-
339
- {#snippet customArticle({ children, props, className, node })}
340
- <article class="prose max-w-none {className}" {...props}>
341
- {@render children()}
342
- </article>
343
- {/snippet}
344
-
345
- <Streamdown
346
- {content}
347
- customElements={{
348
- div: customDiv,
349
- span: customSpan,
350
- section: customSection,
351
- article: customArticle
352
- }}
353
- />
354
- ```
355
-
356
- ### Benefits of `customElements`
357
-
358
- - **Handle Unsupported Elements**: Define components for HTML elements not built into Streamdown
359
- - **Semantic HTML Support**: Use elements like `<section>`, `<article>`, `<aside>`, `<nav>`, etc.
360
- - **Fallback Support**: Automatically handles unknown elements that would otherwise be ignored
361
- - **Type Safety**: Full TypeScript support with `ElementProps` interface
362
-
363
- ### `customElements` vs Individual Props
364
-
365
- Use the right approach for the right elements:
366
-
367
- **Individual Props** (for supported Streamdown elements):
368
-
369
- ```svelte
370
- <Streamdown {content} h1={customH1} p={customP} code={customCode} />
371
- ```
372
-
373
- **CustomElements Record** (for unsupported HTML elements):
374
-
375
- ```svelte
376
- <Streamdown
377
- {content}
378
- customElements={{ div: customDiv, span: customSpan, section: customSection }}
379
- />
380
- ```
381
-
382
- **Note**: For supported elements (h1, p, code, etc.), use individual props or the theme system. For unsupported elements (div, span, section, etc.), use `customElements`.
391
+ **Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system.
383
392
 
384
393
  ## 🎨 Advanced Theming System
385
394
 
package/dist/Block.svelte CHANGED
@@ -4,7 +4,7 @@
4
4
  <script lang="ts">
5
5
  import { parseIncompleteMarkdown } from './utils/parse-incomplete-markdown.js';
6
6
  import Element from './Elements/Element.svelte';
7
- import { lex, type StreamdownToken, type TableRow } from './marked/index.js';
7
+ import { lex, type StreamdownToken } from './marked/index.js';
8
8
 
9
9
  let {
10
10
  block
@@ -15,50 +15,8 @@
15
15
  const tokens = $derived(lex(parseIncompleteMarkdown(block.trim())));
16
16
 
17
17
  const getChildren = (token: StreamdownToken) => {
18
- if (token.type === 'list') {
19
- return token.items as StreamdownToken[];
20
- }
21
-
22
- if (token.type === 'table') {
23
- const headerRow = token.header?.length
24
- ? ({
25
- type: 'tableRow',
26
- raw: '',
27
- tokens: token.header.map((cell) => ({
28
- type: 'th',
29
- raw: '',
30
- ...cell
31
- })),
32
- isHeader: true
33
- } satisfies TableRow)
34
- : null;
35
-
36
- const rows = token.rows.reduce(
37
- (acc, cells) => {
38
- const row = {
39
- type: 'tableRow',
40
- raw: '',
41
- tokens: cells.map((cell) => ({
42
- type: 'td',
43
- raw: '',
44
- ...cell
45
- })),
46
- isHeader: false
47
- } satisfies TableRow;
48
- acc.push(row);
49
- return acc;
50
- },
51
- headerRow
52
- ? ([
53
- {
54
- type: 'tableHead',
55
- tokens: [headerRow]
56
- }
57
- ] as StreamdownToken[])
58
- : []
59
- );
60
-
61
- return rows;
18
+ if (!token) {
19
+ return [];
62
20
  }
63
21
  const children =
64
22
  'tokens' in token ? ((token.tokens || []) as StreamdownToken[]) : ([] as StreamdownToken[]);
@@ -79,15 +37,17 @@
79
37
 
80
38
  {#snippet renderChildren(tokens: StreamdownToken[])}
81
39
  {#each tokens as token}
82
- {@const children = getChildren(token)}
83
- {@const isTextOnlyNode = children.length === 0}
84
- <Element {token}>
85
- {#if isTextOnlyNode}
86
- {'text' in token ? token.text : ''}
87
- {:else}
88
- {@render renderChildren(children)}
89
- {/if}
90
- </Element>
40
+ {#if token}
41
+ {@const children = getChildren(token)}
42
+ {@const isTextOnlyNode = children.length === 0}
43
+ <Element {token}>
44
+ {#if isTextOnlyNode}
45
+ {'text' in token ? token.text : ''}
46
+ {:else}
47
+ {@render renderChildren(children)}
48
+ {/if}
49
+ </Element>
50
+ {/if}
91
51
  {/each}
92
52
  {/snippet}
93
53
 
@@ -13,7 +13,7 @@
13
13
  /><path d="M12 17h.01" />`
14
14
  };
15
15
 
16
- import { useStreamdown } from '../Streamdown.svelte';
16
+ import { useStreamdown } from '../Streamdown.js';
17
17
  import Slot from './Slot.svelte';
18
18
  import type { AlertToken } from '../marked/index.js';
19
19
  import type { Snippet } from 'svelte';
@@ -35,7 +35,6 @@
35
35
  <Slot
36
36
  props={{
37
37
  children,
38
- alertType,
39
38
  token
40
39
  }}
41
40
  render={streamdown.snippets.alert}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { useStreamdown } from '../Streamdown.svelte';
2
+ import { useStreamdown } from '../Streamdown.js';
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';
@@ -7,11 +7,9 @@
7
7
  import type { Snippet } from 'svelte';
8
8
 
9
9
  const {
10
- token,
11
- children
10
+ token
12
11
  }: {
13
12
  token: Tokens.Code;
14
- children: Snippet;
15
13
  } = $props();
16
14
 
17
15
  const streamdown = useStreamdown();
@@ -1,8 +1,6 @@
1
1
  import type { Tokens } from 'marked';
2
- import type { Snippet } from 'svelte';
3
2
  type $$ComponentProps = {
4
3
  token: Tokens.Code;
5
- children: Snippet;
6
4
  };
7
5
  declare const Code: import("svelte").Component<$$ComponentProps, {}, "">;
8
6
  type Code = ReturnType<typeof Code>;
@@ -3,13 +3,12 @@
3
3
  import Link from './Link.svelte';
4
4
  import Code from './Code.svelte';
5
5
  import Image from './Image.svelte';
6
- import Table from './Table.svelte';
7
6
  import Mermaid from './Mermaid.svelte';
8
7
  import Math from './Math.svelte';
9
8
  import Alert from './Alert.svelte';
10
9
  import type { StreamdownToken } from '../marked/index.js';
11
10
  import Slot from './Slot.svelte';
12
- import { useStreamdown } from '../Streamdown.svelte';
11
+ import { useStreamdown } from '../Streamdown.js';
13
12
  import FootnoteRef from './FootnoteRef.svelte';
14
13
  let { token, children }: { token: StreamdownToken; children: Snippet } = $props();
15
14
  const id = $props.id();
@@ -43,9 +42,13 @@
43
42
  </blockquote>
44
43
  </Slot>
45
44
  {:else if token.type === 'code' && token.lang === 'mermaid'}
46
- <Mermaid {token} {children} />
45
+ <Slot props={{ children, token }} render={streamdown.snippets.code}>
46
+ <Mermaid {token} />
47
+ </Slot>
47
48
  {:else if token.type === 'code'}
48
- <Code {token} {children} />
49
+ <Slot props={{ children, token }} render={streamdown.snippets.code}>
50
+ <Code {token} />
51
+ </Slot>
49
52
  {:else if token.type === 'codespan'}
50
53
  <Slot props={{ children, token }} render={streamdown.snippets.codespan}>
51
54
  <code class={streamdown.theme.codespan.base}>
@@ -54,8 +57,6 @@
54
57
  </Slot>
55
58
  {:else if token.type === 'list'}
56
59
  {#if token.ordered}
57
- {@const firstItemValue = token.items[0]?.value || 1}
58
- {@const counterName = `counter-${id}`}
59
60
  <Slot props={{ children, token }} render={streamdown.snippets.ol}>
60
61
  <ol style:list-style-type={token.listType} class={streamdown.theme.ol.base}>
61
62
  {@render children()}
@@ -72,7 +73,7 @@
72
73
  <Slot props={{ children, token }} render={streamdown.snippets.li}>
73
74
  <li
74
75
  style:list-style-type={token.task ? 'none' : undefined}
75
- value={token.value}
76
+ {...token.value && !token.task ? { value: token.value } : {}}
76
77
  class={streamdown.theme.li.base}
77
78
  >
78
79
  {#if token.task}
@@ -87,31 +88,67 @@
87
88
  </li>
88
89
  </Slot>
89
90
  {:else if token.type === 'table'}
90
- <Table {token} {children} />
91
- {:else if token.type === 'tableRow'}
92
- <Slot props={{ token, children }} render={streamdown.snippets.tableRow}>
93
- <tr class={streamdown.theme.tableRow.base}>
94
- {@render children?.()}
95
- </tr>
91
+ <Slot props={{ token, children }} render={streamdown.snippets.table}>
92
+ <div class={streamdown.theme.table.base}>
93
+ <table class={streamdown.theme.table.table}>
94
+ {@render children()}
95
+ </table>
96
+ </div>
96
97
  </Slot>
97
- {:else if token.type === 'tableHead'}
98
- <Slot props={{ token, children }} render={streamdown.snippets.tableHead}>
99
- <thead class={streamdown.theme.tableHead.base}>
98
+ {:else if token.type === 'thead'}
99
+ <Slot props={{ token, children }} render={streamdown.snippets.thead}>
100
+ <thead class={streamdown.theme.thead.base}>
100
101
  {@render children?.()}
101
102
  </thead>
102
103
  </Slot>
103
- {:else if token.type === 'td'}
104
- <Slot props={{ children, token }} render={streamdown.snippets.td}>
105
- <td class={streamdown.theme.td.base}>
104
+ {:else if token.type === 'tbody'}
105
+ <Slot props={{ token, children }} render={streamdown.snippets.tbody}>
106
+ <tbody class={streamdown.theme.tbody.base}>
106
107
  {@render children?.()}
107
- </td>
108
+ </tbody>
108
109
  </Slot>
109
- {:else if token.type === 'th'}
110
- <Slot props={{ children, token }} render={streamdown.snippets.th}>
111
- <th class={streamdown.theme.th.base}>
110
+ {:else if token.type === 'tfoot'}
111
+ <Slot props={{ token, children }} render={streamdown.snippets.tfoot}>
112
+ <tfoot class={streamdown.theme.tfoot.base}>
113
+ {@render children?.()}
114
+ </tfoot>
115
+ </Slot>
116
+ {:else if token.type === 'tr'}
117
+ <Slot props={{ token, children }} render={streamdown.snippets.tr}>
118
+ <tr class={streamdown.theme.tr.base}>
112
119
  {@render children?.()}
113
- </th>
120
+ </tr>
114
121
  </Slot>
122
+ {:else if token.type === 'td'}
123
+ {#if token.rowspan > 0}
124
+ <Slot props={{ children, token }} render={streamdown.snippets.td}>
125
+ <td
126
+ class={streamdown.theme.td.base}
127
+ {...token.colspan > 1 ? { colspan: token.colspan } : {}}
128
+ {...token.rowspan > 1 ? { rowspan: token.rowspan } : {}}
129
+ {...token.align && ['left', 'center', 'right', 'justify', 'char'].includes(token.align)
130
+ ? { align: token.align as 'left' | 'center' | 'right' | 'justify' | 'char' }
131
+ : { align: 'left' }}
132
+ >
133
+ {@render children?.()}
134
+ </td>
135
+ </Slot>
136
+ {/if}
137
+ {:else if token.type === 'th'}
138
+ {#if token.rowspan > 0}
139
+ <Slot props={{ children, token }} render={streamdown.snippets.th}>
140
+ <th
141
+ class={streamdown.theme.th.base}
142
+ {...token.colspan > 1 ? { colspan: token.colspan } : {}}
143
+ {...token.rowspan > 1 ? { rowspan: token.rowspan } : {}}
144
+ {...token.align && ['left', 'center', 'right', 'justify', 'char'].includes(token.align)
145
+ ? { align: token.align as 'left' | 'center' | 'right' | 'justify' | 'char' }
146
+ : { align: 'left' }}
147
+ >
148
+ {@render children?.()}
149
+ </th>
150
+ </Slot>
151
+ {/if}
115
152
  {:else if token.type === 'image'}
116
153
  <Image {token} {children} />
117
154
  {:else if token.type === 'link'}
@@ -153,7 +190,15 @@
153
190
  {:else if token.type === 'br'}
154
191
  <br />
155
192
  {:else if token.type === 'math'}
156
- <Math {token} {children} />
193
+ <Slot
194
+ props={{
195
+ children,
196
+ token
197
+ }}
198
+ render={streamdown.snippets.math}
199
+ >
200
+ <Math {token} />
201
+ </Slot>
157
202
  {:else if token.type === 'alert'}
158
203
  <Alert {token} {children} />
159
204
  {:else if token.type === 'footnoteRef'}