From 017b36aeae075553a95efae23ae2765af6c09961 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 12 Sep 2025 19:19:49 +0200 Subject: [PATCH] fix:More precise element nesting check --- packages/element/src/binding.ts | 8 ++--- packages/element/src/collision.ts | 55 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 3301173e2e..acf218d916 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -1,6 +1,5 @@ import { KEYS, arrayToMap, invariant, isTransparent } from "@excalidraw/common"; -import { isElementInsideBBox } from "@excalidraw/utils"; import { lineSegment, pointFrom, @@ -31,6 +30,7 @@ import { getAllHoveredElementAtPoint, getHoveredElementForBinding, intersectElementWithLineSegment, + isBindableElementInsideOtherBindable, isPointInElement, } from "./collision"; import { distanceToElement } from "./distance"; @@ -351,7 +351,7 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = ( // We are hovering another element with the end point const isNested = hit && - isElementInsideBBox(otherElement, getElementBounds(hit, elementsMap)); + isBindableElementInsideOtherBindable(otherElement, hit, elementsMap); let current: BindingStrategy; if (hit) { const isInsideBinding = @@ -424,7 +424,7 @@ const bindingStrategyForSimpleArrowEndpointDragging = ( const isNested = hit && oppositeElement && - isElementInsideBBox(oppositeElement, getElementBounds(hit, elementsMap)); + isBindableElementInsideOtherBindable(oppositeElement, hit, elementsMap); // If the global bind mode is in free binding mode, just bind // where the pointer is and keep the other end intact @@ -1267,7 +1267,7 @@ export const updateBoundPoint = ( compareElementArea(bindableElement, otherBindableElement) < 0; const isIntersecting = otherBounds && doBoundsIntersect(bounds, otherBounds); // const isNested = - // otherBindableElement && isElementInsideBBox(otherBindableElement, bounds); + // otherBindableElement && isBindableElementInsideOtherBindable(otherBindableElement, bindableElement); const isNested = isIntersecting && isLargerThanOther; const maybeOutlineGlobal = diff --git a/packages/element/src/collision.ts b/packages/element/src/collision.ts index ff65417c70..6c711fe153 100644 --- a/packages/element/src/collision.ts +++ b/packages/element/src/collision.ts @@ -34,6 +34,7 @@ import { elementCenterPoint, getCenterForBounds, getCubicBezierCurveBound, + getDiamondPoints, getElementBounds, } from "./bounds"; import { @@ -657,3 +658,57 @@ export const isPointInElement = ( return intersections.length % 2 === 1; }; + +export const isBindableElementInsideOtherBindable = ( + innerElement: ExcalidrawBindableElement, + outerElement: ExcalidrawBindableElement, + elementsMap: ElementsMap, +): boolean => { + // Get corner points of the inner element based on its type + const getCornerPoints = (element: ExcalidrawElement): GlobalPoint[] => { + const { x, y, width, height, angle } = element; + const center = elementCenterPoint(element, elementsMap); + + if (element.type === "diamond") { + // Diamond has 4 corner points at the middle of each side + const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = + getDiamondPoints(element); + const corners: GlobalPoint[] = [ + pointFrom(x + topX, y + topY), // top + pointFrom(x + rightX, y + rightY), // right + pointFrom(x + bottomX, y + bottomY), // bottom + pointFrom(x + leftX, y + leftY), // left + ]; + return corners.map((corner) => pointRotateRads(corner, center, angle)); + } + if (element.type === "ellipse") { + // For ellipse, test points at the extremes (top, right, bottom, left) + const cx = x + width / 2; + const cy = y + height / 2; + const rx = width / 2; + const ry = height / 2; + const corners: GlobalPoint[] = [ + pointFrom(cx, cy - ry), // top + pointFrom(cx + rx, cy), // right + pointFrom(cx, cy + ry), // bottom + pointFrom(cx - rx, cy), // left + ]; + return corners.map((corner) => pointRotateRads(corner, center, angle)); + } + // Rectangle and other rectangular shapes (image, text, etc.) + const corners: GlobalPoint[] = [ + pointFrom(x, y), // top-left + pointFrom(x + width, y), // top-right + pointFrom(x + width, y + height), // bottom-right + pointFrom(x, y + height), // bottom-left + ]; + return corners.map((corner) => pointRotateRads(corner, center, angle)); + }; + + const innerCorners = getCornerPoints(innerElement); + + // Check if all corner points of the inner element are inside the outer element + return innerCorners.every((corner) => + isPointInElement(corner, outerElement, elementsMap), + ); +};