From 9b181f16d28d26bbc9d3543b86ca212ef1b70c78 Mon Sep 17 00:00:00 2001 From: Shrimbly Date: Sun, 21 Dec 2025 22:28:12 +1300 Subject: [PATCH] macOS trackpad support and improve BaseNode styling --- .claude/settings.local.json | 3 +- src/components/WorkflowCanvas.tsx | 62 +++++++++++++++++++++++++++++-- src/components/nodes/BaseNode.tsx | 2 +- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ba638c4a..7e961320 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,8 @@ "Bash(find:*)", "Bash(xargs cat:*)", "WebSearch", - "WebFetch(domain:ai.google.dev)" + "WebFetch(domain:ai.google.dev)", + "Bash(grep:*)" ], "deny": [], "ask": [] diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index a62243c7..b8ac03e2 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -101,10 +101,25 @@ interface ConnectionDropState { sourceHandleId: string | null; } +// Detect if running on macOS for platform-specific trackpad behavior +const isMacOS = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform); + +// Detect if a wheel event is from a mouse (vs trackpad) +const isMouseWheel = (event: WheelEvent): boolean => { + // Mouse scroll wheel typically uses deltaMode 1 (lines) or has large discrete deltas + // Trackpad uses deltaMode 0 (pixels) with smaller, smoother deltas + if (event.deltaMode === 1) return true; // DOM_DELTA_LINE = mouse + + // Fallback: large delta values suggest mouse wheel + const threshold = 50; + return Math.abs(event.deltaY) >= threshold && + Math.abs(event.deltaY) % 40 === 0; // Mouse deltas often in multiples +}; + export function WorkflowCanvas() { const { nodes, edges, groups, onNodesChange, onEdgesChange, onConnect, addNode, updateNodeData, loadWorkflow, getNodeById, addToGlobalHistory, setNodeGroupId } = useWorkflowStore(); - const { screenToFlowPosition, getViewport } = useReactFlow(); + const { screenToFlowPosition, getViewport, zoomIn, zoomOut, setViewport } = useReactFlow(); const [isDragOver, setIsDragOver] = useState(false); const [dropType, setDropType] = useState<"image" | "workflow" | "node" | null>(null); const [connectionDrop, setConnectionDrop] = useState(null); @@ -503,6 +518,43 @@ export function WorkflowCanvas() { setConnectionDrop(null); }, []); + // Custom wheel handler for macOS trackpad support + const handleWheel = useCallback((event: React.WheelEvent) => { + // Pinch gesture (ctrlKey) always zooms + if (event.ctrlKey) { + event.preventDefault(); + if (event.deltaY < 0) zoomIn(); + else zoomOut(); + return; + } + + // On macOS, differentiate trackpad from mouse + if (isMacOS) { + const nativeEvent = event.nativeEvent; + if (isMouseWheel(nativeEvent)) { + // Mouse wheel → zoom + event.preventDefault(); + if (event.deltaY < 0) zoomIn(); + else zoomOut(); + } else { + // Trackpad scroll → pan + event.preventDefault(); + const viewport = getViewport(); + setViewport({ + x: viewport.x - event.deltaX, + y: viewport.y - event.deltaY, + zoom: viewport.zoom, + }); + } + return; + } + + // Non-macOS: default zoom behavior + event.preventDefault(); + if (event.deltaY < 0) zoomIn(); + else zoomOut(); + }, [zoomIn, zoomOut, getViewport, setViewport]); + // Get copy/paste functions and clipboard from store const { copySelectedNodes, pasteNodes, clearClipboard, clipboard } = useWorkflowStore(); @@ -933,10 +985,14 @@ export function WorkflowCanvas() { fitView deleteKeyCode={["Backspace", "Delete"]} multiSelectionKeyCode="Shift" - selectionOnDrag={false} - panOnDrag + selectionOnDrag={isMacOS} + panOnDrag={!isMacOS} selectNodesOnDrag={false} nodeDragThreshold={5} + zoomOnScroll={false} + zoomOnPinch={true} + panActivationKeyCode="Space" + onWheel={handleWheel} className="bg-neutral-900" defaultEdgeOptions={{ type: "editable", diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx index 550a0a55..84914324 100644 --- a/src/components/nodes/BaseNode.tsx +++ b/src/components/nodes/BaseNode.tsx @@ -74,7 +74,7 @@ export function BaseNode({ bg-neutral-800 rounded-md shadow-lg border h-full w-full ${isCurrentlyExecuting || isExecuting ? "border-blue-500 ring-1 ring-blue-500/20" : "border-neutral-700"} ${hasError ? "border-red-500" : ""} - ${selected ? "border-neutral-400 ring-1 ring-neutral-400/30" : ""} + ${selected ? "border-blue-500 ring-2 ring-blue-500/40 shadow-lg shadow-blue-500/25" : ""} ${className} `} >