svelte-streamdown 2.6.0 → 3.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.
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { useStreamdown } from '../../context.svelte.js';
3
+ import type { Tokens } from 'marked';
4
+
5
+ const {
6
+ token,
7
+ id
8
+ }: {
9
+ token: Tokens.Code;
10
+ id: string;
11
+ } = $props();
12
+
13
+ const streamdown = useStreamdown();
14
+ </script>
15
+
16
+ <div data-streamdown-mermaid={id}>
17
+ <div
18
+ style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
19
+ class={streamdown.theme.code.base}
20
+ >
21
+ <div class={streamdown.theme.code.header}>
22
+ <span class={streamdown.theme.code.language}>mermaid</span>
23
+ </div>
24
+ <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
25
+ <pre class={streamdown.theme.code.pre}><code
26
+ >{#each token.text.split('\n') as line}<span class={streamdown.theme.code.line}
27
+ ><span style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
28
+ >{line.trim().length > 0 ? line : '\u200B'}</span
29
+ ></span
30
+ >{/each}</code
31
+ ></pre>
32
+ </div>
33
+ </div>
34
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { Tokens } from 'marked';
2
+ type $$ComponentProps = {
3
+ token: Tokens.Code;
4
+ id: string;
5
+ };
6
+ declare const MermaidFallback: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type MermaidFallback = ReturnType<typeof MermaidFallback>;
8
+ export default MermaidFallback;
@@ -0,0 +1,3 @@
1
+ export { default as CodeFallback } from './CodeFallback.svelte';
2
+ export { default as MermaidFallback } from './MermaidFallback.svelte';
3
+ export { default as MathFallback } from './MathFallback.svelte';
@@ -0,0 +1,3 @@
1
+ export { default as CodeFallback } from './CodeFallback.svelte';
2
+ export { default as MermaidFallback } from './MermaidFallback.svelte';
3
+ export { default as MathFallback } from './MathFallback.svelte';
@@ -8,13 +8,14 @@
8
8
  content = '',
9
9
  class: className,
10
10
  shikiTheme,
11
- shikiPreloadThemes,
11
+ shikiLanguages,
12
+ shikiThemes,
12
13
  parseIncompleteMarkdown,
13
14
  defaultOrigin,
14
15
  allowedLinkPrefixes = ['*'],
15
16
  allowedImagePrefixes = ['*'],
16
17
  theme,
17
- mermaidConfig,
18
+ mermaidConfig = {},
18
19
  katexConfig,
19
20
  translations,
20
21
  baseTheme,
@@ -29,8 +30,22 @@
29
30
  extensions,
30
31
  sources,
31
32
  inlineCitationsMode = 'carousel',
33
+ mdxComponents,
34
+ components,
35
+ static: isStatic,
32
36
  ...snippets
33
37
  }: StreamdownProps<Source> = $props();
38
+ import { useDarkMode } from './utils/darkMode.svelte.js';
39
+
40
+ const darkMode = useDarkMode();
41
+
42
+ const shikiThemedTheme = $derived(
43
+ shikiThemes ? Object.keys(shikiThemes)[0] || 'github-light' : darkMode.current ? 'github-dark' : 'github-light'
44
+ );
45
+
46
+ const mermaidThemedTheme = $derived(
47
+ mermaidConfig?.theme ? mermaidConfig.theme : darkMode.current ? 'dark' : 'default'
48
+ );
34
49
 
