@cr8rcho/alkahest 0.1.12 → 0.1.14
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/dist/assets/dashboard.html +61 -5
- package/package.json +1 -1
|
@@ -181,6 +181,11 @@
|
|
|
181
181
|
<path d="M1,1 L8,5 L1,9" fill="none" stroke="var(--edge)" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
|
|
182
182
|
</marker>
|
|
183
183
|
</defs>
|
|
184
|
+
<!-- Screen-space hit target so empty areas fire pointer events (SVG's default
|
|
185
|
+
visiblePainted ignores unpainted regions, which on touch killed pan/pinch
|
|
186
|
+
anywhere but on a node). transparent fill is hittable; "none" is not. Sits
|
|
187
|
+
before #viewport so nodes paint on top and keep their own events. -->
|
|
188
|
+
<rect id="bg" x="0" y="0" width="100%" height="100%" fill="transparent" />
|
|
184
189
|
<g id="viewport">
|
|
185
190
|
<g id="edges"></g>
|
|
186
191
|
<g id="nodes"></g>
|
|
@@ -606,18 +611,54 @@
|
|
|
606
611
|
const DRAG_THRESHOLD = 4;
|
|
607
612
|
let down = null; // {node|null, x, y, moved, pointerId}
|
|
608
613
|
|
|
614
|
+
// Active pointers for multi-touch. Two pointers down → pinch-to-zoom.
|
|
615
|
+
const pointers = new Map(); // pointerId -> {x, y}
|
|
616
|
+
let pinch = null; // { dist, midX, midY } for the running gesture
|
|
617
|
+
|
|
618
|
+
function startPinch() {
|
|
619
|
+
const pts = [...pointers.values()];
|
|
620
|
+
const dx = pts[0].x - pts[1].x, dy = pts[0].y - pts[1].y;
|
|
621
|
+
const rect = svg.getBoundingClientRect();
|
|
622
|
+
pinch = {
|
|
623
|
+
dist: Math.hypot(dx, dy) || 1,
|
|
624
|
+
midX: (pts[0].x + pts[1].x) / 2 - rect.left,
|
|
625
|
+
midY: (pts[0].y + pts[1].y) / 2 - rect.top,
|
|
626
|
+
};
|
|
627
|
+
// a pinch is purely a zoom — abandon any in-progress tap / pan / node drag
|
|
628
|
+
dragging = null; panStart = null; down = null;
|
|
629
|
+
}
|
|
630
|
+
|
|
609
631
|
function onNodeDown(ev, n) {
|
|
610
632
|
ev.stopPropagation();
|
|
611
|
-
|
|
633
|
+
pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
|
612
634
|
svg.setPointerCapture(ev.pointerId);
|
|
635
|
+
if (pointers.size === 2) { startPinch(); return; }
|
|
636
|
+
down = { node: n, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
|
|
613
637
|
}
|
|
614
638
|
svg.addEventListener("pointerdown", (ev) => {
|
|
639
|
+
pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
|
640
|
+
svg.setPointerCapture(ev.pointerId);
|
|
641
|
+
if (pointers.size === 2) { startPinch(); return; }
|
|
615
642
|
if (down) return; // already started on a node
|
|
616
643
|
down = { node: null, x: ev.clientX, y: ev.clientY, moved: false, pointerId: ev.pointerId };
|
|
617
644
|
panStart = { x: ev.clientX - tx, y: ev.clientY - ty };
|
|
618
|
-
svg.setPointerCapture(ev.pointerId);
|
|
619
645
|
});
|
|
620
646
|
svg.addEventListener("pointermove", (ev) => {
|
|
647
|
+
if (pointers.has(ev.pointerId)) pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY });
|
|
648
|
+
if (pinch && pointers.size >= 2) {
|
|
649
|
+
const pts = [...pointers.values()];
|
|
650
|
+
const dx = pts[0].x - pts[1].x, dy = pts[0].y - pts[1].y;
|
|
651
|
+
const dist = Math.hypot(dx, dy) || 1;
|
|
652
|
+
const rect = svg.getBoundingClientRect();
|
|
653
|
+
const mx = (pts[0].x + pts[1].x) / 2 - rect.left, my = (pts[0].y + pts[1].y) / 2 - rect.top;
|
|
654
|
+
// pan by how far the two-finger midpoint moved, then zoom around that midpoint
|
|
655
|
+
tx += mx - pinch.midX; ty += my - pinch.midY;
|
|
656
|
+
const nk = Math.min(4, Math.max(0.2, k * (dist / pinch.dist)));
|
|
657
|
+
tx = mx - (mx - tx) * (nk / k); ty = my - (my - ty) * (nk / k); k = nk;
|
|
658
|
+
pinch.dist = dist; pinch.midX = mx; pinch.midY = my;
|
|
659
|
+
applyTransform();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
621
662
|
if (!down) return;
|
|
622
663
|
if (!down.moved && Math.hypot(ev.clientX - down.x, ev.clientY - down.y) > DRAG_THRESHOLD) {
|
|
623
664
|
down.moved = true;
|
|
@@ -627,8 +668,21 @@
|
|
|
627
668
|
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
669
|
else if (panStart) { tx = ev.clientX - panStart.x; ty = ev.clientY - panStart.y; applyTransform(); }
|
|
629
670
|
});
|
|
630
|
-
|
|
631
|
-
|
|
671
|
+
function onPointerUp(ev) {
|
|
672
|
+
pointers.delete(ev.pointerId);
|
|
673
|
+
if (pinch) {
|
|
674
|
+
if (pointers.size < 2) {
|
|
675
|
+
pinch = null;
|
|
676
|
+
if (pointers.size === 1) {
|
|
677
|
+
// one finger left after a pinch → resume panning from there (no jump)
|
|
678
|
+
const [id, p] = [...pointers.entries()][0];
|
|
679
|
+
down = { node: null, x: p.x, y: p.y, moved: true, pointerId: id };
|
|
680
|
+
panStart = { x: p.x - tx, y: p.y - ty };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return; // a pinch gesture never counts as a tap
|
|
684
|
+
}
|
|
685
|
+
if (ev.type !== "pointercancel" && down && !down.moved) { // didn't move = tap
|
|
632
686
|
if (down.node) select(down.node);
|
|
633
687
|
else clearSelection();
|
|
634
688
|
} else if (dragging) {
|
|
@@ -636,7 +690,9 @@
|
|
|
636
690
|
alpha = 0.06;
|
|
637
691
|
}
|
|
638
692
|
dragging = null; panStart = null; down = null;
|
|
639
|
-
}
|
|
693
|
+
}
|
|
694
|
+
svg.addEventListener("pointerup", onPointerUp);
|
|
695
|
+
svg.addEventListener("pointercancel", onPointerUp);
|
|
640
696
|
svg.addEventListener("wheel", (ev) => {
|
|
641
697
|
ev.preventDefault();
|
|
642
698
|
const rect = svg.getBoundingClientRect();
|