@marianmeres/stuic 2.44.0 → 2.45.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
- let zoomLevel = $derived(ZOOM_LEVELS[zoomLevelIdx]);
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-4 md:p-4 {modalClassBackdrop}"
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}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "2.44.0",
3
+ "version": "2.45.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",