svelte-streamdown 1.0.7 → 1.0.9

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 (72) hide show
  1. package/README.md +11 -5
  2. package/dist/Block.svelte +26 -10
  3. package/dist/Elements/Code.svelte +1 -1
  4. package/dist/Elements/Element.svelte +120 -37
  5. package/dist/Elements/FootnoteRef.svelte +113 -0
  6. package/dist/Elements/FootnoteRef.svelte.d.ts +7 -0
  7. package/dist/Elements/Image.svelte +3 -3
  8. package/dist/Elements/{A.svelte → Link.svelte} +3 -3
  9. package/dist/Elements/{A.svelte.d.ts → Link.svelte.d.ts} +3 -3
  10. package/dist/Elements/Math.svelte +3 -2
  11. package/dist/Elements/index.d.ts +5 -25
  12. package/dist/Elements/index.js +5 -25
  13. package/dist/Streamdown.d.ts +1 -1
  14. package/dist/Streamdown.svelte +7 -1
  15. package/dist/Streamdown.svelte.d.ts +6 -0
  16. package/dist/marked/index.d.ts +18 -5
  17. package/dist/marked/index.js +4 -0
  18. package/dist/marked/marked-alert.d.ts +2 -2
  19. package/dist/marked/marked-alert.js +18 -18
  20. package/dist/marked/marked-footnotes.d.ts +1 -0
  21. package/dist/marked/marked-footnotes.js +28 -8
  22. package/dist/marked/marked-list.d.ts +36 -0
  23. package/dist/marked/marked-list.js +215 -0
  24. package/dist/marked/marked-math.js +0 -3
  25. package/dist/marked/marked-subsup.d.ts +28 -0
  26. package/dist/marked/marked-subsup.js +44 -0
  27. package/dist/theme.d.ts +48 -42
  28. package/dist/theme.js +47 -43
  29. package/dist/utils/parse-incomplete-markdown.js +157 -0
  30. package/dist/utils/useClickOutside.svelte.d.ts +8 -0
  31. package/dist/utils/useClickOutside.svelte.js +42 -0
  32. package/dist/utils/useKeyDown.svelte.d.ts +5 -0
  33. package/dist/utils/useKeyDown.svelte.js +20 -0
  34. package/package.json +3 -1
  35. package/dist/Elements/Blockquote.svelte +0 -28
  36. package/dist/Elements/Blockquote.svelte.d.ts +0 -9
  37. package/dist/Elements/Del.svelte +0 -28
  38. package/dist/Elements/Del.svelte.d.ts +0 -9
  39. package/dist/Elements/Em.svelte +0 -28
  40. package/dist/Elements/Em.svelte.d.ts +0 -9
  41. package/dist/Elements/Heading.svelte +0 -25
  42. package/dist/Elements/Heading.svelte.d.ts +0 -9
  43. package/dist/Elements/Hr.svelte +0 -26
  44. package/dist/Elements/Hr.svelte.d.ts +0 -9
  45. package/dist/Elements/InlineCode.svelte +0 -28
  46. package/dist/Elements/InlineCode.svelte.d.ts +0 -9
  47. package/dist/Elements/Li.svelte +0 -28
  48. package/dist/Elements/Li.svelte.d.ts +0 -9
  49. package/dist/Elements/Ol.svelte +0 -30
  50. package/dist/Elements/Ol.svelte.d.ts +0 -9
  51. package/dist/Elements/P.svelte +0 -28
  52. package/dist/Elements/P.svelte.d.ts +0 -9
  53. package/dist/Elements/Strong.svelte +0 -28
  54. package/dist/Elements/Strong.svelte.d.ts +0 -9
  55. package/dist/Elements/Sub.svelte +0 -27
  56. package/dist/Elements/Sub.svelte.d.ts +0 -8
  57. package/dist/Elements/Sup.svelte +0 -28
  58. package/dist/Elements/Sup.svelte.d.ts +0 -8
  59. package/dist/Elements/Tbody.svelte +0 -24
  60. package/dist/Elements/Tbody.svelte.d.ts +0 -7
  61. package/dist/Elements/Td.svelte +0 -28
  62. package/dist/Elements/Td.svelte.d.ts +0 -9
  63. package/dist/Elements/Th.svelte +0 -28
  64. package/dist/Elements/Th.svelte.d.ts +0 -9
  65. package/dist/Elements/Thead.svelte +0 -27
  66. package/dist/Elements/Thead.svelte.d.ts +0 -9
  67. package/dist/Elements/Tr.svelte +0 -27
  68. package/dist/Elements/Tr.svelte.d.ts +0 -9
  69. package/dist/Elements/Ul.svelte +0 -28
  70. package/dist/Elements/Ul.svelte.d.ts +0 -9
  71. /package/dist/{hightlighter.svelte.d.ts → utils/hightlighter.svelte.d.ts} +0 -0
  72. /package/dist/{hightlighter.svelte.js → utils/hightlighter.svelte.js} +0 -0
