From 2a4f7e94b10451891a40e81afac8ea083bc742ec Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 29 Sep 2025 09:51:47 +0200 Subject: [PATCH] fix: Allow already inside bound arrows to continue inside binding Signed-off-by: Mark Tolmacs --- packages/element/src/binding.ts | 14 +++++++++++ packages/excalidraw/components/App.tsx | 35 +++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 2937a516f2..2a368b8ab4 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -390,6 +390,7 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = ( const bindingStrategyForSimpleArrowEndpointDragging = ( point: GlobalPoint, + currentBinding: FixedPointBinding | null, oppositeBinding: FixedPointBinding | null, elementsMap: NonDeletedSceneElementsMap, elements: readonly Ordered[], @@ -441,6 +442,17 @@ const bindingStrategyForSimpleArrowEndpointDragging = ( return { current: { mode: null }, other }; } + // Already inside binding over the same hit element should remain inside bound + if ( + hit.id === currentBinding?.elementId && + currentBinding.mode === "inside" + ) { + return { + current: { mode: "inside", focusPoint: point, element: hit }, + other, + }; + } + // The dragged point is inside the hovered bindable element if (oppositeBinding) { // The opposite binding is on the same element @@ -579,6 +591,7 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = ( const { current, other } = bindingStrategyForSimpleArrowEndpointDragging( globalPoint, + arrow.startBinding, arrow.endBinding, elementsMap, elements, @@ -600,6 +613,7 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = ( ); const { current, other } = bindingStrategyForSimpleArrowEndpointDragging( globalPoint, + arrow.endBinding, arrow.startBinding, elementsMap, elements, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 1c8d3dac3f..c71be79ce8 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1108,6 +1108,38 @@ class App extends React.Component { ); } + const startDragged = + this.state.selectedLinearElement?.selectedPointsIndices?.includes(0); + const endDragged = + this.state.selectedLinearElement?.selectedPointsIndices?.includes( + arrow.points.length - 1, + ); + const currentBinding = startDragged + ? "startBinding" + : endDragged + ? "endBinding" + : null; + const isAlreadyInsideBindingToSameElement = startDragged + ? arrow.startBinding?.mode === "inside" && + arrow.startBinding?.elementId === hoveredElement?.id + : endDragged + ? arrow.endBinding?.mode === "inside" && + arrow.endBinding?.elementId === hoveredElement?.id + : false; + if ( + currentBinding && + arrow[currentBinding]?.mode === "inside" && + hoveredElement?.id !== arrow[currentBinding]?.elementId + ) { + // Update binding out of place to orbit mode + this.scene.mutateElement(arrow, { + [currentBinding]: { + ...arrow[currentBinding], + mode: "orbit", + }, + }); + } + if ( !hoveredElement || (this.previousHoveredBindableElement && @@ -1131,7 +1163,8 @@ class App extends React.Component { this.previousHoveredBindableElement = null; } else if ( !this.bindModeHandler && - (!this.state.newElement || !arrow.startBinding || isOverlapping) + (!this.state.newElement || !arrow.startBinding || isOverlapping) && + !isAlreadyInsideBindingToSameElement ) { // We are hovering a bindable element this.bindModeHandler = setTimeout(effector, BIND_MODE_TIMEOUT);