svelte-streamdown 2.3.2 → 2.3.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.
package/README.md CHANGED
@@ -231,6 +231,13 @@ III. Third item
231
231
  > [!IMPORTANT]
232
232
  > Native support for Github style Alert
233
233
 
234
+ ### Description List
235
+
236
+ : Topic 1 : Description 1
237
+ : **Topic 2** : *Description 2*
238
+ : Topic 3 : Description 3
239
+ : Topic 3 : Description 3
240
+
234
241
  ## 🔄 Differences from Original React Version
235
242
 
236
243
  This Svelte port maintains feature parity with the original [Streamdown](https://streamdown.ai/) while adapting to Svelte's patterns:
@@ -14,7 +14,8 @@
14
14
  } = $props();
15
15
 
16
16
  const streamdown = useStreamdown();
17
- const highlighter = HighlighterManager.create(streamdown.shikiPreloadThemes || []);
17
+ const highlighter = HighlighterManager.create(streamdown.shikiPreloadThemes);
18
+
18
19
  const copy = useCopy({
19
20
  get content() {
20
21
  return token.text;
@@ -44,8 +45,6 @@
44
45
  void highlighter.load(theme, lang);
45
46
  });
46
47
  });
47
-
48
- const isMounted = streamdown.isMounted;
49
48
  </script>
50
49
 
51
50
  <div class={streamdown.theme.code.base}>
@@ -84,7 +83,7 @@
84
83
  <span class={streamdown.theme.code.line}>