package/dist/theme.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type ClassValue } from 'clsx';
2
2
  export declare const cn: (...inputs: ClassValue[]) => string;
3
3
  export declare const theme: {
4
- a: {
4
+ link: {
5
5
  base: string;
6
6
  blocked: string;
7
7
  };
@@ -34,6 +34,7 @@ export declare const theme: {
34
34
  };
35
35
  li: {
36
36
  base: string;
37
+ checkbox: string;
37
38
  };
38
39
  code: {
39
40
  base: string;
@@ -44,17 +45,14 @@ export declare const theme: {
44
45
  skeleton: string;
45
46
  pre: string;
46
47
  };
47
- inlineCode: {
48
+ codespan: {
48
49
  base: string;
49
50
  };
50
- img: {
51
+ image: {
51
52
  base: string;
52
53
  image: string;
53
54
  downloadButton: string;
54
55
  };
55
- pre: {
56
- base: string;
57
- };
58
56
  blockquote: {
59
57
  base: string;
60
58
  };
@@ -72,21 +70,18 @@ export declare const theme: {
72
70
  base: string;
73
71
  table: string;
74
72
  };
75
- thead: {
73
+ tableRow: {
76
74
  base: string;
77
75
  };
78
- tbody: {
76
+ tableHead: {
79
77
  base: string;
80
78
  };
81
- tr: {
79
+ td: {
82
80
  base: string;
83
81
  };
84
82
  th: {
85
83
  base: string;
86
84
  };
87
- td: {
88
- base: string;
89
- };
90
85
  sup: {
91
86
  base: string;
92
87
  };
@@ -107,9 +102,10 @@ export declare const theme: {
107
102
  buttons: string;
108
103
  };
109
104
  math: {
110
- base: string;
105
+ block: string;
106
+ inline: string;
111
107
  };
112
- inlineMath: {
108
+ br: {
113
109
  base: string;
114
110
  };
115
111
  em: {
@@ -118,9 +114,15 @@ export declare const theme: {
118
114
  del: {
119
115
  base: string;
120
116
  };
117
+ footnoteRef: {
118
+ base: string;
119
+ };
120
+ footnotePopover: {
121
+ base: string;
122
+ };
121
123
  };
122
124
  export declare const shadcnTheme: {
123
- a: {
125
+ link: {
124
126
  base: string;
125
127
  blocked: string;
126
128
  };
@@ -153,6 +155,7 @@ export declare const shadcnTheme: {
153
155
  };
154
156
  li: {
155
157
  base: string;
158
+ checkbox: string;
156
159
  };
157
160
  code: {
158
161
  base: string;
@@ -163,17 +166,14 @@ export declare const shadcnTheme: {
163
166
  skeleton: string;
164
167
  pre: string;
165
168
  };
166
- inlineCode: {
169
+ codespan: {
167
170
  base: string;
168
171
  };
169
- img: {
172
+ image: {
170
173
  base: string;
171
174
  image: string;
172
175
  downloadButton: string;
173
176
  };
174
- pre: {
175
- base: string;
176
- };
177
177
  blockquote: {
178
178
  base: string;
179
179
  };
@@ -191,21 +191,18 @@ export declare const shadcnTheme: {
191
191
  base: string;
192
192
  table: string;
193
193
  };
194
- thead: {
194
+ tableRow: {
195
195
  base: string;
196
196
  };
197
- tbody: {
197
+ tableHead: {
198
198
  base: string;
199
199
  };
200
- tr: {
200
+ td: {
201
201
  base: string;
202
202
  };
203
203
  th: {
204
204
  base: string;
205
205
  };
206
- td: {
207
- base: string;
208
- };
209
206
  sup: {
210
207
  base: string;
211
208
  };
@@ -226,9 +223,10 @@ export declare const shadcnTheme: {
226
223
  buttons: string;
227
224
  };
228
225
  math: {
229
- base: string;
226
+ block: string;
227
+ inline: string;
230
228
  };
231
- inlineMath: {
229
+ br: {
232
230
  base: string;
233
231
  };
234
232
  em: {
@@ -237,10 +235,16 @@ export declare const shadcnTheme: {
237
235
  del: {
238
236
  base: string;
239
237
  };
238
+ footnoteRef: {
239
+ base: string;
240
+ };
241
+ footnotePopover: {
242
+ base: string;
243
+ };
240
244
  };
241
245
  export type Theme = typeof theme;
242
246
  export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tailwind" | "shadcn") => {
243
- a: {
247
+ link: {
244
248
  base: string;
245
249
  blocked: string;
246
250
  };
@@ -273,6 +277,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
273
277
  };
274
278
  li: {
275
279
  base: string;
280
+ checkbox: string;
276
281
  };
277
282
  code: {
278
283
  base: string;
@@ -283,17 +288,14 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
283
288
  skeleton: string;
284
289
  pre: string;
285
290
  };
286
- inlineCode: {
291
+ codespan: {
287
292
  base: string;
288
293
  };
289
- img: {
294
+ image: {
290
295
  base: string;
291
296
  image: string;
292
297
  downloadButton: string;
293
298
  };
294
- pre: {
295
- base: string;
296
- };
297
299
  blockquote: {
298
300
  base: string;
299
301
  };
@@ -311,21 +313,18 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
311
313
  base: string;
312
314
  table: string;
313
315
  };
314
- thead: {
316
+ tableRow: {
315
317
  base: string;
316
318
  };
317
- tbody: {
319
+ tableHead: {
318
320
  base: string;
319
321
  };
320
- tr: {
322
+ td: {
321
323
  base: string;
322
324
  };
323
325
  th: {
324
326
  base: string;
325
327
  };
326
- td: {
327
- base: string;
328
- };
329
328
  sup: {
330
329
  base: string;
331
330
  };
@@ -346,9 +345,10 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
346
345
  buttons: string;
347
346
  };
348
347
  math: {
349
- base: string;
348
+ block: string;
349
+ inline: string;
350
350
  };
351
- inlineMath: {
351
+ br: {
352
352
  base: string;
353
353
  };
354
354
  em: {
@@ -357,4 +357,10 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
357
357
  del: {
358
358
  base: string;
359
359
  };
360
+ footnoteRef: {
361
+ base: string;
362
+ };
363
+ footnotePopover: {
364
+ base: string;
365
+ };
360
366
  };
package/dist/theme.js CHANGED
@@ -2,7 +2,7 @@ import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  export const cn = (...inputs) => twMerge(clsx(inputs));
4
4
  export const theme = {
5
- a: {
5
+ link: {
6
6
  base: 'text-blue-600 font-medium underline',
7
7
  blocked: 'text-gray-500'
8
8
  },
@@ -28,13 +28,14 @@ export const theme = {
28
28
  base: ''
29
29
  },
30
30
  ul: {
31
- base: 'ml-4 list-outside list-disc whitespace-normal'
31
+ base: 'ml-4 list-inside list-disc whitespace-normal'
32
32
  },
33
33
  ol: {
34
- base: 'ml-4 list-outside list-decimal whitespace-normal'
34
+ base: 'ml-4 list-inside whitespace-normal'
35
35
  },
36
36
  li: {
37
- base: 'py-1'
37
+ base: 'py-1 marker:hidden',
38
+ checkbox: '-ml-2'
38
39
  },
39
40
  code: {
40
41
  base: 'my-4 w-full overflow-hidden rounded-xl border border-gray-200 flex flex-col',
@@ -45,17 +46,14 @@ export const theme = {
45
46
  skeleton: 'rounded-md font-mono text-transparent bg-gray-200 whitespace-nowrap inline-block',
46
47
  pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40'
47
48
  },
48
- inlineCode: {
49
- base: 'bg-gray-100 rounded px-1.5 py-0.5 font-mono'
49
+ codespan: {
50
+ base: 'bg-gray-100 rounded px-1.5 py-0.5 font-mono text-[0.9em]'
50
51
  },
51
- img: {
52
+ image: {
52
53
  base: 'group relative my-4 mx-auto w-fit block',
53
54
  image: 'max-w-full rounded-lg',
54
55
  downloadButton: 'absolute right-2 bottom-2 flex h-8 w-8 cursor-pointer items-center justify-center rounded-md border border-gray-200 bg-white/90 shadow-sm backdrop-blur-sm transition-all duration-200 hover:bg-white opacity-0 group-hover:opacity-100'
55
56
  },
56
- pre: {
57
- base: 'overflow-x-auto font-mono text-xs p-4 bg-gray-100/40'
58
- },
59
57
  blockquote: {
60
58
  base: 'border-gray-600/30 text-gray-600 my-4 border-l-4 pl-4 italic'
61
59
  },
@@ -73,21 +71,18 @@ export const theme = {
73
71
  base: 'overflow-x-auto max-w-full my-4 border border-gray-200 rounded-lg',
74
72
  table: 'w-full border-collapse min-w-full'
75
73
  },
76
- thead: {
77
- base: 'bg-gray-100/80'
78
- },
79
- tbody: {
80
- base: 'divide-gray-200 bg-gray-100/40 divide-y'
81
- },
82
- tr: {
74
+ tableRow: {
83
75
  base: 'border-gray-200 border-b hover:bg-gray-100/50 transition-colors'
84
76
  },
85
- th: {
86
- base: 'px-4 py-3 text-left text-sm font-semibold whitespace-nowrap min-w-[200px]'
77
+ tableHead: {
78
+ base: 'bg-gray-200/80'
87
79
  },
88
80
  td: {
89
81
  base: 'px-4 py-3 text-sm min-w-[200px] max-w-[400px] break-words'
90
82
  },
83
+ th: {
84
+ base: 'text-left px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
85
+ },
91
86
  sup: {
92
87
  base: 'text-sm'
93
88
  },
@@ -108,9 +103,10 @@ export const theme = {
108
103
  buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
109
104
  },
110
105
  math: {
111
- base: ''
106
+ block: '',
107
+ inline: ''
112
108
  },
113
- inlineMath: {
109
+ br: {
114
110
  base: ''
115
111
  },
116
112
  em: {
@@ -118,10 +114,16 @@ export const theme = {
118
114
  },
119
115
  del: {
120
116
  base: 'line-through'
117
+ },
118
+ footnoteRef: {
119
+ base: 'text-gray-600 px-1 py-0.5 rounded-md bg-gray-100/80'
120
+ },
121
+ footnotePopover: {
122
+ base: 'fixed z-50 max-h-[30vh] max-w-3xl overflow-y-auto rounded-lg bg-background p-4 shadow'
121
123
  }
122
124
  };
123
125
  export const shadcnTheme = {
124
- a: {
126
+ link: {
125
127
  base: 'text-primary font-medium underline hover:text-primary/80',
126
128
  blocked: 'text-muted-foreground'
127
129
  },
@@ -147,13 +149,14 @@ export const shadcnTheme = {
147
149
  base: 'text-foreground'
148
150
  },
149
151
  ul: {
150
- base: 'ml-4 list-outside list-disc whitespace-normal text-foreground'
152
+ base: 'ml-4 list-inside list-disc whitespace-normal text-foreground'
151
153
  },
152
154
  ol: {
153
- base: 'ml-4 list-outside list-decimal whitespace-normal text-foreground'
155
+ base: 'ml-4 list-inside whitespace-normal text-foreground'
154
156
  },
155
157
  li: {
156
- base: 'py-1'
158
+ base: 'py-1',
159
+ checkbox: '-ml-2'
157
160
  },
158
161
  code: {
159
162
  base: 'my-4 w-full overflow-hidden rounded-lg border border-border flex flex-col',
@@ -164,17 +167,14 @@ export const shadcnTheme = {
164
167
  skeleton: 'rounded-md font-mono text-transparent bg-muted whitespace-nowrap inline-block',
165
168
  pre: 'overflow-x-auto font-mono p-0 bg-muted/40'
166
169
  },
167
- inlineCode: {
168
- base: 'bg-muted rounded px-1.5 py-0.5 font-mono text-foreground'
170
+ codespan: {
171
+ base: 'bg-muted rounded px-1.5 py-0.5 font-mono text-foreground text-[0.9em]'
169
172
  },
170
- img: {
173
+ image: {
171
174
  base: 'group relative my-4 mx-auto w-fit block',
172
175
  image: 'max-w-full rounded-lg',
173
176
  downloadButton: 'absolute right-2 bottom-2 flex h-8 w-8 cursor-pointer items-center justify-center rounded-md border border-border bg-background/90 shadow-sm backdrop-blur-sm transition-all duration-200 hover:bg-background opacity-0 group-hover:opacity-100'
174
177
  },
175
- pre: {
176
- base: 'overflow-x-auto font-mono text-xs p-4 bg-muted/40'
177
- },
178
178
  blockquote: {
179
179
  base: 'border-muted-foreground/30 text-muted-foreground my-4 border-l-4 pl-4 italic'
180
180
  },
@@ -192,21 +192,18 @@ export const shadcnTheme = {
192
192
  base: 'overflow-x-auto max-w-full my-4 rounded-lg border border-border',
193
193
  table: 'w-full border-collapse min-w-full'
194
194
  },
195
- thead: {
196
- base: 'bg-muted/50'
197
- },
198
- tbody: {
199
- base: 'divide-border bg-card divide-y'
200
- },
201
- tr: {
195
+ tableRow: {
202
196
  base: 'border-border border-b hover:bg-muted/50 transition-colors'
203
197
  },
204
- th: {
205
- base: 'px-4 py-3 text-left text-sm font-semibold whitespace-nowrap text-foreground min-w-[200px]'
198
+ tableHead: {
199
+ base: 'bg-muted/80'
206
200
  },
207
201
  td: {
208
202
  base: 'px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
209
203
  },
204
+ th: {
205
+ base: 'text-left px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
206
+ },
210
207
  sup: {
211
208
  base: 'text-sm'
212
209
  },
@@ -227,16 +224,23 @@ export const shadcnTheme = {
227
224
  buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
228
225
  },
229
226
  math: {
230
- base: 'text-foreground'
227
+ block: 'text-foreground',
228
+ inline: 'text-foreground'
231
229
  },
232
- inlineMath: {
233
- base: 'text-foreground'
230
+ br: {
231
+ base: ''
234
232
  },
235
233
  em: {
236
234
  base: 'italic text-foreground'
237
235
  },
238
236
  del: {
239
237
  base: 'line-through text-muted-foreground'
238
+ },
239
+ footnoteRef: {
240
+ base: 'text-muted-foreground px-1 text-sm inline-block rounded-full bg-muted/80 aspect-square border border-border'
241
+ },
242
+ footnotePopover: {
243
+ base: 'fixed z-50 max-h-[30vh] shadow max-w-3xl overflow-y-auto rounded-lg bg-background p-4'
240
244
  }
241
245
  };
242
246
  export const mergeTheme = (customTheme, baseTheme) => {
@@ -6,6 +6,8 @@ const singleAsteriskPattern = /(\*)([^*]*?)$/;
6
6
  const singleUnderscorePattern = /(_)([^_]*?)$/;
7
7
  const inlineCodePattern = /(`)([^`]*?)$/;
8
8
  const strikethroughPattern = /(~~)([^~]*?)$/;
9
+ const subPattern = /(~)([^~]*?)$/;
10
+ const supPattern = /(\^)([^\^]*?)$/;
9
11
  // Helper function to check if we have a complete code block
10
12
  const hasCompleteCodeBlock = (text) => {
11
13
  const tripleBackticks = (text.match(/```/g) || []).length;
@@ -196,6 +198,38 @@ const isWithinMathBlock = (text, position) => {
196
198
  }
197
199
  return inInlineMath || inBlockMath;
198
200
  };
201
+ // Check if a position is within a footnote reference pattern [^label]
202
+ const isWithinFootnoteRef = (text, position) => {
203
+ // Look backwards from position to find if we're inside [^...]
204
+ let openBracketPos = -1;
205
+ let caretPos = -1;
206
+ for (let i = position; i >= 0; i--) {
207
+ if (text[i] === ']') {
208
+ // Found closing bracket before our position, not in footnote
209
+ return false;
210
+ }
211
+ if (text[i] === '^' && caretPos === -1) {
212
+ caretPos = i;
213
+ }
214
+ if (text[i] === '[') {
215
+ openBracketPos = i;
216
+ break;
217
+ }
218
+ }
219
+ // Check if we have the pattern [^ and our position is after the caret
220
+ if (openBracketPos !== -1 && caretPos === openBracketPos + 1 && position >= caretPos) {
221
+ // Look forward to see if there's a closing bracket
222
+ for (let i = position + 1; i < text.length; i++) {
223
+ if (text[i] === ']') {
224
+ return true; // We're inside [^...] pattern
225
+ }
226
+ if (text[i] === '[' || text[i] === '\n') {
227
+ break; // Invalid pattern
228
+ }
229
+ }
230
+ }
231
+ return false;
232
+ };
199
233
  // Counts single underscores that are not part of double underscores, not escaped, and not in math blocks
200
234
  const countSingleUnderscores = (text) => {
201
235
  return text.split('').reduce((acc, char, index) => {
@@ -335,6 +369,127 @@ const handleIncompleteStrikethrough = (text) => {
335
369
  }
336
370
  return text;
337
371
  };
372
+ // Counts single tildes that are not part of double tildes and not escaped
373
+ const countSingleTildes = (text) => {
374
+ return text.split('').reduce((acc, char, index) => {
375
+ if (char === '~') {
376
+ const prevChar = text[index - 1];
377
+ const nextChar = text[index + 1];
378
+ // Skip if escaped with backslash
379
+ if (prevChar === '\\') {
380
+ return acc;
381
+ }
382
+ if (prevChar !== '~' && nextChar !== '~') {
383
+ return acc + 1;
384
+ }
385
+ }
386
+ return acc;
387
+ }, 0);
388
+ };
389
+ // Completes incomplete subscript formatting (~)
390
+ const handleIncompleteSub = (text) => {
391
+ // Don't process if inside a complete code block
392
+ if (hasCompleteCodeBlock(text)) {
393
+ return text;
394
+ }
395
+ const singleTildes = countSingleTildes(text);
396
+ if (singleTildes % 2 === 1) {
397
+ // Find the last unmatched tilde and add content until we find a good place to close
398
+ const lastTildeIndex = text.lastIndexOf('~');
399
+ if (lastTildeIndex !== -1) {
400
+ // Check if the tilde is within a math block - if so, don't process
401
+ if (isWithinMathBlock(text, lastTildeIndex)) {
402
+ return text;
403
+ }
404
+ const afterTilde = text.substring(lastTildeIndex + 1);
405
+ // Don't close if there's no meaningful content after the opening marker
406
+ if (!afterTilde || /^[\s_~*`^]*$/.test(afterTilde)) {
407
+ return text;
408
+ }
409
+ // Find the end of the subscript content
410
+ // For typical sub/sup patterns, look for single numbers, letters, or simple expressions
411
+ const endMatch = afterTilde.match(/^([0-9]+|[a-zA-Z]|[a-zA-Z0-9]{1,3})(?=\s|[^a-zA-Z0-9]|$)/);
412
+ if (endMatch) {
413
+ const contentLength = endMatch[1].length;
414
+ const insertPosition = lastTildeIndex + 1 + contentLength;
415
+ return text.substring(0, insertPosition) + '~' + text.substring(insertPosition);
416
+ }
417
+ else {
418
+ // Fallback: if no clear boundary, treat everything until space as content
419
+ const spaceMatch = afterTilde.match(/^([^\s]+)/);
420
+ if (spaceMatch) {
421
+ const contentLength = spaceMatch[1].length;
422
+ const insertPosition = lastTildeIndex + 1 + contentLength;
423
+ return text.substring(0, insertPosition) + '~' + text.substring(insertPosition);
424
+ }
425
+ }
426
+ }
427
+ }
428
+ return text;
429
+ };
430
+ // Counts single carets that are not escaped and not in footnote references
431
+ const countSingleCarets = (text) => {
432
+ return text.split('').reduce((acc, char, index) => {
433
+ if (char === '^') {
434
+ const prevChar = text[index - 1];
435
+ // Skip if escaped with backslash
436
+ if (prevChar === '\\') {
437
+ return acc;
438
+ }
439
+ // Skip if within footnote reference
440
+ if (isWithinFootnoteRef(text, index)) {
441
+ return acc;
442
+ }
443
+ return acc + 1;
444
+ }
445
+ return acc;
446
+ }, 0);
447
+ };
448
+ // Completes incomplete superscript formatting (^)
449
+ const handleIncompleteSup = (text) => {
450
+ // Don't process if inside a complete code block
451
+ if (hasCompleteCodeBlock(text)) {
452
+ return text;
453
+ }
454
+ const singleCarets = countSingleCarets(text);
455
+ if (singleCarets % 2 === 1) {
456
+ // Find the last unmatched caret and add content until we find a good place to close
457
+ const lastCaretIndex = text.lastIndexOf('^');
458
+ if (lastCaretIndex !== -1) {
459
+ // Check if the caret is within a math block - if so, don't process
460
+ if (isWithinMathBlock(text, lastCaretIndex)) {
461
+ return text;
462
+ }
463
+ // Check if the caret is within a footnote reference - if so, don't process
464
+ if (isWithinFootnoteRef(text, lastCaretIndex)) {
465
+ return text;
466
+ }
467
+ const afterCaret = text.substring(lastCaretIndex + 1);
468
+ // Don't close if there's no meaningful content after the opening marker
469
+ if (!afterCaret || /^[\s_~*`^]*$/.test(afterCaret)) {
470
+ return text;
471
+ }
472
+ // Find the end of the superscript content
473
+ // For typical sub/sup patterns, look for single numbers, letters, or simple expressions
474
+ const endMatch = afterCaret.match(/^([0-9]+|[a-zA-Z]|[a-zA-Z0-9]{1,3})(?=\s|[^a-zA-Z0-9]|$)/);
475
+ if (endMatch) {
476
+ const contentLength = endMatch[1].length;
477
+ const insertPosition = lastCaretIndex + 1 + contentLength;
478
+ return text.substring(0, insertPosition) + '^' + text.substring(insertPosition);
479
+ }
480
+ else {
481
+ // Fallback: if no clear boundary, treat everything until space as content
482
+ const spaceMatch = afterCaret.match(/^([^\s]+)/);
483
+ if (spaceMatch) {
484
+ const contentLength = spaceMatch[1].length;
485
+ const insertPosition = lastCaretIndex + 1 + contentLength;
486
+ return text.substring(0, insertPosition) + '^' + text.substring(insertPosition);
487
+ }
488
+ }
489
+ }
490
+ }
491
+ return text;
492
+ };
338
493
  // Counts single dollar signs that are not part of double dollar signs and not escaped
339
494
  const _countSingleDollarSigns = (text) => {
340
495
  return text.split('').reduce((acc, char, index) => {
@@ -435,6 +590,8 @@ export const parseIncompleteMarkdown = (text) => {
435
590
  result = handleIncompleteSingleUnderscoreItalic(result);
436
591
  result = handleIncompleteInlineCode(result);
437
592
  result = handleIncompleteStrikethrough(result);
593
+ result = handleIncompleteSub(result);
594
+ result = handleIncompleteSup(result);
438
595
  // Handle KaTeX formatting (only block math with $$)
439
596
  result = handleIncompleteBlockKatex(result);
440
597
  // Note: We don't handle inline KaTeX with single $ as they're likely currency symbols
@@ -0,0 +1,8 @@
1
+ type ClickOutsideHandlerOptions = {
2
+ isActive: boolean;
3
+ callback: () => void;
4
+ };
5
+ export declare const useClickOutside: (props: ClickOutsideHandlerOptions) => {
6
+ readonly attachment: ((node: HTMLElement) => () => void) | null;
7
+ };
8
+ export {};
@@ -0,0 +1,42 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ import { on } from 'svelte/events';
3
+ export const useClickOutside = (props) => {
4
+ const refs = new SvelteSet();
5
+ let listener = null;
6
+ const onClickOutside = (e) => {
7
+ let inRef = false;
8
+ if (props.isActive) {
9
+ const path = e.composedPath();
10
+ path.forEach((node) => {
11
+ refs.forEach((ref) => {
12
+ if (ref instanceof Node && node instanceof Node && ref?.isSameNode(node)) {
13
+ inRef = true;
14
+ }
15
+ });
16
+ });
17
+ if (!inRef) {
18
+ props.callback();
19
+ }
20
+ }
21
+ };
22
+ return {
23
+ get attachment() {
24
+ return props.isActive
25
+ ? (node) => {
26
+ refs.add(node);
27
+ if (!listener) {
28
+ listener = on(node.ownerDocument, 'pointerdown', onClickOutside);
29
+ }
30
+ return () => {
31
+ refs.delete(node);
32
+ if (listener && refs.size === 0) {
33
+ listener();
34
+ listener = null;
35
+ console.log('clean up', listener);
36
+ }
37
+ };
38
+ }
39
+ : null;
40
+ }
41
+ };
42
+ };
@@ -0,0 +1,5 @@
1
+ export declare const useKeyDown: (opts: {
2
+ isActive: boolean;
3
+ callback: () => void;
4
+ keys: string[];
5
+ }) => void;