@cr8rcho/alkahest 0.1.12 → 0.1.13

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.
@@ -606,18 +606,54 @@
606
606
  const DRAG_THRESHOLD = 4;
607
607
  let down = null; // {node|null, x, y, moved, pointerId}
608
608
 
609
+ // Active pointers for multi-touch. Two pointers down → pinch-to-zoom.
610
+ const pointers = new Map(); // pointerId -> {x, y}
611
+ let pinch = null; // { dist, midX, midY } for the running gesture
612
+
613
+ function startPinch() {
614
+ const pts = [...pointers.values()];
615
+ const dx = pts[0].x - pts[1].x, dy = pts[0].y - pts[1].y;
616
+ const rect = svg.getBoundingClientRect();
617
+ pinch = {
618
+ dist: Math.hypot(dx, dy) || 1,
619
+ midX: (pts[0].x + pts[1].x) / 2 - rect.left,
620
+ midY: (pts[0].y + pts[1].y) / 2 - rect.top,
621
+ };
622
+ // a pinch is purely a zoom — abandon any in-progress tap / pan / node drag
623
+ dragging = null; panStart = null; down = null;
624
+ }
625
+
609
626
  function onNodeDown(ev, n) {
610
627
  ev.stopPropagation();
611
- down = { node: n, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
628
+ pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
612
629
  svg.setPointerCapture(ev.pointerId);
630
+ if (pointers.size === 2) { startPinch(); return; }
631
+ down = { node: n, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
613
632
  }
614
633
  svg.addEventListener("pointerdown", (ev) => {
634
+ pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
635
+ svg.setPointerCapture(ev.pointerId);
636
+ if (pointers.size === 2) { startPinch(); return; }
615
637
  if (down) return; // already started on a node
616
638
  down = { node: null, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
617
639
  panStart = { x: ev.clientX - tx, y: ev.clientY - ty };
618
- svg.setPointerCapture(ev.pointerId);
619
640
  });
620
641
  svg.addEventListener("pointermove", (ev) => {
642
+ if (pointers.has(ev.pointerId)) pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
643
+ if (pinch && pointers.size >= 2) {
644
+ const pts = [...pointers.values()];
645
+ const dx = pts[0].x - pts[1].x, dy = pts[0].y - pts[1].y;
646
+ const dist = Math.hypot(dx, dy) || 1;
647
+ const rect = svg.getBoundingClientRect();
648
+ const mx = (pts[0].x + pts[1].x) / 2 - rect.left, my = (pts[0].y + pts[1].y) / 2 - rect.top;
649
+ // pan by how far the two-finger midpoint moved, then zoom around that midpoint
650
+ tx += mx - pinch.midX; ty += my - pinch.midY;
651
+ const nk = Math.min(4, Math.max(0.2, k * (dist / pinch.dist)));
652
+ tx = mx - (mx - tx) * (nk / k); ty = my - (my - ty) * (nk / k); k = nk;
653
+ pinch.dist = dist; pinch.midX = mx; pinch.midY = my;
654
+ applyTransform();
655
+ return;
656
+ }
621
657
  if (!down) return;
622
658
  if (!down.moved && Math.hypot(ev.clientX - down.x, ev.clientY - down.y) > DRAG_THRESHOLD) {
623
659
  down.moved = true;
@@ -627,8 +663,21 @@
627
663
  if (dragging) { const w = toWorld(ev.clientX, ev.clientY); dragging.x = w.x; dragging.y = w.y; dragging.vx = 0; dragging.vy = 0; } // free 2D since it's force-based
628
664
  else if (panStart) { tx = ev.clientX - panStart.x; ty = ev.clientY - panStart.y; applyTransform(); }
629
665
  });
630
- svg.addEventListener("pointerup", () => {
631
- if (down && !down.moved) { // didn't move = tap
666
+ function onPointerUp(ev) {
667
+ pointers.delete(ev.pointerId);
668
+ if (pinch) {
669
+ if (pointers.size < 2) {
670
+ pinch = null;
671
+ if (pointers.size === 1) {
672
+ // one finger left after a pinch → resume panning from there (no jump)
673
+ const [id, p] = [...pointers.entries()][0];
674
+ down = { node: null, x: p.x, y: p.y, moved: true, pointerId: id };
675
+ panStart = { x: p.x - tx, y: p.y - ty };
676
+ }
677
+ }
678
+ return; // a pinch gesture never counts as a tap
679
+ }
680
+ if (ev.type !== "pointercancel" && down && !down.moved) { // didn't move = tap
632
681
  if (down.node) select(down.node);
633
682
  else clearSelection();
634
683
  } else if (dragging) {
@@ -636,7 +685,9 @@
636
685
  alpha = 0.06;
637
686
  }
638
687
  dragging = null; panStart = null; down = null;
639
- });
688
+ }
689
+ svg.addEventListener("pointerup", onPointerUp);
690
+ svg.addEventListener("pointercancel", onPointerUp);
640
691
  svg.addEventListener("wheel", (ev) => {
641
692
  ev.preventDefault();
642
693
  const rect = svg.getBoundingClientRect();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cr8rcho/alkahest",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },