From 892d2f425d078acc45502b5db3a2673d03067ffa Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 18 Jul 2025 19:11:09 +0200 Subject: [PATCH] Bind mode on precise binding Fix binding to inside element Fix initial arrow not following cursor (white dot) Fix elbow arrow --- packages/element/src/binding.ts | 2 +- packages/excalidraw/components/App.tsx | 97 ++++++++++++-------------- 2 files changed, 47 insertions(+), 52 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 292f79f00c..c0edc6dae0 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -139,7 +139,7 @@ export const bindOrUnbindBindingElement = ( ); bindOrUnbindBindingElementEdge(arrow, start, "start", scene); bindOrUnbindBindingElementEdge(arrow, end, "end", scene); - if (start.focusPoint || end.focusPoint) { + if (!isElbowArrow(arrow) && (start.focusPoint || end.focusPoint)) { // If the strategy dictates a focus point override, then // update the arrow points to point to the focus point. const updates: PointsPositionUpdates = new Map(); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index d9f0246fd5..f9b08de1a2 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -6233,41 +6233,52 @@ class App extends React.Component { multiElement.startBinding.mode === "orbit" ) { const elementsMap = this.scene.getNonDeletedElementsMap(); - const startPoint = - LinearElementEditor.getPointAtIndexGlobalCoordinates( - multiElement, - 0, - elementsMap, - ); - const startElement = this.scene.getElement( - multiElement.startBinding.elementId, - ) as ExcalidrawBindableElement; - const localPoint = updateBoundPoint( - multiElement, - "startBinding", - multiElement.startBinding, - startElement, - elementsMap, + const hoveredElement = getHoveredElementForBinding( + pointFrom(scenePointerX, scenePointerY), + this.scene.getNonDeletedElements(), + this.scene.getNonDeletedElementsMap(), + this.state.zoom, ); - const avoidancePoint = localPoint - ? LinearElementEditor.getPointGlobalCoordinates( + if ( + !hoveredElement || + hoveredElement.id !== multiElement.startBinding.elementId + ) { + const startPoint = + LinearElementEditor.getPointAtIndexGlobalCoordinates( multiElement, - localPoint, + 0, elementsMap, - ) - : null; - if (avoidancePoint && !pointsEqual(startPoint, avoidancePoint)) { - const point = LinearElementEditor.pointFromAbsoluteCoords( + ); + const startElement = this.scene.getElement( + multiElement.startBinding.elementId, + ) as ExcalidrawBindableElement; + const localPoint = updateBoundPoint( multiElement, - avoidancePoint, + "startBinding", + multiElement.startBinding, + startElement, elementsMap, ); + const avoidancePoint = localPoint + ? LinearElementEditor.getPointGlobalCoordinates( + multiElement, + localPoint, + elementsMap, + ) + : null; + if (avoidancePoint && !pointsEqual(startPoint, avoidancePoint)) { + const point = LinearElementEditor.pointFromAbsoluteCoords( + multiElement, + avoidancePoint, + elementsMap, + ); - LinearElementEditor.movePoints( - multiElement, - this.scene, - new Map([[0, { point }]]), - ); + LinearElementEditor.movePoints( + multiElement, + this.scene, + new Map([[0, { point }]]), + ); + } } } @@ -6933,9 +6944,13 @@ class App extends React.Component { clearTimeout(this.bindModeHandler); } this.bindModeHandler = null; - this.setState({ - bindMode: "orbit", - }); + // We need this iteration to complete binding and change + // back to orbit mode after that + setTimeout(() => + this.setState({ + bindMode: "orbit", + }), + ); } const scenePointer = viewportCoordsToSceneCoords( @@ -8494,26 +8509,6 @@ class App extends React.Component { event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(), ); - // for arrows/lines, don't start dragging until a given threshold - // to ensure we don't create a 2-point arrow by mistake when - // user clicks mouse in a way that it moves a tiny bit (thus - // triggering pointermove) - if ( - !pointerDownState.drag.hasOccurred && - (this.state.activeTool.type === "arrow" || - this.state.activeTool.type === "line") - ) { - if ( - pointDistance( - pointFrom(pointerCoords.x, pointerCoords.y), - pointFrom(pointerDownState.origin.x, pointerDownState.origin.y), - ) * - this.state.zoom.value < - MINIMUM_ARROW_SIZE - ) { - return; - } - } if (pointerDownState.resize.isResizing) { pointerDownState.lastCoords.x = pointerCoords.x; pointerDownState.lastCoords.y = pointerCoords.y;