svelte-streamdown 2.4.3 → 2.5.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.
@@ -5,8 +5,11 @@ export class StreamdownContext {
5
5
  footnotes: new Map()
6
6
  };
7
7
  isMounted = false;
8
- animationTextStyle = $derived(this.animation.enabled
9
- ? `animation-name: sd-${this.animation.type};
8
+ get animationTextStyle() {
9
+ return getContext('POPOVER')
10
+ ? undefined
11
+ : this.animation.enabled
12
+ ? `animation-name: sd-${this.animation.type};
10
13
  animation-duration: ${this.animation.duration}ms;
11
14
  animation-timing-function: ${this.animation.timingFunction};
12
15
  animation-iteration-count: 1;
@@ -14,14 +17,19 @@ animation-fill-mode: forwards;
14
17
  white-space: pre-wrap;
15
18
  display: inline-block;
16
19
  text-decoration: inherit;`
17
- : undefined);
18
- animationBlockStyle = $derived(this.animation.enabled
19
- ? `animation-name: sd-${this.animation.type};
20
+ : undefined;
21
+ }
22
+ get animationBlockStyle() {
23
+ return getContext('POPOVER')
24
+ ? undefined
25
+ : this.animation.enabled
26
+ ? `animation-name: sd-${this.animation.type};
20
27
  animation-duration: ${this.animation.duration}ms;
21
28
  animation-timing-function: ${this.animation.timingFunction};
22
29
  animation-iteration-count: 1;
23
30
  animation-fill-mode: forwards;`
24
- : undefined);
31
+ : undefined;
32
+ }
25
33
  constructor(props) {
26
34
  bind(this, props);
27
35
  setContext('streamdown', this);
@@ -8,6 +8,8 @@ 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
10
  import { type DescriptionDetailToken, type DescriptionListToken, type DescriptionTermToken, type DescriptionToken } from './marked-dl.js';
11
+ import { type AlignToken } from './marked-align.js';
12
+ import { type CitationToken } from './marked-citations.js';
11
13
  export type GenericToken = {
12
14
  type: string;
13
15
  raw: string;
@@ -20,8 +22,8 @@ export type Extension = {
20
22
  start?: TokenizerStartFunction;
21
23
  applyInBlockParsing?: boolean;
22
24
  };
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;
25
+ 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 | AlignToken | CitationToken;
24
26
  export type { TableToken, THead, TBody, TFoot, THeadRow, TRow, TH, TD } from './marked-table.js';
25
27
  export declare const lex: (markdown: string, extensions?: Extension[]) => StreamdownToken[];
26
28
  export declare const parseBlocks: (markdown: string, extensions?: Extension[]) => string[];
27
- export type { MathToken, AlertToken, FootnoteToken, SubSupToken, BrToken, HrToken };
29
+ export type { MathToken, AlertToken, FootnoteToken, SubSupToken, BrToken, HrToken, AlignToken, CitationToken };
@@ -8,6 +8,8 @@ import { markedBr } from './marked-br.js';
8
8
  import { markedHr } from './marked-hr.js';
9
9
  import { markedTable } from './marked-table.js';
10
10
  import { markedDl } from './marked-dl.js';
11
+ import { markedAlign } from './marked-align.js';
12
+ import { markedCitations } from './marked-citations.js';
11
13
  const parseExtensions = (...extensions) => {
12
14
  const options = {
13
15
  gfm: true,
@@ -41,12 +43,12 @@ const parseExtensions = (...extensions) => {
41
43
  return options;
42
44
  };
43
45
  export const lex = (markdown, extensions = []) => {
44
- return new Lexer(parseExtensions(markedHr, markedTable, ...markedFootnote(), markedAlert, ...markedMath, markedSub, markedSup, markedList, markedBr, markedDl, ...extensions))
46
+ return new Lexer(parseExtensions(markedHr, markedTable, ...markedFootnote(), markedAlert, ...markedMath, markedSub, markedSup, markedList, markedBr, markedDl, markedAlign, markedCitations, ...extensions))
45
47
  .lex(markdown)
46
48
  .filter((token) => token.type !== 'space' && token.type !== 'footnote');
47
49
  };
48
50
  export const parseBlocks = (markdown, extensions = []) => {
49
- const blockLexer = new Lexer(parseExtensions(markedHr, ...markedFootnote(), markedDl, markedTable, ...extensions.filter(({ level, applyInBlockParsing }) => level === 'block' && applyInBlockParsing)));
51
+ const blockLexer = new Lexer(parseExtensions(markedHr, ...markedFootnote(), markedDl, markedTable, markedAlign, ...extensions.filter(({ level, applyInBlockParsing }) => level === 'block' && applyInBlockParsing)));
50
52
  return blockLexer.blockTokens(markdown, []).reduce((acc, block) => {
51
53
  if (block.type === 'space' || block.type === 'footnote') {
52
54
  return acc;
@@ -0,0 +1,10 @@
1
+ import type { Extension } from './index.js';
2
+ import type { Token } from 'marked';
3
+ export declare const markedAlign: Extension;
4
+ export type AlignToken = {
5
+ type: 'align';
6
+ align: 'center' | 'right';
7
+ raw: string;
8
+ text: string;
9
+ tokens: Token[];
10
+ };
@@ -0,0 +1,36 @@
1
+ export const markedAlign = {
2
+ name: 'align',
3
+ level: 'block',
4
+ tokenizer(src) {
5
+ // Check if the source starts with [center] or [right] blocks
6
+ const centerMatch = src.match(/^\[center\]\n([\s\S]*?)\n\[\/center\]/);
7
+ const rightMatch = src.match(/^\[right\]\n([\s\S]*?)\n\[\/right\]/);
8
+ if (centerMatch) {
9
+ const text = centerMatch[1];
10
+ const raw = centerMatch[0];
11
+ // Tokenize the content inside the alignment block
12
+ const tokens = this.lexer.blockTokens(text, []);
13
+ return {
14
+ type: 'align',
15
+ align: 'center',
16
+ raw,
17
+ text,
18
+ tokens
19
+ };
20
+ }
21
+ if (rightMatch) {
22
+ const text = rightMatch[1];
23
+ const raw = rightMatch[0];
24
+ // Tokenize the content inside the alignment block
25
+ const tokens = this.lexer.blockTokens(text, []);
26
+ return {
27
+ type: 'align',
28
+ align: 'right',
29
+ raw,
30
+ text,
31
+ tokens
32
+ };
33
+ }
34
+ return undefined;
35
+ }
36
+ };
@@ -0,0 +1,8 @@
1
+ import type { Extension } from './index.js';
2
+ export declare const markedCitations: Extension;
3
+ export type CitationToken = {
4
+ type: 'inline-citations';
5
+ keys: string[];
6
+ text: string;
7
+ raw: string;
8
+ };
@@ -0,0 +1,49 @@
1
+ export const markedCitations = {
2
+ name: 'citations',
3
+ level: 'inline',
4
+ start(src) {
5
+ return src.indexOf('[') === -1 ? -1 : 0;
6
+ },
7
+ tokenizer(src) {
8
+ // Match inline citations like [1], [ref], [1] [2], [ref] [ref2], etc.
9
+ // Requires non-empty bracket contents and spaces between adjacent citation brackets
10
+ const match = src.match(/^\[[^\]]+\](?:\s+\[[^\]]+\])*/);
11
+ if (match) {
12
+ // Early exit: if first closing bracket is immediately followed by '[', it's likely link-style syntax
13
+ const firstClosingBracketIndex = src.indexOf(']');
14
+ if (firstClosingBracketIndex !== -1 && src[firstClosingBracketIndex + 1] === '[') {
15
+ return undefined;
16
+ }
17
+ // If followed by parentheses, it's likely a markdown link or image, so don't treat as citation
18
+ const remainingSrc = src.slice(match[0].length);
19
+ if (remainingSrc.match(/^\s*\(/)) {
20
+ return undefined;
21
+ }
22
+ // Extract all citation keys (anything inside brackets)
23
+ const citations = match[0].match(/\[([^\]]+)\]/g);
24
+ if (citations) {
25
+ // Filter out task list syntax ([ ], [x], [X]) after trimming
26
+ const validCitations = citations.filter((citation) => {
27
+ const content = citation.slice(1, -1).trim();
28
+ return content !== '' && content !== 'x' && content !== 'X';
29
+ });
30
+ if (validCitations.length > 0) {
31
+ // Process each bracket content and split on spaces, commas, or semicolons
32
+ const keys = validCitations.flatMap((citation) => {
33
+ const content = citation.slice(1, -1).trim(); // Remove brackets and trim
34
+ // Split on spaces, commas, or semicolons and filter out empty strings
35
+ return content.split(/[\s,;]+/).filter((key) => key.length > 0);
36
+ });
37
+ // Deduplicate keys after trimming
38
+ const uniqueKeys = Array.from(new Set(keys.map((key) => key.trim()).filter((key) => key.length > 0)));
39
+ return {
40
+ type: 'inline-citations',
41
+ keys: uniqueKeys,
42
+ text: match[0],
43
+ raw: match[0]
44
+ };
45
+ }
46
+ }
47
+ }
48
+ }
49
+ };
@@ -35,7 +35,6 @@ function splitRow(src) {
35
35
  continue;
36
36
  }
37
37
  if (ch === '`') {
38
- // count backticks
39
38
  let run = 1;
40
39
  while (i + run < src.length && src[i + run] === '`')
41
40
  run++;
@@ -52,7 +51,19 @@ function splitRow(src) {
52
51
  continue;
53
52
  }
54
53
  if (ch === '|' && !inCode) {
55
- out.push(buf.trim());
54
+ // Count consecutive pipes for colspan
55
+ let consecutivePipes = 1;
56
+ while (i + consecutivePipes < src.length && src[i + consecutivePipes] === '|') {
57
+ consecutivePipes++;
58
+ }
59
+ if (consecutivePipes > 1) {
60
+ // Multiple pipes = colspan marker
61
+ out.push(buf.trim() + '\x00COLSPAN:' + consecutivePipes);
62
+ i += consecutivePipes - 1;
63
+ }
64
+ else {
65
+ out.push(buf.trim());
66
+ }
56
67
  buf = '';
57
68
  continue;
58
69
  }
@@ -83,29 +94,41 @@ const processSpans = (cells, count, prevRow = [], maxColspan = null) => {
83
94
  let cellIndex = 0;
84
95
  const mergedIndices = new Set();
85
96
  for (i = 0; i < cells.length; i++) {
86
- // Skip cells that were merged into previous colspans
87
97
  if (mergedIndices.has(i))
88
98
  continue;
89
99
  trimmedCell = cells[i];
90
- // Count consecutive empty cells for colspan
91
100
  let colspan = 1;
92
- if (!trimmedCell.trim()) {
93
- // Count how many consecutive empty cells we have
94
- let j = i + 1;
95
- while (j < cells.length && !cells[j].trim()) {
96
- colspan++;
97
- mergedIndices.add(j); // Mark as merged
98
- j++;
101
+ // Check for colspan marker from consecutive pipes
102
+ if (trimmedCell.includes('\x00COLSPAN:')) {
103
+ const parts = trimmedCell.split('\x00COLSPAN:');
104
+ trimmedCell = parts[0];
105
+ colspan = parseInt(parts[1], 10);
106
+ }
107
+ else if (!trimmedCell.trim()) {
108
+ // Fallback: merge empty run into previous cell (backward compatibility)
109
+ let run = 1, k = i + 1;
110
+ while (k < cells.length && !cells[k].trim()) {
111
+ run++;
112
+ mergedIndices.add(k++);
113
+ }
114
+ if (processedCells.length) {
115
+ const target = processedCells[processedCells.length - 1];
116
+ const allowed = maxColspan != null ? Math.min(run, Math.max(0, maxColspan - target.colspan)) : run;
117
+ target.colspan += allowed;
118
+ numCols += allowed;
119
+ continue;
120
+ }
121
+ else {
122
+ colspan = maxColspan != null ? Math.min(run, maxColspan) : run;
99
123
  }
100
124
  }
101
- // Apply maxColspan limit if specified
102
125
  if (maxColspan !== null && colspan > maxColspan)
103
126
  colspan = maxColspan;
104
127
  processedCells[cellIndex] = {
105
128
  rowspan: 1,
106
129
  colspan: colspan,
107
130
  text: trimmedCell.trim().replace(/\\\|/g, '|'),
108
- position: numCols // Store original column position for better tracking
131
+ position: numCols
109
132
  };
110
133
  numCols += processedCells[cellIndex].colspan;
111
134
  cellIndex++;
package/dist/theme.d.ts CHANGED
@@ -41,7 +41,6 @@ export declare const theme: {
41
41
  container: string;
42
42
  header: string;
43
43
  buttons: string;
44
- button: string;
45
44
  language: string;
46
45
  skeleton: string;
47
46
  pre: string;
@@ -53,7 +52,6 @@ export declare const theme: {
53
52
  image: {
54
53
  base: string;
55
54
  image: string;
56
- downloadButton: string;
57
55
  };
58
56
  blockquote: {
59
57
  base: string;
@@ -104,8 +102,6 @@ export declare const theme: {
104
102
  };
105
103
  mermaid: {
106
104
  base: string;
107
- downloadButton: string;
108
- button: string;
109
105
  icon: string;
110
106
  buttons: string;
111
107
  };
@@ -125,9 +121,6 @@ export declare const theme: {
125
121
  footnoteRef: {
126
122
  base: string;
127
123
  };
128
- footnotePopover: {
129
- base: string;
130
- };
131
124
  descriptionList: {
132
125
  base: string;
133
126
  };
@@ -137,6 +130,28 @@ export declare const theme: {
137
130
  descriptionDetail: {
138
131
  base: string;
139
132
  };
133
+ inlineCitation: {
134
+ preview: string;
135
+ carousel: {
136
+ header: string;
137
+ stepCounter: string;
138
+ buttons: string;
139
+ title: string;
140
+ url: string;
141
+ favicon: string;
142
+ };
143
+ list: {
144
+ base: string;
145
+ item: string;
146
+ title: string;
147
+ url: string;
148
+ favicon: string;
149
+ };
150
+ };
151
+ components: {
152
+ button: string;
153
+ popover: string;
154
+ };
140
155
  };
141
156
  export declare const shadcnTheme: {
142
157
  link: {
@@ -179,7 +194,6 @@ export declare const shadcnTheme: {
179
194
  container: string;
180
195
  header: string;
181
196
  buttons: string;
182
- button: string;
183
197
  language: string;
184
198
  skeleton: string;
185
199
  pre: string;
@@ -191,7 +205,6 @@ export declare const shadcnTheme: {
191
205
  image: {
192
206
  base: string;
193
207
  image: string;
194
- downloadButton: string;
195
208
  };
196
209
  blockquote: {
197
210
  base: string;
@@ -242,8 +255,6 @@ export declare const shadcnTheme: {
242
255
  };
243
256
  mermaid: {
244
257
  base: string;
245
- downloadButton: string;
246
- button: string;
247
258
  icon: string;
248
259
  buttons: string;
249
260
  };
@@ -263,9 +274,6 @@ export declare const shadcnTheme: {
263
274
  footnoteRef: {
264
275
  base: string;
265
276
  };
266
- footnotePopover: {
267
- base: string;
268
- };
269
277
  descriptionList: {
270
278
  base: string;
271
279
  };
@@ -275,6 +283,28 @@ export declare const shadcnTheme: {
275
283
  descriptionDetail: {
276
284
  base: string;
277
285
  };
286
+ inlineCitation: {
287
+ preview: string;
288
+ carousel: {
289
+ header: string;
290
+ stepCounter: string;
291
+ buttons: string;
292
+ title: string;
293
+ url: string;
294
+ favicon: string;
295
+ };
296
+ list: {
297
+ base: string;
298
+ item: string;
299
+ title: string;
300
+ url: string;
301
+ favicon: string;
302
+ };
303
+ };
304
+ components: {
305
+ button: string;
306
+ popover: string;
307
+ };
278
308
  };
279
309
  export type Theme = typeof theme;
280
310
  type DeepPartial<T> = {
@@ -322,7 +352,6 @@ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "t
322
352
  container: string;
323
353
  header: string;
324
354
  buttons: string;
325
- button: string;
326
355
  language: string;
327
356
  skeleton: string;
328
357
  pre: string;
@@ -334,7 +363,6 @@ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "t
334
363
  image: {
335
364
  base: string;
336
365
  image: string;
337
- downloadButton: string;
338
366
  };
339
367
  blockquote: {
340
368
  base: string;
@@ -385,8 +413,6 @@ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "t
385
413
  };
386
414
  mermaid: {
387
415
  base: string;
388
- downloadButton: string;
389
- button: string;
390
416
  icon: string;
391
417
  buttons: string;
392
418
  };
@@ -406,9 +432,6 @@ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "t
406
432
  footnoteRef: {
407
433
  base: string;
408
434
  };
409
- footnotePopover: {
410
- base: string;
411
- };
412
435
  descriptionList: {
413
436
  base: string;
414
437
  };
@@ -418,5 +441,27 @@ export declare const mergeTheme: (customTheme?: DeepPartialTheme, baseTheme?: "t
418
441
  descriptionDetail: {
419
442
  base: string;
420
443
  };
444
+ inlineCitation: {
445
+ preview: string;
446
+ carousel: {
447
+ header: string;
448
+ stepCounter: string;
449
+ buttons: string;
450
+ title: string;
451
+ url: string;
452
+ favicon: string;
453
+ };
454
+ list: {
455
+ base: string;
456
+ item: string;
457
+ title: string;
458
+ url: string;
459
+ favicon: string;
460
+ };
461
+ };
462
+ components: {
463
+ button: string;
464
+ popover: string;
465
+ };
421
466
  };
422
467
  export {};
package/dist/theme.js CHANGED
@@ -42,7 +42,6 @@ export const theme = {
42
42
  container: ' relative overflow-visible bg-gray-100 p-2 font-mono text-sm ',
43
43
  header: 'flex items-center justify-between bg-gray-100/80 p-2 text-gray-600 text-xs',
44
44
  buttons: 'flex items-center gap-2',
45
- button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
46
45
  language: 'ml-1 font-mono lowercase',
47
46
  skeleton: 'block rounded-md font-mono text-transparent bg-gray-200 scale-y-90 animate-pulse whitespace-nowrap',
48
47
  pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40',
@@ -53,8 +52,7 @@ export const theme = {
53
52
  },
54
53
  image: {
55
54
  base: 'group relative my-4 mx-auto w-fit block',
56
- image: 'max-w-full rounded-lg',
57
- 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
+ image: 'max-w-full rounded-lg'
58
56
  },
59
57
  blockquote: {
60
58
  base: 'border-gray-600/30 text-gray-600 my-4 border-l-4 pl-4 italic'
@@ -105,8 +103,6 @@ export const theme = {
105
103
  },
106
104
  mermaid: {
107
105
  base: 'group relative my-4 h-auto rounded-xl border border-gray-200 bg-white overflow-hidden items-center min-h-[500px]',
108
- downloadButton: 'cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900',
109
- button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
110
106
  icon: 'size-5',
111
107
  buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
112
108
  },
@@ -126,9 +122,6 @@ export const theme = {
126
122
  footnoteRef: {
127
123
  base: 'text-gray-600 px-1 py-0.5 rounded-md bg-gray-100/80'
128
124
  },
129
- footnotePopover: {
130
- base: 'fixed z-50 max-h-[30vh] max-w-3xl overflow-y-auto rounded-lg bg-background p-4 shadow'
131
- },
132
125
  descriptionList: {
133
126
  base: 'my-4 space-y-2'
134
127
  },
@@ -137,6 +130,28 @@ export const theme = {
137
130
  },
138
131
  descriptionDetail: {
139
132
  base: 'text-gray-700 ml-4 leading-relaxed'
133
+ },
134
+ inlineCitation: {
135
+ preview: 'text-sm text-muted-foreground bg-muted rounded-md px-2 py-0.5 cursor-pointer inline-flex border border-border hover:bg-muted/50 outline-none focus:ring-1 focus:ring-primary',
136
+ carousel: {
137
+ header: 'flex items-center justify-between',
138
+ stepCounter: 'h-fit text-xs font-semibold text-muted-foreground tabular-nums',
139
+ buttons: 'flex w-fit items-center justify-end gap-2',
140
+ title: 'mb-2 line-clamp-2 font-semibold',
141
+ url: 'flex items-center gap-2 text-sm text-muted-foreground',
142
+ favicon: 'h-4 w-4 rounded'
143
+ },
144
+ list: {
145
+ base: 'grid gap-2',
146
+ item: 'grid gap-1 hover:bg-muted rounded-md p-2',
147
+ title: 'line-clamp-1 font-semibold text-sm',
148
+ url: 'flex items-center gap-2 text-xs text-muted-foreground',
149
+ favicon: 'h-3 w-3 rounded'
150
+ }
151
+ },
152
+ components: {
153
+ button: 'disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100 w-6 h-6',
154
+ popover: 'fixed z-[1000] max-h-[30vh] max-w-3xl overflow-y-auto rounded-lg bg-white p-4 shadow'
140
155
  }
141
156
  };
142
157
  export const shadcnTheme = {
@@ -180,7 +195,6 @@ export const shadcnTheme = {
180
195
  container: 'relative overflow-visible bg-muted p-2 font-mono text-sm',
181
196
  header: 'flex items-center justify-between bg-muted/80 px-2 py-1 text-muted-foreground text-xs',
182
197
  buttons: 'flex items-center gap-2',
183
- button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
184
198
  language: 'ml-1 font-mono lowercase',
185
199
  skeleton: 'block rounded-md font-mono text-transparent bg-border/80 scale-y-90 w-fit animate-pulse whitespace-nowrap',
186
200
  pre: 'overflow-x-auto font-mono p-0 bg-muted/40',
@@ -191,8 +205,7 @@ export const shadcnTheme = {
191
205
  },
192
206
  image: {
193
207
  base: 'group relative my-4 mx-auto w-fit block',
194
- image: 'max-w-full rounded-lg',
195
- 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'
208
+ image: 'max-w-full rounded-lg'
196
209
  },
197
210
  blockquote: {
198
211
  base: 'border-muted-foreground/30 text-muted-foreground my-4 border-l-4 pl-4 italic'
@@ -243,8 +256,6 @@ export const shadcnTheme = {
243
256
  },
244
257
  mermaid: {
245
258
  base: 'group relative my-4 h-auto rounded-lg border border-border bg-card overflow-hidden items-center min-h-[500px]',
246
- downloadButton: 'cursor-pointer p-1 text-muted-foreground transition-all hover:text-foreground',
247
- button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
248
259
  icon: 'size-5',
249
260
  buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
250
261
  },
@@ -262,10 +273,7 @@ export const shadcnTheme = {
262
273
  base: 'text-muted-foreground'
263
274
  },
264
275
  footnoteRef: {
265
- base: 'text-muted-foreground px-1 text-sm inline-block rounded-full bg-muted/80 aspect-square border border-border'
266
- },
267
- footnotePopover: {
268
- base: 'fixed z-50 max-h-[30vh] shadow max-w-3xl overflow-y-auto rounded-lg bg-background p-4'
276
+ base: 'text-muted-foreground text-sm rounded-full bg-muted cursor-pointer border border-border hover:bg-muted/50 tabular-nums min-w-5 min-h-5 outline-none focus:ring-1 focus:ring-primary'
269
277
  },
270
278
  descriptionList: {
271
279
  base: 'my-4 space-y-2'
@@ -275,6 +283,28 @@ export const shadcnTheme = {
275
283
  },
276
284
  descriptionDetail: {
277
285
  base: 'text-muted-foreground ml-4 leading-relaxed'
286
+ },
287
+ inlineCitation: {
288
+ preview: 'text-sm text-muted-foreground bg-muted rounded-md px-2 py-0.5 cursor-pointer inline-flex border border-border hover:bg-muted/50 outline-none focus:ring-1 focus:ring-primary',
289
+ carousel: {
290
+ header: 'flex items-center justify-between',
291
+ stepCounter: 'h-fit text-xs font-semibold text-muted-foreground tabular-nums',
292
+ buttons: 'flex w-fit items-center justify-end gap-2',
293
+ title: 'mb-2 line-clamp-2 font-semibold',
294
+ url: 'flex items-center gap-2 text-sm text-muted-foreground',
295
+ favicon: 'h-4 w-4 rounded'
296
+ },
297
+ list: {
298
+ base: 'grid gap-2',
299
+ item: 'grid gap-1 hover:bg-muted rounded-md p-2',
300
+ title: 'line-clamp-1 font-semibold text-sm',
301
+ url: 'flex items-center gap-2 text-xs text-muted-foreground',
302
+ favicon: 'h-3 w-3 rounded'
303
+ }
304
+ },
305
+ components: {
306
+ button: 'disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-border flex items-center justify-center w-6 h-6',
307
+ popover: 'fixed z-[1000] max-h-[30vh] max-w-3xl overflow-y-auto rounded-lg bg-popover border border-border p-2 shadow'
278
308
  }
279
309
  };
280
310
  export const mergeTheme = (customTheme, baseTheme) => {
@@ -0,0 +1 @@
1
+ export declare const get: <T>(obj: Record<string, any> | undefined, path: string) => T | null;
@@ -0,0 +1,25 @@
1
+ // A method that gets a value from an object using a path
2
+ // It should work with nested objects and arrays
3
+ export const get = (obj, path) => {
4
+ if (!obj)
5
+ return null;
6
+ const keys = path.split('.');
7
+ if (keys.length === 1) {
8
+ return obj[path];
9
+ }
10
+ let value = obj;
11
+ for (const key of keys) {
12
+ if (value == null)
13
+ return null;
14
+ if (Array.isArray(value)) {
15
+ const index = Number(key);
16
+ if (isNaN(index) || index < 0 || index >= value.length)
17
+ return null;
18
+ value = value[index];
19
+ }
20
+ else {
21
+ value = value[key];
22
+ }
23
+ }
24
+ return value;
25
+ };
@@ -23,8 +23,7 @@ export declare const usePanzoom: (opts?: PanzoomOptions) => {
23
23
  zoomOut: (factor?: number) => void;
24
24
  moveBy: (dx: number, dy: number) => void;
25
25
  setTransform: (nx: number, ny: number, ns: number) => void;
26
- expand: () => void;
27
- collapse: () => void;
26
+ expand: (expand: boolean) => void;
28
27
  toggleExpand: () => Promise<void>;
29
28
  readonly transform: {
30
29
  readonly x: number;