35
50
  streamdown = new StreamdownContext({
36
51
  get element() {
@@ -52,7 +67,7 @@
52
67
  return allowedImagePrefixes;
53
68
  },
54
69
  get shikiTheme() {
55
- return shikiTheme || 'github-light';
70
+ return shikiTheme || shikiThemedTheme;
56
71
  },
57
72
  get snippets() {
58
73
  return snippets;
@@ -66,7 +81,10 @@
66
81
  return baseTheme;
67
82
  },
68
83
  get mermaidConfig() {
69
- return mermaidConfig;
84
+ return {
85
+ theme: mermaidThemedTheme,
86
+ ...mermaidConfig
87
+ };
70
88
  },
71
89
  get katexConfig() {
72
90
  return katexConfig;
@@ -77,8 +95,11 @@
77
95
  get translations() {
78
96
  return translations;
79
97
  },
80
- get shikiPreloadThemes() {
81
- return shikiPreloadThemes;
98
+ get shikiLanguages() {
99
+ return shikiLanguages;
100
+ },
101
+ get shikiThemes() {
102
+ return shikiThemes;
82
103
  },
83
104
  get sources() {
84
105
  return sources;
@@ -118,18 +139,28 @@
118
139
  },
119
140
  get icons() {
120
141
  return icons;
142
+ },
143
+ get mdxComponents() {
144
+ return mdxComponents;
145
+ },
146
+ get components() {
147
+ return components;
121
148
  }
122
149
  });
123
150
 
124
151
  const id = $props.id();
125
152
 
126
- const blocks = $derived(parseBlocks(content, streamdown.extensions));
153
+ const blocks = $derived(isStatic ? content : parseBlocks(content, streamdown.extensions));
127
154
  </script>
128
155
 
129
156
  <div bind:this={element} class={className}>
130
- {#each blocks as block, index (`${id}-block-${index}`)}
131
- <Block {block} />
132
- {/each}
157
+ {#if isStatic}
158
+ <Block static={isStatic} block={content} />
159
+ {:else}
160
+ {#each blocks as block, index (`${id}-block-${index}`)}
161
+ <Block static={isStatic} {block} />
162
+ {/each}
163
+ {/if}
133
164
  </div>
134
165
 
135
166
  <style global>
@@ -1,11 +1,12 @@
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';
5
- import type { BundledTheme } from 'shiki';
5
+ import type { LanguageInfo } from './utils/bundledLanguages.js';
6
+ import type { ThemeRegistration } from 'shiki';
6
7
  export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets | 'class' | 'theme' | 'shikiTheme' | 'inlineCitationsMode'> {
7
8
  snippets: Snippets;
8
- shikiTheme: BundledTheme;
9
+ shikiTheme: string;
9
10
  theme: Theme;
10
11
  controls: {
11
12
  code: boolean;
@@ -30,7 +31,7 @@ export declare class StreamdownContext<Source extends Record<string, any> = Reco
30
31
  });
31
32
  }
32
33
  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';
34
+ import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH, Extension, GenericToken, CitationToken, MdxToken } from './marked/index.js';
34
35
  import type { Tokens } from 'marked';
35
36
  import type { ListItemToken, ListToken } from './marked/marked-list.js';
36
37
  import type { Footnote, FootnoteRef, FootnoteToken } from './marked/marked-footnotes.js';
@@ -73,6 +74,7 @@ type TokenSnippet = {
73
74
  inlineCitationPopover: CitationToken;
74
75
  inlineCitationContent: CitationToken;
75
76
  inlineCitationPreview: CitationToken;
77
+ mdx: MdxToken;
76
78
  };
77
79
  type PredefinedElements = keyof TokenSnippet;
78
80
  export type Snippets<Source extends Record<string, any> = Record<string, any>> = {
@@ -83,11 +85,14 @@ export type Snippets<Source extends Record<string, any> = Record<string, any>> =
83
85
  } & (K extends 'inlineCitationContent' ? {
84
86
  source: Source;
85
87
  key: string;
88
+ } : K extends 'mdx' ? {
89
+ props: Record<string, number | string | boolean | null | undefined>;
86
90
  } : {})
87
91
  ]>;
88
92
  };
89
93
  export type StreamdownProps<Source extends Record<string, any> = Record<string, any>> = {
90
94
  streamdown?: StreamdownContext;
95
+ static?: boolean;
91
96
  sources?: {
92
97
  [key: string]: Source;
93
98
  };
@@ -102,8 +107,9 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
102
107
  theme?: DeepPartialTheme;
103
108
  baseTheme?: 'tailwind' | 'shadcn';
104
109
  mergeTheme?: boolean;
105
- shikiTheme?: BundledTheme;
106
- shikiPreloadThemes?: BundledTheme[];
110
+ shikiTheme?: string;
111
+ shikiLanguages?: LanguageInfo[];
112
+ shikiThemes?: Record<string, ThemeRegistration>;
107
113
  mermaidConfig?: MermaidConfig;
108
114
  katexConfig?: KatexOptions | ((inline: boolean) => KatexOptions);
109
115
  translations?: {
@@ -151,5 +157,24 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
151
157
  token: GenericToken;
152
158
  children: Snippet;
153
159
  }]>;
160
+ mdxComponents?: Record<string, Component<{
161
+ token: MdxToken;
162
+ children: Snippet;
163
+ props: any;
164
+ }, any, any>>;
165
+ components?: {
166
+ code?: Component<{
167
+ token: Tokens.Code;
168
+ id: string;
169
+ }, any, any>;
170
+ mermaid?: Component<{
171
+ token: Tokens.Code;
172
+ id: string;
173
+ }, any, any>;
174
+ math?: Component<{
175
+ token: MathToken;
176
+ id: string;
177
+ }, any, any>;
178
+ };
154
179
  } & Partial<Snippets<Source>>;
