Browse Source

Skip hover state updates during node drag to prevent re-renders

When dragging a node over another, onMouseEnter/onMouseLeave were
firing on underlying nodes, updating hoveredNodeId in Zustand and
triggering re-renders of nodes with large base64 images.

- Add isDraggingNodeRef (set on onNodeDragStart, cleared on
  onNodeDragStop) to suppress hover events during drag
- Add CSS contain: layout style paint on BaseNode content area to
  isolate repaints within each node

https://claude.ai/code/session_01MvD1n4QeXutgwUpKJuDGHa
handoff-20260429-1057
Claude 4 months ago
parent
commit
5b1e380660
Failed to extract signature
  1. 5
      src/components/WorkflowCanvas.tsx
  2. 8
      src/components/nodes/BaseNode.tsx

5
src/components/WorkflowCanvas.tsx

@ -268,6 +268,8 @@ const findScrollableAncestor = (target: HTMLElement, deltaX: number, deltaY: num
/** Shared ref so child components (BaseNode) can check panning state without re-rendering */
export const isPanningRef = { current: false };
/** Shared ref so child components (BaseNode) can skip hover updates during node drags */
export const isDraggingNodeRef = { current: false };
export function WorkflowCanvas() {
const { nodes, edges, groups, isModalOpen, showQuickstart, navigationTarget, canvasNavigationSettings, dimmedNodeIds } =
@ -1964,7 +1966,8 @@ export function WorkflowCanvas() {
onConnectEnd={handleConnectEnd}
onMoveStart={() => { isPanningRef.current = true; setHoveredNodeId(null); }}
onMoveEnd={() => { isPanningRef.current = false; }}
onNodeDragStop={handleNodeDragStop}
onNodeDragStart={() => { isDraggingNodeRef.current = true; }}
onNodeDragStop={(event, node) => { isDraggingNodeRef.current = false; handleNodeDragStop(event, node); }}
onSelectionChange={handleSelectionChange}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}

8
src/components/nodes/BaseNode.tsx

@ -3,7 +3,7 @@
import { ReactNode, useCallback, useRef, useLayoutEffect } from "react";
import { Node, NodeResizer, OnResize, useReactFlow } from "@xyflow/react";
import { useWorkflowStore } from "@/store/workflowStore";
import { isPanningRef } from "@/components/WorkflowCanvas";
import { isPanningRef, isDraggingNodeRef } from "@/components/WorkflowCanvas";
import { getMediaDimensions, calculateAspectFitSize } from "@/utils/nodeDimensions";
const DEFAULT_NODE_DIMENSION = 300;
@ -297,15 +297,15 @@ export function BaseNode({
${className}
`}
onMouseEnter={() => {
if (isPanningRef.current) return;
if (isPanningRef.current || isDraggingNodeRef.current) return;
setHoveredNodeId(id);
}}
onMouseLeave={() => {
if (isPanningRef.current) return;
if (isPanningRef.current || isDraggingNodeRef.current) return;
setHoveredNodeId(null);
}}
>
<div ref={contentRef} className={contentClassName ?? (fullBleed ? "flex-1 min-h-0 relative" : "px-3 pb-4 flex-1 min-h-0 overflow-hidden flex flex-col")}>{children}</div>
<div ref={contentRef} style={{ contain: "layout style paint" }} className={contentClassName ?? (fullBleed ? "flex-1 min-h-0 relative" : "px-3 pb-4 flex-1 min-h-0 overflow-hidden flex flex-col")}>{children}</div>
</div>
{settingsPanel && (
<div ref={settingsPanelRef}>

Loading…
Cancel
Save