85
84
  {#each line as token}
86
85
  <span
87
- style={isMounted ? streamdown.animationTextStyle : undefined}
86
+ style={streamdown.isMounted ? streamdown.animationTextStyle : undefined}
88
87
  style:color={token.color}
89
88
  style:background-color={token.bgColor}
90
89
  >
@@ -97,7 +96,10 @@
97
96
 
98
97
  {#snippet Skeleton(lines: string[])}
99
98
  {#each lines as line}
100
- <span class={streamdown.theme.code.skeleton}>
99
+ <span
100
+ style={streamdown.isMounted ? streamdown.animationTextStyle : undefined}
101
+ class={streamdown.theme.code.skeleton}
102
+ >
101
103
  {line.trim().length > 0 ? line : '\u200B'}
102
104
  </span>
103
105
  {/each}
@@ -225,6 +225,30 @@
225
225
  <FootnoteRef {token} />
226
226
  {:else if token.type === 'footnote'}
227
227
  <!-- TODO Footnotes are rendered inside the FootnoteRef popover -->
228
+ {:else if token.type === 'descriptionList'}
229
+ <Slot props={{ children, token }} render={streamdown.snippets.descriptionList}>
230
+ <dl class={streamdown.theme.descriptionList.base}>
231
+ {@render children()}
232
+ </dl>
233
+ </Slot>
234
+ {:else if token.type === 'description'}
235
+ <Slot props={{ children, token }} render={streamdown.snippets.description}>
236
+ <div class={streamdown.theme.description.base}>
237
+ {@render children()}
238
+ </div>
239
+ </Slot>
240
+ {:else if token.type === 'descriptionTerm'}
241
+ <Slot props={{ children, token }} render={streamdown.snippets.descriptionTerm}>
242
+ <dt class={streamdown.theme.descriptionTerm.base}>
243
+ {@render children()}
244
+ </dt>
245
+ </Slot>
246
+ {:else if token.type === 'descriptionDetail'}
247
+ <Slot props={{ children, token }} render={streamdown.snippets.descriptionDetail}>
248
+ <dd class={streamdown.theme.descriptionDetail.base}>
249
+ {@render children()}
250
+ </dd>
251
+ </Slot>
228
252
  {:else if token.type === 'def'}
229
253
  <!-- TODO This does not seems to be tokenized for now -->
230
254
  {:else if token.type === 'escape'}
@@ -32,6 +32,7 @@ import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoo
32
32
  import type { Tokens } from 'marked';
33
33
  import type { ListItemToken, ListToken } from './marked/marked-list.js';
34
34
  import type { Footnote, FootnoteRef, FootnoteToken } from './marked/marked-footnotes.js';
35
+ import type { DescriptionDetailToken, DescriptionListToken, DescriptionTermToken, DescriptionToken } from './marked/marked-dl.js';
35
36
  type TokenSnippet = {
36
37
  heading: Tokens.Heading;
37
38
  paragraph: Tokens.Paragraph;
@@ -62,6 +63,10 @@ type TokenSnippet = {
62
63
  footnotePopover: FootnoteToken;
63
64
  sup: SubSupToken;
64
65
  sub: SubSupToken;
66
+ descriptionList: DescriptionListToken;
67
+ description: DescriptionToken;
68
+ descriptionTerm: DescriptionTermToken;
69
+ descriptionDetail: DescriptionDetailToken;
65
70
  };
66
71
  type PredefinedElements = keyof TokenSnippet;
67
72
  export type Snippets = {
@@ -7,6 +7,7 @@ import { type ListItemToken, type ListToken } from './marked-list.js';
7
7
  import { type BrToken } from './marked-br.js';
8
8
  import { type HrToken } from './marked-hr.js';
9
9
  import { type TableToken, type THead, type TBody, type TFoot, type THeadRow, type TRow, type TH, type TD } from './marked-table.js';
10
+ import { type DescriptionDetailToken, type DescriptionListToken, type DescriptionTermToken, type DescriptionToken } from './marked-dl.js';
10
11
  export type GenericToken = {
11
12
  type: string;
12
13
  raw: string;
@@ -19,7 +20,7 @@ export type Extension = {
19
20
  start?: TokenizerStartFunction;
20
21
  applyInBlockParsing?: boolean;
21
22
  };
22
- export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem> | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | BrToken | HrToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD;
23
+ export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem> | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | BrToken | HrToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD | DescriptionListToken | DescriptionToken | DescriptionDetailToken | DescriptionTermToken;
23
24
  export type { TableToken, THead, TBody, TFoot, THeadRow, TRow, TH, TD } from './marked-table.js';
24
25
  export declare const lex: (markdown: string, extensions?: Extension[]) => StreamdownToken[];
25
26
  export declare const parseBlocks: (markdown: string, extensions?: Extension[]) => string[];
@@ -7,6 +7,7 @@ import { markedList } from './marked-list.js';
7
7
  import { markedBr } from './marked-br.js';
8
8
  import { markedHr } from './marked-hr.js';
9
9
  import { markedTable } from './marked-table.js';
10
+ import { markedDl, markedDt } from './marked-dl.js';
10
11
  const parseExtensions = (...extensions) => {
11
12
  const options = {
12
13
  gfm: true,
@@ -40,12 +41,12 @@ const parseExtensions = (...extensions) => {
40
41
  return options;
41
42
  };
42
43
  export const lex = (markdown, extensions = []) => {
43
- return new Lexer(parseExtensions(markedHr, markedTable, ...markedFootnote(), markedAlert, ...markedMath, markedSub, markedSup, markedList, markedBr, ...extensions))
44
+ return new Lexer(parseExtensions(markedHr, markedTable, ...markedFootnote(), markedAlert, ...markedMath, markedSub, markedSup, markedList, markedBr, markedDl, markedDt, ...extensions))
44
45
  .lex(markdown)
45
46
  .filter((token) => token.type !== 'space' && token.type !== 'footnote');
46
47
  };
47
48
  export const parseBlocks = (markdown, extensions = []) => {
48
- const blockLexer = new Lexer(parseExtensions(markedHr, ...markedFootnote(), markedTable, ...extensions.filter(({ level, applyInBlockParsing }) => level === 'block' && applyInBlockParsing)));
49
+ const blockLexer = new Lexer(parseExtensions(markedHr, ...markedFootnote(), markedDl, markedTable, ...extensions.filter(({ level, applyInBlockParsing }) => level === 'block' && applyInBlockParsing)));
49
50
  return blockLexer.blockTokens(markdown, []).reduce((acc, block) => {
50
51
  if (block.type === 'space' || block.type === 'footnote') {
51
52
  return acc;
@@ -0,0 +1,24 @@
1
+ import type { Extension, GenericToken } from './index.js';
2
+ export declare const markedDl: Extension;
3
+ export declare const markedDt: Extension;
4
+ export type DescriptionListToken = {
5
+ type: 'descriptionList';
6
+ raw: string;
7
+ text: string;
8
+ tokens: DescriptionToken[];
9
+ };
10
+ export type DescriptionToken = {
11
+ type: 'description';
12
+ raw: string;
13
+ tokens: [DescriptionTermToken, DescriptionDetailToken];
14
+ };
15
+ export type DescriptionTermToken = {
16
+ type: 'descriptionTerm';
17
+ raw: string;
18
+ tokens: GenericToken[];
19
+ };
20
+ export type DescriptionDetailToken = {
21
+ type: 'descriptionDetail';
22
+ raw: string;
23
+ tokens: GenericToken[];
24
+ };
@@ -0,0 +1,45 @@
1
+ export const markedDl = {
2
+ name: 'descriptionList',
3
+ level: 'block', // Is this a block-level or inline-level tokenizer?
4
+ tokenizer(src) {
5
+ const rule = /^(?:[ \t]*:[^:\n]+:[ \t]?[^\n]*(?:\n|$))+/;
6
+ const match = rule.exec(src);
7
+ if (match) {
8
+ const token = {
9
+ type: 'descriptionList',
10
+ raw: match[0],
11
+ text: match[0].trim(),
12
+ tokens: this.lexer.inlineTokens(match[0].trim()).filter((t) => t.type === 'description')
13
+ };
14
+ return token;
15
+ }
16
+ }
17
+ };
18
+ export const markedDt = {
19
+ name: 'description',
20
+ level: 'inline',
21
+ tokenizer(src) {
22
+ const rule = /^\s*:([^:\n]+):([^:\n]*)(?:\n|$)/;
23
+ const match = rule.exec(src);
24
+ if (match) {
25
+ const term = match[1].trim();
26
+ const detail = match[2].trim();
27
+ return {
28
+ type: 'description',
29
+ raw: match[0],
30
+ tokens: [
31
+ {
32
+ type: 'descriptionTerm',
33
+ raw: term,
34
+ tokens: this.lexer.inlineTokens(term)
35
+ },
36
+ {
37
+ type: 'descriptionDetail',
38
+ raw: detail,
39
+ tokens: this.lexer.inlineTokens(detail)
40
+ }
41
+ ]
42
+ };
43
+ }
44
+ }
45
+ };
package/dist/theme.d.ts CHANGED
@@ -128,6 +128,18 @@ export declare const theme: {
128
128
  footnotePopover: {
129
129
  base: string;
130
130
  };
131
+ descriptionList: {
132
+ base: string;
133
+ };
134
+ description: {
135
+ base: string;
136
+ };
137
+ descriptionTerm: {
138
+ base: string;
139
+ };
140
+ descriptionDetail: {
141
+ base: string;
142
+ };
131
143
  };
132
144
  export declare const shadcnTheme: {
133
145
  link: {
@@ -257,6 +269,18 @@ export declare const shadcnTheme: {
257
269
  footnotePopover: {
258
270
  base: string;
259
271
  };
272
+ descriptionList: {
273
+ base: string;
274
+ };
275
+ description: {
276
+ base: string;
277
+ };
278
+ descriptionTerm: {
279
+ base: string;
280
+ };
281
+ descriptionDetail: {
282
+ base: string;
283
+ };
260
284
  };
261
285
  export type Theme = typeof theme;
262
286
  type DeepPartial<T> = {
@@ -391,5 +415,17 @@ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "t
391
415
  footnotePopover: {
392
416
  base: string;
393
417
  };
418
+ descriptionList: {
419
+ base: string;
420
+ };
421
+ description: {
422
+ base: string;
423
+ };
424
+ descriptionTerm: {
425
+ base: string;
426
+ };
427
+ descriptionDetail: {
428
+ base: string;
429
+ };
394
430
  };
395
431
  export {};
package/dist/theme.js CHANGED
@@ -128,6 +128,18 @@ export const theme = {
128
128
  },
129
129
  footnotePopover: {
130
130
  base: 'fixed z-50 max-h-[30vh] max-w-3xl overflow-y-auto rounded-lg bg-background p-4 shadow'
131
+ },
132
+ descriptionList: {
133
+ base: 'my-4 space-y-2'
134
+ },
135
+ description: {
136
+ base: 'border-l-2 border-gray-200 pl-4'
137
+ },
138
+ descriptionTerm: {
139
+ base: 'font-semibold text-gray-900'
140
+ },
141
+ descriptionDetail: {
142
+ base: 'text-gray-700 ml-4 leading-relaxed'
131
143
  }
132
144
  };
133
145
  export const shadcnTheme = {
@@ -247,7 +259,7 @@ export const shadcnTheme = {
247
259
  base: ''
248
260
  },
249
261
  em: {
250
- base: 'italic text-foreground'
262
+ base: 'italic'
251
263
  },
252
264
  del: {
253
265
  base: 'text-muted-foreground'
@@ -257,6 +269,18 @@ export const shadcnTheme = {
257
269
  },
258
270
  footnotePopover: {
259
271
  base: 'fixed z-50 max-h-[30vh] shadow max-w-3xl overflow-y-auto rounded-lg bg-background p-4'
272
+ },
273
+ descriptionList: {
274
+ base: 'my-4 space-y-2'
275
+ },
276
+ description: {
277
+ base: 'border-l-2 border-border pl-4'
278
+ },
279
+ descriptionTerm: {
280
+ base: 'font-semibold text-foreground'
281
+ },
282
+ descriptionDetail: {
283
+ base: 'text-muted-foreground ml-4 leading-relaxed'
260
284
  }
261
285
  };
262
286
  export const mergeTheme = (customTheme, baseTheme) => {
@@ -17,7 +17,7 @@ declare class HighlighterManager {
17
17
  * Preloads themes by creating minimal highlighter instances.
18
18
  * This reduces flickering when switching themes.
19
19
  */
20
- preloadThemes(highlighter: Highlighter): Promise<void>;
20
+ preloadThemes(highlighter: Highlighter): Promise<void[]>;
21
21
  /**
22
22
  * Ensures the highlighter is ready for the given theme and language.
23
23
  */
@@ -26,7 +26,7 @@ declare class HighlighterManager {
26
26
  * Highlights code synchronously. Must call isReady() first.
27
27
  */
28
28
  highlightCode(code: string, language: string | undefined, theme: BundledTheme): ThemedToken[][];
29
- static create(preloadedThemes: BundledTheme[]): HighlighterManager;
29
+ static create(preloadedThemes?: BundledTheme[]): HighlighterManager;
30
30
  }
31
31
  export { HighlighterManager };
32
32
  export declare const languageExtensionMap: Record<string, string>;
@@ -23,7 +23,7 @@ class HighlighterManager {
23
23
  if (this.highlighter instanceof Promise) {
24
24
  return this.highlighter;
25
25
  }
26
- else {
26
+ else if (!this.highlighter) {
27
27
  this.highlighter = new Promise((resolve, reject) => {
28
28
  loadShiki().then(([engine, createHighlighter]) => {
29
29
  return createHighlighter({
@@ -38,6 +38,9 @@ class HighlighterManager {
38
38
  });
39
39
  return this.highlighter;
40
40
  }
41
+ else {
42
+ return this.highlighter;
43
+ }
41
44
  }
42
45
  async loadTheme(theme, highlighter) {
43
46
  const themeLoader = this.loadedThemes.get(theme);
@@ -50,6 +53,7 @@ class HighlighterManager {
50
53
  });
51
54
  this.loadedThemes.set(theme, themeLoaderPromise);
52
55
  await themeLoaderPromise;
56
+ this.loadedThemes.set(theme, true);
53
57
  }
54
58
  }
55
59
  async loadLanguage(language, highlighter) {
@@ -63,6 +67,7 @@ class HighlighterManager {
63
67
  });
64
68
  this.loadedLanguages.set(language, languageLoaderPromise);
65
69
  await languageLoaderPromise;
70
+ this.loadedLanguages.set(language, true);
66
71
  }
67
72
  }
68
73
  isLanguageSupported = (language) => {
@@ -70,26 +75,15 @@ class HighlighterManager {
70
75
  };
71
76
  isReady(theme, language = 'bash') {
72
77
  return (!(this.highlighter instanceof Promise) &&
73
- this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash') === true &&
74
- this.loadedThemes.get(theme) === true);
78
+ this.loadedThemes.get(theme) === true &&
79
+ this.loadedLanguages.get(this.isLanguageSupported(language) ? language : 'bash') === true);
75
80
  }
76
81
  /**
77
82
  * Preloads themes by creating minimal highlighter instances.
78
83
  * This reduces flickering when switching themes.
79
84
  */
80
85
  async preloadThemes(highlighter) {
81
- for (const theme of this.preloadedThemes) {
82
- const themeLoader = this.loadedThemes.get(theme);
83
- if (themeLoader === true) {
84
- return;
85
- }
86
- else if (themeLoader instanceof Promise) {
87
- await themeLoader;
88
- }
89
- else {
90
- await this.loadTheme(theme, highlighter);
91
- }
92
- }
86
+ return Promise.all(Array.from(this.preloadedThemes).map((theme) => this.loadTheme(theme, highlighter)));
93
87
  }
94
88
  /**
95
89
  * Ensures the highlighter is ready for the given theme and language.
@@ -101,7 +95,6 @@ class HighlighterManager {
101
95
  !(this.highlighter instanceof Promise) &&
102
96
  themeLoader === true &&
103
97
  languageLoader === true) {
104
- // already loaded
105
98
  return;
106
99
  }
107
100
  const highlighter = await this.loadHighlighter();
@@ -131,7 +124,7 @@ class HighlighterManager {
131
124
  return [];
132
125
  }
133
126
  }
134
- static create(preloadedThemes) {
127
+ static create(preloadedThemes = []) {
135
128
  if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
136
129
  const previousHighlighter = window.STREAMDOWN_HIGHLIGHTER;
137
130
  preloadedThemes.forEach((theme) => previousHighlighter.preloadedThemes.add(theme));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-streamdown",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",