From d46c38cb279fff41c275fa8cf9d7a55dd8c2952c Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 17 Nov 2025 19:20:18 +0100 Subject: [PATCH] Revert "fix: Arrow angle" This reverts commit 7f0aa5854a97a968c481f775821dda782977cc6b. --- packages/element/src/linearElementEditor.ts | 74 +++++++++---------- .../tests/linearElementEditor.test.tsx | 63 +--------------- packages/excalidraw/data/restore.ts | 26 ++++++- 3 files changed, 63 insertions(+), 100 deletions(-) diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index a1e1e5bb9c..07bab96e97 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -2243,36 +2243,44 @@ const pointDraggingUpdates = ( } // Simulate the updated arrow for the bind point calculation - const updatedPoints = element.points.map((p, idx) => { - const update = naiveDraggingPoints.get(idx); - return update ? update.point : p; - }); - - const offsetX = updatedPoints[0][0]; - const offsetY = updatedPoints[0][1]; - const normalizedPoints = updatedPoints.map((p) => - pointFrom(p[0] - offsetX, p[1] - offsetY), - ); - - const nextCoords = getElementPointsCoords(element, normalizedPoints); - const prevCoords = getElementPointsCoords(element, element.points); - const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2; - const nextCenterY = (nextCoords[1] + nextCoords[3]) / 2; - const prevCenterX = (prevCoords[0] + prevCoords[2]) / 2; - const prevCenterY = (prevCoords[1] + prevCoords[3]) / 2; - const dX = prevCenterX - nextCenterX; - const dY = prevCenterY - nextCenterY; - const rotatedOffset = pointRotateRads( - pointFrom(offsetX, offsetY), - pointFrom(dX, dY), - element.angle, + const originalStartGlobalPoint = + LinearElementEditor.getPointAtIndexGlobalCoordinates( + element, + 0, + elementsMap, + ); + const offsetStartGlobalPoint = startIsDragged + ? pointFrom( + originalStartGlobalPoint[0] + deltaX, + originalStartGlobalPoint[1] + deltaY, + ) + : originalStartGlobalPoint; + const offsetStartLocalPoint = LinearElementEditor.pointFromAbsoluteCoords( + element, + offsetStartGlobalPoint, + elementsMap, ); + const offsetEndLocalPoint = endIsDragged + ? pointFrom( + element.points[element.points.length - 1][0] + deltaX, + element.points[element.points.length - 1][1] + deltaY, + ) + : element.points[element.points.length - 1]; const nextArrow = { ...element, - points: normalizedPoints, - x: element.x + rotatedOffset[0], - y: element.y + rotatedOffset[1], + points: [ + offsetStartLocalPoint, + ...element.points + .slice(1, -1) + .map((p) => + pointFrom( + p[0] + element.x - offsetStartGlobalPoint[0], + p[1] + element.y - offsetStartGlobalPoint[1], + ), + ), + offsetEndLocalPoint, + ], startBinding: updates.startBinding === undefined ? element.startBinding @@ -2365,16 +2373,6 @@ const pointDraggingUpdates = ( ) || nextArrow.points[0] : nextArrow.points[0]; - const startOffset = pointFrom(offsetX, offsetY); - const startLocalPointAbsolute = pointFrom( - startLocalPoint[0] + startOffset[0], - startLocalPoint[1] + startOffset[1], - ); - const endLocalPointAbsolute = pointFrom( - endLocalPoint[0] + startOffset[0], - endLocalPoint[1] + startOffset[1], - ); - const endChanged = pointDistance( endLocalPoint, @@ -2405,9 +2403,9 @@ const pointDraggingUpdates = ( return [ idx, idx === 0 - ? { point: startLocalPointAbsolute, isDragging: true } + ? { point: startLocalPoint, isDragging: true } : idx === element.points.length - 1 - ? { point: endLocalPointAbsolute, isDragging: true } + ? { point: endLocalPoint, isDragging: true } : naiveDraggingPoints.get(idx)!, ]; }), diff --git a/packages/element/tests/linearElementEditor.test.tsx b/packages/element/tests/linearElementEditor.test.tsx index d97d57b2f0..5759c591dd 100644 --- a/packages/element/tests/linearElementEditor.test.tsx +++ b/packages/element/tests/linearElementEditor.test.tsx @@ -1,4 +1,4 @@ -import { pointCenter, pointFrom, pointRotateRads } from "@excalidraw/math"; +import { pointCenter, pointFrom } from "@excalidraw/math"; import { act, queryByTestId, queryByText } from "@testing-library/react"; import { vi } from "vitest"; @@ -24,13 +24,12 @@ import { unmountComponent, } from "@excalidraw/excalidraw/tests/test-utils"; -import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math"; +import type { GlobalPoint, LocalPoint } from "@excalidraw/math"; import { wrapText } from "../src"; import * as textElementUtils from "../src/textElement"; import { getBoundTextElementPosition, getBoundTextMaxWidth } from "../src"; import { LinearElementEditor } from "../src"; -import { elementCenterPoint } from "../src/bounds"; import { newArrowElement } from "../src"; import { @@ -60,7 +59,7 @@ describe("Test Linear Elements", () => { beforeEach(async () => { unmountComponent(); - //localStorage.clear(); + localStorage.clear(); renderInteractiveScene.mockClear(); renderStaticScene.mockClear(); reseed(7); @@ -955,62 +954,6 @@ describe("Test Linear Elements", () => { ] `); }); - - it("keeps rotated arrow start point aligned with pointer while dragging", () => { - const arrow = createThreePointerLinearElement("arrow"); - const angle = 1.2; - h.app.scene.mutateElement(arrow, { angle: angle as Radians }); - - const elementsMap = h.app.scene.getNonDeletedElementsMap(); - const center = elementCenterPoint(arrow, elementsMap); - const expectedStart = pointRotateRads( - pointFrom( - arrow.x + arrow.points[0][0], - arrow.y + arrow.points[0][1], - ), - center, - angle as Radians, - ); - const actualStart = LinearElementEditor.getPointAtIndexGlobalCoordinates( - arrow, - 0, - elementsMap, - ); - - const initialOffset = { - x: expectedStart[0] - actualStart[0], - y: expectedStart[1] - actualStart[1], - }; - expect(Math.hypot(initialOffset.x, initialOffset.y)).toBeGreaterThan(0); - - API.setSelectedElements([arrow]); - enterLineEditingMode(arrow, true); - - const dragOffset = { x: 25, y: -15 }; - const dragTarget = pointFrom( - expectedStart[0] + dragOffset.x, - expectedStart[1] + dragOffset.y, - ); - - mouse.downAt(expectedStart[0], expectedStart[1]); - mouse.moveTo(dragTarget[0], dragTarget[1]); - mouse.upAt(dragTarget[0], dragTarget[1]); - - const updatedMap = h.app.scene.getNonDeletedElementsMap(); - const movedStart = LinearElementEditor.getPointAtIndexGlobalCoordinates( - arrow, - 0, - updatedMap, - ); - - const finalOffset = { - x: dragTarget[0] - movedStart[0], - y: dragTarget[1] - movedStart[1], - }; - - expect(finalOffset.x).toBeCloseTo(initialOffset.x, 6); - expect(finalOffset.y).toBeCloseTo(initialOffset.y, 6); - }); }); describe("Test bound text element", () => { diff --git a/packages/excalidraw/data/restore.ts b/packages/excalidraw/data/restore.ts index 9dd19f1dab..de6027c58e 100644 --- a/packages/excalidraw/data/restore.ts +++ b/packages/excalidraw/data/restore.ts @@ -393,14 +393,35 @@ export const restoreElement = ( ...getSizeFromPoints(points), }); case "arrow": { - const { startArrowhead = null, endArrowhead = "arrow" } = element; + const { + startArrowhead = null, + endArrowhead = "arrow", + angle = 0, + } = element; const x: number | undefined = element.x; const y: number | undefined = element.y; - const points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one + let points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one !Array.isArray(element.points) || element.points.length < 2 ? [pointFrom(0, 0), pointFrom(element.width, element.height)] : element.points; + if (angle !== 0) { + points = LinearElementEditor.getPointsGlobalCoordinates( + element, + elementsMap, + ).map((point) => + LinearElementEditor.pointFromAbsoluteCoords( + element as ExcalidrawArrowElement, + pointRotateRads( + point, + elementCenterPoint(element, elementsMap), + angle, + ), + elementsMap, + ), + ); + } + const base = { type: element.type, startBinding: repairBinding( @@ -422,6 +443,7 @@ export const restoreElement = ( y, elbowed: (element as ExcalidrawArrowElement).elbowed, ...getSizeFromPoints(points), + angle: 0 as Radians, }; // TODO: Separate arrow from linear element