svelte-streamdown 0.0.1 → 1.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.
@@ -1,102 +1,128 @@
1
1
  import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
2
2
  import { createHighlighter } from 'shiki';
3
+ import { SvelteMap } from 'svelte/reactivity';
3
4
  // Remove background styles from <pre> tags (inline style)
4
5
  const removePreBackground = (html) => {
5
6
  return html.replace(/<pre[^>]*style="[^"]*background[^";]*;?[^"]*"[^>]*>/g, (match) => match.replace(/style="[^"]*background[^";]*;?[^"]*"/, ''));
6
7
  };
7
8
  class HighlighterManager {
8
9
  initialized = $state(false);
9
- initializedPromise = $state(null);
10
- highlighter = null;
11
- theme = null;
12
- loadedLanguages = new Set();
13
- initializationPromise = null;
14
- readyStates = new Map();
15
- getReadyKey(theme, language) {
16
- return `${theme}-${language}`;
10
+ state = null;
11
+ preloadedThemes = new Set();
12
+ readyCache = new SvelteMap();
13
+ initPromise = $state(null);
14
+ getCacheKey(theme, language) {
15
+ return `${theme}:${language}`;
16
+ }
17
+ isLoaded(theme, language) {
18
+ return this.readyCache.get(this.getCacheKey(theme, language)) ?? false;
19
+ }
20
+ /**
21
+ * Preloads themes by creating minimal highlighter instances.
22
+ * This reduces flickering when switching themes.
23
+ */
24
+ async preloadThemes(themes) {
25
+ const promises = themes
26
+ .filter((theme) => !this.preloadedThemes.has(theme))
27
+ .map(async (theme) => {
28
+ try {
29
+ // Create minimal highlighter just to load theme assets
30
+ const preloader = await createHighlighter({
31
+ themes: [theme],
32
+ langs: ['javascript'], // Minimal language for preloading
33
+ engine: createJavaScriptRegexEngine({ forgiving: true })
34
+ });
35
+ preloader.dispose();
36
+ this.preloadedThemes.add(theme);
37
+ }
38
+ catch (error) {
39
+ console.warn(`Failed to preload theme ${theme}:`, error);
40
+ }
41
+ });
42
+ await Promise.all(promises);
17
43
  }
18
44
  /**
19
- * Prepares the highlighter for the given themes and language.
20
- * This method must be called and awaited before using highlightCode.
45
+ * Ensures the highlighter is ready for the given theme and language.
21
46
  */
22
47
  async isReady(theme, language) {
23
- const readyKey = this.getReadyKey(theme, language);
24
- // If already ready for this combination, return immediately
25
- if (this.readyStates.get(readyKey)) {
48
+ const cacheKey = this.getCacheKey(theme, language);
49
+ // Return immediately if already ready
50
+ if (this.readyCache.get(cacheKey)) {
26
51
  return;
27
52
  }
28
- // Ensure only one initialization happens at a time
29
- if (this.initializationPromise) {
30
- await this.initializationPromise;
53
+ // Wait for any ongoing initialization
54
+ if (this.initPromise) {
55
+ await this.initPromise;
31
56
  }
32
- // Check again after waiting, in case another call initialized it
33
- if (this.readyStates.get(readyKey)) {
57
+ // Check again after waiting
58
+ if (this.readyCache.get(cacheKey)) {
34
59
  return;
35
60
  }
36
- // Initialize for this combination
37
- this.initializationPromise = this.ensureHighlightersInitialized(theme, language);
38
- await this.initializationPromise;
39
- this.initializationPromise = null;
40
- // Mark as ready
41
- this.readyStates.set(readyKey, true);
61
+ // Initialize the highlighter
62
+ this.initPromise = this.initialize(theme, language);
63
+ await this.initPromise;
64
+ this.readyCache.set(cacheKey, true);
42
65
  this.initialized = true;
66
+ this.initPromise = null;
43
67
  }
44
- async ensureHighlightersInitialized(theme, language) {
45
- const jsEngine = createJavaScriptRegexEngine({ forgiving: true });
46
- // Check if we need to recreate highlighters due to theme change
47
- const needsLightRecreation = !this.highlighter || this.theme !== theme;
48
- if (needsLightRecreation) {
49
- // If themes changed, reset loaded languages and ready states
50
- this.loadedLanguages.clear();
51
- this.readyStates.clear();
52
- }
53
- // Check if we need to load the language
54
- const needsLanguageLoad = !this.loadedLanguages.has(language);
55
- // Create or recreate light highlighter if needed
56
- if (needsLightRecreation) {
57
- this.highlighter = await createHighlighter({
68
+ async initialize(theme, language) {
69
+ const needsNewHighlighter = !this.state || this.state.theme !== theme;
70
+ if (needsNewHighlighter) {
71
+ // Dispose old highlighter if it exists
72
+ this.state?.highlighter.dispose();
73
+ // Clear cache when theme changes
74
+ this.readyCache.clear();
75
+ // Create new highlighter with the theme
76
+ const highlighter = await createHighlighter({
58
77
  themes: [theme],
59
78
  langs: [language],
60
- engine: jsEngine
79
+ engine: createJavaScriptRegexEngine({ forgiving: true })
61
80
  });
62
- this.theme = theme;
63
- this.loadedLanguages.add(language);
64
- }
65
- else if (needsLanguageLoad) {
66
- // Load the language if not already loaded
67
- await this.highlighter?.loadLanguage(language);
81
+ this.state = {
82
+ highlighter,
83
+ theme,
84
+ languages: new Set([language])
85
+ };
86
+ // Mark theme as preloaded since we now have it
87
+ this.preloadedThemes.add(theme);
68
88
  }
69
- // Mark language as loaded after both highlighters have it
70
- if (needsLanguageLoad) {
71
- this.loadedLanguages.add(language);
89
+ else if (this.state && !this.state.languages.has(language)) {
90
+ // Add language to existing highlighter
91
+ await this.state.highlighter.loadLanguage(language);
92
+ this.state.languages.add(language);
72
93
  }
73
94
  }
74
95
  /**
75
- * Synchronously highlights code. Must call isReady() first for the same themes and language.
76
- * @throws {Error} If isReady() hasn't been called for this combination of themes and language
96
+ * Highlights code synchronously. Must call isReady() first.
77
97
  */
78
98
  highlightCode(code, language, theme, preClassName) {
79
- const readyKey = this.getReadyKey(theme, language);
80
- // Ensure isReady was called first
81
- if (!this.readyStates.get(readyKey)) {
82
- throw new Error(`Highlighter not ready for themes [${theme}] and language "${language}". ` +
99
+ if (!this.isLoaded(theme, language)) {
100
+ throw new Error(`Highlighter not ready for theme "${theme}" and language "${language}". ` +
83
101
  `Call await highlighter.isReady(theme, language) first.`);
84
102
  }
85
- // Ensure highlighters exist
86
- if (!this.highlighter) {
87
- throw new Error('Highlighters not initialized. This should not happen after calling isReady().');
103
+ if (!this.state) {
104
+ throw new Error('Highlighter not initialized. This should not happen after calling isReady().');
88
105
  }
89
- const addPreClass = (html) => {
90
- if (!preClassName) {
91
- return html;
92
- }
93
- return html.replace(/<pre(\s|>)/, `<pre class="${preClassName}"$1`);
94
- };
95
- const light = this.highlighter.codeToHtml(code, {
106
+ let html = this.state.highlighter.codeToHtml(code, {
96
107
  lang: language,
97
108
  theme: theme
98
109
  });
99
- return removePreBackground(addPreClass(light));
110
+ // Remove background and add custom class if needed
111
+ html = removePreBackground(html);
112
+ if (preClassName) {
113
+ html = html.replace(/<pre(\s|>)/, `<pre class="${preClassName}"$1`);
114
+ }
115
+ return html;
116
+ }
117
+ /**
118
+ * Clean up resources
119
+ */
120
+ dispose() {
121
+ this.state?.highlighter.dispose();
122
+ this.state = null;
123
+ this.readyCache.clear();
124
+ this.preloadedThemes.clear();
125
+ this.initialized = false;
100
126
  }
101
127
  }
102
128
  // Export the class for those who want to create their own instances
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as Streamdown, useStreamdown } from './Streamdown.svelte';
2
2
  export * from './Elements/index.js';
3
+ export { theme, shadcnTheme, mergeTheme, cn, type Theme } from './theme.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as Streamdown, useStreamdown } from './Streamdown.svelte';
2
2
  export * from './Elements/index.js';
3
+ export { theme, shadcnTheme, mergeTheme, cn } from './theme.js';
package/dist/theme.d.ts CHANGED
@@ -48,8 +48,8 @@ export declare const theme: {
48
48
  base: string;
49
49
  };
50
50
  img: {
51
- container: string;
52
51
  base: string;
52
+ image: string;
53
53
  downloadButton: string;
54
54
  };
55
55
  pre: {
@@ -70,7 +70,7 @@ export declare const theme: {
70
70
  };
71
71
  table: {
72
72
  base: string;
73
- container: string;
73
+ table: string;
74
74
  };
75
75
  thead: {
76
76
  base: string;
@@ -102,6 +102,9 @@ export declare const theme: {
102
102
  mermaid: {
103
103
  base: string;
104
104
  downloadButton: string;
105
+ button: string;
106
+ icon: string;
107
+ buttons: string;
105
108
  };
106
109
  math: {
107
110
  base: string;
@@ -112,18 +115,131 @@ export declare const theme: {
112
115
  em: {
113
116
  base: string;
114
117
  };
115
- ins: {
118
+ del: {
116
119
  base: string;
117
120
  };
118
- del: {
121
+ };
122
+ export declare const shadcnTheme: {
123
+ a: {
124
+ base: string;
125
+ blocked: string;
126
+ };
127
+ h1: {
128
+ base: string;
129
+ };
130
+ h2: {
131
+ base: string;
132
+ };
133
+ h3: {
134
+ base: string;
135
+ };
136
+ h4: {
137
+ base: string;
138
+ };
139
+ h5: {
119
140
  base: string;
120
141
  };
121
- small: {
142
+ h6: {
143
+ base: string;
144
+ };
145
+ p: {
146
+ base: string;
147
+ };
148
+ ul: {
149
+ base: string;
150
+ };
151
+ ol: {
152
+ base: string;
153
+ };
154
+ li: {
155
+ base: string;
156
+ };
157
+ code: {
158
+ base: string;
159
+ container: string;
160
+ header: string;
161
+ button: string;
162
+ language: string;
163
+ skeleton: string;
164
+ pre: string;
165
+ };
166
+ inlineCode: {
167
+ base: string;
168
+ };
169
+ img: {
170
+ base: string;
171
+ image: string;
172
+ downloadButton: string;
173
+ };
174
+ pre: {
175
+ base: string;
176
+ };
177
+ blockquote: {
178
+ base: string;
179
+ };
180
+ alert: {
181
+ base: string;
182
+ title: string;
183
+ icon: string;
184
+ note: string;
185
+ tip: string;
186
+ warning: string;
187
+ caution: string;
188
+ important: string;
189
+ };
190
+ table: {
191
+ base: string;
192
+ table: string;
193
+ };
194
+ thead: {
195
+ base: string;
196
+ };
197
+ tbody: {
198
+ base: string;
199
+ };
200
+ tr: {
201
+ base: string;
202
+ };
203
+ th: {
204
+ base: string;
205
+ };
206
+ td: {
207
+ base: string;
208
+ };
209
+ sup: {
210
+ base: string;
211
+ };
212
+ sub: {
213
+ base: string;
214
+ };
215
+ hr: {
216
+ base: string;
217
+ };
218
+ strong: {
219
+ base: string;
220
+ };
221
+ mermaid: {
222
+ base: string;
223
+ downloadButton: string;
224
+ button: string;
225
+ icon: string;
226
+ buttons: string;
227
+ };
228
+ math: {
229
+ base: string;
230
+ };
231
+ inlineMath: {
232
+ base: string;
233
+ };
234
+ em: {
235
+ base: string;
236
+ };
237
+ del: {
122
238
  base: string;
123
239
  };
124
240
  };
125
241
  export type Theme = typeof theme;
126
- export declare const mergeTheme: (customTheme?: Partial<Theme>) => {
242
+ export declare const mergeTheme: (customTheme?: Partial<Theme>, baseTheme?: "tailwind" | "shadcn") => {
127
243
  a: {
128
244
  base: string;
129
245
  blocked: string;
@@ -171,8 +287,8 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>) => {
171
287
  base: string;
172
288
  };
173
289
  img: {
174
- container: string;
175
290
  base: string;
291
+ image: string;
176
292
  downloadButton: string;
177
293
  };
178
294
  pre: {
@@ -193,7 +309,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>) => {
193
309
  };
194
310
  table: {
195
311
  base: string;
196
- container: string;
312
+ table: string;
197
313
  };
198
314
  thead: {
199
315
  base: string;
@@ -225,6 +341,9 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>) => {
225
341
  mermaid: {
226
342
  base: string;
227
343
  downloadButton: string;
344
+ button: string;
345
+ icon: string;
346
+ buttons: string;
228
347
  };
229
348
  math: {
230
349
  base: string;
@@ -235,13 +354,7 @@ export declare const mergeTheme: (customTheme?: Partial<Theme>) => {
235
354
  em: {
236
355
  base: string;
237
356
  };
238
- ins: {
239
- base: string;
240
- };
241
357
  del: {
242
358
  base: string;
243
359
  };
244
- small: {
245
- base: string;
246
- };
247
360
  };
package/dist/theme.js CHANGED
@@ -37,10 +37,10 @@ export const theme = {
37
37
  base: 'py-1'
38
38
  },
39
39
  code: {
40
- base: 'bg-gray-100 rounded p-2 font-mono text-sm',
41
- container: 'my-4 w-full overflow-hidden rounded-xl border border-gray-200 flex flex-col',
40
+ base: 'my-4 w-full overflow-hidden rounded-xl border border-gray-200 flex flex-col',
41
+ container: ' relative overflow-visible bg-gray-100 rounded p-2 font-mono text-sm ',
42
42
  header: 'flex items-center justify-between bg-gray-100/80 p-2 pb-0 text-gray-600 text-xs',
43
- button: 'cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900 size-6 p-1',
43
+ button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
44
44
  language: 'ml-1 font-mono lowercase',
45
45
  skeleton: 'rounded-md font-mono text-transparent bg-gray-200 whitespace-nowrap inline-block',
46
46
  pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40'
@@ -49,8 +49,8 @@ export const theme = {
49
49
  base: 'bg-gray-100 rounded px-1.5 py-0.5 font-mono text-sm'
50
50
  },
51
51
  img: {
52
- container: 'group relative my-4 mx-auto w-fit block',
53
- base: 'max-w-full rounded-lg',
52
+ base: 'group relative my-4 mx-auto w-fit block',
53
+ image: 'max-w-full rounded-lg',
54
54
  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
55
  },
56
56
  pre: {
@@ -70,8 +70,8 @@ export const theme = {
70
70
  important: '[&>[data-alert-title]]:text-purple-600 border-purple-600 stroke-purple-600'
71
71
  },
72
72
  table: {
73
- base: 'border-gray-200 w-full border-collapse border',
74
- container: 'my-4 flex flex-col space-y-2'
73
+ base: 'overflow-x-auto max-w-full my-4 border border-gray-200 rounded-lg',
74
+ table: 'w-full border-collapse min-w-full'
75
75
  },
76
76
  thead: {
77
77
  base: 'bg-gray-100/80'
@@ -83,10 +83,10 @@ export const theme = {
83
83
  base: 'border-gray-200 border-b'
84
84
  },
85
85
  th: {
86
- base: 'px-4 py-2 text-left text-sm font-semibold whitespace-nowrap'
86
+ base: 'px-4 py-3 text-left text-sm font-semibold whitespace-nowrap min-w-[200px]'
87
87
  },
88
88
  td: {
89
- base: 'px-4 py-2 text-sm'
89
+ base: 'px-4 py-3 text-sm min-w-[200px] max-w-[400px] break-words'
90
90
  },
91
91
  sup: {
92
92
  base: 'text-sm'
@@ -101,8 +101,11 @@ export const theme = {
101
101
  base: 'font-semibold'
102
102
  },
103
103
  mermaid: {
104
- base: 'group relative my-4 h-auto rounded-xl border border-gray-200 p-4',
105
- downloadButton: 'cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900'
104
+ base: 'group relative my-4 h-auto rounded-xl border border-gray-200 bg-white overflow-hidden items-center min-h-[500px]',
105
+ downloadButton: 'cursor-pointer p-1 text-gray-600 transition-all hover:text-gray-900',
106
+ button: 'cursor-pointer size-6 p-1 text-gray-600 transition-all hover:text-gray-900 rounded hover:bg-gray-100',
107
+ icon: 'size-5',
108
+ buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
106
109
  },
107
110
  math: {
108
111
  base: ''
@@ -111,28 +114,142 @@ export const theme = {
111
114
  base: ''
112
115
  },
113
116
  em: {
114
- base: 'font-italic'
115
- },
116
- ins: {
117
- base: 'underline'
117
+ base: 'italic'
118
118
  },
119
119
  del: {
120
120
  base: 'line-through'
121
+ }
122
+ };
123
+ export const shadcnTheme = {
124
+ a: {
125
+ base: 'text-primary font-medium underline hover:text-primary/80',
126
+ blocked: 'text-muted-foreground'
127
+ },
128
+ h1: {
129
+ base: 'mt-6 mb-2 text-3xl font-semibold text-foreground'
130
+ },
131
+ h2: {
132
+ base: 'mt-6 mb-2 text-2xl font-semibold text-foreground'
133
+ },
134
+ h3: {
135
+ base: 'mt-6 mb-2 text-xl font-semibold text-foreground'
136
+ },
137
+ h4: {
138
+ base: 'mt-6 mb-2 text-lg font-semibold text-foreground'
139
+ },
140
+ h5: {
141
+ base: 'mt-6 mb-2 text-base font-semibold text-foreground'
142
+ },
143
+ h6: {
144
+ base: 'mt-6 mb-2 text-sm font-semibold text-foreground'
145
+ },
146
+ p: {
147
+ base: 'text-foreground'
148
+ },
149
+ ul: {
150
+ base: 'ml-4 list-outside list-disc whitespace-normal text-foreground'
151
+ },
152
+ ol: {
153
+ base: 'ml-4 list-outside list-decimal whitespace-normal text-foreground'
154
+ },
155
+ li: {
156
+ base: 'py-1'
157
+ },
158
+ code: {
159
+ base: 'my-4 w-full overflow-hidden rounded-lg border border-border flex flex-col',
160
+ container: 'relative overflow-visible bg-muted rounded p-2 font-mono text-sm',
161
+ header: 'flex items-center justify-between bg-muted/80 p-2 pb-0 text-muted-foreground text-xs',
162
+ button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
163
+ language: 'ml-1 font-mono lowercase',
164
+ skeleton: 'rounded-md font-mono text-transparent bg-muted whitespace-nowrap inline-block',
165
+ pre: 'overflow-x-auto font-mono p-0 bg-muted/40'
166
+ },
167
+ inlineCode: {
168
+ base: 'bg-muted rounded px-1.5 py-0.5 font-mono text-sm text-foreground'
169
+ },
170
+ img: {
171
+ base: 'group relative my-4 mx-auto w-fit block',
172
+ image: 'max-w-full rounded-lg',
173
+ 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
+ },
175
+ pre: {
176
+ base: 'overflow-x-auto font-mono text-xs p-4 bg-muted/40'
177
+ },
178
+ blockquote: {
179
+ base: 'border-muted-foreground/30 text-muted-foreground my-4 border-l-4 pl-4 italic'
180
+ },
181
+ alert: {
182
+ base: 'relative my-4 border-l-4 p-4 bg-card',
183
+ title: 'text-sm font-semibold flex items-center gap-2 mb-2 capitalize',
184
+ icon: 'size-5',
185
+ note: '[&>[data-alert-title]]:text-blue-600 border-blue-600 stroke-blue-600',
186
+ tip: '[&>[data-alert-title]]:text-green-600 border-green-600 stroke-green-600',
187
+ warning: '[&>[data-alert-title]]:text-yellow-600 border-yellow-600 stroke-yellow-600',
188
+ caution: '[&>[data-alert-title]]:text-destructive border-destructive stroke-destructive',
189
+ important: '[&>[data-alert-title]]:text-purple-600 border-purple-600 stroke-purple-600'
190
+ },
191
+ table: {
192
+ base: 'overflow-x-auto max-w-full my-4 rounded-lg border border-border',
193
+ table: 'w-full border-collapse min-w-full'
194
+ },
195
+ thead: {
196
+ base: 'bg-muted/50'
197
+ },
198
+ tbody: {
199
+ base: 'divide-border bg-card divide-y'
200
+ },
201
+ tr: {
202
+ base: 'border-border border-b hover:bg-muted/50 transition-colors'
203
+ },
204
+ th: {
205
+ base: 'px-4 py-3 text-left text-sm font-semibold whitespace-nowrap text-foreground min-w-[200px]'
206
+ },
207
+ td: {
208
+ base: 'px-4 py-3 text-sm text-foreground min-w-[200px] max-w-[400px] break-words'
121
209
  },
122
- small: {
210
+ sup: {
123
211
  base: 'text-sm'
212
+ },
213
+ sub: {
214
+ base: 'text-sm'
215
+ },
216
+ hr: {
217
+ base: 'border-border my-6'
218
+ },
219
+ strong: {
220
+ base: 'font-semibold text-foreground'
221
+ },
222
+ mermaid: {
223
+ base: 'group relative my-4 h-auto rounded-lg border border-border bg-card overflow-hidden items-center min-h-[500px]',
224
+ downloadButton: 'cursor-pointer p-1 text-muted-foreground transition-all hover:text-foreground',
225
+ button: 'cursor-pointer size-6 p-1 text-muted-foreground transition-all hover:text-foreground rounded hover:bg-muted',
226
+ icon: 'size-5',
227
+ buttons: 'absolute right-1 top-1 z-10 flex h-fit w-fit items-center gap-1'
228
+ },
229
+ math: {
230
+ base: 'text-foreground'
231
+ },
232
+ inlineMath: {
233
+ base: 'text-foreground'
234
+ },
235
+ em: {
236
+ base: 'italic text-foreground'
237
+ },
238
+ del: {
239
+ base: 'line-through text-muted-foreground'
124
240
  }
125
241
  };
126
- export const mergeTheme = (customTheme) => {
242
+ export const mergeTheme = (customTheme, baseTheme) => {
243
+ const base = baseTheme === 'shadcn' ? shadcnTheme : theme;
127
244
  if (!customTheme)
128
- return theme;
129
- const baseTheme = { ...theme };
245
+ return base;
246
+ const mergedTheme = { ...base };
130
247
  for (const key in customTheme) {
131
248
  for (const subKey in customTheme[key]) {
132
- Object.assign(baseTheme[key], {
133
- [subKey]: cn(baseTheme[key], customTheme[key])
249
+ Object.assign(mergedTheme[key], {
250
+ [subKey]: cn(mergedTheme[key], customTheme[key])
134
251
  });
135
252
  }
136
253
  }
137
- return baseTheme;
254
+ return mergedTheme;
138
255
  };
@@ -0,0 +1,36 @@
1
+ export interface PanzoomOptions {
2
+ minZoom?: number;
3
+ maxZoom?: number;
4
+ zoomSpeed?: number;
5
+ doubleClickScale?: number;
6
+ initialScale?: number;
7
+ initialX?: number;
8
+ initialY?: number;
9
+ activateMouseWheel?: boolean;
10
+ }
11
+ export interface PanzoomInstance {
12
+ attach: (node: HTMLElement | SVGSVGElement) => () => void;
13
+ zoomToFit: (padding?: number) => void;
14
+ zoomBy: (factor: number) => void;
15
+ zoomIn: (factor?: number) => void;
16
+ zoomOut: (factor?: number) => void;
17
+ moveBy: (dx: number, dy: number) => void;
18
+ setTransform: (x: number, y: number, scale: number) => void;
19
+ expand: () => void;
20
+ collapse: () => void;
21
+ toggleExpand: (options?: ExpandOptions | number) => Promise<void>;
22
+ 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;