Browse Source

fix: suppress hover state during canvas panning to prevent cascading re-renders

Replace broken `.react-flow__pane.dragging` DOM query guard with a shared
`isPanningRef` set by ReactFlow's onMoveStart/onMoveEnd callbacks. Clear
hoveredNodeId when panning starts. Use a derived boolean selector in
useVideoAutoplay so hovering node A doesn't re-render video nodes B–E.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
2adf82f70b
  1. 6
      src/components/WorkflowCanvas.tsx
  2. 5
      src/components/nodes/BaseNode.tsx
  3. 4
      src/hooks/useVideoAutoplay.ts

6
src/components/WorkflowCanvas.tsx

@ -264,6 +264,9 @@ const findScrollableAncestor = (target: HTMLElement, deltaX: number, deltaY: num
return null;
};
/** Shared ref so child components (BaseNode) can check panning state without re-rendering */
export const isPanningRef = { current: false };
export function WorkflowCanvas() {
const { nodes, edges, groups, isModalOpen, showQuickstart, navigationTarget, canvasNavigationSettings, dimmedNodeIds } =
useWorkflowStore(useShallow((state) => ({
@ -294,6 +297,7 @@ export function WorkflowCanvas() {
const setShortcutsDialogOpen = useWorkflowStore((state) => state.setShortcutsDialogOpen);
const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
const clearWorkflow = useWorkflowStore((state) => state.clearWorkflow);
const setHoveredNodeId = useWorkflowStore((state) => state.setHoveredNodeId);
const openAnnotationModal = useAnnotationStore((state) => state.openModal);
const { screenToFlowPosition, getViewport, zoomIn, zoomOut, setViewport, setCenter } = useReactFlow();
const { show: showToast } = useToast();
@ -1953,6 +1957,8 @@ export function WorkflowCanvas() {
onEdgesChange={onEdgesChange}
onConnect={handleConnect}
onConnectEnd={handleConnectEnd}
onMoveStart={() => { isPanningRef.current = true; setHoveredNodeId(null); }}
onMoveEnd={() => { isPanningRef.current = false; }}
onNodeDragStop={handleNodeDragStop}
onSelectionChange={handleSelectionChange}
nodeTypes={nodeTypes}

5
src/components/nodes/BaseNode.tsx

@ -3,6 +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 { getMediaDimensions, calculateAspectFitSize } from "@/utils/nodeDimensions";
const DEFAULT_NODE_DIMENSION = 300;
@ -296,11 +297,11 @@ export function BaseNode({
${className}
`}
onMouseEnter={() => {
if (document.querySelector('.react-flow__pane.dragging')) return;
if (isPanningRef.current) return;
setHoveredNodeId(id);
}}
onMouseLeave={() => {
if (document.querySelector('.react-flow__pane.dragging')) return;
if (isPanningRef.current) return;
setHoveredNodeId(null);
}}
>

4
src/hooks/useVideoAutoplay.ts

@ -22,9 +22,7 @@ export function useVideoAutoplay(
): React.RefObject<HTMLVideoElement | null> {
const videoRef = useRef<HTMLVideoElement | null>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const hoveredNodeId = useWorkflowStore((s) => s.hoveredNodeId);
const isHovered = hoveredNodeId === nodeId;
const isHovered = useWorkflowStore((s) => s.hoveredNodeId === nodeId);
useEffect(() => {
const video = videoRef.current;

Loading…
Cancel
Save