svelte-streamdown 4.0.0 → 4.1.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 (45) hide show
  1. package/README.md +211 -38
  2. package/dist/Block.svelte +10 -4
  3. package/dist/Block.svelte.d.ts +2 -0
  4. package/dist/Elements/Alert.svelte +2 -1
  5. package/dist/Elements/Citation.svelte +9 -2
  6. package/dist/Elements/Code.svelte +65 -24
  7. package/dist/Elements/Code.svelte.d.ts +4 -2
  8. package/dist/Elements/Element.svelte +36 -11
  9. package/dist/Elements/Element.svelte.d.ts +1 -0
  10. package/dist/Elements/FootnoteRef.svelte +1 -0
  11. package/dist/Elements/Image.svelte +3 -2
  12. package/dist/Elements/Link.svelte +3 -2
  13. package/dist/Elements/Mermaid.svelte +69 -14
  14. package/dist/Elements/Mermaid.svelte.d.ts +4 -2
  15. package/dist/Elements/MermaidDownload.svelte +30 -9
  16. package/dist/Elements/MermaidDownload.svelte.d.ts +2 -0
  17. package/dist/Elements/TableDownload.svelte +60 -78
  18. package/dist/Elements/fallbacks/CodeFallback.svelte +28 -3
  19. package/dist/Elements/fallbacks/CodeFallback.svelte.d.ts +2 -0
  20. package/dist/Elements/fallbacks/MermaidFallback.svelte +12 -3
  21. package/dist/Elements/fallbacks/MermaidFallback.svelte.d.ts +2 -0
  22. package/dist/Elements/icons.js +10 -1
  23. package/dist/Elements/srOnly.d.ts +1 -0
  24. package/dist/Elements/srOnly.js +3 -0
  25. package/dist/Streamdown.svelte +68 -13
  26. package/dist/context.svelte.d.ts +98 -22
  27. package/dist/context.svelte.js +38 -0
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.js +2 -1
  30. package/dist/marked/index.d.ts +8 -1
  31. package/dist/marked/index.js +65 -14
  32. package/dist/marked/marked-footnotes.js +6 -2
  33. package/dist/marked/marked-math.js +40 -1
  34. package/dist/marked/marked-subsup.js +16 -3
  35. package/dist/utils/fence.d.ts +16 -0
  36. package/dist/utils/fence.js +39 -0
  37. package/dist/utils/parse-incomplete-markdown.d.ts +5 -1
  38. package/dist/utils/parse-incomplete-markdown.js +347 -122
  39. package/dist/utils/save.js +4 -1
  40. package/dist/utils/table-export.d.ts +14 -0
  41. package/dist/utils/table-export.js +82 -0
  42. package/dist/utils/url.js +6 -2
  43. package/dist/utils/usePinnedScroll.svelte.d.ts +22 -0
  44. package/dist/utils/usePinnedScroll.svelte.js +36 -0
  45. package/package.json +4 -2
@@ -1,20 +1,41 @@
1
1
  <script lang="ts">
2
2
  import { useStreamdown } from '../../context.svelte.js';
3
+ import { usePinnedScroll } from '../../utils/usePinnedScroll.svelte.js';
3
4
  import type { Tokens } from 'marked';
4
5
 
5
6
  const {
6
7
  token,
7
- id
8
+ id,
9
+ incomplete = false
8
10
  }: {
9
11
  token: Tokens.Code;
10
12
  id: string;
13
+ /** The fence is still being streamed; nothing below it is final yet. */
14
+ incomplete?: boolean;
11
15
  } = $props();
12
16
 
13
17
  const streamdown = useStreamdown();
18
+
19
+ // Same trim as Code.svelte/Mermaid.svelte: marked keeps the fence's trailing
20
+ // blank lines in `text`, so they render as empty lines and flicker in and out
21
+ // on nearly every streamed chunk. This is the default renderer, so it needs it
22
+ // too.
23
+ const code = $derived(token.text.replace(/\n+$/, ''));
24
+
25
+ // Same scroll container as Code.svelte: `pre` already scrolls horizontally.
26
+ const pinnedScroll = usePinnedScroll({
27
+ get maxHeight() {
28
+ return streamdown.codeBlockMaxHeight;
29
+ },
30
+ get content() {
31
+ return code;
32
+ }
33
+ });
14
34
  </script>
