@@ -60,6 +60,10 @@ interface NodeProps {
6060 onSliderChange ?: ( value : number ) => void ;
6161 onSliderChangeComplete ?: ( ) => void ;
6262 showAddArrows ?: boolean ;
63+ screenToCanvasCoords : (
64+ screenX : number ,
65+ screenY : number
66+ ) => { x : number ; y : number } ;
6367 onAddArrow ?: (
6468 direction : "top" | "bottom" | "left" | "right" ,
6569 mousePos ?: { clientX : number ; clientY : number }
@@ -98,6 +102,7 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
98102 onSliderChange,
99103 onSliderChangeComplete,
100104 showAddArrows,
105+ screenToCanvasCoords,
101106 onAddArrow,
102107 } ,
103108 ref
@@ -108,8 +113,10 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
108113 const [ isDragging , setIsDragging ] = useState ( false ) ;
109114 const [ shiftHeld , setShiftHeld ] = useState ( false ) ; // Track if Shift is held during drag
110115 const dragStartRef = useRef < {
111- mouseX : number ;
112- mouseY : number ;
116+ // Offset from mouse position to node center in canvas coordinates
117+ nodeOffsetX : number ;
118+ nodeOffsetY : number ;
119+ // Original node position for calculating deltas
113120 nodeX : number ;
114121 nodeY : number ;
115122 } | null > ( null ) ;
@@ -339,9 +346,14 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
339346 onDragStateChange ( true , false , e . clientX , e . clientY ) ;
340347 didDragRef . current = false ;
341348 lastUpdateTimeRef . current = Date . now ( ) ; // Reset timer for throttling
349+
350+ // Convert mouse position to canvas coordinates
351+ const canvasPos = screenToCanvasCoords ( e . clientX , e . clientY ) ;
352+
353+ // Store offset from mouse to node center in canvas coordinates
342354 dragStartRef . current = {
343- mouseX : e . clientX ,
344- mouseY : e . clientY ,
355+ nodeOffsetX : x - canvasPos . x ,
356+ nodeOffsetY : y - canvasPos . y ,
345357 nodeX : x ,
346358 nodeY : y ,
347359 } ;
@@ -372,24 +384,22 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
372384 }
373385 onDragStateChange ( true , e . shiftKey , e . clientX , e . clientY ) ;
374386
375- // Calculate raw delta in screen space
376- const rawDeltaX = e . clientX - dragStartRef . current . mouseX ;
377- const rawDeltaY = e . clientY - dragStartRef . current . mouseY ;
387+ // Convert current mouse position to canvas coordinates
388+ const canvasPos = screenToCanvasCoords ( e . clientX , e . clientY ) ;
389+
390+ // Calculate new node position by adding the stored offset
391+ let newX = canvasPos . x + dragStartRef . current . nodeOffsetX ;
392+ let newY = canvasPos . y + dragStartRef . current . nodeOffsetY ;
393+
394+ // Calculate raw delta to check if we've moved enough to count as dragging
395+ const rawDeltaX = newX - dragStartRef . current . nodeX ;
396+ const rawDeltaY = newY - dragStartRef . current . nodeY ;
378397
379398 // Mark that we've dragged (moved more than a few pixels)
380399 if ( Math . abs ( rawDeltaX ) > 3 || Math . abs ( rawDeltaY ) > 3 ) {
381400 didDragRef . current = true ;
382401 }
383402
384- // Convert to canvas coordinates
385- const zoomFactor = zoom / 100 ;
386- const canvasDeltaX = rawDeltaX / zoomFactor ;
387- const canvasDeltaY = rawDeltaY / zoomFactor ;
388-
389- // Calculate new position in canvas coordinates
390- let newX = dragStartRef . current . nodeX + canvasDeltaX ;
391- let newY = dragStartRef . current . nodeY + canvasDeltaY ;
392-
393403 // Apply snap to grid unless Shift is held
394404 // Simple, predictable: what you see during drag = final position
395405 if ( ! e . shiftKey ) {
@@ -412,9 +422,7 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
412422 lastUpdateTimeRef . current = now ;
413423
414424 // Trigger bounds recalculation so arrows update (use snapped position)
415- const snappedCanvasDeltaX = newX - dragStartRef . current . nodeX ;
416- const snappedCanvasDeltaY = newY - dragStartRef . current . nodeY ;
417- onDragMove ( node . index , snappedCanvasDeltaX , snappedCanvasDeltaY ) ;
425+ onDragMove ( node . index , snappedDeltaX , snappedDeltaY ) ;
418426 }
419427 } ;
420428
@@ -424,15 +432,11 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
424432 if ( ! dragStartRef . current ) return ;
425433
426434 // Calculate final position (same logic as mousemove for consistency)
427- const rawDeltaX = e . clientX - dragStartRef . current . mouseX ;
428- const rawDeltaY = e . clientY - dragStartRef . current . mouseY ;
435+ const canvasPos = screenToCanvasCoords ( e . clientX , e . clientY ) ;
429436
430- const zoomFactor = zoom / 100 ;
431- const canvasDeltaX = rawDeltaX / zoomFactor ;
432- const canvasDeltaY = rawDeltaY / zoomFactor ;
433-
434- let newX = dragStartRef . current . nodeX + canvasDeltaX ;
435- let newY = dragStartRef . current . nodeY + canvasDeltaY ;
437+ // Calculate new node position by adding the stored offset
438+ let newX = canvasPos . x + dragStartRef . current . nodeOffsetX ;
439+ let newY = canvasPos . y + dragStartRef . current . nodeOffsetY ;
436440
437441 // Apply snap to grid unless Shift was held
438442 if ( ! e . shiftKey ) {
@@ -463,7 +467,17 @@ const Node = forwardRef<HTMLDivElement, NodeProps>(
463467 window . removeEventListener ( "mousemove" , handleGlobalMouseMove ) ;
464468 window . removeEventListener ( "mouseup" , handleGlobalMouseUp ) ;
465469 } ;
466- } , [ isDragging , zoom , onDragMove , onDragEnd , node . id , node . index ] ) ;
470+ } , [
471+ isDragging ,
472+ zoom ,
473+ onDragMove ,
474+ onDragEnd ,
475+ onDragStateChange ,
476+ node . id ,
477+ node . index ,
478+ screenToCanvasCoords ,
479+ shiftHeld ,
480+ ] ) ;
467481
468482 // Format text (replace | with line breaks)
469483 const formattedText = text . replace ( / \| / g, "\n" ) ;
0 commit comments