Fix floating camera drag/resize not working on iOS/iPadOS (#687)

The embedded camera viewer only handled mouse events (mousedown,
  mousemove, mouseup) for window dragging and resizing. On iOS/iPadOS,
  touch input doesn't trigger mouse events, so the camera window
  couldn't be repositioned — touch just scrolled the page underneath.

  Add touch event handlers (touchstart, touchmove, touchend, touchcancel)
  alongside mouse handlers for both the header drag handle and the
  bottom-right resize handle. Uses preventDefault on touchmove to
  prevent page scrolling during drag.
This commit is contained in:
maziggy
2026-03-13 12:44:51 +01:00
parent 220717f174
commit cfec18eca4
4 changed files with 87 additions and 43 deletions
@@ -470,12 +470,27 @@ export function EmbeddedCameraViewer({ printerId, printerName, viewerIndex = 0,
});
};
const handleDragTouchStart = (e: React.TouchEvent) => {
if ((e.target as HTMLElement).closest('.no-drag')) return;
const touch = e.touches[0];
setIsDragging(true);
setDragOffset({
x: touch.clientX - state.x,
y: touch.clientY - state.y,
});
};
// Resize handlers
const handleResizeMouseDown = (e: React.MouseEvent) => {
e.stopPropagation();
setIsResizing(true);
};
const handleResizeTouchStart = (e: React.TouchEvent) => {
e.stopPropagation();
setIsResizing(true);
};
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (isDragging) {
@@ -494,6 +509,26 @@ export function EmbeddedCameraViewer({ printerId, printerName, viewerIndex = 0,
}
};
const handleTouchMove = (e: TouchEvent) => {
if (!isDragging && !isResizing) return;
e.preventDefault();
const touch = e.touches[0];
if (isDragging) {
setState((prev) => ({
...prev,
x: Math.max(0, Math.min(touch.clientX - dragOffset.x, window.innerWidth - prev.width)),
y: Math.max(0, Math.min(touch.clientY - dragOffset.y, window.innerHeight - prev.height)),
}));
} else if (isResizing && containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
setState((prev) => ({
...prev,
width: Math.max(200, Math.min(touch.clientX - rect.left, window.innerWidth - prev.x - 10)),
height: Math.max(150, Math.min(touch.clientY - rect.top, window.innerHeight - prev.y - 10)),
}));
}
};
const handleMouseUp = () => {
setIsDragging(false);
setIsResizing(false);
@@ -502,9 +537,15 @@ export function EmbeddedCameraViewer({ printerId, printerName, viewerIndex = 0,
if (isDragging || isResizing) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleMouseUp);
document.addEventListener('touchcancel', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleMouseUp);
document.removeEventListener('touchcancel', handleMouseUp);
};
}
}, [isDragging, isResizing, dragOffset]);
@@ -527,6 +568,7 @@ export function EmbeddedCameraViewer({ printerId, printerName, viewerIndex = 0,
<div
className="flex items-center justify-between px-3 py-2 bg-bambu-dark border-b border-bambu-dark-tertiary cursor-grab active:cursor-grabbing"
onMouseDown={handleMouseDown}
onTouchStart={handleDragTouchStart}
>
<div className="flex items-center gap-2 text-sm text-white truncate">
<GripVertical className="w-4 h-4 text-bambu-gray flex-shrink-0" />
@@ -685,6 +727,7 @@ export function EmbeddedCameraViewer({ printerId, printerName, viewerIndex = 0,
<div
className="absolute bottom-0 right-0 w-6 h-6 cursor-se-resize no-drag hover:bg-white/10 rounded-tl transition-colors"
onMouseDown={handleResizeMouseDown}
onTouchStart={handleResizeTouchStart}
title="Drag to resize"
>
<svg