Browse Source

macOS trackpad support and improve BaseNode styling

handoff-20260429-1057
Shrimbly 7 months ago
parent
commit
9b181f16d2
  1. 3
      .claude/settings.local.json
  2. 62
      src/components/WorkflowCanvas.tsx
  3. 2
      src/components/nodes/BaseNode.tsx

3
.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": []

62
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<ConnectionDropState | null>(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",

2
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}
`}
>

Loading…
Cancel
Save