155
180
  export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as Streamdown } from './Streamdown.svelte';
2
2
  export { useStreamdown, type StreamdownProps } from './context.svelte.js';
3
3
  export { theme, shadcnTheme, mergeTheme, type Theme } from './theme.js';
4
- export { type Extension, type StreamdownToken } from './marked/index.js';
5
- export { lex, parseBlocks } from './marked/index.js';
4
+ export { type Extension, type StreamdownToken, lex, parseBlocks } from './marked/index.js';
5
+ export { parseIncompleteMarkdown, type Plugin, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
6
+ export { bundledLanguagesInfo, createLanguageSet, type LanguageInfo } from './utils/bundledLanguages.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as Streamdown } from './Streamdown.svelte';
2
2
  export { useStreamdown } from './context.svelte.js';
3
3
  export { theme, shadcnTheme, mergeTheme } from './theme.js';
4
- export {} from './marked/index.js';
5
4
  export { lex, parseBlocks } from './marked/index.js';
5
+ export { parseIncompleteMarkdown, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
6
+ export { bundledLanguagesInfo, createLanguageSet } from './utils/bundledLanguages.js';
@@ -0,0 +1,8 @@
1
+ export type LanguageInfo = {
2
+ id: string;
3
+ aliases?: string[];
4
+ import: () => Promise<any>;
5
+ };
6
+ export declare const bundledLanguagesInfo: LanguageInfo[];
7
+ export declare function createLanguageSet(languages: LanguageInfo[]): Set<string>;
8
+ export declare const supportedLanguages: Set<string>;
@@ -0,0 +1,143 @@
1
+ export const bundledLanguagesInfo = [
2
+ // Web essentials
3
+ {
4
+ id: 'javascript',
5
+ aliases: ['js'],
6
+ import: () => import('@shikijs/langs/javascript')
7
+ },
8
+ {
9
+ id: 'typescript',
10
+ aliases: ['ts'],
11
+ import: () => import('@shikijs/langs/typescript')
12
+ },
13
+ {
14
+ id: 'html',
15
+ import: () => import('@shikijs/langs/html')
16
+ },
17
+ {
18
+ id: 'css',
19
+ import: () => import('@shikijs/langs/css')
20
+ },
21
+ {
22
+ id: 'json',
23
+ import: () => import('@shikijs/langs/json')
24
+ },
25
+ {
26
+ id: 'jsx',
27
+ import: () => import('@shikijs/langs/jsx')
28
+ },
29
+ {
30
+ id: 'tsx',
31
+ import: () => import('@shikijs/langs/tsx')
32
+ },
33
+ {
34
+ id: 'markdown',
35
+ aliases: ['md'],
36
+ import: () => import('@shikijs/langs/markdown')
37
+ },
38
+ {
39
+ id: 'yaml',
40
+ aliases: ['yml'],
41
+ import: () => import('@shikijs/langs/yaml')
42
+ },
43
+ {
44
+ id: 'xml',
45
+ import: () => import('@shikijs/langs/xml')
46
+ },
47
+ // Backend languages
48
+ {
49
+ id: 'python',
50
+ aliases: ['py'],
51
+ import: () => import('@shikijs/langs/python')
52
+ },
53
+ {
54
+ id: 'java',
55
+ import: () => import('@shikijs/langs/java')
56
+ },
57
+ {
58
+ id: 'go',
59
+ import: () => import('@shikijs/langs/go')
60
+ },
61
+ {
62
+ id: 'rust',
63
+ aliases: ['rs'],
64
+ import: () => import('@shikijs/langs/rust')
65
+ },
66
+ {
67
+ id: 'ruby',
68
+ aliases: ['rb'],
69
+ import: () => import('@shikijs/langs/ruby')
70
+ },
71
+ {
72
+ id: 'php',
73
+ import: () => import('@shikijs/langs/php')
74
+ },
75
+ {
76
+ id: 'c',
77
+ import: () => import('@shikijs/langs/c')
78
+ },
79
+ {
80
+ id: 'cpp',
81
+ aliases: ['c++'],
82
+ import: () => import('@shikijs/langs/cpp')
83
+ },
84
+ {
85
+ id: 'csharp',
86
+ aliases: ['c#', 'cs'],
87
+ import: () => import('@shikijs/langs/csharp')
88
+ },
89
+ {
90
+ id: 'sql',
91
+ import: () => import('@shikijs/langs/sql')
92
+ },
93
+ {
94
+ id: 'swift',
95
+ import: () => import('@shikijs/langs/swift')
96
+ },
97
+ {
98
+ id: 'kotlin',
99
+ aliases: ['kt', 'kts'],
100
+ import: () => import('@shikijs/langs/kotlin')
101
+ },
102
+ // Config/Shell
103
+ {
104
+ id: 'shellscript',
105
+ aliases: ['bash', 'sh', 'shell', 'zsh'],
106
+ import: () => import('@shikijs/langs/shellscript')
107
+ },
108
+ {
109
+ id: 'docker',
110
+ aliases: ['dockerfile'],
111
+ import: () => import('@shikijs/langs/docker')
112
+ },
113
+ {
114
+ id: 'toml',
115
+ import: () => import('@shikijs/langs/toml')
116
+ },
117
+ {
118
+ id: 'graphql',
119
+ aliases: ['gql'],
120
+ import: () => import('@shikijs/langs/graphql')
121
+ },
122
+ {
123
+ id: 'svelte',
124
+ import: () => import('@shikijs/langs/svelte')
125
+ },
126
+ {
127
+ id: 'vue',
128
+ import: () => import('@shikijs/langs/vue')
129
+ }
130
+ ];
131
+ // Helper function to create a Set of all language IDs and aliases from language info
132
+ export function createLanguageSet(languages) {
133
+ const set = new Set();
134
+ languages.forEach((lang) => {
135
+ set.add(lang.id);
136
+ if (lang.aliases) {
137
+ lang.aliases.forEach((alias) => set.add(alias));
138
+ }
139
+ });
140
+ return set;
141
+ }
142
+ // Create a Set of all supported language IDs and aliases for fast lookup (default set)
143
+ export const supportedLanguages = createLanguageSet(bundledLanguagesInfo);
@@ -0,0 +1,3 @@
1
+ export declare const useDarkMode: () => {
2
+ readonly current: boolean;
3
+ };
@@ -0,0 +1,49 @@
1
+ import { onMount } from 'svelte';
2
+ export const useDarkMode = () => {
3
+ let isDark = $state(false);
4
+ const checkTheme = () => {
5
+ const elements = [document.documentElement, document.body];
6
+ // First check for explicit light mode
7
+ for (const element of elements) {
8
+ if (element.classList.contains('light') ||
9
+ element.dataset.theme === 'light' ||
10
+ element.style.colorScheme === 'light') {
11
+ isDark = false;
12
+ return;
13
+ }
14
+ }
15
+ // Then check for dark mode
16
+ for (const element of elements) {
17
+ if (element.classList.contains('dark') ||
18
+ element.dataset.theme === 'dark' ||
19
+ element.style.colorScheme === 'dark') {
20
+ isDark = true;
21
+ return;
22
+ }
23
+ }
24
+ // Default to light mode if no explicit theme is set
25
+ isDark = false;
26
+ };
27
+ onMount(() => {
28
+ // Initial check
29
+ checkTheme();
30
+ // Watch for class, attribute, and style changes on html and body
31
+ const observer = new MutationObserver(() => {
32
+ checkTheme();
33
+ });
34
+ const observerOptions = {
35
+ attributes: true,
36
+ attributeFilter: ['class', 'data-theme', 'style']
37
+ };
38
+ observer.observe(document.documentElement, observerOptions);
39
+ observer.observe(document.body, observerOptions);
40
+ return () => {
41
+ observer.disconnect();
42
+ };
43
+ });
44
+ return {
45
+ get current() {
46
+ return isDark;
47
+ }
48
+ };
49
+ };
@@ -1,32 +1,30 @@
1
- import { type BundledLanguage, type BundledTheme, type HighlighterGeneric, type ThemedToken } from 'shiki';
2
- import { SvelteMap, SvelteSet } from 'svelte/reactivity';
3
- export declare const loadShiki: () => Promise<[any, import("shiki").CreateHighlighterFactory<BundledLanguage, BundledTheme>]>;
4
- export type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
1
+ import type { ThemedToken, ThemeRegistration } from 'shiki';
2
+ import type { HighlighterCore } from 'shiki/core';
3
+ import { SvelteMap } from 'svelte/reactivity';
4
+ import { type LanguageInfo } from './bundledLanguages.js';
5
+ export type Highlighter = HighlighterCore;
5
6
  declare class HighlighterManager {
6
- loadedLanguages: SvelteMap<BundledLanguage, boolean | Promise<void>>;
7
- loadedThemes: SvelteMap<BundledTheme, boolean | Promise<void>>;
8
- preloadedThemes: SvelteSet<BundledTheme>;
9
- highlighter: Highlighter | Promise<Highlighter> | null;
10
- constructor(preloadedThemes: BundledTheme[]);
7
+ loadedLanguages: SvelteMap<string, boolean | Promise<void>>;
8
+ highlighter: any;
9
+ customLanguages: Set<string>;
10
+ languageLoaders: Map<string, () => Promise<any>>;
11
+ additionalThemes: Record<string, ThemeRegistration>;
12
+ constructor(languages: LanguageInfo[], additionalThemes?: Record<string, ThemeRegistration>, additionalLanguages?: LanguageInfo[]);
11
13
  private loadHighlighter;
12
- private loadTheme;
14
+ private isThemeAvailable;
13
15
  private loadLanguage;
14
16
  private isLanguageSupported;
15
- isReady(theme: BundledTheme, language?: string | undefined): boolean;
16
- /**
17
- * Preloads themes by creating minimal highlighter instances.
18
- * This reduces flickering when switching themes.
19
- */
20
- preloadThemes(highlighter: Highlighter): Promise<void>;
17
+ isReady(theme: string, language: string | undefined): boolean;
21
18
  /**
22
19
  * Ensures the highlighter is ready for the given theme and language.
23
20
  */
24
- load(theme: BundledTheme, language?: string | undefined): Promise<void>;
21
+ load(theme: string, language: string | undefined): Promise<void>;
25
22
  /**
26
23
  * Highlights code synchronously. Must call isReady() first.
24
+ * Returns plaintext tokens for unsupported languages.
27
25
  */
28
- highlightCode(code: string, language: string | undefined, theme: BundledTheme): ThemedToken[][];
29
- static create(preloadedThemes?: BundledTheme[]): HighlighterManager;
26
+ highlightCode(code: string, language: string | undefined, theme: string): ThemedToken[][];
27
+ static create(languages?: LanguageInfo[], additionalThemes?: Record<string, ThemeRegistration>, additionalLanguages?: LanguageInfo[]): HighlighterManager;
30
28
  }
31
29
  export { HighlighterManager };
32
30
  export declare const languageExtensionMap: Record<string, string>;