fix: prevent pointer events from hijacking two-finger touch pan
On touch devices, both pointer and touch events fire. When a second finger was added, the pointer handler re-captured the interaction, fighting the touch-based pan/pinch. Now the touch handler releases pointer captures and sets a flag that blocks the pointer handler during two-finger gestures. Also cancels shape drag on multi-touch and closes the context menu on touchstart for reliable mobile dismiss. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
658eb966d6
commit
5e0f30567a
|
|
@ -438,6 +438,16 @@ export class FolkShape extends FolkElement {
|
||||||
const isDragHandle = target?.closest?.(".header, [data-drag]") !== null;
|
const isDragHandle = target?.closest?.(".header, [data-drag]") !== null;
|
||||||
const isValidDragTarget = target === this || isDragHandle;
|
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 (event.type === "touchstart" && event.touches.length === 1) {
|
||||||
if (!isValidDragTarget) return;
|
if (!isValidDragTarget) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2778,11 +2778,17 @@
|
||||||
if (memoryPanel.classList.contains("open")) renderMemoryPanel();
|
if (memoryPanel.classList.contains("open")) renderMemoryPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close context menu on click elsewhere
|
// Close context menu on click/touch elsewhere
|
||||||
document.addEventListener("click", () => {
|
document.addEventListener("click", () => {
|
||||||
shapeContextMenu.classList.remove("open");
|
shapeContextMenu.classList.remove("open");
|
||||||
contextShapeId = null;
|
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
|
// Memory panel — browse and remember forgotten shapes
|
||||||
const memoryPanel = document.getElementById("memory-panel");
|
const memoryPanel = document.getElementById("memory-panel");
|
||||||
|
|
@ -3100,6 +3106,7 @@
|
||||||
// Touch gesture handling for two-finger pan + pinch-to-zoom
|
// Touch gesture handling for two-finger pan + pinch-to-zoom
|
||||||
let lastTouchCenter = null;
|
let lastTouchCenter = null;
|
||||||
let lastTouchDist = null;
|
let lastTouchDist = null;
|
||||||
|
let isTouchPanning = false;
|
||||||
|
|
||||||
function getTouchCenter(touches) {
|
function getTouchCenter(touches) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -3117,9 +3124,17 @@
|
||||||
canvas.addEventListener("touchstart", (e) => {
|
canvas.addEventListener("touchstart", (e) => {
|
||||||
if (e.touches.length === 2) {
|
if (e.touches.length === 2) {
|
||||||
e.preventDefault();
|
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;
|
isPanning = false;
|
||||||
panPointerId = null;
|
panPointerId = null;
|
||||||
|
interactionMode = "none";
|
||||||
|
clearTimeout(holdTimer);
|
||||||
|
holdTimer = null;
|
||||||
canvas.style.cursor = "";
|
canvas.style.cursor = "";
|
||||||
lastTouchCenter = getTouchCenter(e.touches);
|
lastTouchCenter = getTouchCenter(e.touches);
|
||||||
lastTouchDist = getTouchDist(e.touches);
|
lastTouchDist = getTouchDist(e.touches);
|
||||||
|
|
@ -3127,7 +3142,7 @@
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
|
|
||||||
canvas.addEventListener("touchmove", (e) => {
|
canvas.addEventListener("touchmove", (e) => {
|
||||||
if (e.touches.length === 2) {
|
if (e.touches.length === 2 && isTouchPanning) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const currentCenter = getTouchCenter(e.touches);
|
const currentCenter = getTouchCenter(e.touches);
|
||||||
|
|
@ -3163,6 +3178,7 @@
|
||||||
if (e.touches.length < 2) {
|
if (e.touches.length < 2) {
|
||||||
lastTouchCenter = null;
|
lastTouchCenter = null;
|
||||||
lastTouchDist = null;
|
lastTouchDist = null;
|
||||||
|
isTouchPanning = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3242,6 +3258,7 @@
|
||||||
const PAN_THRESHOLD = 4; // px movement to confirm pan intent
|
const PAN_THRESHOLD = 4; // px movement to confirm pan intent
|
||||||
|
|
||||||
canvas.addEventListener("pointerdown", (e) => {
|
canvas.addEventListener("pointerdown", (e) => {
|
||||||
|
if (isTouchPanning) return; // two-finger gesture owns the canvas
|
||||||
if (e.target !== canvas && e.target !== canvasContent) return;
|
if (e.target !== canvas && e.target !== canvasContent) return;
|
||||||
if (connectMode) return;
|
if (connectMode) return;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue