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 { KEYS, arrayToMap, invariant, isTransparent } from "@excalidraw/common";
import { isElementInsideBBox } from "@excalidraw/utils";
import { import {
lineSegment, lineSegment,
pointFrom, pointFrom,
@@ -31,6 +30,7 @@ import {
getAllHoveredElementAtPoint, getAllHoveredElementAtPoint,
getHoveredElementForBinding, getHoveredElementForBinding,
intersectElementWithLineSegment, intersectElementWithLineSegment,
isBindableElementInsideOtherBindable,
isPointInElement, isPointInElement,
} from "./collision"; } from "./collision";
import { distanceToElement } from "./distance"; import { distanceToElement } from "./distance";
@@ -351,7 +351,7 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = (
// We are hovering another element with the end point // We are hovering another element with the end point
const isNested = const isNested =
hit && hit &&
isElementInsideBBox(otherElement, getElementBounds(hit, elementsMap)); isBindableElementInsideOtherBindable(otherElement, hit, elementsMap);
let current: BindingStrategy; let current: BindingStrategy;
if (hit) { if (hit) {
const isInsideBinding = const isInsideBinding =
@@ -424,7 +424,7 @@ const bindingStrategyForSimpleArrowEndpointDragging = (
const isNested = const isNested =
hit && hit &&
oppositeElement && oppositeElement &&
isElementInsideBBox(oppositeElement, getElementBounds(hit, elementsMap)); isBindableElementInsideOtherBindable(oppositeElement, hit, elementsMap);
// If the global bind mode is in free binding mode, just bind // If the global bind mode is in free binding mode, just bind
// where the pointer is and keep the other end intact // where the pointer is and keep the other end intact
@@ -1267,7 +1267,7 @@ export const updateBoundPoint = (
compareElementArea(bindableElement, otherBindableElement) < 0; compareElementArea(bindableElement, otherBindableElement) < 0;
const isIntersecting = otherBounds && doBoundsIntersect(bounds, otherBounds); const isIntersecting = otherBounds && doBoundsIntersect(bounds, otherBounds);
// const isNested = // const isNested =
// otherBindableElement && isElementInsideBBox(otherBindableElement, bounds); // otherBindableElement && isBindableElementInsideOtherBindable(otherBindableElement, bindableElement);
const isNested = isIntersecting && isLargerThanOther; const isNested = isIntersecting && isLargerThanOther;
const maybeOutlineGlobal = const maybeOutlineGlobal =

View File

@@ -34,6 +34,7 @@ import {
elementCenterPoint, elementCenterPoint,
getCenterForBounds, getCenterForBounds,
getCubicBezierCurveBound, getCubicBezierCurveBound,
getDiamondPoints,
getElementBounds, getElementBounds,
} from "./bounds"; } from "./bounds";
import { import {
@@ -657,3 +658,57 @@ export const isPointInElement = (
return intersections.length % 2 === 1; 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),
);
};