svelte-streamdown 2.6.0 → 2.6.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/README.md
CHANGED
|
@@ -490,6 +490,7 @@ This heading will use a custom component!`;
|
|
|
490
490
|
| `animation.tokenize` | `'word' \| 'char'` | `'word'` | Tokenization method for text animations |
|
|
491
491
|
| `animation.animateOnMount` | `boolean` | `false` | Run the token animation on mount or not, useful if you render the Streamdown component in the same time as the first token is receive from the LLM |
|
|
492
492
|
| `extensions` | `Array<Extension>` | `[]` | Custom marked tokenizers to render special markdown blocks or inline tokens |
|
|
493
|
+
| `mdxComponents` | `Record<string, Component>` | `{}` | Map of MDX component names to Svelte components (e.g., `{ Card, Button }`) |
|
|
493
494
|
| `children` | `Snippet<[{token:GenericToken, streamdown: StreamdownContext, children: Snippet` | `undefined` | Snippet used to render elements not supported by Streamdown, custom extensions, and MDX components |
|
|
494
495
|
|
|
495
496
|
#### All Available Customizable Elements:
|
|
@@ -506,9 +507,9 @@ This heading will use a custom component!`;
|
|
|
506
507
|
|
|
507
508
|
**Special Content**: `blockquote`, `hr`, `alert`, `mermaid`, `math`, `footnoteRef`, `inlineCitation`
|
|
508
509
|
|
|
509
|
-
**MDX Components**:
|
|
510
|
+
**MDX Components**: Handled via a single `mdx` snippet that receives `token`, `props`, and `children`. Use `token.tagName` to differentiate between components.
|
|
510
511
|
|
|
511
|
-
**Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system.
|
|
512
|
+
**Note**: The above elements are **supported by Streamdown** and should be customized using individual props or the theme system. MDX components require the `mdx` snippet.
|
|
512
513
|
|
|
513
514
|
## 🎨 Theming System
|
|
514
515
|
|
|
@@ -637,24 +638,80 @@ This is **markdown content** inside a component!
|
|
|
637
638
|
</script>
|
|
638
639
|
|
|
639
640
|
<Streamdown {content}>
|
|
640
|
-
{#snippet
|
|
641
|
-
|
|
642
|
-
<
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
641
|
+
{#snippet mdx({ token, props, children })}
|
|
642
|
+
{#if token.tagName === 'Card'}
|
|
643
|
+
<div class="rounded-lg border border-gray-200 p-4 shadow-sm">
|
|
644
|
+
<h3 class="text-xl font-bold">{props.title}</h3>
|
|
645
|
+
<p class="text-gray-600">Count: {props.count}</p>
|
|
646
|
+
<div class="mt-2">
|
|
647
|
+
{@render children()}
|
|
648
|
+
</div>
|
|
646
649
|
</div>
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
{
|
|
653
|
-
|
|
650
|
+
{:else if token.tagName === 'Button'}
|
|
651
|
+
<button class="rounded px-4 py-2 {props.active ? 'bg-blue-500 text-white' : 'bg-gray-200'}">
|
|
652
|
+
{props.label}
|
|
653
|
+
</button>
|
|
654
|
+
{:else}
|
|
655
|
+
{@render children()}
|
|
656
|
+
{/if}
|
|
654
657
|
{/snippet}
|
|
655
658
|
</Streamdown>
|
|
656
659
|
```
|
|
657
660
|
|
|
661
|
+
### Alternative: Using Svelte Components Directly
|
|
662
|
+
|
|
663
|
+
Instead of using the `mdx` snippet with conditional logic, you can pass Svelte components directly using the `mdxComponents` prop:
|
|
664
|
+
|
|
665
|
+
```svelte
|
|
666
|
+
<script>
|
|
667
|
+
import { Streamdown } from 'svelte-streamdown';
|
|
668
|
+
import Card from './Card.svelte';
|
|
669
|
+
import Button from './Button.svelte';
|
|
670
|
+
|
|
671
|
+
let content = `
|
|
672
|
+
# Using MDX Components
|
|
673
|
+
|
|
674
|
+
<Card title="Hello" count={42}>
|
|
675
|
+
This is **markdown content** inside a component!
|
|
676
|
+
</Card>
|
|
677
|
+
|
|
678
|
+
<Button label="Click me" active={true} />
|
|
679
|
+
`;
|
|
680
|
+
</script>
|
|
681
|
+
|
|
682
|
+
<Streamdown {content} mdxComponents={{ Card, Button }} />
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Your Svelte components** (`Card.svelte`, `Button.svelte`) should accept props and a `children` snippet:
|
|
686
|
+
|
|
687
|
+
```svelte
|
|
688
|
+
<!-- Card.svelte -->
|
|
689
|
+
<script>
|
|
690
|
+
let { title, count, children } = $props();
|
|
691
|
+
</script>
|
|
692
|
+
|
|
693
|
+
<div class="rounded-lg border border-gray-200 p-4 shadow-sm">
|
|
694
|
+
<h3 class="text-xl font-bold">{title}</h3>
|
|
695
|
+
<p class="text-gray-600">Count: {count}</p>
|
|
696
|
+
<div class="mt-2">
|
|
697
|
+
{@render children()}
|
|
698
|
+
</div>
|
|
699
|
+
</div>
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
```svelte
|
|
703
|
+
<!-- Button.svelte -->
|
|
704
|
+
<script>
|
|
705
|
+
let { label, active } = $props();
|
|
706
|
+
</script>
|
|
707
|
+
|
|
708
|
+
<button class="rounded px-4 py-2 {active ? 'bg-blue-500 text-white' : 'bg-gray-200'}">
|
|
709
|
+
{label}
|
|
710
|
+
</button>
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
This approach is cleaner when you have standalone component files, while the `mdx` snippet approach is better for inline component definitions or when you need shared logic across components.
|
|
714
|
+
|
|
658
715
|
### Supported Syntax
|
|
659
716
|
|
|
660
717
|
**Self-closing components:**
|
|
@@ -689,7 +746,7 @@ MDX components support three attribute value types:
|
|
|
689
746
|
|
|
690
747
|
MDX components are streaming-safe. Incomplete components are automatically handled during AI streaming:
|
|
691
748
|
|
|
692
|
-
- Incomplete tags like `<Component attr`
|
|
749
|
+
- Incomplete tags like `<Component attr` not rendered to prevent runtime errors
|
|
693
750
|
- Unclosed components like `<Card>content` are auto-closed with `</Card>`
|
|
694
751
|
- Malformed attributes are escaped to prevent rendering errors
|
|
695
752
|
|
|
@@ -697,21 +754,31 @@ This ensures your UI remains stable even when receiving partial markdown from st
|
|
|
697
754
|
|
|
698
755
|
### Component Props
|
|
699
756
|
|
|
700
|
-
|
|
701
|
-
- `token`: The full MdxToken with `tagName`, `attributes`, etc.
|
|
757
|
+
The `mdx` snippet receives three parameters:
|
|
758
|
+
- `token`: The full MdxToken with `tagName`, `attributes`, `selfClosing`, etc.
|
|
759
|
+
- `props`: Object containing all parsed attributes (e.g., `props.title`, `props.count`)
|
|
702
760
|
- `children`: Snippet containing parsed markdown content
|
|
703
|
-
- **All attributes spread directly**: Access attributes by name (e.g., `title`, `count`, `active`)
|
|
704
761
|
|
|
705
|
-
|
|
762
|
+
Use `token.tagName` to determine which component is being rendered:
|
|
763
|
+
|
|
706
764
|
```svelte
|
|
707
765
|
<!-- Markdown: <Card title="Hello" count={5}>Content</Card> -->
|
|
708
766
|
<Streamdown {content}>
|
|
709
|
-
{#snippet
|
|
710
|
-
|
|
711
|
-
<
|
|
712
|
-
|
|
767
|
+
{#snippet mdx({ token, props, children })}
|
|
768
|
+
{#if token.tagName === 'Card'}
|
|
769
|
+
<div>
|
|
770
|
+
<h3>{props.title}</h3>
|
|
771
|
+
<span>Count: {props.count}</span>
|
|
772
|
+
{@render children()}
|
|
773
|
+
</div>
|
|
774
|
+
{:else if token.tagName === 'Alert'}
|
|
775
|
+
<div class="alert alert-{props.type}">
|
|
776
|
+
{@render children()}
|
|
777
|
+
</div>
|
|
778
|
+
{:else}
|
|
779
|
+
<!-- Fallback for unknown components -->
|
|
713
780
|
{@render children()}
|
|
714
|
-
|
|
781
|
+
{/if}
|
|
715
782
|
{/snippet}
|
|
716
783
|
</Streamdown>
|
|
717
784
|
```
|
|
@@ -299,16 +299,13 @@
|
|
|
299
299
|
{@html content}
|
|
300
300
|
{/if}
|
|
301
301
|
{:else if token.type === 'mdx'}
|
|
302
|
-
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
>
|
|
302
|
+
{@const Component = streamdown.mdxComponents?.[token.tagName]}
|
|
303
|
+
{#if Component}
|
|
304
|
+
<Component {token} {children} props={token.attributes} />
|
|
305
|
+
{:else}
|
|
306
|
+
<Slot props={{ token, children, props: token.attributes }} render={streamdown.snippets.mdx}>
|
|
307
307
|
{@render children()}
|
|
308
308
|
</Slot>
|
|
309
|
-
{:else}
|
|
310
|
-
<!-- Fallback if no snippet provided for this component -->
|
|
311
|
-
{@render children()}
|
|
312
309
|
{/if}
|
|
313
310
|
{:else}
|
|
314
311
|
<!-- For tokens we don't handle specifically, it may certainely be a custom extension to to the children props to handle -->
|
package/dist/Streamdown.svelte
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
extensions,
|
|
30
30
|
sources,
|
|
31
31
|
inlineCitationsMode = 'carousel',
|
|
32
|
+
mdxComponents,
|
|
32
33
|
...snippets
|
|
33
34
|
}: StreamdownProps<Source> = $props();
|
|
34
35
|
|
|
@@ -118,6 +119,9 @@
|
|
|
118
119
|
},
|
|
119
120
|
get icons() {
|
|
120
121
|
return icons;
|
|
122
|
+
},
|
|
123
|
+
get mdxComponents() {
|
|
124
|
+
return mdxComponents;
|
|
121
125
|
}
|
|
122
126
|
});
|
|
123
127
|
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
1
|
+
import type { Component, Snippet } from 'svelte';
|
|
2
2
|
import type { DeepPartialTheme, Theme } from './theme.js';
|
|
3
3
|
import type { MermaidConfig } from 'mermaid';
|
|
4
4
|
import type { KatexOptions } from 'katex';
|
|
@@ -30,7 +30,7 @@ export declare class StreamdownContext<Source extends Record<string, any> = Reco
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
export declare const useStreamdown: () => StreamdownContext<Record<string, any>>;
|
|
33
|
-
import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH, Extension, GenericToken, CitationToken } from './marked/index.js';
|
|
33
|
+
import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH, Extension, GenericToken, CitationToken, MdxToken } from './marked/index.js';
|
|
34
34
|
import type { Tokens } from 'marked';
|
|
35
35
|
import type { ListItemToken, ListToken } from './marked/marked-list.js';
|
|
36
36
|
import type { Footnote, FootnoteRef, FootnoteToken } from './marked/marked-footnotes.js';
|
|
@@ -73,6 +73,7 @@ type TokenSnippet = {
|
|
|
73
73
|
inlineCitationPopover: CitationToken;
|
|
74
74
|
inlineCitationContent: CitationToken;
|
|
75
75
|
inlineCitationPreview: CitationToken;
|
|
76
|
+
mdx: MdxToken;
|
|
76
77
|
};
|
|
77
78
|
type PredefinedElements = keyof TokenSnippet;
|
|
78
79
|
export type Snippets<Source extends Record<string, any> = Record<string, any>> = {
|
|
@@ -83,6 +84,8 @@ export type Snippets<Source extends Record<string, any> = Record<string, any>> =
|
|
|
83
84
|
} & (K extends 'inlineCitationContent' ? {
|
|
84
85
|
source: Source;
|
|
85
86
|
key: string;
|
|
87
|
+
} : K extends 'mdx' ? {
|
|
88
|
+
props: Record<string, number | string | boolean | null | undefined>;
|
|
86
89
|
} : {})
|
|
87
90
|
]>;
|
|
88
91
|
};
|
|
@@ -151,5 +154,10 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
|
|
|
151
154
|
token: GenericToken;
|
|
152
155
|
children: Snippet;
|
|
153
156
|
}]>;
|
|
157
|
+
mdxComponents?: Record<string, Component<{
|
|
158
|
+
token: MdxToken;
|
|
159
|
+
children: Snippet;
|
|
160
|
+
props: any;
|
|
161
|
+
}, any, any>>;
|
|
154
162
|
} & Partial<Snippets<Source>>;
|
|
155
163
|
export {};
|
|
@@ -762,7 +762,7 @@ class IncompleteMarkdownParser {
|
|
|
762
762
|
};
|
|
763
763
|
},
|
|
764
764
|
handler: ({ line, state }) => {
|
|
765
|
-
//
|
|
765
|
+
// Remove incomplete MDX syntax (don't render it)
|
|
766
766
|
const lineStates = state.mdxLineStates || [];
|
|
767
767
|
const currentState = lineStates[state.currentLine];
|
|
768
768
|
if (currentState?.incompletePositions && currentState.incompletePositions.length > 0) {
|
|
@@ -771,8 +771,8 @@ class IncompleteMarkdownParser {
|
|
|
771
771
|
for (let i = currentState.incompletePositions.length - 1; i >= 0; i--) {
|
|
772
772
|
const pos = currentState.incompletePositions[i];
|
|
773
773
|
const before = result.substring(0, pos);
|
|
774
|
-
|
|
775
|
-
result = before
|
|
774
|
+
// Simply remove the incomplete MDX tag
|
|
775
|
+
result = before;
|
|
776
776
|
}
|
|
777
777
|
return result;
|
|
778
778
|
}
|