fix:More precise element nesting check

This commit is contained in:
Mark Tolmacs
2025-09-12 19:19:49 +02:00
parent 3ab8f67bc6
commit 017b36aeae
2 changed files with 59 additions and 4 deletions

View File

@@ -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 =

View File

@@ -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),
);
};