@marianmeres/stuic 2.44.0 → 2.46.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.
|
@@ -195,8 +195,18 @@
|
|
|
195
195
|
|
|
196
196
|
// Zoom state
|
|
197
197
|
const ZOOM_LEVELS = [1, 1.5, 2, 3, 4] as const;
|
|
198
|
+
const MIN_ZOOM = ZOOM_LEVELS[0];
|
|
199
|
+
const MAX_ZOOM = ZOOM_LEVELS[ZOOM_LEVELS.length - 1];
|
|
198
200
|
let zoomLevelIdx = $state(0);
|
|
199
|
-
|
|
201
|
+
|
|
202
|
+
// Pinch zoom state
|
|
203
|
+
let isPinching = $state(false);
|
|
204
|
+
let initialPinchDistance = 0;
|
|
205
|
+
let initialPinchZoom = 1;
|
|
206
|
+
let continuousZoom = $state(1);
|
|
207
|
+
|
|
208
|
+
// Use continuous zoom during pinch, discrete levels otherwise
|
|
209
|
+
let zoomLevel = $derived(isPinching ? continuousZoom : ZOOM_LEVELS[zoomLevelIdx]);
|
|
200
210
|
|
|
201
211
|
// Pan state
|
|
202
212
|
let isPanning = $state(false);
|
|
@@ -232,11 +242,22 @@
|
|
|
232
242
|
}
|
|
233
243
|
});
|
|
234
244
|
|
|
245
|
+
// Wheel zoom handler
|
|
246
|
+
function handleWheel(e: WheelEvent) {
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
if (e.deltaY > 0) {
|
|
249
|
+
zoomOut();
|
|
250
|
+
} else {
|
|
251
|
+
zoomIn();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
235
255
|
// Svelte action for pan event listeners - guaranteed to run when element is created
|
|
236
256
|
function pannable(node: HTMLImageElement) {
|
|
237
257
|
imgEl = node;
|
|
238
258
|
node.addEventListener("mousedown", panStart);
|
|
239
259
|
node.addEventListener("touchstart", panStart, { passive: false });
|
|
260
|
+
node.addEventListener("wheel", handleWheel, { passive: false });
|
|
240
261
|
|
|
241
262
|
document.addEventListener("mousemove", panMove);
|
|
242
263
|
document.addEventListener("mouseup", panEnd);
|
|
@@ -249,6 +270,7 @@
|
|
|
249
270
|
imgEl = null;
|
|
250
271
|
node.removeEventListener("mousedown", panStart);
|
|
251
272
|
node.removeEventListener("touchstart", panStart);
|
|
273
|
+
node.removeEventListener("wheel", handleWheel);
|
|
252
274
|
document.removeEventListener("mousemove", panMove);
|
|
253
275
|
document.removeEventListener("mouseup", panEnd);
|
|
254
276
|
document.removeEventListener("touchmove", panMove);
|
|
@@ -300,12 +322,45 @@
|
|
|
300
322
|
|
|
301
323
|
function resetZoom() {
|
|
302
324
|
zoomLevelIdx = 0;
|
|
325
|
+
continuousZoom = 1;
|
|
303
326
|
panX = 0;
|
|
304
327
|
panY = 0;
|
|
328
|
+
isPinching = false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Pinch zoom helpers
|
|
332
|
+
function getDistance(touch1: Touch, touch2: Touch): number {
|
|
333
|
+
const dx = touch1.clientX - touch2.clientX;
|
|
334
|
+
const dy = touch1.clientY - touch2.clientY;
|
|
335
|
+
return Math.hypot(dx, dy);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function findNearestZoomLevelIdx(zoom: number): number {
|
|
339
|
+
let nearestIdx = 0;
|
|
340
|
+
let minDiff = Math.abs(ZOOM_LEVELS[0] - zoom);
|
|
341
|
+
for (let i = 1; i < ZOOM_LEVELS.length; i++) {
|
|
342
|
+
const diff = Math.abs(ZOOM_LEVELS[i] - zoom);
|
|
343
|
+
if (diff < minDiff) {
|
|
344
|
+
minDiff = diff;
|
|
345
|
+
nearestIdx = i;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return nearestIdx;
|
|
305
349
|
}
|
|
306
350
|
|
|
307
351
|
// Pan/drag handlers
|
|
308
352
|
function panStart(e: MouseEvent | TouchEvent) {
|
|
353
|
+
// Detect two-finger pinch gesture
|
|
354
|
+
if ("touches" in e && e.touches.length === 2) {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
isPinching = true;
|
|
357
|
+
isPanning = false;
|
|
358
|
+
initialPinchDistance = getDistance(e.touches[0], e.touches[1]);
|
|
359
|
+
initialPinchZoom = continuousZoom;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Single-finger pan (only when zoomed in)
|
|
309
364
|
if (zoomLevel <= 1) return;
|
|
310
365
|
e.preventDefault();
|
|
311
366
|
isPanning = true;
|
|
@@ -320,6 +375,16 @@
|
|
|
320
375
|
}
|
|
321
376
|
|
|
322
377
|
function panMove(e: MouseEvent | TouchEvent) {
|
|
378
|
+
// Handle pinch zoom
|
|
379
|
+
if ("touches" in e && e.touches.length === 2 && isPinching) {
|
|
380
|
+
e.preventDefault();
|
|
381
|
+
const currentDistance = getDistance(e.touches[0], e.touches[1]);
|
|
382
|
+
const scale = currentDistance / initialPinchDistance;
|
|
383
|
+
continuousZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, initialPinchZoom * scale));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Handle single-finger pan
|
|
323
388
|
if (!isPanning) return;
|
|
324
389
|
e.preventDefault();
|
|
325
390
|
|
|
@@ -340,6 +405,18 @@
|
|
|
340
405
|
}
|
|
341
406
|
|
|
342
407
|
function panEnd() {
|
|
408
|
+
// Handle pinch end - snap to nearest discrete level
|
|
409
|
+
if (isPinching) {
|
|
410
|
+
isPinching = false;
|
|
411
|
+
zoomLevelIdx = findNearestZoomLevelIdx(continuousZoom);
|
|
412
|
+
continuousZoom = ZOOM_LEVELS[zoomLevelIdx];
|
|
413
|
+
// Reset pan when zoomed out to 1x
|
|
414
|
+
if (zoomLevelIdx === 0) {
|
|
415
|
+
panX = 0;
|
|
416
|
+
panY = 0;
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
343
420
|
isPanning = false;
|
|
344
421
|
}
|
|
345
422
|
|
|
@@ -402,7 +479,7 @@
|
|
|
402
479
|
<Modal
|
|
403
480
|
bind:this={modal}
|
|
404
481
|
onEscape={modal?.close}
|
|
405
|
-
classBackdrop="p-
|
|
482
|
+
classBackdrop="p-2 md:p-2 {modalClassBackdrop}"
|
|
406
483
|
classInner="max-w-full h-full {modalClassInner}"
|
|
407
484
|
class="max-h-full md:max-h-full rounded-lg {modalClass}"
|
|
408
485
|
classMain="flex items-center justify-center relative stuic-assets-preview stuic-assets-preview-open {modalClassMain}"
|
|
@@ -466,7 +543,15 @@
|
|
|
466
543
|
</div>
|
|
467
544
|
{/if}
|
|
468
545
|
|
|
469
|
-
<div class="absolute top-4 right-4 flex items-center
|
|
546
|
+
<div class="absolute top-4 left-4 right-4 flex items-center justify-between gap-3">
|
|
547
|
+
{#if !noName && previewAsset?.name}
|
|
548
|
+
<span class="truncate bg-white px-1 rounded">
|
|
549
|
+
{previewAsset?.name}
|
|
550
|
+
</span>
|
|
551
|
+
{:else}
|
|
552
|
+
<span></span>
|
|
553
|
+
{/if}
|
|
554
|
+
<div class="flex items-center space-x-3 shrink-0">
|
|
470
555
|
{#if previewAsset.isImage}
|
|
471
556
|
<button
|
|
472
557
|
class={twMerge(TOP_BUTTON_CLS, classControls)}
|
|
@@ -525,14 +610,9 @@
|
|
|
525
610
|
>
|
|
526
611
|
<X />
|
|
527
612
|
</button>
|
|
613
|
+
</div>
|
|
528
614
|
</div>
|
|
529
615
|
|
|
530
|
-
{#if !noName && previewAsset?.name}
|
|
531
|
-
<span class="absolute top-4 left-4 bg-white px-1 rounded">
|
|
532
|
-
{previewAsset?.name}
|
|
533
|
-
</span>
|
|
534
|
-
{/if}
|
|
535
|
-
|
|
536
616
|
{#if assets.length > 1}
|
|
537
617
|
{#if !noName && dotTooltip}
|
|
538
618
|
<div
|