From 54ca52e0634970fab29abcafaebc1ae534c9eeed Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 22 Sep 2025 16:44:34 +0200 Subject: [PATCH] fix: Arrow eraser precision & lasso arrow selection --- packages/element/src/bounds.ts | 22 +++++++++++++++++++++- packages/excalidraw/eraser/index.ts | 25 +++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/element/src/bounds.ts b/packages/element/src/bounds.ts index 6b190de1b7..0d57e39333 100644 --- a/packages/element/src/bounds.ts +++ b/packages/element/src/bounds.ts @@ -42,6 +42,7 @@ import { isBoundToContainer, isFreeDrawElement, isLinearElement, + isLineElement, isTextElement, } from "./typeChecks"; @@ -324,9 +325,28 @@ export const getElementLineSegments = ( const points = curves .map((curve) => pointsOnBezierCurves(curve, 10)) .flat(); - let i = 0; const segments: LineSegment[] = []; + + let i = 0; + while (i < points.length - 1) { + const p1 = pointFrom(points[i + 1][0], points[i][1]); + const p2 = pointFrom(points[i][0], points[i + 1][1]); + const alignsWithStart = pointDistance(p1, points[0] as GlobalPoint) < 1; + const alignsWithEnd = + pointDistance(p2, points[points.length - 1] as GlobalPoint) < 1; + + // Avoid closing the polycurve for non-polygon lines and arrows + if ( + ((isLineElement(element) && !element.polygon) || + isArrowElement(element)) && + alignsWithStart && + alignsWithEnd + ) { + i++; + continue; + } + segments.push( lineSegment( pointFrom(points[i][0], points[i][1]), diff --git a/packages/excalidraw/eraser/index.ts b/packages/excalidraw/eraser/index.ts index 8d09b1aafc..d587bb3811 100644 --- a/packages/excalidraw/eraser/index.ts +++ b/packages/excalidraw/eraser/index.ts @@ -2,10 +2,10 @@ import { arrayToMap, easeOut, THEME } from "@excalidraw/common"; import { computeBoundTextPosition, - distanceToElement, doBoundsIntersect, getBoundTextElement, getElementBounds, + getElementLineSegments, getFreedrawOutlineAsSegments, getFreedrawOutlinePoints, intersectElementWithLineSegment, @@ -265,19 +265,28 @@ const eraserTest = ( } return false; - } else if ( - isArrowElement(element) || - (isLineElement(element) && !element.polygon) - ) { + } + + const boundTextElement = getBoundTextElement(element, elementsMap); + + if (isArrowElement(element) || (isLineElement(element) && !element.polygon)) { const tolerance = Math.max( element.strokeWidth, (element.strokeWidth * 2) / zoom, ); - return distanceToElement(element, elementsMap, lastPoint) <= tolerance; - } + // If the eraser movement is so fast that a large distance is covered + // between the last two points, the distanceToElement miss, so we test + // agaist each segment of the linear element + const segments = getElementLineSegments(element, elementsMap); + for (const seg of segments) { + if (lineSegmentsDistance(seg, pathSegment) <= tolerance) { + return true; + } + } - const boundTextElement = getBoundTextElement(element, elementsMap); + return false; + } return ( intersectElementWithLineSegment(element, elementsMap, pathSegment, 0, true)