From b01eea9eb4dd309ca999366a969b3144b0b6ea17 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 5 Sep 2025 16:03:24 +0200 Subject: [PATCH] fix: Overlap behavior --- packages/element/src/binding.ts | 49 +++++++-------------- packages/element/src/linearElementEditor.ts | 22 ++++++++- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 4f48c8a54..2d1728202 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -3,6 +3,7 @@ import { arrayToMap, invariant, isAlwaysInsideBinding, + isTransparent, } from "@excalidraw/common"; import { @@ -303,49 +304,31 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = ( } // Check and handle nested shapes - if (arrow.startBinding) { + if (hit && arrow.startBinding) { const otherElement = elementsMap.get( arrow.startBinding.elementId, ) as ExcalidrawBindableElement; - invariant(otherElement, "Other element must be in the elements map"); - const startFocusElements = getAllHoveredElementAtPoint( - getGlobalFixedPointForBindableElement( - arrow.startBinding.fixedPoint, - otherElement, - elementsMap, - ), - elements, - elementsMap, - ); - const startHoverElements = getAllHoveredElementAtPoint( - LinearElementEditor.getPointAtIndexGlobalCoordinates( - arrow, - 0, - elementsMap, - ), - elements, - elementsMap, - ); - if ( - hit && - otherElement.id !== hit.id && - (startHoverElements.find((el) => el.id === hit.id) || - startFocusElements.find((el) => el.id === hit.id)) - ) { + invariant(otherElement, "Other element must be in the elements map"); + + const allHits = getAllHoveredElementAtPoint(point, elements, elementsMap); + + if (allHits.find((el) => el.id === otherElement.id)) { + const otherIsTransparent = isTransparent(otherElement.backgroundColor); + return { start: isMultiPoint ? { mode: undefined } : { - mode: "orbit", + mode: "inside", element: otherElement, - focusPoint: snapToCenter( - otherElement, - elementsMap, - origin ?? pointFrom(arrow.x, arrow.y), - ), + focusPoint: origin ?? pointFrom(arrow.x, arrow.y), }, - end: { mode: "inside", element: hit, focusPoint: point }, + end: { + mode: "inside", + element: otherIsTransparent ? hit : otherElement, + focusPoint: point, + }, }; } } diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 5e742d056..913d66e5e 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -117,6 +117,7 @@ type PointMoveOtherUpdates = { startBinding?: FixedPointBinding | null; endBinding?: FixedPointBinding | null; moveMidPointsWithElement?: boolean | null; + suggestedBinding?: AppState["suggestedBinding"] | null; }; export class LinearElementEditor { @@ -484,6 +485,7 @@ export class LinearElementEditor { } // Apply the point movement if needed + let suggestedBinding: AppState["suggestedBinding"] = null; if (deltaX || deltaY) { const { positions, updates } = pointDraggingUpdates( selectedPointsIndices, @@ -497,6 +499,13 @@ export class LinearElementEditor { LinearElementEditor.movePoints(element, app.scene, positions, updates); + // Set the suggested binding from the updates if available + if (isBindingElement(element, false)) { + if (isBindingEnabled(app.state) && (startIsSelected || endIsSelected)) { + suggestedBinding = updates?.suggestedBinding ?? null; + } + } + // Move the arrow over the bindable object in terms of z-index if (isBindingElement(element) && startIsSelected !== endIsSelected) { moveArrowAboveBindable( @@ -522,7 +531,6 @@ export class LinearElementEditor { } // Suggest bindings for first and last point if selected - let suggestedBinding: AppState["suggestedBinding"] = null; if (isBindingElement(element, false)) { if (isBindingEnabled(app.state) && (startIsSelected || endIsSelected)) { suggestedBinding = maybeSuggestBindingsForBindingElementAtCoords( @@ -2086,7 +2094,9 @@ const pointDraggingUpdates = ( ); // Generate the next bindings for the arrow - const updates: PointMoveOtherUpdates = {}; + const updates: PointMoveOtherUpdates = { + suggestedBinding: null, + }; if (start.mode === null) { updates.startBinding = null; } else if (start.mode) { @@ -2101,6 +2111,10 @@ const pointDraggingUpdates = ( start.focusPoint, ), }; + + if (startIsDragged) { + updates.suggestedBinding = start.element; + } } if (end.mode === null) { updates.endBinding = null; @@ -2116,6 +2130,10 @@ const pointDraggingUpdates = ( end.focusPoint, ), }; + + if (endIsDragged) { + updates.suggestedBinding = end.element; + } } // Simulate the updated arrow for the bind point calculation