15
35
 
16
36
  <div
17
37
  data-streamdown-code={id}
38
+ data-incomplete={incomplete || undefined}
18
39
  style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
19
40
  class={streamdown.theme.code.base}
20
41
  >
@@ -22,8 +43,12 @@
22
43
  <span class={streamdown.theme.code.language}>{token.lang}</span>
23
44
  </div>
24
45
  <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}
46
+ <pre
47
+ class={streamdown.theme.code.pre}
48
+ style:max-height={streamdown.codeBlockMaxHeight}
49
+ style:overflow-y={streamdown.codeBlockMaxHeight ? 'auto' : undefined}
50
+ {@attach pinnedScroll}><code
51
+ >{#each code.split('\n') as line}<span class={streamdown.theme.code.line}
27
52
  ><span style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
28
53
  >{line.trim().length > 0 ? line : '\u200B'}</span
29
54
  ></span
@@ -2,6 +2,8 @@ import type { Tokens } from 'marked';
2
2
  type $$ComponentProps = {
3
3
  token: Tokens.Code;
4
4
  id: string;
5
+ /** The fence is still being streamed; nothing below it is final yet. */
6
+ incomplete?: boolean;
5
7
  };
6
8
  declare const CodeFallback: import("svelte").Component<$$ComponentProps, {}, "">;
7
9
  type CodeFallback = ReturnType<typeof CodeFallback>;
@@ -4,16 +4,25 @@
4
4
 
5
5
  const {
6
6
  token,
7
- id
7
+ id,
8
+ incomplete = false
8
9
  }: {
9
10
  token: Tokens.Code;
10
11
  id: string;
12
+ /** The fence is still being streamed; nothing below it is final yet. */
13
+ incomplete?: boolean;
11
14
  } = $props();
12
15
 
13
16
  const streamdown = useStreamdown();
17
+
18
+ // Same trim as Code.svelte/Mermaid.svelte: marked keeps the fence's trailing
19
+ // blank lines in `text`, so they render as empty lines and flicker in and out
20
+ // on nearly every streamed chunk. This is the default renderer, so it needs it
21
+ // too.
22
+ const chart = $derived(token.text.replace(/\n+$/, ''));
14
23
  </script>
15
24
 
16
- <div data-streamdown-mermaid={id}>
25
+ <div data-streamdown-mermaid={id} data-incomplete={incomplete || undefined}>
17
26
  <div
18
27
  style={streamdown.isMounted ? streamdown.animationBlockStyle : ''}
19
28
  class={streamdown.theme.code.base}
@@ -23,7 +32,7 @@
23
32
  </div>
24
33
  <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
25
34
  <pre class={streamdown.theme.code.pre}><code
26
- >{#each token.text.split('\n') as line}<span class={streamdown.theme.code.line}
35
+ >{#each chart.split('\n') as line}<span class={streamdown.theme.code.line}
27
36
  ><span style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
28
37
  >{line.trim().length > 0 ? line : '\u200B'}</span
29
38
  ></span
@@ -2,6 +2,8 @@ import type { Tokens } from 'marked';
2
2
  type $$ComponentProps = {
3
3
  token: Tokens.Code;
4
4
  id: string;
5
+ /** The fence is still being streamed; nothing below it is final yet. */
6
+ incomplete?: boolean;
5
7
  };
6
8
  declare const MermaidFallback: import("svelte").Component<$$ComponentProps, {}, "">;
7
9
  type MermaidFallback = ReturnType<typeof MermaidFallback>;
@@ -4,6 +4,7 @@ export const copyIcon = createRawSnippet(() => {
4
4
  render: () => {
5
5
  return `
6
6
  <svg
7
+ aria-hidden="true"
7
8
  xmlns="http://www.w3.org/2000/svg"
8
9
  width="100%"
9
10
  height="100%"
@@ -24,6 +25,7 @@ export const downloadIcon = createRawSnippet(() => {
24
25
  return {
25
26
  render: () => {
26
27
  return `<svg
28
+ aria-hidden="true"
27
29
  xmlns="http://www.w3.org/2000/svg"
28
30
  width="100%"
29
31
  height="100%"
@@ -44,7 +46,8 @@ export const checkIcon = createRawSnippet(() => {
44
46
  return {
45
47
  render: () => {
46
48
  return `
47
- <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
49
+ <svg
50
+ aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
48
51
  `;
49
52
  }
50
53
  };
@@ -53,6 +56,7 @@ export const zoomInIcon = createRawSnippet(() => {
53
56
  return {
54
57
  render: () => `
55
58
  <svg
59
+ aria-hidden="true"
56
60
  width="100%"
57
61
  height="100%"
58
62
  viewBox="0 0 24 24"
@@ -75,6 +79,7 @@ export const zoomOutIcon = createRawSnippet(() => {
75
79
  return {
76
80
  render: () => `
77
81
  <svg
82
+ aria-hidden="true"
78
83
  width="100%"
79
84
  height="100%"
80
85
  viewBox="0 0 24 24"
@@ -96,6 +101,7 @@ export const fitViewIcon = createRawSnippet(() => {
96
101
  return {
97
102
  render: () => `
98
103
  <svg
104
+ aria-hidden="true"
99
105
  width="100%"
100
106
  height="100%"
101
107
  viewBox="0 0 24 24"
@@ -119,6 +125,7 @@ export const fullscreenIcon = createRawSnippet(() => {
119
125
  return {
120
126
  render: () => `
121
127
  <svg
128
+ aria-hidden="true"
122
129
  width="100%"
123
130
  height="100%"
124
131
  viewBox="0 0 24 24"
@@ -139,6 +146,7 @@ export const chevronRight = createRawSnippet(() => {
139
146
  return {
140
147
  render: () => `
141
148
  <svg
149
+ aria-hidden="true"
142
150
  xmlns="http://www.w3.org/2000/svg"
143
151
  width="100%"
144
152
  height="100%"
@@ -158,6 +166,7 @@ export const chevronLeft = createRawSnippet(() => {
158
166
  return {
159
167
  render: () => `
160
168
  <svg
169
+ aria-hidden="true"
161
170
  xmlns="http://www.w3.org/2000/svg"
162
171
  width="100%"
163
172
  height="100%"
@@ -0,0 +1 @@
1
+ export declare const srOnly = "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;";
@@ -0,0 +1,3 @@
1
+ // Visually hidden but announced. Inline rather than a theme class: a live region
2
+ // must keep working in apps that do not ship Tailwind's `sr-only`.
3
+ export const srOnly = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;';
@@ -1,8 +1,13 @@
1
1
  <script lang="ts" generics="Source extends Record<string, any> = Record<string, any>">
2
2
  import Block from './Block.svelte';
3
- import { StreamdownContext, type StreamdownProps } from './context.svelte.js';
3
+ import {
4
+ StreamdownContext,
5
+ defaultTranslations,
6
+ type StreamdownProps
7
+ } from './context.svelte.js';
4
8
  import { mergeTheme, shadcnTheme } from './theme.js';
5
9
  import { parseBlocks, createParseBlocksCache } from './marked/index.js';
10
+ import { hasUnclosedFence } from './utils/fence.js';
6
11
 
7
12
  let {
8
13
  content = '',
@@ -23,6 +28,8 @@
23
28
  streamdown = $bindable(),
24
29
  renderHtml,
25
30
  controls,
31
+ codeBlockMaxHeight,
32
+ tableMaxHeight,
26
33
  animation,
27
34
  element = $bindable(),
28
35
  icons,
@@ -94,7 +101,11 @@
94
101
  return renderHtml;
95
102
  },
96
103
  get translations() {
97
- return translations;
104
+ // Resolved once, like the theme, so components never fall back themselves.
105
+ return {
106
+ alert: { ...defaultTranslations.alert, ...translations?.alert },
107
+ controls: { ...defaultTranslations.controls, ...translations?.controls }
108
+ };
98
109
  },
99
110
  get highlightLanguages() {
100
111
  return highlightLanguages;
@@ -123,19 +134,53 @@
123
134
  };
124
135
  },
125
136
  get controls() {
126
- const codeControls = controls?.code ?? true;
127
- const mermaid = controls?.mermaid;
128
- const isMermaidObject = typeof mermaid === 'object' && mermaid !== null;
129
- const mermaidControls = isMermaidObject ? (mermaid.enabled ?? true) : (mermaid ?? true);
130
- const mermaidMouseWheelZoom = isMermaidObject ? (mermaid.mouseWheelZoom ?? true) : true;
131
- const tableControls = controls?.table ?? true;
137
+ // `controls` is sugar: `false` turns every control off, `true`/undefined
138
+ // turns them all on, and each section is a boolean or an object of the
139
+ // same shape. Flatten it once here so components stay boolean lookups.
140
+ const sections = typeof controls === 'object' && controls !== null ? controls : {};
141
+ const resolve = (section: boolean | Record<string, any> | undefined) => {
142
+ const config = typeof section === 'object' && section !== null ? section : {};
143
+ const enabled = controls === false || section === false ? false : (config.enabled ?? true);
144
+ const download = config.download;
145
+ return {
146
+ enabled,
147
+ copy: enabled && config.copy !== false,
148
+ download: enabled && download !== false,
149
+ filename:
150
+ typeof download === 'object' && download !== null ? download.filename : undefined
151
+ };
152
+ };
153
+ const code = resolve(sections.code);
154
+ const table = resolve(sections.table);
155
+ const mermaid = resolve(sections.mermaid);
156
+ const tableSection =
157
+ typeof sections.table === 'object' && sections.table ? sections.table : {};
158
+ const mermaidSection =
159
+ typeof sections.mermaid === 'object' && sections.mermaid ? sections.mermaid : {};
132
160
  return {
133
- code: codeControls,
134
- mermaid: mermaidControls,
135
- mermaidMouseWheelZoom,
136
- table: tableControls
161
+ code: code.enabled,
162
+ codeCopy: code.copy,
163
+ codeDownload: code.download,
164
+ codeDownloadFilename: code.filename ?? 'file',
165
+ table: table.enabled,
166
+ tableCopy: table.copy,
167
+ tableDownload: table.download,
168
+ tableDownloadFilename: table.filename ?? 'table',
169
+ tableCsvSeparator: tableSection.csvSeparator ?? ',',
170
+ mermaid: mermaid.enabled,
171
+ mermaidDownload: mermaid.download,
172
+ mermaidDownloadFilename: mermaid.filename ?? 'diagram',
173
+ // Wheel zoom is a gesture, not a button: it stays on unless it is
174
+ // turned off explicitly or every control is.
175
+ mermaidMouseWheelZoom: controls !== false && mermaidSection.mouseWheelZoom !== false
137
176
  };
138
177
  },
178
+ get codeBlockMaxHeight() {
179
+ return codeBlockMaxHeight;
180
+ },
181
+ get tableMaxHeight() {
182
+ return tableMaxHeight;
183
+ },
139
184
  get children() {
140
185
  return children;
141
186
  },
@@ -161,6 +206,12 @@
161
206
  const blocks = $derived(
162
207
  isStatic ? content : parseBlocks(content, streamdown.extensions, blocksCache)
163
208
  );
209
+
210
+ // Only the tail of a live stream can be mid-fence — a static render is finished
211
+ // by definition. Computed once here, not per block or per token.
212
+ const lastBlockIncomplete = $derived(
213
+ !isStatic && blocks.length > 0 && hasUnclosedFence(blocks[blocks.length - 1])
214
+ );
164
215
  </script>
165
216
 
166
217
  <div bind:this={element} class={className}>
@@ -168,7 +219,11 @@
168
219
  <Block static={isStatic} block={content} />
169
220
  {:else}
170
221
  {#each blocks as block, index (`${id}-block-${index}`)}
171
- <Block static={isStatic} {block} />
222
+ <Block
223
+ static={isStatic}
224
+ {block}
225
+ incomplete={lastBlockIncomplete && index === blocks.length - 1}
226
+ />
172
227
  {/each}
173
228
  {/if}
174
229
  </div>
@@ -4,16 +4,55 @@ import type { MermaidConfig } from 'mermaid';
4
4
  import type { KatexOptions } from 'katex';
5
5
  import type { HighlightTheme } from '@tanstack/highlight/theme';
6
6
  import type { LanguageDefinition } from '@tanstack/highlight';
7
+ /**
8
+ * Every user-visible string the components render themselves. Nested by area,
9
+ * like the theme; `Streamdown` merges the `translations` prop over
10
+ * `defaultTranslations` once so components always read a complete object.
11
+ */
12
+ export type Translations = {
13
+ alert: {
14
+ note: string;
15
+ tip: string;
16
+ warning: string;
17
+ caution: string;
18
+ important: string;
19
+ };
20
+ controls: {
21
+ copyCode: string;
22
+ copiedCode: string;
23
+ downloadCode: string;
24
+ copyTable: string;
25
+ copiedTable: string;
26
+ downloadTable: string;
27
+ tableFormatMarkdown: string;
28
+ tableFormatHtml: string;
29
+ tableFormatCsv: string;
30
+ tableFormatTsv: string;
31
+ downloadDiagram: string;
32
+ downloadDiagramPng: string;
33
+ downloadDiagramSvg: string;
34
+ downloadDiagramMmd: string;
35
+ zoomIn: string;
36
+ zoomOut: string;
37
+ resetView: string;
38
+ fullscreen: string;
39
+ exitFullscreen: string;
40
+ diagram: string;
41
+ previousCitation: string;
42
+ nextCitation: string;
43
+ blockedUrl: string;
44
+ imageBlocked: string;
45
+ imageNoDescription: string;
46
+ linkBlocked: string;
47
+ };
48
+ };
49
+ export declare const defaultTranslations: Translations;
7
50
  export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets | 'class' | 'theme' | 'highlightTheme' | 'inlineCitationsMode'> {
8
51
  snippets: Snippets;
9
52
  highlightTheme: HighlightTheme;
10
53
  theme: Theme;
11
- controls: {
12
- code: boolean;
13
- mermaid: boolean;
14
- mermaidMouseWheelZoom: boolean;
15
- table: boolean;
16
- };
54
+ translations: Translations;
55
+ controls: ResolvedControls;
17
56
  inlineCitationsMode: 'list' | 'carousel';
18
57
  animation: {
19
58
  enabled: boolean;
@@ -33,8 +72,9 @@ export declare class StreamdownContext<Source extends Record<string, any> = Reco
33
72
  });
34
73
  }
35
74
  export declare const useStreamdown: () => StreamdownContext<Record<string, any>>;
36
- import type { AlertToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH, Extension, GenericToken, CitationToken, MdxToken } from './marked/index.js';
75
+ import type { AlertToken, CodeToken, MathToken, SubSupToken, TableToken, THead, TBody, TFoot, THeadRow, TRow, TD, TH, Extension, GenericToken, CitationToken, MdxToken } from './marked/index.js';
37
76
  import type { Tokens } from 'marked';
77
+ import type { CsvSeparator } from './utils/table-export.js';
38
78
  import type { ListItemToken, ListToken } from './marked/marked-list.js';
39
79
  import type { Footnote, FootnoteRef, FootnoteToken } from './marked/marked-footnotes.js';
40
80
  import type { DescriptionDetailToken, DescriptionListToken, DescriptionTermToken, DescriptionToken } from './marked/marked-dl.js';
@@ -89,9 +129,53 @@ export type Snippets<Source extends Record<string, any> = Record<string, any>> =
89
129
  key: string;
90
130
  } : K extends 'mdx' ? {
91
131
  props: Record<string, number | string | boolean | null | undefined>;
132
+ } : K extends 'code' | 'mermaid' ? {
133
+ /** The fence is still streaming: defer expensive work. */
134
+ incomplete: boolean;
92
135
  } : {})
93
136
  ]>;
94
137
  };
138
+ /** `filename` is the base name; the extension comes from the format. */
139
+ type DownloadControl<Token> = boolean | {
140
+ filename?: string | ((token: Token) => string);
141
+ };
142
+ export type CodeControls = boolean | {
143
+ enabled?: boolean;
144
+ copy?: boolean;
145
+ download?: DownloadControl<CodeToken>;
146
+ };
147
+ export type TableControls = boolean | {
148
+ enabled?: boolean;
149
+ copy?: boolean;
150
+ download?: DownloadControl<TableToken>;
151
+ csvSeparator?: CsvSeparator;
152
+ };
153
+ export type MermaidControls = boolean | {
154
+ enabled?: boolean;
155
+ download?: DownloadControl<CodeToken>;
156
+ mouseWheelZoom?: boolean;
157
+ };
158
+ export type Controls = boolean | {
159
+ code?: CodeControls;
160
+ table?: TableControls;
161
+ mermaid?: MermaidControls;
162
+ };
163
+ /** What `Streamdown.svelte` flattens `controls` into, so components stay dumb. */
164
+ export type ResolvedControls = {
165
+ code: boolean;
166
+ codeCopy: boolean;
167
+ codeDownload: boolean;
168
+ codeDownloadFilename: string | ((token: CodeToken) => string);
169
+ table: boolean;
170
+ tableCopy: boolean;
171
+ tableDownload: boolean;
172
+ tableDownloadFilename: string | ((token: TableToken) => string);
173
+ tableCsvSeparator: CsvSeparator;
174
+ mermaid: boolean;
175
+ mermaidDownload: boolean;
176
+ mermaidDownloadFilename: string | ((token: CodeToken) => string);
177
+ mermaidMouseWheelZoom: boolean;
178
+ };
95
179
  export type StreamdownProps<Source extends Record<string, any> = Record<string, any>> = {
96
180
  streamdown?: StreamdownContext;
97
181
  static?: boolean;
@@ -114,23 +198,13 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
114
198
  highlightThemes?: Record<string, HighlightTheme>;
115
199
  mermaidConfig?: MermaidConfig;
116
200
  katexConfig?: KatexOptions | ((inline: boolean) => KatexOptions);
201
+ /** Partial overrides; unset keys fall back to `defaultTranslations`. */
117
202
  translations?: {
118
- alert?: {
119
- note?: string;
120
- tip?: string;
121
- warning?: string;
122
- caution?: string;
123
- important?: string;
124
- };
125
- };
126
- controls?: {
127
- code?: boolean;
128
- mermaid?: boolean | {
129
- enabled?: boolean;
130
- mouseWheelZoom?: boolean;
131
- };
132
- table?: boolean;
203
+ [K in keyof Translations]?: Partial<Translations[K]>;
133
204
  };
205
+ controls?: Controls;
206
+ codeBlockMaxHeight?: string;
207
+ tableMaxHeight?: string;
134
208
  renderHtml?: boolean | ((token: Tokens.HTML | Tokens.Tag) => string);
135
209
  animation?: {
136
210
  animateOnMount?: boolean;
@@ -171,10 +245,12 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
171
245
  code?: Component<{
172
246
  token: Tokens.Code;
173
247
  id: string;
248
+ incomplete: boolean;
174
249
  }, any, any>;
175
250
  mermaid?: Component<{
176
251
  token: Tokens.Code;
177
252
  id: string;
253
+ incomplete: boolean;
178
254
  }, any, any>;
179
255
  math?: Component<{
180
256
  token: MathToken;
@@ -1,4 +1,42 @@
1
1
  import { getContext, onMount, setContext } from 'svelte';
2
+ // Alert titles stay lowercase: the theme capitalizes them with CSS.
3
+ export const defaultTranslations = {
4
+ alert: {
5
+ note: 'note',
6
+ tip: 'tip',
7
+ warning: 'warning',
8
+ caution: 'caution',
9
+ important: 'important'
10
+ },
11
+ controls: {
12
+ copyCode: 'Copy code',
13
+ copiedCode: 'Code copied',
14
+ downloadCode: 'Download code',
15
+ copyTable: 'Copy table',
16
+ copiedTable: 'Table copied',
17
+ downloadTable: 'Download table',
18
+ tableFormatMarkdown: 'Markdown',
19
+ tableFormatHtml: 'HTML',
20
+ tableFormatCsv: 'CSV',
21
+ tableFormatTsv: 'TSV',
22
+ downloadDiagram: 'Download diagram',
23
+ downloadDiagramPng: 'PNG',
24
+ downloadDiagramSvg: 'SVG',
25
+ downloadDiagramMmd: 'MMD',
26
+ zoomIn: 'Zoom in',
27
+ zoomOut: 'Zoom out',
28
+ resetView: 'Zoom to fit',
29
+ fullscreen: 'Expand diagram',
30
+ exitFullscreen: 'Collapse diagram',
31
+ diagram: 'Diagram',
32
+ previousCitation: 'Previous citation',
33
+ nextCitation: 'Next citation',
34
+ blockedUrl: 'Blocked URL',
35
+ imageBlocked: 'Image blocked',
36
+ imageNoDescription: 'No description',
37
+ linkBlocked: 'blocked'
38
+ }
39
+ };
2
40
  export class StreamdownContext {
3
41
  footnotes = {
4
42
  refs: new Map(),
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { default as Streamdown } from './Streamdown.svelte';
2
- export { useStreamdown, type StreamdownProps } from './context.svelte.js';
2
+ export { useStreamdown, defaultTranslations, type StreamdownProps, type Translations, type Controls, type CodeControls, type TableControls, type MermaidControls, type ResolvedControls } from './context.svelte.js';
3
+ export { extractTableData, tableDataToCSV, tableDataToTSV, tableDataToMarkdown, tableDataToHTML, type TableData, type CsvSeparator } from './utils/table-export.js';
3
4
  export { theme, shadcnTheme, mergeTheme, type Theme } from './theme.js';
4
- export { type Extension, type StreamdownToken, lex, parseBlocks } from './marked/index.js';
5
+ export { type CodeToken, type Extension, type StreamdownToken, lex, parseBlocks } from './marked/index.js';
5
6
  export { parseIncompleteMarkdown, type Plugin, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
6
7
  export { defineLanguage, type LanguageDefinition } from '@tanstack/highlight';
7
8
  export type { HighlightTheme } from '@tanstack/highlight/theme';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as Streamdown } from './Streamdown.svelte';
2
- export { useStreamdown } from './context.svelte.js';
2
+ export { useStreamdown, defaultTranslations } from './context.svelte.js';
3
+ export { extractTableData, tableDataToCSV, tableDataToTSV, tableDataToMarkdown, tableDataToHTML } from './utils/table-export.js';
3
4
  export { theme, shadcnTheme, mergeTheme } from './theme.js';
4
5
  export { lex, parseBlocks } from './marked/index.js';
5
6
  export { parseIncompleteMarkdown, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
@@ -23,7 +23,14 @@ export type Extension = {
23
23
  start?: TokenizerStartFunction;
24
24
  applyInBlockParsing?: boolean;
25
25
  };
26
- export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem | Tokens.Table> | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | BrToken | HrToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD | DescriptionListToken | DescriptionToken | DescriptionDetailToken | DescriptionTermToken | AlignToken | CitationToken | MdxToken;
26
+ /**
27
+ * A fenced code block. `lang` is the first word of the info string; everything
28
+ * after it is `meta` (`undefined` when there is none).
29
+ */
30
+ export type CodeToken = Tokens.Code & {
31
+ meta?: string;
32
+ };
33
+ export type StreamdownToken = Exclude<MarkedToken, Tokens.List | Tokens.ListItem | Tokens.Table | Tokens.Code> | CodeToken | ListToken | ListItemToken | MathToken | AlertToken | FootnoteToken | SubSupToken | BrToken | HrToken | TableToken | THead | TBody | TFoot | THeadRow | TRow | TH | TD | DescriptionListToken | DescriptionToken | DescriptionDetailToken | DescriptionTermToken | AlignToken | CitationToken | MdxToken;
27
34
  export type { TableToken, THead, TBody, TFoot, THeadRow, TRow, TH, TD } from './marked-table.js';
28
35
  export declare const lex: (markdown: string, extensions?: Extension[]) => StreamdownToken[];
29
36
  /**