svelte-streamdown 2.2.6 → 2.3.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 (38) hide show
  1. package/README.md +47 -47
  2. package/dist/AnimatedText.svelte +6 -8
  3. package/dist/Block.svelte +2 -2
  4. package/dist/Elements/Alert.svelte +19 -18
  5. package/dist/Elements/Code.svelte +20 -17
  6. package/dist/Elements/Image.svelte +22 -20
  7. package/dist/Elements/Link.svelte +1 -1
  8. package/dist/Elements/Math.svelte +4 -7
  9. package/dist/Elements/Mermaid.svelte +76 -68
  10. package/dist/Streamdown.svelte +4 -0
  11. package/dist/context.svelte.d.ts +16 -3
  12. package/dist/context.svelte.js +1 -3
  13. package/dist/marked/index.d.ts +4 -2
  14. package/dist/marked/index.js +10 -6
  15. package/dist/marked/marked-alert.d.ts +1 -1
  16. package/dist/marked/marked-alert.js +14 -5
  17. package/dist/marked/marked-br.d.ts +12 -0
  18. package/dist/marked/marked-br.js +21 -0
  19. package/dist/marked/marked-footnotes.d.ts +1 -1
  20. package/dist/marked/marked-footnotes.js +1 -1
  21. package/dist/marked/marked-hr.d.ts +12 -0
  22. package/dist/marked/marked-hr.js +24 -0
  23. package/dist/marked/marked-list.d.ts +2 -1
  24. package/dist/marked/marked-list.js +9 -3
  25. package/dist/marked/marked-math.d.ts +3 -2
  26. package/dist/marked/marked-math.js +41 -21
  27. package/dist/marked/marked-subsup.d.ts +1 -1
  28. package/dist/marked/marked-subsup.js +1 -1
  29. package/dist/marked/marked-table.d.ts +1 -0
  30. package/dist/marked/marked-table.js +11 -4
  31. package/dist/theme.d.ts +9 -1
  32. package/dist/theme.js +2 -0
  33. package/dist/utils/hightlighter.svelte.d.ts +15 -13
  34. package/dist/utils/hightlighter.svelte.js +101 -57
  35. package/dist/utils/parse-incomplete-markdown.js +352 -157
  36. package/package.json +4 -2
  37. package/dist/Elements/Table.svelte +0 -30
  38. package/dist/Elements/Table.svelte.d.ts +0 -9
@@ -114,8 +114,11 @@ const processSpans = (cells, count, prevRow = [], maxColspan = null) => {
114
114
  for (i = 0; i < processedCells.length; i++) {
115
115
  const cell = processedCells[i];
116
116
  let cellText = cell.text;
117
- // Handle Rowspan - cells ending with ^
118
- if (cellText.slice(-1) === '^' && prevRow.length > 0) {
117
+ // Handle Rowspan - cells ending with ^ (but not superscript ^text^)
118
+ // Check if it's a rowspan indicator (single ^ at end) vs superscript (^text^)
119
+ const isRowspanIndicator = cellText.slice(-1) === '^' &&
120
+ !cellText.match(/\^[^^\n\r]+\^$/); // Not a superscript pattern ^text^
121
+ if (isRowspanIndicator && prevRow.length > 0) {
119
122
  // Clean the ^ indicator from the cell text
120
123
  cell.text = cellText.slice(0, -1).trim();
121
124
  cellText = cell.text;
@@ -179,7 +182,10 @@ const processSpans = (cells, count, prevRow = [], maxColspan = null) => {
179
182
  }
180
183
  // If no target was found but it's a rowspan cell, clean the ^ indicator
181
184
  if (!targetFound && cell.rowspan > 0) {
182
- cell.text = cell.text.slice(0, -1);
185
+ // Only clean if it was actually a rowspan indicator, not superscript
186
+ if (isRowspanIndicator) {
187
+ cell.text = cell.text.slice(0, -1);
188
+ }
183
189
  }
184
190
  }
185
191
  }
@@ -485,7 +491,8 @@ export function markedTable(options = {}) {
485
491
  const item = {
486
492
  type: 'table',
487
493
  tokens,
488
- raw: cap[0]
494
+ raw: cap[0],
495
+ align: alignment
489
496
  };
490
497
  return item;
491
498
  }
package/dist/theme.d.ts CHANGED
@@ -40,6 +40,7 @@ export declare const theme: {
40
40
  base: string;
41
41
  container: string;
42
42
  header: string;
43
+ buttons: string;
43
44
  button: string;
44
45
  language: string;
45
46
  skeleton: string;
@@ -168,6 +169,7 @@ export declare const shadcnTheme: {
168
169
  base: string;
169
170
  container: string;
170
171
  header: string;
172
+ buttons: string;
171
173
  button: string;
172
174
  language: string;
173
175
  skeleton: string;
@@ -257,7 +259,11 @@ export declare const shadcnTheme: {
257
259
  };
258
260
  };
259
261
  export type Theme = typeof theme;
260
- export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tailwind" | "shadcn") => {
262
+ type DeepPartial<T> = {
263
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
264
+ };
265
+ export type DeepPartialTheme = DeepPartial<Theme>;
266
+ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "tailwind" | "shadcn") => {
261
267
  link: {
262
268
  base: string;
263
269
  blocked: string;
@@ -297,6 +303,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
297
303
  base: string;
298
304
  container: string;
299
305
  header: string;
306
+ buttons: string;
300
307
  button: string;
301
308
  language: string;
302
309
  skeleton: string;
@@ -385,3 +392,4 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
385
392
  base: string;
386
393
  };
387
394
  };
395
+ export {};
package/dist/theme.js CHANGED
@@ -41,6 +41,7 @@ export const theme = {
41
41
  base: 'my-4 w-full overflow-hidden rounded-xl border border-gray-200 flex flex-col',
42
42
  container: ' relative overflow-visible bg-gray-100 p-2 font-mono text-sm ',
43
43
  header: 'flex items-center justify-between bg-gray-100/80 p-2 text-gray-600 text-xs',
44
+ buttons: 'flex items-center gap-2',
44
45
  button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
45
46
  language: 'ml-1 font-mono lowercase',
46
47
  skeleton: 'block rounded-md font-mono text-transparent bg-gray-200 scale-y-90 animate-pulse whitespace-nowrap',
@@ -169,6 +170,7 @@ export const shadcnTheme = {
169
170
  base: 'my-4 w-full overflow-hidden rounded-lg border border-border flex flex-col',
170
171
  container: 'relative overflow-visible bg-muted p-2 font-mono text-sm',
171
172
  header: 'flex items-center justify-between bg-muted/80 px-2 py-1 text-muted-foreground text-xs',
173
+ buttons: 'flex items-center gap-2',
172
174
  button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
173
175
  language: 'ml-1 font-mono lowercase',
174
176
  skeleton: 'block rounded-md font-mono text-transparent bg-border/80 scale-y-90 w-fit animate-pulse whitespace-nowrap',
@@ -1,29 +1,31 @@
1
- import { type BundledLanguage, type BundledTheme, type ThemedToken } from 'shiki';
2
- import { SvelteSet } from 'svelte/reactivity';
1
+ import { type BundledLanguage, type BundledTheme, type HighlighterGeneric, type ThemedToken } from 'shiki';
2
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
3
3
  export declare const loadShiki: () => Promise<[any, import("shiki").CreateHighlighterFactory<BundledLanguage, BundledTheme>]>;
4
+ export type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
4
5
  declare class HighlighterManager {
5
- initialized: boolean;
6
- private highlighters;
7
- private createHighlighter;
8
- private engine;
9
- preloadedThemes: BundledTheme[];
10
- loadedLanguages: SvelteSet<BundledLanguage>;
11
- loadedThemes: SvelteSet<BundledTheme>;
6
+ loadedLanguages: SvelteMap<BundledLanguage, boolean | Promise<void>>;
7
+ loadedThemes: SvelteMap<BundledTheme, boolean | Promise<void>>;
8
+ preloadedThemes: SvelteSet<BundledTheme>;
9
+ highlighter: Highlighter | Promise<Highlighter> | null;
12
10
  constructor(preloadedThemes: BundledTheme[]);
13
- isReady(theme: BundledTheme, language: BundledLanguage): boolean;
11
+ private loadHighlighter;
12
+ private loadTheme;
13
+ private loadLanguage;
14
+ private isLanguageSupported;
15
+ isReady(theme: BundledTheme, language?: string | undefined): boolean;
14
16
  /**
15
17
  * Preloads themes by creating minimal highlighter instances.
16
18
  * This reduces flickering when switching themes.
17
19
  */
18
- preloadThemes(themes: BundledTheme[], language: BundledLanguage): Promise<void>;
20
+ preloadThemes(highlighter: Highlighter): Promise<void>;
19
21
  /**
20
22
  * Ensures the highlighter is ready for the given theme and language.
21
23
  */
22
- load(theme: BundledTheme, language: BundledLanguage): Promise<void>;
24
+ load(theme: BundledTheme, language?: string | undefined): Promise<void>;
23
25
  /**
24
26
  * Highlights code synchronously. Must call isReady() first.
25
27
  */
26
- highlightCode(code: string, language: BundledLanguage, theme: BundledTheme): ThemedToken[][];
28
+ highlightCode(code: string, language: string | undefined, theme: BundledTheme): ThemedToken[][];
27
29
  static create(preloadedThemes: BundledTheme[]): HighlighterManager;
28
30
  }
29
31
  export { HighlighterManager };
@@ -1,9 +1,5 @@
1
1
  import { bundledLanguages } from 'shiki';
2
- import { untrack } from 'svelte';
3
2
  import { SvelteMap, SvelteSet } from 'svelte/reactivity';
4
- const isLanguageSupported = (language) => {
5
- return Object.hasOwn(bundledLanguages, language);
6
- };
7
3
  export const loadShiki = async () => {
8
4
  return Promise.all([
9
5
  import('shiki/engine/javascript').then((mod) => mod.createJavaScriptRegexEngine({ forgiving: true })),
@@ -11,87 +7,135 @@ export const loadShiki = async () => {
11
7
  ]);
12
8
  };
13
9
  class HighlighterManager {
14
- initialized = $state(false);
15
- highlighters = new SvelteMap();
16
- createHighlighter = null;
17
- engine = null;
18
- preloadedThemes;
19
- loadedLanguages = new SvelteSet();
20
- loadedThemes = new SvelteSet();
10
+ loadedLanguages = new SvelteMap();
11
+ loadedThemes = new SvelteMap();
12
+ preloadedThemes = new SvelteSet();
13
+ highlighter = $state(null);
21
14
  constructor(preloadedThemes) {
22
- this.preloadedThemes = preloadedThemes;
15
+ preloadedThemes.forEach((theme) => this.preloadedThemes.add(theme));
23
16
  if (typeof window !== 'undefined') {
24
17
  Object.assign(window, {
25
18
  STREAMDOWN_HIGHLIGHTER: this
26
19
  });
27
20
  }
28
21
  }
29
- isReady(theme, language) {
30
- return this.highlighters.has(`${theme}:${language}`);
22
+ async loadHighlighter() {
23
+ if (this.highlighter instanceof Promise) {
24
+ return this.highlighter;
25
+ }
26
+ else {
27
+ this.highlighter = new Promise((resolve, reject) => {
28
+ loadShiki().then(([engine, createHighlighter]) => {
29
+ return createHighlighter({
30
+ themes: [],
31
+ langs: [],
32
+ engine
33
+ }).then((highlighter) => {
34
+ this.highlighter = highlighter;
35
+ resolve(highlighter);
36
+ });
37
+ });
38
+ });
39
+ return this.highlighter;
40
+ }
41
+ }
42
+ async loadTheme(theme, highlighter) {
43
+ const themeLoader = this.loadedThemes.get(theme);
44
+ if (themeLoader instanceof Promise) {
45
+ await themeLoader;
46
+ }
47
+ else {
48
+ const themeLoaderPromise = highlighter.loadTheme(theme).then(() => {
49
+ this.loadedThemes.set(theme, true);
50
+ });
51
+ this.loadedThemes.set(theme, themeLoaderPromise);
52
+ await themeLoaderPromise;
53
+ }
54
+ }
55
+ async loadLanguage(language, highlighter) {
56
+ const languageLoader = this.loadedLanguages.get(language);
57
+ if (languageLoader instanceof Promise) {
58
+ await languageLoader;
59
+ }
60
+ else {
61
+ const languageLoaderPromise = highlighter.loadLanguage(language).then(() => {
62
+ this.loadedLanguages.set(language, true);
63
+ });
64
+ this.loadedLanguages.set(language, languageLoaderPromise);
65
+ await languageLoaderPromise;
66
+ }
67
+ }
68
+ isLanguageSupported = (language) => {
69
+ return Object.hasOwn(bundledLanguages, language);
70
+ };
71
+ isReady(theme, language = 'bash') {
72
+ return (!(this.highlighter instanceof Promise) &&
73
+ this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash') === true &&
74
+ this.loadedThemes.get(theme) === true);
31
75
  }
32
76
  /**
33
77
  * Preloads themes by creating minimal highlighter instances.
34
78
  * This reduces flickering when switching themes.
35
79
  */
36
- async preloadThemes(themes, language) {
37
- if (!themes.length) {
38
- return;
39
- }
40
- await Promise.all(themes
41
- .filter((theme) => !this.highlighters.has(`${theme}:${language}`))
42
- .map(async (theme) => {
43
- if (!this.createHighlighter || !this.engine) {
80
+ async preloadThemes(highlighter) {
81
+ for (const theme of this.preloadedThemes) {
82
+ const themeLoader = this.loadedThemes.get(theme);
83
+ if (themeLoader === true) {
44
84
  return;
45
85
  }
46
- const highlighter = await this.createHighlighter?.({
47
- themes: [theme],
48
- langs: isLanguageSupported(language) ? [language] : ['text'],
49
- engine: this.engine
50
- });
51
- this.highlighters.set(`${theme}:${language}`, highlighter);
52
- }));
86
+ else if (themeLoader instanceof Promise) {
87
+ await themeLoader;
88
+ }
89
+ else {
90
+ await this.loadTheme(theme, highlighter);
91
+ }
92
+ }
53
93
  }
54
94
  /**
55
95
  * Ensures the highlighter is ready for the given theme and language.
56
96
  */
57
- async load(theme, language) {
58
- return untrack(async () => {
59
- if (!this.createHighlighter || !this.engine) {
60
- const [engine, createHighlighter] = await loadShiki();
61
- this.createHighlighter = createHighlighter;
62
- this.engine = engine;
63
- }
64
- if (!this.highlighters.has(`${theme}:${language}`)) {
65
- const highlighter = await this.createHighlighter({
66
- themes: [theme],
67
- langs: isLanguageSupported(language) ? [language] : ['text'],
68
- engine: this.engine
69
- });
70
- this.highlighters.set(`${theme}:${language}`, highlighter);
71
- }
72
- void this.preloadThemes(this.preloadedThemes || [], language);
73
- });
97
+ async load(theme, language = 'bash') {
98
+ const themeLoader = this.loadedThemes.get(theme);
99
+ const languageLoader = this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash');
100
+ if (this.highlighter !== null &&
101
+ !(this.highlighter instanceof Promise) &&
102
+ themeLoader === true &&
103
+ languageLoader === true) {
104
+ // already loaded
105
+ return;
106
+ }
107
+ const highlighter = await this.loadHighlighter();
108
+ await Promise.all([
109
+ this.loadTheme(theme, highlighter),
110
+ this.loadLanguage(this.isLanguageSupported(language) ? language : 'bash', highlighter)
111
+ ]);
112
+ await this.preloadThemes(highlighter);
74
113
  }
75
114
  /**
76
115
  * Highlights code synchronously. Must call isReady() first.
77
116
  */
78
- highlightCode(code, language, theme) {
79
- const highlighter = this.highlighters.get(`${theme}:${language}`);
80
- if (!highlighter) {
117
+ highlightCode(code, language = 'bash', theme) {
118
+ try {
119
+ // const highlighter = this.highlighters.get(`${theme}:${language}`);
120
+ const highlighter = this.highlighter;
121
+ if (!highlighter || highlighter instanceof Promise) {
122
+ return [];
123
+ }
124
+ const tokens = highlighter.codeToTokensBase(code, {
125
+ lang: this.isLanguageSupported(language) ? language : 'bash',
126
+ theme
127
+ });
128
+ return tokens;
129
+ }
130
+ catch (error) {
81
131
  return [];
82
132
  }
83
- const tokens = highlighter.codeToTokensBase(code, {
84
- lang: isLanguageSupported(language) ? language : 'text',
85
- theme: theme
86
- });
87
- return tokens;
88
133
  }
89
134
  static create(preloadedThemes) {
90
135
  if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
91
136
  const previousHighlighter = window.STREAMDOWN_HIGHLIGHTER;
92
- previousHighlighter.preloadedThemes = Array.from(new Set([...previousHighlighter.preloadedThemes, ...preloadedThemes]));
93
- // @ts-ignore
94
- return window.STREAMDOWN_HIGHLIGHTER;
137
+ preloadedThemes.forEach((theme) => previousHighlighter.preloadedThemes.add(theme));
138
+ return previousHighlighter;
95
139
  }
96
140
  return new HighlighterManager(preloadedThemes);
97
141
  }