@@ -2361,6 +2361,7 @@ function NodeCanvas() {
23612361 // CRITICAL: Update ref synchronously BEFORE setPanOffset
23622362 // This ensures handleMouseMove's RAF sees the new value immediately
23632363 // (useEffect that syncs ref from state runs AFTER RAF callbacks)
2364+ console.log('[PanLoop] Updating panOffsetRef:', newPan);
23642365 panOffsetRef.current = newPan;
23652366
23662367 // Use setPanOffset directly to allow React to batch this update with the
@@ -5208,7 +5209,9 @@ function NodeCanvas() {
52085209 zoomLevel,
52095210 canvasSize.offsetX,
52105211 canvasSize.offsetY,
5211- storeActions
5212+ canvasSize.offsetY,
5213+ storeActions,
5214+ triggerDragZoomOut
52125215 ]);
52135216
52145217 const handleNodeMouseDown = (nodeData, e) => { // nodeData is now a hydrated node (instance + prototype)
@@ -7104,16 +7107,26 @@ function NodeCanvas() {
71047107 const dropWorldX = (dropX - currentPan.x) / currentZoom + canvasSizeRef.current.offsetX;
71057108 const dropWorldY = (dropY - currentPan.y) / currentZoom + canvasSizeRef.current.offsetY;
71067109
7107- // 2. Calculate NEW pan to keep that world point at the CENTER of the viewport using TARGET zoom
7110+ // 2. Calculate NEW pan to keep that world point under the MOUSE CURSOR using TARGET zoom
71087111 // panX = screenX - (worldX - offsetX) * zoom
7109- const viewportCenterX = rect.width / 2;
7110- const viewportCenterY = rect.height / 2;
7111- const targetPanX = viewportCenterX - (dropWorldX - canvasSizeRef.current.offsetX) * targetZoom;
7112- const targetPanY = viewportCenterY - (dropWorldY - canvasSizeRef.current.offsetY) * targetZoom;
7113-
7114- // Clamp the new pan to bounds
7115- const minPanX = viewportSizeRef.current.width - canvasSizeRef.current.width * targetZoom;
7116- const minPanY = viewportSizeRef.current.height - canvasSizeRef.current.height * targetZoom;
7112+ // This prevents "snapping" to the center if the node was at the edge
7113+ const targetPanX = dropX - (dropWorldX - canvasSizeRef.current.offsetX) * targetZoom;
7114+ const targetPanY = dropY - (dropWorldY - canvasSizeRef.current.offsetY) * targetZoom;
7115+
7116+ console.log('[ZoomRestore] Debug:', {
7117+ dropX, dropY,
7118+ currentPan, currentZoom,
7119+ dropWorldX, dropWorldY,
7120+ targetZoom,
7121+ targetPanX, targetPanY,
7122+ viewportSize: viewportSizeRef.current
7123+ });
7124+
7125+ // Relaxed clamping - prevent aggressive snap-back by allowing full canvas traverse
7126+ // If node was dragged to edge, targetPan might briefly exceed standard bounds logic
7127+ const minPanX = -(canvasSizeRef.current.width * targetZoom);
7128+ const minPanY = -(canvasSizeRef.current.height * targetZoom);
7129+
71177130 const clampedTargetPanX = Math.min(0, Math.max(targetPanX, minPanX));
71187131 const clampedTargetPanY = Math.min(0, Math.max(targetPanY, minPanY));
71197132
@@ -10597,9 +10610,11 @@ function NodeCanvas() {
1059710610 const curveInfo = edgeCurveInfo.get(edge.id);
1059810611 if (curveInfo && curveInfo.totalInPair > 1) {
1059910612 const { pairIndex, totalInPair } = curveInfo;
10600- const curveSpacing = 40;
10601- const offsetIndex = pairIndex - (totalInPair - 1) / 2;
10602- const perpOffset = offsetIndex * curveSpacing;
10613+ const curveSpacing = 100;
10614+ // Alternating direction logic matching utils
10615+ const direction = pairIndex % 2 === 0 ? 1 : -1;
10616+ const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
10617+ const perpOffset = direction * offsetMagnitude;
1060310618 const edgeDx = endX - startX;
1060410619 const edgeDy = endY - startY;
1060510620 const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
@@ -10667,10 +10682,10 @@ function NodeCanvas() {
1066710682 if (curveInfo && curveInfo.totalInPair > 1) {
1066810683 // Calculate curve offset for parallel edges
1066910684 const { pairIndex, totalInPair } = curveInfo;
10670- const curveSpacing = 40 ; // Pixels between parallel edge curves
10671- // Center the curves: offset from -half to +half of total spread
10672- const offsetIndex = pairIndex - (totalInPair - 1) / 2;
10673- const perpOffset = offsetIndex * curveSpacing ;
10685+ const curveSpacing = 100 ; // Exaggerated curves
10686+ const direction = pairIndex % 2 === 0 ? 1 : -1;
10687+ const offsetMagnitude = Math.floor(( pairIndex + 1) / 2) * curveSpacing ;
10688+ const perpOffset = direction * offsetMagnitude ;
1067410689
1067510690 // Calculate perpendicular direction
1067610691 const edgeDx = endX - startX;
@@ -10728,9 +10743,31 @@ function NodeCanvas() {
1072810743 angle = 90;
1072910744 }
1073010745 } else {
10731- midX = (x1 + x2) / 2;
10732- midY = (y1 + y2) / 2;
10733- angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);
10746+ // Curve apex logic for labels
10747+ const curveInfo = edgeCurveInfo.get(edge.id);
10748+ if (curveInfo && curveInfo.totalInPair > 1) {
10749+ const { pairIndex } = curveInfo;
10750+ const curveSpacing = 100;
10751+ const direction = pairIndex % 2 === 0 ? 1 : -1;
10752+ const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
10753+ const perpOffset = direction * offsetMagnitude;
10754+ const edgeDx = endX - startX;
10755+ const edgeDy = endY - startY;
10756+ const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
10757+ const perpX = edgeLen > 0 ? -edgeDy / edgeLen : 0;
10758+ const perpY = edgeLen > 0 ? edgeDx / edgeLen : 0;
10759+ const curveMidX = (startX + endX) / 2;
10760+ const curveMidY = (startY + endY) / 2;
10761+ const ctrlX = curveMidX + perpX * perpOffset;
10762+ const ctrlY = curveMidY + perpY * perpOffset;
10763+ midX = 0.25 * startX + 0.5 * ctrlX + 0.25 * endX;
10764+ midY = 0.25 * startY + 0.5 * ctrlY + 0.25 * endY;
10765+ angle = Math.atan2(edgeDy, edgeDx) * (180 / Math.PI);
10766+ } else {
10767+ midX = (x1 + x2) / 2;
10768+ midY = (y1 + y2) / 2;
10769+ angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);
10770+ }
1073410771 }
1073510772
1073610773 // Determine connection name to display
@@ -11728,9 +11765,11 @@ function NodeCanvas() {
1172811765 const curveInfo = edgeCurveInfo.get(edge.id);
1172911766 if (curveInfo && curveInfo.totalInPair > 1) {
1173011767 const { pairIndex, totalInPair } = curveInfo;
11731- const curveSpacing = 40;
11732- const offsetIndex = pairIndex - (totalInPair - 1) / 2;
11733- const perpOffset = offsetIndex * curveSpacing;
11768+ const curveSpacing = 100;
11769+ // Alternating direction logic matching utils
11770+ const direction = pairIndex % 2 === 0 ? 1 : -1;
11771+ const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
11772+ const perpOffset = direction * offsetMagnitude;
1173411773 const edgeDx = endX - startX;
1173511774 const edgeDy = endY - startY;
1173611775 const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
@@ -11798,10 +11837,11 @@ function NodeCanvas() {
1179811837 if (curveInfo && curveInfo.totalInPair > 1) {
1179911838 // Calculate curve offset for parallel edges
1180011839 const { pairIndex, totalInPair } = curveInfo;
11801- const curveSpacing = 40; // Pixels between parallel edge curves
11802- // Center the curves: offset from -half to +half of total spread
11803- const offsetIndex = pairIndex - (totalInPair - 1) / 2;
11804- const perpOffset = offsetIndex * curveSpacing;
11840+ const curveSpacing = 100; // Pixels between parallel edge curves
11841+ // Alternating direction logic matching utils
11842+ const direction = pairIndex % 2 === 0 ? 1 : -1;
11843+ const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
11844+ const perpOffset = direction * offsetMagnitude;
1180511845
1180611846 // Calculate perpendicular direction
1180711847 const edgeDx = endX - startX;
@@ -11859,9 +11899,35 @@ function NodeCanvas() {
1185911899 angle = 90;
1186011900 }
1186111901 } else {
11862- midX = (x1 + x2) / 2;
11863- midY = (y1 + y2) / 2;
11864- angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);
11902+ // Default Straight / Curve logic
11903+ const curveInfo = edgeCurveInfo.get(edge.id);
11904+ if (curveInfo && curveInfo.totalInPair > 1) {
11905+ // Curve Apex Logic for Labels
11906+ const { pairIndex } = curveInfo;
11907+ const curveSpacing = 100;
11908+ const direction = pairIndex % 2 === 0 ? 1 : -1;
11909+ const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
11910+ const perpOffset = direction * offsetMagnitude;
11911+
11912+ const edgeDx = endX - startX;
11913+ const edgeDy = endY - startY;
11914+ const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
11915+ const perpX = edgeLen > 0 ? -edgeDy / edgeLen : 0;
11916+ const perpY = edgeLen > 0 ? edgeDx / edgeLen : 0;
11917+ const curveMidX = (startX + endX) / 2;
11918+ const curveMidY = (startY + endY) / 2;
11919+ const ctrlX = curveMidX + perpX * perpOffset;
11920+ const ctrlY = curveMidY + perpY * perpOffset;
11921+
11922+ // Apex at t=0.5
11923+ midX = 0.25 * startX + 0.5 * ctrlX + 0.25 * endX;
11924+ midY = 0.25 * startY + 0.5 * ctrlY + 0.25 * endY;
11925+ angle = Math.atan2(edgeDy, edgeDx) * (180 / Math.PI); // Tangent at apex is parallel to edge
11926+ } else {
11927+ midX = (x1 + x2) / 2;
11928+ midY = (y1 + y2) / 2;
11929+ angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);
11930+ }
1186511931 }
1186611932
1186711933 // Determine connection name to display
0 commit comments