Skip to content

fix: curved connector's control-point drag now tracks the cursor at any zoom/pan - #560

Open
bvnaik05 wants to merge 1 commit into
frappe:mainfrom
bvnaik05:fix/curved-connector-drag
Open

fix: curved connector's control-point drag now tracks the cursor at any zoom/pan#560
bvnaik05 wants to merge 1 commit into
frappe:mainfrom
bvnaik05:fix/curved-connector-drag

Conversation

@bvnaik05

Copy link
Copy Markdown
Contributor

Problem

Dragging the midpoint control handle on a curved connector (the dot used to bend the curve) would visibly jump away from the cursor mid-drag, worse the further the drag traveled — described as the dot "randomly spinning off somewhere" instead of tracing the pointer.

Root cause

ConnectorView.vue's toLogical() converted the screen point to logical (world) coordinates via:

const ctm = node.ownerSVGElement.getScreenCTM()

ownerSVGElement is the root canvas <svg>, which has no transform of its own — the pan/zoom lives on a descendant <g :transform="groupTransform"> that every connector and shape renders inside. Calling getScreenCTM() on the root therefore returns a CTM that entirely excludes the pan/zoom transform. At the default 100% zoom with no pan this is indistinguishable from correct (identity difference), which is why it read as "no bug found" under static review — it only shows up once the canvas is panned or zoomed, which is exactly how anyone actually uses a connector mid-session.

Four sibling files already get this right: HoverArrows, MindmapHoverHandles, FlowchartHoverHandles, and HoverOutline all call getScreenCTM() on an element that sits inside the transformed <g> (their own root, via a template ref), which correctly includes the pan/zoom. ConnectorView was the outlier.

Fix

Call getScreenCTM() on the dragged node itself (the handle circle, or the connector's hit-path), not on ownerSVGElement. The node already sits inside the transformed <g>, so its own CTM is exactly the coordinate space its own cx/cy (or the connector's endpoint coordinates) are expressed in.

This helper is shared by all three connector drag gestures (control-point, endpoint re-attach, and whole-body move), so the fix corrects all three — endpoint dragging and body dragging had the identical latent bug, just less noticeable since endpoints snap to shapes.

Testing

  • yarn vitest run connector — 11 files / 63 tests pass.
  • Verified live: created a curved connector, zoomed to 118% and panned the canvas, selected the connector, dragged the control handle to a specific screen point (400, 300). Measured the handle's actual rendered position after the drag: (400.32, 300.46) — sub-pixel accurate. Before the fix this same drag would land far from the release point, growing worse with distance from the pan/zoom origin.

🤖 Generated with Claude Code

…ny zoom/pan

Dragging the midpoint control handle on a curved connector computed
its screen→logical conversion via node.ownerSVGElement.getScreenCTM()
— the root canvas <svg>'s own CTM, which excludes the ancestor <g>'s
pan/zoom transform entirely (the root svg has no transform of its
own; the g does). At the default 100% zoom / no pan this is
indistinguishable from correct, but at any other zoom or pan the
computed logical point diverges from the cursor's real position,
worse the further the cursor sits from the pan/zoom origin — which
reads as the handle "spinning off" during a drag.

HoverArrows, MindmapHoverHandles, FlowchartHoverHandles, and
HoverOutline already get this right: each calls getScreenCTM() on an
element that sits *inside* the transformed <g>, which correctly
includes it. Apply the same fix to ConnectorView's toLogical() — call
getScreenCTM() on the dragged node itself. This also fixes the same
latent bug in endpoint re-attach dragging and connector body dragging,
which shared the same helper.
Copilot AI lite review requested due to automatic review settings August 20, 2026 09:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants