diff --git a/lib/folk-shape.ts b/lib/folk-shape.ts index d095c18..2657e0a 100644 --- a/lib/folk-shape.ts +++ b/lib/folk-shape.ts @@ -438,6 +438,16 @@ export class FolkShape extends FolkElement { const isDragHandle = target?.closest?.(".header, [data-drag]") !== null; const isValidDragTarget = target === this || isDragHandle; + // Two-finger gesture → cancel shape drag so canvas pan takes over + if (event.touches.length >= 2) { + if (this.#isTouchDragging) { + this.#lastTouchPos = null; + this.#isTouchDragging = false; + this.#internals.states.delete("move"); + } + return; + } + if (event.type === "touchstart" && event.touches.length === 1) { if (!isValidDragTarget) return; diff --git a/website/canvas.html b/website/canvas.html index de29f44..ead6c4c 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -2778,11 +2778,17 @@ if (memoryPanel.classList.contains("open")) renderMemoryPanel(); }); - // Close context menu on click elsewhere + // Close context menu on click/touch elsewhere document.addEventListener("click", () => { shapeContextMenu.classList.remove("open"); contextShapeId = null; }); + document.addEventListener("touchstart", (e) => { + if (!e.target.closest("#shape-context-menu")) { + shapeContextMenu.classList.remove("open"); + contextShapeId = null; + } + }); // Memory panel — browse and remember forgotten shapes const memoryPanel = document.getElementById("memory-panel"); @@ -3100,6 +3106,7 @@ // Touch gesture handling for two-finger pan + pinch-to-zoom let lastTouchCenter = null; let lastTouchDist = null; + let isTouchPanning = false; function getTouchCenter(touches) { return { @@ -3117,9 +3124,17 @@ canvas.addEventListener("touchstart", (e) => { if (e.touches.length === 2) { e.preventDefault(); - // Cancel any single-finger pan to avoid conflict + isTouchPanning = true; + // Release any captured pointer so pointer events stop competing + if (panPointerId !== null) { + try { canvas.releasePointerCapture(panPointerId); } catch {} + } + // Cancel pointer-based pan state completely isPanning = false; panPointerId = null; + interactionMode = "none"; + clearTimeout(holdTimer); + holdTimer = null; canvas.style.cursor = ""; lastTouchCenter = getTouchCenter(e.touches); lastTouchDist = getTouchDist(e.touches); @@ -3127,7 +3142,7 @@ }, { passive: false }); canvas.addEventListener("touchmove", (e) => { - if (e.touches.length === 2) { + if (e.touches.length === 2 && isTouchPanning) { e.preventDefault(); const currentCenter = getTouchCenter(e.touches); @@ -3163,6 +3178,7 @@ if (e.touches.length < 2) { lastTouchCenter = null; lastTouchDist = null; + isTouchPanning = false; } }); @@ -3242,6 +3258,7 @@ const PAN_THRESHOLD = 4; // px movement to confirm pan intent canvas.addEventListener("pointerdown", (e) => { + if (isTouchPanning) return; // two-finger gesture owns the canvas if (e.target !== canvas && e.target !== canvasContent) return; if (connectMode) return;