From 5bc59d11e3cd2a40b9b6577fd06af50d2fb978b9 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Tue, 11 Nov 2025 16:48:42 +0100 Subject: [PATCH] fix: Elbow arrow direction at binding --- packages/element/src/binding.ts | 39 ++++++++++++++++++--- packages/element/src/elbowArrow.ts | 23 ++++++++++-- packages/element/src/linearElementEditor.ts | 27 ++++++++++---- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index f14ee633cd..6bc3deb995 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -223,6 +223,7 @@ const bindingStrategyForElbowArrowEndpointDragging = ( draggingPoints: PointsPositionUpdates, elementsMap: NonDeletedSceneElementsMap, elements: readonly Ordered[], + zoom?: AppState["zoom"], ): { start: BindingStrategy; end: BindingStrategy; @@ -242,7 +243,13 @@ const bindingStrategyForElbowArrowEndpointDragging = ( point, elementsMap, ); - const hit = getHoveredElementForBinding(globalPoint, elements, elementsMap); + const hit = getHoveredElementForBinding( + globalPoint, + elements, + elementsMap, + (element) => + maxBindingGap_simple(element, element.width, element.height, zoom), + ); const current = hit ? { @@ -558,6 +565,7 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = ( altKey?: boolean; finalize?: boolean; initialBinding?: boolean; + zoom?: AppState["zoom"]; }, ): { start: BindingStrategy; end: BindingStrategy } => { if (getFeatureFlag("COMPLEX_BINDINGS")) { @@ -593,6 +601,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( altKey?: boolean; finalize?: boolean; initialBinding?: boolean; + zoom?: AppState["zoom"]; }, ): { start: BindingStrategy; end: BindingStrategy } => { const startIdx = 0; @@ -635,6 +644,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( draggingPoints, elementsMap, elements, + opts?.zoom, ); } @@ -1156,6 +1166,7 @@ export const getHeadingForElbowArrowSnap = ( aabb: Bounds | undefined | null, origPoint: GlobalPoint, elementsMap: ElementsMap, + zoom?: AppState["zoom"], ): Heading => { const otherPointHeading = vectorToHeading(vectorFromPoint(otherPoint, p)); @@ -1163,9 +1174,12 @@ export const getHeadingForElbowArrowSnap = ( return otherPointHeading; } - const d = distanceToElement(bindableElement, elementsMap, origPoint); - - const distance = d > 0 ? null : d; + const distance = getDistanceForBinding( + origPoint, + bindableElement, + elementsMap, + zoom, + ); if (!distance) { return vectorToHeading( @@ -1176,6 +1190,23 @@ export const getHeadingForElbowArrowSnap = ( return headingForPointFromElement(bindableElement, aabb, p); }; +const getDistanceForBinding = ( + point: Readonly, + bindableElement: ExcalidrawBindableElement, + elementsMap: ElementsMap, + zoom?: AppState["zoom"], +) => { + const distance = distanceToElement(bindableElement, elementsMap, point); + const bindDistance = maxBindingGap_simple( + bindableElement, + bindableElement.width, + bindableElement.height, + zoom, + ); + + return distance > bindDistance ? null : distance; +}; + export const bindPointToSnapToElementOutline = ( linearElement: ExcalidrawArrowElement, bindableElement: ExcalidrawBindableElement, diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index d62f328a71..e8b9cbffe0 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -30,6 +30,7 @@ import { getHeadingForElbowArrowSnap, getGlobalFixedPointForBindableElement, getFixedBindingDistance, + maxBindingGap_simple, } from "./binding"; import { distanceToElement } from "./distance"; import { @@ -1217,9 +1218,19 @@ const getElbowArrowData = ( if (options?.isDragging) { const elements = Array.from(elementsMap.values()); hoveredStartElement = - getHoveredElement(origStartGlobalPoint, elementsMap, elements) || null; + getHoveredElement( + origStartGlobalPoint, + elementsMap, + elements, + options?.zoom, + ) || null; hoveredEndElement = - getHoveredElement(origEndGlobalPoint, elementsMap, elements) || null; + getHoveredElement( + origEndGlobalPoint, + elementsMap, + elements, + options?.zoom, + ) || null; } else { hoveredStartElement = arrow.startBinding ? getBindableElementForId(arrow.startBinding.elementId, elementsMap) || @@ -1264,6 +1275,7 @@ const getElbowArrowData = ( hoveredStartElement, origStartGlobalPoint, elementsMap, + options?.zoom, ); const endHeading = getBindPointHeading( endGlobalPoint, @@ -1271,6 +1283,7 @@ const getElbowArrowData = ( hoveredEndElement, origEndGlobalPoint, elementsMap, + options?.zoom, ); const startPointBounds = [ startGlobalPoint[0] - 2, @@ -2229,6 +2242,7 @@ const getBindPointHeading = ( hoveredElement: ExcalidrawBindableElement | null | undefined, origPoint: GlobalPoint, elementsMap: ElementsMap, + zoom?: AppState["zoom"], ): Heading => getHeadingForElbowArrowSnap( p, @@ -2247,18 +2261,21 @@ const getBindPointHeading = ( ), origPoint, elementsMap, + zoom, ); const getHoveredElement = ( origPoint: GlobalPoint, elementsMap: NonDeletedSceneElementsMap, elements: readonly Ordered[], + zoom?: AppState["zoom"], ) => { return getHoveredElementForBinding( origPoint, elements, elementsMap, - (element) => getFixedBindingDistance(element) + 1, + (element) => + maxBindingGap_simple(element, element.width, element.height, zoom), ); }; diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 3b0484f2cc..e8b089cf46 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -2142,7 +2142,7 @@ const pointDraggingUpdates = ( ); // Linear elements have no special logic - if (!isArrowElement(element) || isElbowArrow(element)) { + if (!isArrowElement(element)) { return { positions: naiveDraggingPoints, }; @@ -2153,12 +2153,6 @@ const pointDraggingUpdates = ( element.points.length - 1, ); - if (startIsDragged === endIsDragged) { - return { - positions: naiveDraggingPoints, - }; - } - const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints( element, naiveDraggingPoints, @@ -2172,6 +2166,25 @@ const pointDraggingUpdates = ( }, ); + if (isElbowArrow(element)) { + return { + positions: naiveDraggingPoints, + updates: { + suggestedBinding: startIsDragged + ? start.element + : endIsDragged + ? end.element + : null, + }, + }; + } + + if (startIsDragged === endIsDragged) { + return { + positions: naiveDraggingPoints, + }; + } + // Generate the next bindings for the arrow const updates: PointMoveOtherUpdates = { suggestedBinding: null,