svelte-streamdown 2.2.3 → 2.2.4

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.
@@ -4,6 +4,7 @@
4
4
  import { useCopy } from '../utils/copy.svelte.js';
5
5
  import { HighlighterManager, languageExtensionMap } from '../utils/hightlighter.svelte.js';
6
6
  import type { Tokens } from 'marked';
7
+ import { type ThemedToken } from 'shiki';
7
8
 
8
9
  const {
9
10
  token
@@ -41,6 +42,8 @@
41
42
  $effect(() => {
42
43
  void highlighter.load(theme, language as any);
43
44
  });
45
+
46
+ const isMounted = streamdown.isMounted;
44
47
  </script>
45
48
 
46
49
  <div class={streamdown.theme.code.base} data-language={language}>
@@ -65,37 +68,37 @@
65
68
  {/if}
66
69
  </div>
67
70
  <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
68
- <div>
69
- <!-- {@render Skeleton()} -->
70
- {#if highlighter.isReady(theme, language as any)}
71
- <!-- {@render Skeleton()} -->
72
-
73
- {@const code = highlighter.highlightCode(
74
- codeContent,
75
- language as any,
76
- theme,
77
- streamdown.theme.code.pre
78
- )}
79
- {@html code}
80
- {:else}
81
- {@render Skeleton()}
82
- {/if}
83
- </div>
71
+ {#if highlighter.isReady(theme, language as any)}
72
+ {@const tokens = highlighter.highlightCode(codeContent, language as any, theme)}
73
+ <pre class={streamdown.theme.code.pre}><code>{@render Tokens(tokens)}</code></pre>
74
+ {:else}
75
+ <pre class={streamdown.theme.code.pre}><code>{@render Skeleton(token.text.split('\n'))}</code
76
+ ></pre>
77
+ {/if}
84
78
  </div>
85
79
  </div>
86
80
 
87
- {#snippet Skeleton()}
88
- {@const lines = token.text.split('\n')}
89
- <!-- -->
90
-
91
- <!-- --><code
92
- class={streamdown.theme.code.pre}
93
- style="height: fit-content; width: 100%; display: flex; flex-direction: column;"
94
- ><!-- -->{#each lines as line}<!-- --><span class={streamdown.theme.code.skeleton}
95
- >{line.trim().length > 0 ? line : '\u200B'}</span
96
- ><!-- -->
97
- <!-- -->{/each}<!-- --></code
98
- ><!-- -->
81
+ {#snippet Tokens(tokens: ThemedToken[][])}
82
+ {#each tokens as line}
83
+ <span class={streamdown.theme.code.line}>
84
+ {#each line as token}
85
+ <span
86
+ style={isMounted ? streamdown.animationTextStyle : undefined}
87
+ style:color={token.color}
88
+ style:background-color={token.bgColor}
89
+ >
90
+ {token.content}
91
+ </span>
92
+ {/each}
93
+ </span>
94
+ {/each}
95
+ {/snippet}
96
+ {#snippet Skeleton(lines: string[])}
97
+ {#each lines as line}
98
+ <span class={streamdown.theme.code.line}>
99
+ {line.trim().length > 0 ? line : '\u200B'}
100
+ </span>
101
+ {/each}
99
102
  {/snippet}
100
103
  {#snippet copyIcon()}
101
104
  <svg
@@ -21,8 +21,8 @@ export declare class StreamdownContext {
21
21
  footnotes: Map<string, Footnote>;
22
22
  };
23
23
  isMounted: boolean;
24
- animationTextStyle: string;
25
- animationBlockStyle: string;
24
+ animationTextStyle: string | undefined;
25
+ animationBlockStyle: string | undefined;
26
26
  constructor(props: Omit<StreamdownProps, keyof Snippets | 'class'> & {
27
27
  snippets: Snippets;
28
28
  });
@@ -5,18 +5,22 @@ export class StreamdownContext {
5
5
  footnotes: new Map()
6
6
  };
7
7
  isMounted = $state(false);
8
- animationTextStyle = $derived(`animation-name: sd-${this.animation.type};
8
+ animationTextStyle = $derived(this.animation.enabled
9
+ ? `animation-name: sd-${this.animation.type};
9
10
  animation-duration: ${this.animation.duration}ms;
10
11
  animation-timing-function: ${this.animation.timingFunction};
11
12
  animation-iteration-count: 1;
12
13
  animation-fill-mode: forwards;
13
14
  white-space: pre-wrap;
14
- display: inline-block;`);
15
- animationBlockStyle = $derived(`animation-name: sd-${this.animation.type};
15
+ display: inline-block;`
16
+ : undefined);
17
+ animationBlockStyle = $derived(this.animation.enabled
18
+ ? `animation-name: sd-${this.animation.type};
16
19
  animation-duration: ${this.animation.duration}ms;
17
20
  animation-timing-function: ${this.animation.timingFunction};
18
21
  animation-iteration-count: 1;
19
- animation-fill-mode: forwards;`);
22
+ animation-fill-mode: forwards;`
23
+ : undefined);
20
24
  constructor(props) {
21
25
  bind(this, props);
22
26
  setContext('streamdown', this);
@@ -49,7 +49,7 @@ const parseExtensions = (...ext) => {
49
49
  };
50
50
  const blockLexer = new Lexer(parseExtensions(markedFootnote(), markedTable()));
51
51
  export const lex = (markdown) => {
52
- return new Lexer(parseExtensions(...extensions))
52
+ return new Lexer(parseExtensions(markedTable(), markedFootnote(), markedAlert(), markedMath(), markedSubSup(), markedList()))
53
53
  .lex(markdown)
54
54
  .filter((token) => token.type !== 'space' && token.type !== 'footnote');
55
55
  };
package/dist/theme.d.ts CHANGED
@@ -44,6 +44,7 @@ export declare const theme: {
44
44
  language: string;
45
45
  skeleton: string;
46
46
  pre: string;
47
+ line: string;
47
48
  };
48
49
  codespan: {
49
50
  base: string;
@@ -171,6 +172,7 @@ export declare const shadcnTheme: {
171
172
  language: string;
172
173
  skeleton: string;
173
174
  pre: string;
175
+ line: string;
174
176
  };
175
177
  codespan: {
176
178
  base: string;
@@ -299,6 +301,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
299
301
  language: string;
300
302
  skeleton: string;
301
303
  pre: string;
304
+ line: string;
302
305
  };
303
306
  codespan: {
304
307
  base: string;
package/dist/theme.js CHANGED
@@ -44,7 +44,8 @@ export const theme = {
44
44
  button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
45
45
  language: 'ml-1 font-mono lowercase',
46
46
  skeleton: 'rounded-md font-mono text-transparent bg-gray-200 scale-y-90 animate-pulse whitespace-nowrap inline-block',
47
- pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40'
47
+ pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40',
48
+ line: 'block'
48
49
  },
49
50
  codespan: {
50
51
  base: 'bg-gray-100 rounded px-1.5 py-0.5 font-mono text-[0.9em]'
@@ -171,7 +172,8 @@ export const shadcnTheme = {
171
172
  button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
172
173
  language: 'ml-1 font-mono lowercase',
173
174
  skeleton: 'rounded-md font-mono text-transparent bg-border/80 scale-y-90 w-fit animate-pulse whitespace-nowrap inline-block',
174
- pre: 'overflow-x-auto font-mono p-0 bg-muted/40'
175
+ pre: 'overflow-x-auto font-mono p-0 bg-muted/40',
176
+ line: 'block '
175
177
  },
176
178
  codespan: {
177
179
  base: 'bg-muted rounded px-1.5 py-0.5 font-mono text-foreground text-[0.9em]'
@@ -1,4 +1,4 @@
1
- import { type BundledLanguage, type BundledTheme } from 'shiki';
1
+ import { type BundledLanguage, type BundledTheme, type ThemedToken } from 'shiki';
2
2
  import { SvelteSet } from 'svelte/reactivity';
3
3
  export declare const loadShiki: () => Promise<[any, import("shiki").CreateHighlighterFactory<BundledLanguage, BundledTheme>]>;
4
4
  declare class HighlighterManager {
@@ -23,7 +23,7 @@ declare class HighlighterManager {
23
23
  /**
24
24
  * Highlights code synchronously. Must call isReady() first.
25
25
  */
26
- highlightCode(code: string, language: BundledLanguage, theme: BundledTheme, preClassName?: string): string;
26
+ highlightCode(code: string, language: BundledLanguage, theme: BundledTheme): ThemedToken[][];
27
27
  static create(preloadedThemes: BundledTheme[]): HighlighterManager;
28
28
  }
29
29
  export { HighlighterManager };
@@ -4,10 +4,6 @@ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
4
4
  const isLanguageSupported = (language) => {
5
5
  return Object.hasOwn(bundledLanguages, language);
6
6
  };
7
- // Remove background styles from <pre> tags (inline style)
8
- const removePreBackground = (html) => {
9
- return html.replace(/<pre[^>]*style="[^"]*background[^";]*;?[^"]*"[^>]*>/g, (match) => match.replace(/style="[^"]*background[^";]*;?[^"]*"/, ''));
10
- };
11
7
  export const loadShiki = async () => {
12
8
  return Promise.all([
13
9
  import('shiki/engine/javascript').then((mod) => mod.createJavaScriptRegexEngine({ forgiving: true })),
@@ -79,21 +75,16 @@ class HighlighterManager {
79
75
  /**
80
76
  * Highlights code synchronously. Must call isReady() first.
81
77
  */
82
- highlightCode(code, language, theme, preClassName) {
78
+ highlightCode(code, language, theme) {
83
79
  const highlighter = this.highlighters.get(`${theme}:${language}`);
84
80
  if (!highlighter) {
85
- return '';
81
+ return [];
86
82
  }
87
- let html = highlighter.codeToHtml(code, {
83
+ const tokens = highlighter.codeToTokensBase(code, {
88
84
  lang: isLanguageSupported(language) ? language : 'text',
89
85
  theme: theme
90
86
  });
91
- // Remove background and add custom class if needed
92
- html = removePreBackground(html);
93
- if (preClassName) {
94
- html = html.replace(/<pre(\s|>)/, `<pre class="${preClassName}"$1`);
95
- }
96
- return html;
87
+ return tokens;
97
88
  }
98
89
  static create(preloadedThemes) {
99
90
  if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-streamdown",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",