svelte-streamdown 1.0.8 → 2.0.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 (83) hide show
  1. package/README.md +155 -140
  2. package/dist/Block.svelte +17 -41
  3. package/dist/Elements/Alert.svelte +1 -2
  4. package/dist/Elements/Code.svelte +3 -5
  5. package/dist/Elements/Code.svelte.d.ts +0 -2
  6. package/dist/Elements/Element.svelte +170 -42
  7. package/dist/Elements/FootnoteRef.svelte +126 -0
  8. package/dist/Elements/FootnoteRef.svelte.d.ts +7 -0
  9. package/dist/Elements/Image.svelte +4 -4
  10. package/dist/Elements/{A.svelte → Link.svelte} +4 -4
  11. package/dist/Elements/{A.svelte.d.ts → Link.svelte.d.ts} +3 -3
  12. package/dist/Elements/Math.svelte +13 -24
  13. package/dist/Elements/Math.svelte.d.ts +0 -2
  14. package/dist/Elements/Mermaid.svelte +105 -115
  15. package/dist/Elements/Mermaid.svelte.d.ts +0 -2
  16. package/dist/Elements/Table.svelte +1 -1
  17. package/dist/Elements/index.d.ts +5 -24
  18. package/dist/Elements/index.js +5 -24
  19. package/dist/Streamdown.d.ts +52 -10
  20. package/dist/Streamdown.js +19 -1
  21. package/dist/Streamdown.svelte +2 -34
  22. package/dist/Streamdown.svelte.d.ts +1 -14
  23. package/dist/marked/index.d.ts +6 -10
  24. package/dist/marked/index.js +29 -17
  25. package/dist/marked/marked-alert.d.ts +2 -2
  26. package/dist/marked/marked-alert.js +11 -24
  27. package/dist/marked/marked-footnotes.d.ts +1 -0
  28. package/dist/marked/marked-footnotes.js +27 -8
  29. package/dist/marked/marked-list.d.ts +36 -0
  30. package/dist/marked/marked-list.js +232 -0
  31. package/dist/marked/marked-math.js +1 -4
  32. package/dist/marked/marked-subsup.d.ts +28 -0
  33. package/dist/marked/marked-subsup.js +45 -0
  34. package/dist/marked/marked-table.d.ts +59 -0
  35. package/dist/marked/marked-table.js +495 -0
  36. package/dist/theme.d.ts +54 -30
  37. package/dist/theme.js +51 -35
  38. package/dist/utils/panzoom.svelte.d.ts +17 -18
  39. package/dist/utils/panzoom.svelte.js +0 -2
  40. package/dist/utils/parse-incomplete-markdown.js +157 -0
  41. package/dist/utils/useClickOutside.svelte.d.ts +8 -0
  42. package/dist/utils/useClickOutside.svelte.js +41 -0
  43. package/dist/utils/useKeyDown.svelte.d.ts +5 -0
  44. package/dist/utils/useKeyDown.svelte.js +23 -0
  45. package/package.json +2 -1
  46. package/dist/Elements/Blockquote.svelte +0 -28
  47. package/dist/Elements/Blockquote.svelte.d.ts +0 -9
  48. package/dist/Elements/Del.svelte +0 -28
  49. package/dist/Elements/Del.svelte.d.ts +0 -9
  50. package/dist/Elements/Em.svelte +0 -28
  51. package/dist/Elements/Em.svelte.d.ts +0 -9
  52. package/dist/Elements/Heading.svelte +0 -25
  53. package/dist/Elements/Heading.svelte.d.ts +0 -9
  54. package/dist/Elements/Hr.svelte +0 -26
  55. package/dist/Elements/Hr.svelte.d.ts +0 -9
  56. package/dist/Elements/InlineCode.svelte +0 -28
  57. package/dist/Elements/InlineCode.svelte.d.ts +0 -9
  58. package/dist/Elements/Li.svelte +0 -28
  59. package/dist/Elements/Li.svelte.d.ts +0 -9
  60. package/dist/Elements/Ol.svelte +0 -30
  61. package/dist/Elements/Ol.svelte.d.ts +0 -9
  62. package/dist/Elements/P.svelte +0 -28
  63. package/dist/Elements/P.svelte.d.ts +0 -9
  64. package/dist/Elements/Strong.svelte +0 -28
  65. package/dist/Elements/Strong.svelte.d.ts +0 -9
  66. package/dist/Elements/Sub.svelte +0 -27
  67. package/dist/Elements/Sub.svelte.d.ts +0 -8
  68. package/dist/Elements/Sup.svelte +0 -28
  69. package/dist/Elements/Sup.svelte.d.ts +0 -8
  70. package/dist/Elements/Tbody.svelte +0 -24
  71. package/dist/Elements/Tbody.svelte.d.ts +0 -7
  72. package/dist/Elements/Td.svelte +0 -28
  73. package/dist/Elements/Td.svelte.d.ts +0 -9
  74. package/dist/Elements/Th.svelte +0 -28
  75. package/dist/Elements/Th.svelte.d.ts +0 -9
  76. package/dist/Elements/Thead.svelte +0 -27
  77. package/dist/Elements/Thead.svelte.d.ts +0 -9
  78. package/dist/Elements/Tr.svelte +0 -27
  79. package/dist/Elements/Tr.svelte.d.ts +0 -9
  80. package/dist/Elements/Ul.svelte +0 -28
  81. package/dist/Elements/Ul.svelte.d.ts +0 -9
  82. /package/dist/{hightlighter.svelte.d.ts → utils/hightlighter.svelte.d.ts} +0 -0
  83. /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
  };
