From 5a3c1469d119b1910c751e82be09058c52000920 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 26 Jun 2025 18:33:13 +0200 Subject: [PATCH] Update simple arrow fixed point when arrow is dragged or moved by arrow keys --- packages/element/src/binding.ts | 2 +- packages/excalidraw/components/App.tsx | 93 +++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index bca0e16282..5d5f30b53c 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -1400,7 +1400,7 @@ export const snapToMid = ( return p; }; -const updateBoundPoint = ( +export const updateBoundPoint = ( linearElement: NonDeleted, startOrEnd: "startBinding" | "endBinding", binding: PointBinding | null | undefined, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 5b910b921a..36cf8947ae 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -235,6 +235,8 @@ import { isLineElement, isSimpleArrow, getOutlineAvoidingPoint, + isFixedPointBinding, + calculateFixedPointForNonElbowArrowBinding, } from "@excalidraw/element"; import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math"; @@ -261,6 +263,7 @@ import type { MagicGenerationData, ExcalidrawArrowElement, ExcalidrawElbowArrowElement, + ExcalidrawBindableElement, } from "@excalidraw/element/types"; import type { Mutable, ValueOf } from "@excalidraw/common/utility-types"; @@ -4629,6 +4632,51 @@ class App extends React.Component { this.scene, this.state.zoom, ); + + const elementsMap = this.scene.getNonDeletedElementsMap(); + + this.scene + .getSelectedElements(this.state) + .filter(isSimpleArrow) + .forEach((element) => { + // Update the fixed point bindings for non-elbow arrows + // when the pointer is released, so that they are correctly positioned + // after the drag. + if ( + element.startBinding && + isFixedPointBinding(element.startBinding) + ) { + this.scene.mutateElement(element, { + startBinding: { + ...element.startBinding, + ...calculateFixedPointForNonElbowArrowBinding( + element, + elementsMap.get( + element.startBinding.elementId, + ) as ExcalidrawBindableElement, + "start", + elementsMap, + ), + }, + }); + } + if (element.endBinding && isFixedPointBinding(element.endBinding)) { + this.scene.mutateElement(element, { + endBinding: { + ...element.endBinding, + ...calculateFixedPointForNonElbowArrowBinding( + element, + elementsMap.get( + element.endBinding.elementId, + ) as ExcalidrawBindableElement, + "end", + elementsMap, + ), + }, + }); + } + }); + this.setState({ suggestedBindings: [] }); } @@ -9077,6 +9125,8 @@ class App extends React.Component { pointerDownState: PointerDownState, ): (event: PointerEvent) => void { return withBatchedUpdates((childEvent: PointerEvent) => { + const elementsMap = this.scene.getNonDeletedElementsMap(); + this.removePointer(childEvent); if (pointerDownState.eventListeners.onMove) { pointerDownState.eventListeners.onMove.flush(); @@ -9168,7 +9218,6 @@ class App extends React.Component { selectedElementsAreBeingDragged: false, bindMode: "focus", }); - const elementsMap = this.scene.getNonDeletedElementsMap(); if ( pointerDownState.drag.hasOccurred && @@ -9242,6 +9291,48 @@ class App extends React.Component { } } + this.scene + .getSelectedElements(this.state) + .filter(isSimpleArrow) + .forEach((element) => { + // Update the fixed point bindings for non-elbow arrows + // when the pointer is released, so that they are correctly positioned + // after the drag. + if ( + element.startBinding && + isFixedPointBinding(element.startBinding) + ) { + this.scene.mutateElement(element, { + startBinding: { + ...element.startBinding, + ...calculateFixedPointForNonElbowArrowBinding( + element, + elementsMap.get( + element.startBinding.elementId, + ) as ExcalidrawBindableElement, + "start", + elementsMap, + ), + }, + }); + } + if (element.endBinding && isFixedPointBinding(element.endBinding)) { + this.scene.mutateElement(element, { + endBinding: { + ...element.endBinding, + ...calculateFixedPointForNonElbowArrowBinding( + element, + elementsMap.get( + element.endBinding.elementId, + ) as ExcalidrawBindableElement, + "end", + elementsMap, + ), + }, + }); + } + }); + this.missingPointerEventCleanupEmitter.clear(); window.removeEventListener(