Update simple arrow fixed point when arrow is dragged or moved by arrow keys

This commit is contained in:
Mark Tolmacs
2025-06-26 18:33:13 +02:00
parent 8a2d3f7874
commit 5a3c1469d1
2 changed files with 93 additions and 2 deletions

View File

@ -1400,7 +1400,7 @@ export const snapToMid = (
return p; return p;
}; };
const updateBoundPoint = ( export const updateBoundPoint = (
linearElement: NonDeleted<ExcalidrawLinearElement>, linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "startBinding" | "endBinding", startOrEnd: "startBinding" | "endBinding",
binding: PointBinding | null | undefined, binding: PointBinding | null | undefined,

View File

@ -235,6 +235,8 @@ import {
isLineElement, isLineElement,
isSimpleArrow, isSimpleArrow,
getOutlineAvoidingPoint, getOutlineAvoidingPoint,
isFixedPointBinding,
calculateFixedPointForNonElbowArrowBinding,
} from "@excalidraw/element"; } from "@excalidraw/element";
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math"; import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
@ -261,6 +263,7 @@ import type {
MagicGenerationData, MagicGenerationData,
ExcalidrawArrowElement, ExcalidrawArrowElement,
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
ExcalidrawBindableElement,
} from "@excalidraw/element/types"; } from "@excalidraw/element/types";
import type { Mutable, ValueOf } from "@excalidraw/common/utility-types"; import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
@ -4629,6 +4632,51 @@ class App extends React.Component<AppProps, AppState> {
this.scene, this.scene,
this.state.zoom, 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: [] }); this.setState({ suggestedBindings: [] });
} }
@ -9077,6 +9125,8 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState: PointerDownState, pointerDownState: PointerDownState,
): (event: PointerEvent) => void { ): (event: PointerEvent) => void {
return withBatchedUpdates((childEvent: PointerEvent) => { return withBatchedUpdates((childEvent: PointerEvent) => {
const elementsMap = this.scene.getNonDeletedElementsMap();
this.removePointer(childEvent); this.removePointer(childEvent);
if (pointerDownState.eventListeners.onMove) { if (pointerDownState.eventListeners.onMove) {
pointerDownState.eventListeners.onMove.flush(); pointerDownState.eventListeners.onMove.flush();
@ -9168,7 +9218,6 @@ class App extends React.Component<AppProps, AppState> {
selectedElementsAreBeingDragged: false, selectedElementsAreBeingDragged: false,
bindMode: "focus", bindMode: "focus",
}); });
const elementsMap = this.scene.getNonDeletedElementsMap();
if ( if (
pointerDownState.drag.hasOccurred && pointerDownState.drag.hasOccurred &&
@ -9242,6 +9291,48 @@ class App extends React.Component<AppProps, AppState> {
} }
} }
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(); this.missingPointerEventCleanupEmitter.clear();
window.removeEventListener( window.removeEventListener(