From 3ae89aba47f423f54f89d09f388d6ef7942e5998 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 23 Jun 2025 17:01:12 +0200 Subject: [PATCH] Unbind arrow if bound and moved at shaft of arrow key --- packages/element/src/dragElements.ts | 9 ++++++++- packages/element/src/linearElementEditor.ts | 21 ++++++++++++++++++--- packages/excalidraw/components/App.tsx | 9 +++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/element/src/dragElements.ts b/packages/element/src/dragElements.ts index 4b17ba20c..dcff9b406 100644 --- a/packages/element/src/dragElements.ts +++ b/packages/element/src/dragElements.ts @@ -13,7 +13,7 @@ import type { import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types"; -import { updateBoundElements } from "./binding"; +import { bindOrUnbindLinearElement, updateBoundElements } from "./binding"; import { getCommonBounds } from "./bounds"; import { getPerfectElementSize } from "./sizeHelpers"; import { getBoundTextElement } from "./textElement"; @@ -118,9 +118,16 @@ export const dragSelectedElements = ( adjustedOffset, ); } + updateBoundElements(element, scene, { simultaneouslyUpdated: Array.from(elementsToUpdate), }); + } else { + // NOTE: Moving the bound arrow should unbind it, otherwise we would + // have weird situations, like 0 lenght arrow when the user moves + // the arrow outside a filled shape suddenly forcing the arrow start + // and end point to jump "outside" the shape. + bindOrUnbindLinearElement(element, null, null, scene); } }); }; diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index d6ac42a39..a4eecfc58 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -2056,14 +2056,29 @@ const pointDraggingUpdates = ( isElbowArrow(element), ); - const otherBinding = - element[pointIndex === 0 ? "endBinding" : "startBinding"]; + const otherGlobalPoint = + LinearElementEditor.getPointAtIndexGlobalCoordinates( + element, + pointIndex === 0 ? element.points.length - 1 : 0, + elementsMap, + ); + const otherHoveredElement = getHoveredElementForBinding( + { + x: otherGlobalPoint[0], + y: otherGlobalPoint[1], + }, + elements, + elementsMap, + zoom, + shouldTestInside(element), + isElbowArrow(element), + ); // Allow binding inside the element if both ends are inside if ( isArrowElement(element) && !( - hoveredElement?.id === otherBinding?.elementId && + hoveredElement?.id === otherHoveredElement?.id && hoveredElement != null ) ) { diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 8cd8608a3..b88d813fc 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -233,6 +233,7 @@ import { isLineElement, isSimpleArrow, getOutlineAvoidingPoint, + bindOrUnbindLinearElement, } from "@excalidraw/element"; import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math"; @@ -4388,6 +4389,14 @@ class App extends React.Component { { informMutation: false, isDragging: false }, ); + if (isSimpleArrow(element)) { + // NOTE: Moving the bound arrow should unbind it, otherwise we would + // have weird situations, like 0 lenght arrow when the user moves + // the arrow outside a filled shape suddenly forcing the arrow start + // and end point to jump "outside" the shape. + bindOrUnbindLinearElement(element, null, null, this.scene); + } + updateBoundElements(element, this.scene, { simultaneouslyUpdated: selectedElements, });