@@ -78,15 +76,18 @@ export declare const theme: {
78
76
  tbody: {
79
77
  base: string;
80
78
  };
81
- tr: {
79
+ tfoot: {
82
80
  base: string;
83
81
  };
84
- th: {
82
+ tr: {
85
83
  base: string;
86
84
  };
87
85
  td: {
88
86
  base: string;
89
87
  };
88
+ th: {
89
+ base: string;
90
+ };
90
91
  sup: {
91
92
  base: string;
92
93
  };
@@ -107,9 +108,10 @@ export declare const theme: {
107
108
  buttons: string;
108
109
  };
109
110
  math: {
110
- base: string;
111
+ block: string;
112
+ inline: string;
111
113
  };
112
- inlineMath: {
114
+ br: {
113
115
  base: string;
114
116
  };
115
117
  em: {
@@ -118,9 +120,15 @@ export declare const theme: {
118
120
  del: {
119
121
  base: string;
120
122
  };
123
+ footnoteRef: {
124
+ base: string;
125
+ };
126
+ footnotePopover: {
127
+ base: string;
128
+ };
121
129
  };
122
130
  export declare const shadcnTheme: {
123
- a: {
131
+ link: {
124
132
  base: string;
125
133
  blocked: string;
126
134
  };
@@ -153,6 +161,7 @@ export declare const shadcnTheme: {
153
161
  };
154
162
  li: {
155
163
  base: string;
164
+ checkbox: string;
156
165
  };
157
166
  code: {
158
167
  base: string;
@@ -163,17 +172,14 @@ export declare const shadcnTheme: {
163
172
  skeleton: string;
164
173
  pre: string;
165
174
  };
166
- inlineCode: {
175
+ codespan: {
167
176
  base: string;
168
177
  };
169
- img: {
178
+ image: {
170
179
  base: string;
171
180
  image: string;
172
181
  downloadButton: string;
173
182
  };
174
- pre: {
175
- base: string;
176
- };
177
183
  blockquote: {
178
184
  base: string;
179
185
  };
@@ -197,15 +203,18 @@ export declare const shadcnTheme: {
197
203
  tbody: {
198
204
  base: string;
199
205
  };
200
- tr: {
206
+ tfoot: {
201
207
  base: string;
202
208
  };
203
- th: {
209
+ tr: {
204
210
  base: string;
205
211
  };
206
212
  td: {
207
213
  base: string;
208
214
  };
215
+ th: {
216
+ base: string;
217
+ };
209
218
  sup: {
210
219
  base: string;
211
220
  };
@@ -226,9 +235,10 @@ export declare const shadcnTheme: {
226
235
  buttons: string;
227
236
  };
228
237
  math: {
229
- base: string;
238
+ block: string;
239
+ inline: string;
230
240
  };
231
- inlineMath: {
241
+ br: {
232
242
  base: string;
233
243
  };
234
244
  em: {
@@ -237,10 +247,16 @@ export declare const shadcnTheme: {
237
247
  del: {
238
248
  base: string;
239
249
  };
250
+ footnoteRef: {
251
+ base: string;
252
+ };
253
+ footnotePopover: {
254
+ base: string;
255
+ };
240
256
  };
241
257
  export type Theme = typeof theme;
242
258
  export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tailwind" | "shadcn") => {
243
- a: {
259
+ link: {
244
260
  base: string;
245
261
  blocked: string;
246
262
  };
@@ -273,6 +289,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
273
289
  };
274
290
  li: {
275
291
  base: string;
292
+ checkbox: string;
276
293
  };
277
294
  code: {
278
295
  base: string;
@@ -283,17 +300,14 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
283
300
  skeleton: string;
284
301
  pre: string;
285
302
  };
286
- inlineCode: {
303
+ codespan: {
287
304
  base: string;
288
305
  };
289
- img: {
306
+ image: {
290
307
  base: string;
291
308
  image: string;
292
309
  downloadButton: string;
293
310
  };
294
- pre: {
295
- base: string;
296
- };
297
311
  blockquote: {
298
312
  base: string;
299
313
  };
@@ -317,15 +331,18 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
317
331
  tbody: {
318
332
  base: string;
319
333
  };
320
- tr: {
334
+ tfoot: {
321
335
  base: string;
322
336
  };
323
- th: {
337
+ tr: {
324
338
  base: string;
325
339
  };
326
340
  td: {
327
341
  base: string;
328
342
  };
343
+ th: {
344
+ base: string;
345
+ };
329
346
  sup: {
330
347
  base: string;
331
348
  };
@@ -346,9 +363,10 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
346
363
  buttons: string;
347
364
  };
348
365
  math: {
349
- base: string;
366
+ block: string;
367
+ inline: string;
350
368
  };
351
- inlineMath: {
369
+ br: {
352
370
  base: string;
353
371
  };
354
372
  em: {
@@ -357,4 +375,10 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tai
357
375
  del: {
358
376
  base: string;
359
377
  };
378
+ footnoteRef: {
379
+ base: string;
380
+ };
381
+ footnotePopover: {
382
+ base: string;
383
+ };
360
384
  };
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
  },
@@ -74,20 +72,23 @@ export const theme = {
74
72
  table: 'w-full border-collapse min-w-full'
75
73
  },
76
74
  thead: {
77
- base: 'bg-gray-100/80'
75
+ base: 'bg-gray-200/80'
78
76
  },
79
77
  tbody: {
80
- base: 'divide-gray-200 bg-gray-100/40 divide-y'
78
+ base: ''
79
+ },
80
+ tfoot: {
81
+ base: 'bg-gray-100/50 border-t border-gray-300'
81
82
  },
82
83
  tr: {
83
84
  base: 'border-gray-200 border-b hover:bg-gray-100/50 transition-colors'
84
85
  },
85
- th: {
86
- base: 'px-4 py-3 text-left text-sm font-semibold whitespace-nowrap min-w-[200px]'
87
- },
88
86
  td: {
89
87
  base: 'px-4 py-3 text-sm min-w-[200px] max-w-[400px] break-words'
90
88
  },
89
+ th: {
90
+ base: 'px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
91
+ },
91
92
  sup: {
92
93
  base: 'text-sm'
93
94
  },
@@ -108,9 +109,10 @@ export const theme = {
108
109
  buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
109
110
  },
110
111
  math: {
111
- base: ''
112
+ block: '',
113
+ inline: ''
112
114
  },
113
- inlineMath: {
115
+ br: {
114
116
  base: ''
115
117
  },
116
118
  em: {
@@ -118,10 +120,16 @@ export const theme = {
118
120
  },
119
121
  del: {
120
122
  base: 'line-through'
123
+ },
124
+ footnoteRef: {
125
+ base: 'text-gray-600 px-1 py-0.5 rounded-md bg-gray-100/80'
126
+ },
127
+ footnotePopover: {
128
+ base: 'fixed z-50 max-h-[30vh] max-w-3xl overflow-y-auto rounded-lg bg-background p-4 shadow'
121
129
  }
122
130
  };
123
131
  export const shadcnTheme = {
124
- a: {
132
+ link: {
125
133
  base: 'text-primary font-medium underline hover:text-primary/80',
126
134
  blocked: 'text-muted-foreground'
127
135
  },
@@ -147,13 +155,14 @@ export const shadcnTheme = {
147
155
  base: 'text-foreground'
148
156
  },
149
157
  ul: {
150
- base: 'ml-4 list-outside list-disc whitespace-normal text-foreground'
158
+ base: 'ml-4 list-inside list-disc whitespace-normal text-foreground'
151
159
  },
152
160
  ol: {
153
- base: 'ml-4 list-outside list-decimal whitespace-normal text-foreground'
161
+ base: 'ml-4 list-inside whitespace-normal text-foreground'
154
162
  },
155
163
  li: {
156
- base: 'py-1'
164
+ base: 'py-1',
165
+ checkbox: '-ml-2'
157
166
  },
158
167
  code: {
159
168
  base: 'my-4 w-full overflow-hidden rounded-lg border border-border flex flex-col',
@@ -164,17 +173,14 @@ export const shadcnTheme = {
164
173
  skeleton: 'rounded-md font-mono text-transparent bg-muted whitespace-nowrap inline-block',
165
174
  pre: 'overflow-x-auto font-mono p-0 bg-muted/40'
166
175
  },
167
- inlineCode: {
168
- base: 'bg-muted rounded px-1.5 py-0.5 font-mono text-foreground'
176
+ codespan: {
177
+ base: 'bg-muted rounded px-1.5 py-0.5 font-mono text-foreground text-[0.9em]'
169
178
  },
170
- img: {
179
+ image: {
171
180
  base: 'group relative my-4 mx-auto w-fit block',
172
181
  image: 'max-w-full rounded-lg',
173
182
  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
183
  },
175
- pre: {
176
- base: 'overflow-x-auto font-mono text-xs p-4 bg-muted/40'
177
- },
178
184
  blockquote: {
179
185
  base: 'border-muted-foreground/30 text-muted-foreground my-4 border-l-4 pl-4 italic'
180
186
  },
@@ -193,20 +199,23 @@ export const shadcnTheme = {
193
199
  table: 'w-full border-collapse min-w-full'
194
200
  },
195
201
  thead: {
196
- base: 'bg-muted/50'
202
+ base: 'bg-muted/80'
197
203
  },
198
204
  tbody: {
199
- base: 'divide-border bg-card divide-y'
205
+ base: ''
206
+ },
207
+ tfoot: {
208
+ base: 'bg-muted/50 border-t border-border'
200
209
  },
201
210
  tr: {
202
211
  base: 'border-border border-b hover:bg-muted/50 transition-colors'
203
212
  },
204
- th: {
205
- base: 'px-4 py-3 text-left text-sm font-semibold whitespace-nowrap text-foreground min-w-[200px]'
206
- },
207
213
  td: {
208
214
  base: 'px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
209
215
  },
216
+ th: {
217
+ base: 'px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
218
+ },
210
219
  sup: {
211
220
  base: 'text-sm'
212
221
  },
@@ -227,16 +236,23 @@ export const shadcnTheme = {
227
236
  buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
228
237
  },
229
238
  math: {
230
- base: 'text-foreground'
239
+ block: 'text-foreground',
240
+ inline: 'text-foreground'
231
241
  },
232
- inlineMath: {
233
- base: 'text-foreground'
242
+ br: {
243
+ base: ''
234
244
  },
235
245
  em: {
236
246
  base: 'italic text-foreground'
237
247
  },
238
248
  del: {
239
249
  base: 'line-through text-muted-foreground'
250
+ },
251
+ footnoteRef: {
252
+ base: 'text-muted-foreground px-1 text-sm inline-block rounded-full bg-muted/80 aspect-square border border-border'
253
+ },
254
+ footnotePopover: {
255
+ base: 'fixed z-50 max-h-[30vh] shadow max-w-3xl overflow-y-auto rounded-lg bg-background p-4'
240
256
  }
241
257
  };
242
258
  export const mergeTheme = (customTheme, baseTheme) => {
@@ -8,29 +8,28 @@ export interface PanzoomOptions {
8
8
  initialY?: number;
9
9
  activateMouseWheel?: boolean;
10
10
  }
11
- export interface PanzoomInstance {
12
- attach: (node: HTMLElement | SVGSVGElement) => () => void;
11
+ export interface ExpandOptions {
12
+ padding?: number;
13
+ duration?: number;
14
+ easing?: string;
15
+ zIndex?: number;
16
+ fitRatio?: number;
17
+ }
18
+ export declare const usePanzoom: (opts?: PanzoomOptions) => {
19
+ attach: (target: HTMLElement | SVGSVGElement) => () => void;
13
20
  zoomToFit: (padding?: number) => void;
14
21
  zoomBy: (factor: number) => void;
15
22
  zoomIn: (factor?: number) => void;
16
23
  zoomOut: (factor?: number) => void;
17
24
  moveBy: (dx: number, dy: number) => void;
18
- setTransform: (x: number, y: number, scale: number) => void;
25
+ setTransform: (nx: number, ny: number, ns: number) => void;
19
26
  expand: () => void;
20
27
  collapse: () => void;
21
- toggleExpand: (options?: ExpandOptions | number) => Promise<void>;
28
+ toggleExpand: () => Promise<void>;
29
+ readonly transform: {
30
+ readonly x: number;
31
+ readonly y: number;
32
+ readonly scale: number;
33
+ };
22
34
  readonly expanded: boolean;
23
- readonly transform: Readonly<{
24
- x: number;
25
- y: number;
26
- scale: number;
27
- }>;
28
- }
29
- export interface ExpandOptions {
30
- padding?: number;
31
- duration?: number;
32
- easing?: string;
33
- zIndex?: number;
34
- fitRatio?: number;
35
- }
36
- export declare const usePanzoom: (opts?: PanzoomOptions) => PanzoomInstance;
35
+ };
@@ -429,7 +429,6 @@ export const usePanzoom = (opts = {}) => {
429
429
  apply();
430
430
  }
431
431
  function zoomBy(factor) {
432
- console.log('zoomBy', factor);
433
432
  if (!node)
434
433
  return;
435
434
  // Use zoomAt with the center of the container for consistent behavior
@@ -448,7 +447,6 @@ export const usePanzoom = (opts = {}) => {
448
447
  zoomBy(factor);
449
448
  }
450
449
  function zoomOut(factor = 1.25) {
451
- console.log('zoomOut', factor);
452
450
  // zoom out by inverse multiplier
453
451
  if (factor <= 0)
454
452
  return;
@@ -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 {};