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.
- package/LICENSE +21 -0
- package/README.md +511 -31
- package/dist/Elements/Code.svelte +27 -17
- package/dist/Elements/Element.svelte +11 -3
- package/dist/Elements/Image.svelte +2 -7
- package/dist/Elements/Math.svelte +29 -47
- package/dist/Elements/Mermaid.svelte +207 -5
- package/dist/Elements/Table.svelte +2 -2
- package/dist/Elements/element.d.ts +1 -1
- package/dist/Streamdown.d.ts +8 -35
- package/dist/Streamdown.svelte +22 -3
- package/dist/hightlighter.svelte.d.ts +18 -12
- package/dist/hightlighter.svelte.js +91 -65
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/theme.d.ts +127 -14
- package/dist/theme.js +139 -22
- package/dist/utils/panzoom.svelte.d.ts +36 -0
- package/dist/utils/panzoom.svelte.js +483 -0
- package/package.json +3 -2
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { onDestroy, untrack } from 'svelte';
|
|
2
|
+
export const usePanzoom = (opts = {}) => {
|
|
3
|
+
// state
|
|
4
|
+
let x = opts.initialX ?? 0;
|
|
5
|
+
let y = opts.initialY ?? 0;
|
|
6
|
+
let scale = opts.initialScale ?? 1;
|
|
7
|
+
// options
|
|
8
|
+
const minZoom = opts.minZoom ?? 0.1;
|
|
9
|
+
const maxZoom = opts.maxZoom ?? Number.POSITIVE_INFINITY;
|
|
10
|
+
const zoomSpeed = opts.zoomSpeed ?? 1;
|
|
11
|
+
const doubleClickScale = opts.doubleClickScale ?? 1.75;
|
|
12
|
+
let node = null; // element we transform
|
|
13
|
+
let eventTarget = null; // element we listen on (parent container if available)
|
|
14
|
+
let createdWrapper = null; // wrapper we created if no parent existed
|
|
15
|
+
const listeners = new Set();
|
|
16
|
+
// drag state
|
|
17
|
+
let dragging = false;
|
|
18
|
+
let lastClientX = 0;
|
|
19
|
+
let lastClientY = 0;
|
|
20
|
+
let dragOffMove = null;
|
|
21
|
+
let dragOffUp = null;
|
|
22
|
+
let previousStyle = null;
|
|
23
|
+
// touch state
|
|
24
|
+
let touchMode = 'none';
|
|
25
|
+
let pinchDistance = 0;
|
|
26
|
+
let pinchCenter = null;
|
|
27
|
+
// expand/collapse state
|
|
28
|
+
let isExpanded = $state(false);
|
|
29
|
+
let animating = false;
|
|
30
|
+
let placeholderEl = null;
|
|
31
|
+
function onKeyDown(e) {
|
|
32
|
+
if (e.key === 'Escape' || e.keyCode === 27) {
|
|
33
|
+
if (isExpanded && !animating) {
|
|
34
|
+
// fire and forget
|
|
35
|
+
void collapse();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const destroy = () => {
|
|
40
|
+
listeners.forEach((off) => off());
|
|
41
|
+
listeners.clear();
|
|
42
|
+
if (placeholderEl) {
|
|
43
|
+
placeholderEl.remove();
|
|
44
|
+
placeholderEl = null;
|
|
45
|
+
}
|
|
46
|
+
if (createdWrapper) {
|
|
47
|
+
// Move node back out of wrapper before removing wrapper
|
|
48
|
+
if (node && createdWrapper.parentElement) {
|
|
49
|
+
createdWrapper.parentElement.insertBefore(node, createdWrapper);
|
|
50
|
+
}
|
|
51
|
+
createdWrapper.remove();
|
|
52
|
+
createdWrapper = null;
|
|
53
|
+
}
|
|
54
|
+
if (dragOffMove)
|
|
55
|
+
dragOffMove();
|
|
56
|
+
if (dragOffUp)
|
|
57
|
+
dragOffUp();
|
|
58
|
+
};
|
|
59
|
+
onDestroy(() => {
|
|
60
|
+
destroy();
|
|
61
|
+
});
|
|
62
|
+
function normalize() {
|
|
63
|
+
// clamp and round to reduce subpixel jitter
|
|
64
|
+
scale = clampScale(scale);
|
|
65
|
+
const r = (v) => Math.round(v * 1000) / 1000;
|
|
66
|
+
x = r(x);
|
|
67
|
+
y = r(y);
|
|
68
|
+
scale = r(scale);
|
|
69
|
+
}
|
|
70
|
+
function apply() {
|
|
71
|
+
if (!node)
|
|
72
|
+
return;
|
|
73
|
+
normalize();
|
|
74
|
+
// Use CSS transform regardless of DOM/SVG root; modern browsers support this.
|
|
75
|
+
const el = node;
|
|
76
|
+
el.style.transformOrigin = '0 0';
|
|
77
|
+
el.style.willChange = 'transform';
|
|
78
|
+
// transform order is right-to-left. We want screen = translate + scale * local
|
|
79
|
+
// so use translate() first, then scale().
|
|
80
|
+
el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale})`;
|
|
81
|
+
}
|
|
82
|
+
function clampScale(s) {
|
|
83
|
+
if (s < minZoom)
|
|
84
|
+
return minZoom;
|
|
85
|
+
if (s > maxZoom)
|
|
86
|
+
return maxZoom;
|
|
87
|
+
return s;
|
|
88
|
+
}
|
|
89
|
+
function zoomAt(clientX, clientY, factor) {
|
|
90
|
+
if (!node)
|
|
91
|
+
return;
|
|
92
|
+
if (!Number.isFinite(factor) || factor === 1)
|
|
93
|
+
return;
|
|
94
|
+
const nextScale = clampScale(scale * factor);
|
|
95
|
+
const ratio = nextScale / scale;
|
|
96
|
+
if (ratio === 1)
|
|
97
|
+
return;
|
|
98
|
+
const owner = (eventTarget ?? node.parentElement ?? node);
|
|
99
|
+
const ownerRect = owner.getBoundingClientRect();
|
|
100
|
+
const ox = clientX - ownerRect.left;
|
|
101
|
+
const oy = clientY - ownerRect.top;
|
|
102
|
+
// Keep (ox, oy) stationary in owner's coordinate space
|
|
103
|
+
x = ratio * x + (1 - ratio) * ox;
|
|
104
|
+
y = ratio * y + (1 - ratio) * oy;
|
|
105
|
+
scale = nextScale;
|
|
106
|
+
apply();
|
|
107
|
+
}
|
|
108
|
+
function kineticWheel(deltaY) {
|
|
109
|
+
// deltaY > 0 => zoom out; use smooth exponential scaling
|
|
110
|
+
const sign = Math.sign(deltaY);
|
|
111
|
+
const step = Math.min(0.25, Math.abs((zoomSpeed * deltaY) / 128));
|
|
112
|
+
return 1 - sign * step;
|
|
113
|
+
}
|
|
114
|
+
function onWheel(e) {
|
|
115
|
+
if (!opts.activateMouseWheel)
|
|
116
|
+
return;
|
|
117
|
+
if (!node)
|
|
118
|
+
return;
|
|
119
|
+
if (animating) {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
e.stopPropagation();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Always prevent default to stop page scroll
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
const factor = kineticWheel(e.deltaY * (e.deltaMode ? 100 : 1));
|
|
128
|
+
zoomAt(e.clientX, e.clientY, factor);
|
|
129
|
+
// re-apply using latest rect
|
|
130
|
+
apply();
|
|
131
|
+
}
|
|
132
|
+
function onDblClick(e) {
|
|
133
|
+
// Ignore dblclicks that originate outside of the pan/zoom content (the node)
|
|
134
|
+
const t = e.target;
|
|
135
|
+
if (node && t && !(t === node || node.contains(t))) {
|
|
136
|
+
return; // likely UI control inside container; don't zoom
|
|
137
|
+
}
|
|
138
|
+
// Ignore dblclicks on interactive elements or those explicitly marked to ignore
|
|
139
|
+
const isInteractive = (el) => {
|
|
140
|
+
const tag = el.tagName.toLowerCase();
|
|
141
|
+
return (el.closest('[data-panzoom-ignore]') !== null ||
|
|
142
|
+
['button', 'a', 'input', 'textarea', 'select', 'label', 'summary', 'details'].includes(tag) ||
|
|
143
|
+
el.isContentEditable);
|
|
144
|
+
};
|
|
145
|
+
if (t && isInteractive(t))
|
|
146
|
+
return;
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
// Anchor double-click zoom at parent center to avoid shifts on rapid clicks
|
|
149
|
+
const baseEl = (eventTarget ?? node?.parentElement ?? node);
|
|
150
|
+
const base = baseEl.getBoundingClientRect();
|
|
151
|
+
const cx = base.left + base.width / 2;
|
|
152
|
+
const cy = base.top + base.height / 2;
|
|
153
|
+
zoomAt(cx, cy, doubleClickScale);
|
|
154
|
+
}
|
|
155
|
+
function startDrag(e) {
|
|
156
|
+
if (animating) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (e.button !== 0)
|
|
161
|
+
return; // left only
|
|
162
|
+
dragging = true;
|
|
163
|
+
lastClientX = e.clientX;
|
|
164
|
+
lastClientY = e.clientY;
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
if (node)
|
|
167
|
+
node.style.cursor = 'grabbing';
|
|
168
|
+
dragOffMove = () => window.removeEventListener('mousemove', onDragMove);
|
|
169
|
+
dragOffUp = () => window.removeEventListener('mouseup', endDrag);
|
|
170
|
+
window.addEventListener('mousemove', onDragMove, { passive: false });
|
|
171
|
+
window.addEventListener('mouseup', endDrag, { passive: true });
|
|
172
|
+
listeners.add(dragOffMove);
|
|
173
|
+
listeners.add(dragOffUp);
|
|
174
|
+
}
|
|
175
|
+
function onDragMove(e) {
|
|
176
|
+
if (!dragging)
|
|
177
|
+
return;
|
|
178
|
+
const dx = e.clientX - lastClientX;
|
|
179
|
+
const dy = e.clientY - lastClientY;
|
|
180
|
+
lastClientX = e.clientX;
|
|
181
|
+
lastClientY = e.clientY;
|
|
182
|
+
x += dx;
|
|
183
|
+
y += dy;
|
|
184
|
+
apply();
|
|
185
|
+
}
|
|
186
|
+
function endDrag() {
|
|
187
|
+
dragging = false;
|
|
188
|
+
if (node)
|
|
189
|
+
node.style.cursor = 'grab';
|
|
190
|
+
if (dragOffMove) {
|
|
191
|
+
dragOffMove();
|
|
192
|
+
listeners.delete(dragOffMove);
|
|
193
|
+
}
|
|
194
|
+
if (dragOffUp) {
|
|
195
|
+
dragOffUp();
|
|
196
|
+
listeners.delete(dragOffUp);
|
|
197
|
+
}
|
|
198
|
+
dragOffMove = dragOffUp = null;
|
|
199
|
+
}
|
|
200
|
+
function onTouchStart(e) {
|
|
201
|
+
if (!node)
|
|
202
|
+
return;
|
|
203
|
+
if (animating) {
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (e.touches.length === 1) {
|
|
208
|
+
touchMode = 'pan';
|
|
209
|
+
lastClientX = e.touches[0].clientX;
|
|
210
|
+
lastClientY = e.touches[0].clientY;
|
|
211
|
+
}
|
|
212
|
+
else if (e.touches.length >= 2) {
|
|
213
|
+
touchMode = 'pinch';
|
|
214
|
+
const [t1, t2] = [e.touches[0], e.touches[1]];
|
|
215
|
+
pinchDistance = Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY);
|
|
216
|
+
pinchCenter = {
|
|
217
|
+
x: (t1.clientX + t2.clientX) / 2,
|
|
218
|
+
y: (t1.clientY + t2.clientY) / 2
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// prevent default scrolling/zooming
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
const offMove = () => window.removeEventListener('touchmove', onTouchMove);
|
|
224
|
+
const offEnd = () => window.removeEventListener('touchend', onTouchEnd);
|
|
225
|
+
const offCancel = () => window.removeEventListener('touchcancel', onTouchEnd);
|
|
226
|
+
window.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
227
|
+
window.addEventListener('touchend', onTouchEnd, { passive: true });
|
|
228
|
+
window.addEventListener('touchcancel', onTouchEnd, { passive: true });
|
|
229
|
+
listeners.add(offMove);
|
|
230
|
+
listeners.add(offEnd);
|
|
231
|
+
listeners.add(offCancel);
|
|
232
|
+
}
|
|
233
|
+
function onTouchMove(e) {
|
|
234
|
+
if (!node)
|
|
235
|
+
return;
|
|
236
|
+
if (touchMode === 'pan' && e.touches.length === 1) {
|
|
237
|
+
const t = e.touches[0];
|
|
238
|
+
const dx = t.clientX - lastClientX;
|
|
239
|
+
const dy = t.clientY - lastClientY;
|
|
240
|
+
lastClientX = t.clientX;
|
|
241
|
+
lastClientY = t.clientY;
|
|
242
|
+
x += dx;
|
|
243
|
+
y += dy;
|
|
244
|
+
apply();
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
}
|
|
247
|
+
else if (touchMode === 'pinch' && e.touches.length >= 2) {
|
|
248
|
+
const [t1, t2] = [e.touches[0], e.touches[1]];
|
|
249
|
+
const dist = Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY);
|
|
250
|
+
const factor = dist / (pinchDistance || dist);
|
|
251
|
+
const center = {
|
|
252
|
+
x: (t1.clientX + t2.clientX) / 2,
|
|
253
|
+
y: (t1.clientY + t2.clientY) / 2
|
|
254
|
+
};
|
|
255
|
+
zoomAt(center.x, center.y, factor);
|
|
256
|
+
pinchDistance = dist;
|
|
257
|
+
pinchCenter = center;
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function onTouchEnd() {
|
|
262
|
+
if (!node)
|
|
263
|
+
return;
|
|
264
|
+
if (touchMode === 'pinch') {
|
|
265
|
+
// reset pinch state, keep resulting transform
|
|
266
|
+
touchMode = 'none';
|
|
267
|
+
pinchDistance = 0;
|
|
268
|
+
pinchCenter = null;
|
|
269
|
+
}
|
|
270
|
+
else if (touchMode === 'pan') {
|
|
271
|
+
touchMode = 'none';
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function internalAttach(target) {
|
|
275
|
+
return untrack(() => {
|
|
276
|
+
node = target;
|
|
277
|
+
// Ensure eventTarget and node are different elements to avoid FLIP conflicts
|
|
278
|
+
const isSVG = typeof SVGSVGElement !== 'undefined' && target instanceof SVGSVGElement;
|
|
279
|
+
if (isSVG) {
|
|
280
|
+
// For SVG, prefer parent element
|
|
281
|
+
eventTarget = target.parentElement;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// For non-SVG, try to use parent element when available
|
|
285
|
+
const parent = target.parentElement;
|
|
286
|
+
if (parent && parent instanceof HTMLElement) {
|
|
287
|
+
eventTarget = parent;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// No suitable parent - create wrapper to ensure separation
|
|
291
|
+
const wrapper = document.createElement('div');
|
|
292
|
+
wrapper.style.cssText = `
|
|
293
|
+
display: contents;
|
|
294
|
+
contain: layout style;
|
|
295
|
+
`
|
|
296
|
+
.replace(/\s+/g, ' ')
|
|
297
|
+
.trim();
|
|
298
|
+
// Insert wrapper and move target into it
|
|
299
|
+
if (target.parentElement) {
|
|
300
|
+
target.parentElement.insertBefore(wrapper, target);
|
|
301
|
+
}
|
|
302
|
+
wrapper.appendChild(target);
|
|
303
|
+
eventTarget = wrapper;
|
|
304
|
+
createdWrapper = wrapper;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Final fallback if no eventTarget was established
|
|
308
|
+
if (!eventTarget) {
|
|
309
|
+
eventTarget = target;
|
|
310
|
+
}
|
|
311
|
+
apply();
|
|
312
|
+
// helper to add and track listeners with options
|
|
313
|
+
const add = (type, handler, options) => {
|
|
314
|
+
const n = (eventTarget ?? node);
|
|
315
|
+
n.addEventListener(type, handler, options);
|
|
316
|
+
const off = () => n.removeEventListener(type, handler, options);
|
|
317
|
+
listeners.add(off);
|
|
318
|
+
return off;
|
|
319
|
+
};
|
|
320
|
+
const addWindow = (type, handler, options) => {
|
|
321
|
+
window.addEventListener(type, handler, options);
|
|
322
|
+
const off = () => window.removeEventListener(type, handler, options);
|
|
323
|
+
listeners.add(off);
|
|
324
|
+
return off;
|
|
325
|
+
};
|
|
326
|
+
// core events with passive: false where needed
|
|
327
|
+
add('mousedown', startDrag, { passive: false });
|
|
328
|
+
add('wheel', onWheel, { passive: false, capture: true });
|
|
329
|
+
add('dblclick', onDblClick, { passive: false });
|
|
330
|
+
add('touchstart', onTouchStart, { passive: false });
|
|
331
|
+
addWindow('keydown', onKeyDown, { passive: true });
|
|
332
|
+
// prevent text selection/scrolling while interacting
|
|
333
|
+
const t = (eventTarget ?? node);
|
|
334
|
+
t.style.userSelect = 'none';
|
|
335
|
+
t.style.touchAction = 'none';
|
|
336
|
+
t.style.cursor = 'grab';
|
|
337
|
+
t.style.overscrollBehavior = 'contain';
|
|
338
|
+
// For SVG root, use fill-box so translation math stays in CSS px space
|
|
339
|
+
const n = node;
|
|
340
|
+
if (typeof SVGSVGElement !== 'undefined' && target instanceof SVGSVGElement) {
|
|
341
|
+
n.style.transformBox = 'fill-box';
|
|
342
|
+
}
|
|
343
|
+
return () => {
|
|
344
|
+
destroy();
|
|
345
|
+
};
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
const collapse = () => {
|
|
349
|
+
if (!eventTarget)
|
|
350
|
+
return;
|
|
351
|
+
// Add view transition name for CSS targeting
|
|
352
|
+
eventTarget.style.viewTransitionName = 'panzoom-element';
|
|
353
|
+
isExpanded = false;
|
|
354
|
+
const api = document.startViewTransition;
|
|
355
|
+
const run = () => {
|
|
356
|
+
if (!eventTarget)
|
|
357
|
+
return;
|
|
358
|
+
eventTarget.dataset.expanded = 'false';
|
|
359
|
+
zoomToFit();
|
|
360
|
+
};
|
|
361
|
+
if (typeof api === 'function') {
|
|
362
|
+
api(run).finished.finally(() => {
|
|
363
|
+
if (eventTarget)
|
|
364
|
+
eventTarget.style.viewTransitionName = '';
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
run();
|
|
369
|
+
if (eventTarget)
|
|
370
|
+
eventTarget.style.viewTransitionName = '';
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
const expand = () => {
|
|
374
|
+
if (!eventTarget)
|
|
375
|
+
return;
|
|
376
|
+
// Add view transition name for CSS targeting
|
|
377
|
+
eventTarget.style.viewTransitionName = 'panzoom-element';
|
|
378
|
+
isExpanded = true;
|
|
379
|
+
const api = document.startViewTransition;
|
|
380
|
+
const run = () => {
|
|
381
|
+
if (!eventTarget)
|
|
382
|
+
return;
|
|
383
|
+
eventTarget.dataset.expanded = 'true';
|
|
384
|
+
zoomToFit();
|
|
385
|
+
};
|
|
386
|
+
if (typeof api === 'function') {
|
|
387
|
+
api(run).finished.finally(() => {
|
|
388
|
+
if (eventTarget)
|
|
389
|
+
eventTarget.style.viewTransitionName = '';
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
run();
|
|
394
|
+
if (eventTarget)
|
|
395
|
+
eventTarget.style.viewTransitionName = '';
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
async function toggleExpand() {
|
|
399
|
+
zoomToFit();
|
|
400
|
+
if (isExpanded)
|
|
401
|
+
return collapse();
|
|
402
|
+
return expand();
|
|
403
|
+
}
|
|
404
|
+
function zoomToFit(padding = 0.05) {
|
|
405
|
+
if (!node)
|
|
406
|
+
return;
|
|
407
|
+
const parent = node.parentElement;
|
|
408
|
+
if (!parent)
|
|
409
|
+
return;
|
|
410
|
+
const parentRect = parent.getBoundingClientRect();
|
|
411
|
+
const rect = node.getBoundingClientRect();
|
|
412
|
+
// infer unscaled natural size by dividing by current scale
|
|
413
|
+
const naturalWidth = rect.width / scale || 0;
|
|
414
|
+
const naturalHeight = rect.height / scale || 0;
|
|
415
|
+
const targetWidth = parentRect.width * (1 - 2 * padding);
|
|
416
|
+
const targetHeight = parentRect.height * (1 - 2 * padding);
|
|
417
|
+
if (naturalWidth <= 0 || naturalHeight <= 0 || targetWidth <= 0 || targetHeight <= 0)
|
|
418
|
+
return;
|
|
419
|
+
const s = clampScale(Math.min(targetWidth / naturalWidth, targetHeight / naturalHeight));
|
|
420
|
+
// Temporarily apply scale with zero translate to measure new size
|
|
421
|
+
scale = s;
|
|
422
|
+
x = 0;
|
|
423
|
+
y = 0;
|
|
424
|
+
apply();
|
|
425
|
+
const newRect = node.getBoundingClientRect();
|
|
426
|
+
// compute centered position inside parent
|
|
427
|
+
const targetLeft = parentRect.left + (parentRect.width - newRect.width) / 2;
|
|
428
|
+
const targetTop = parentRect.top + (parentRect.height - newRect.height) / 2;
|
|
429
|
+
x = targetLeft - newRect.left;
|
|
430
|
+
y = targetTop - newRect.top;
|
|
431
|
+
apply();
|
|
432
|
+
}
|
|
433
|
+
function zoomBy(factor) {
|
|
434
|
+
console.log('zoomBy', factor);
|
|
435
|
+
if (!node)
|
|
436
|
+
return;
|
|
437
|
+
// Use zoomAt with the center of the container for consistent behavior
|
|
438
|
+
const owner = (eventTarget ?? node.parentElement ?? node);
|
|
439
|
+
const ownerRect = owner.getBoundingClientRect();
|
|
440
|
+
const cx = ownerRect.left + ownerRect.width / 2;
|
|
441
|
+
const cy = ownerRect.top + ownerRect.height / 2;
|
|
442
|
+
zoomAt(cx, cy, factor);
|
|
443
|
+
}
|
|
444
|
+
function moveBy(dx, dy) {
|
|
445
|
+
x += dx;
|
|
446
|
+
y += dy;
|
|
447
|
+
apply();
|
|
448
|
+
}
|
|
449
|
+
function zoomIn(factor = 1.25) {
|
|
450
|
+
zoomBy(factor);
|
|
451
|
+
}
|
|
452
|
+
function zoomOut(factor = 1.25) {
|
|
453
|
+
console.log('zoomOut', factor);
|
|
454
|
+
// zoom out by inverse multiplier
|
|
455
|
+
if (factor <= 0)
|
|
456
|
+
return;
|
|
457
|
+
zoomBy(1 / factor);
|
|
458
|
+
}
|
|
459
|
+
function setTransform(nx, ny, ns) {
|
|
460
|
+
scale = clampScale(ns);
|
|
461
|
+
x = nx;
|
|
462
|
+
y = ny;
|
|
463
|
+
apply();
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
attach: internalAttach,
|
|
467
|
+
zoomToFit,
|
|
468
|
+
zoomBy,
|
|
469
|
+
zoomIn,
|
|
470
|
+
zoomOut,
|
|
471
|
+
moveBy,
|
|
472
|
+
setTransform,
|
|
473
|
+
expand,
|
|
474
|
+
collapse,
|
|
475
|
+
toggleExpand,
|
|
476
|
+
get transform() {
|
|
477
|
+
return { x, y, scale };
|
|
478
|
+
},
|
|
479
|
+
get expanded() {
|
|
480
|
+
return isExpanded;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-streamdown",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"publint": "^0.3.2",
|
|
50
50
|
"svelte": "^5.0.0",
|
|
51
51
|
"svelte-check": "^4.0.0",
|
|
52
|
+
"svelte-themes": "^2.0.8",
|
|
52
53
|
"tailwindcss": "^4.0.0",
|
|
53
54
|
"typescript": "^5.0.0",
|
|
54
55
|
"vite": "^7.0.4",
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"vitest-browser-svelte": "^0.1.0",
|
|
58
59
|
"wrangler": "^4.34.0"
|
|
59
60
|
},
|
|
61
|
+
"license": "MIT",
|
|
60
62
|
"keywords": [
|
|
61
63
|
"svelte"
|
|
62
64
|
],
|
|
@@ -76,7 +78,6 @@
|
|
|
76
78
|
"mdast": "^3.0.0",
|
|
77
79
|
"mdast-squeeze-paragraphs": "^6.0.0",
|
|
78
80
|
"mermaid": "^11.11.0",
|
|
79
|
-
"react-markdown": "^10.1.0",
|
|
80
81
|
"rehype-github-alerts": "^4.1.1",
|
|
81
82
|
"rehype-katex": "^7.0.1",
|
|
82
83
|
"remark-gfm": "^4.0.1",
|