fix: Elbow arrow direction at binding

This commit is contained in:
Mark Tolmacs
2025-11-11 16:48:42 +01:00
parent dfcda03a0a
commit 5bc59d11e3
3 changed files with 75 additions and 14 deletions

View File

@@ -223,6 +223,7 @@ const bindingStrategyForElbowArrowEndpointDragging = (
draggingPoints: PointsPositionUpdates,
elementsMap: NonDeletedSceneElementsMap,
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
zoom?: AppState["zoom"],
): {
start: BindingStrategy;
end: BindingStrategy;
@@ -242,7 +243,13 @@ const bindingStrategyForElbowArrowEndpointDragging = (
point,
elementsMap,
);
const hit = getHoveredElementForBinding(globalPoint, elements, elementsMap);
const hit = getHoveredElementForBinding(
globalPoint,
elements,
elementsMap,
(element) =>
maxBindingGap_simple(element, element.width, element.height, zoom),
);
const current = hit
? {
@@ -558,6 +565,7 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
altKey?: boolean;
finalize?: boolean;
initialBinding?: boolean;
zoom?: AppState["zoom"];
},
): { start: BindingStrategy; end: BindingStrategy } => {
if (getFeatureFlag("COMPLEX_BINDINGS")) {
@@ -593,6 +601,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
altKey?: boolean;
finalize?: boolean;
initialBinding?: boolean;
zoom?: AppState["zoom"];
},
): { start: BindingStrategy; end: BindingStrategy } => {
const startIdx = 0;
@@ -635,6 +644,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
draggingPoints,
elementsMap,
elements,
opts?.zoom,
);
}
@@ -1156,6 +1166,7 @@ export const getHeadingForElbowArrowSnap = (
aabb: Bounds | undefined | null,
origPoint: GlobalPoint,
elementsMap: ElementsMap,
zoom?: AppState["zoom"],
): Heading => {
const otherPointHeading = vectorToHeading(vectorFromPoint(otherPoint, p));
@@ -1163,9 +1174,12 @@ export const getHeadingForElbowArrowSnap = (
return otherPointHeading;
}
const d = distanceToElement(bindableElement, elementsMap, origPoint);
const distance = d > 0 ? null : d;
const distance = getDistanceForBinding(
origPoint,
bindableElement,
elementsMap,
zoom,
);
if (!distance) {
return vectorToHeading(
@@ -1176,6 +1190,23 @@ export const getHeadingForElbowArrowSnap = (
return headingForPointFromElement(bindableElement, aabb, p);
};
const getDistanceForBinding = (
point: Readonly<GlobalPoint>,
bindableElement: ExcalidrawBindableElement,
elementsMap: ElementsMap,
zoom?: AppState["zoom"],
) => {
const distance = distanceToElement(bindableElement, elementsMap, point);
const bindDistance = maxBindingGap_simple(
bindableElement,
bindableElement.width,
bindableElement.height,
zoom,
);
return distance > bindDistance ? null : distance;
};
export const bindPointToSnapToElementOutline = (
linearElement: ExcalidrawArrowElement,
bindableElement: ExcalidrawBindableElement,

View File

@@ -30,6 +30,7 @@ import {
getHeadingForElbowArrowSnap,
getGlobalFixedPointForBindableElement,
getFixedBindingDistance,
maxBindingGap_simple,
} from "./binding";
import { distanceToElement } from "./distance";
import {
@@ -1217,9 +1218,19 @@ const getElbowArrowData = (
if (options?.isDragging) {
const elements = Array.from(elementsMap.values());
hoveredStartElement =
getHoveredElement(origStartGlobalPoint, elementsMap, elements) || null;
getHoveredElement(
origStartGlobalPoint,
elementsMap,
elements,
options?.zoom,
) || null;
hoveredEndElement =
getHoveredElement(origEndGlobalPoint, elementsMap, elements) || null;
getHoveredElement(
origEndGlobalPoint,
elementsMap,
elements,
options?.zoom,
) || null;
} else {
hoveredStartElement = arrow.startBinding
? getBindableElementForId(arrow.startBinding.elementId, elementsMap) ||
@@ -1264,6 +1275,7 @@ const getElbowArrowData = (
hoveredStartElement,
origStartGlobalPoint,
elementsMap,
options?.zoom,
);
const endHeading = getBindPointHeading(
endGlobalPoint,
@@ -1271,6 +1283,7 @@ const getElbowArrowData = (
hoveredEndElement,
origEndGlobalPoint,
elementsMap,
options?.zoom,
);
const startPointBounds = [
startGlobalPoint[0] - 2,
@@ -2229,6 +2242,7 @@ const getBindPointHeading = (
hoveredElement: ExcalidrawBindableElement | null | undefined,
origPoint: GlobalPoint,
elementsMap: ElementsMap,
zoom?: AppState["zoom"],
): Heading =>
getHeadingForElbowArrowSnap(
p,
@@ -2247,18 +2261,21 @@ const getBindPointHeading = (
),
origPoint,
elementsMap,
zoom,
);
const getHoveredElement = (
origPoint: GlobalPoint,
elementsMap: NonDeletedSceneElementsMap,
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
zoom?: AppState["zoom"],
) => {
return getHoveredElementForBinding(
origPoint,
elements,
elementsMap,
(element) => getFixedBindingDistance(element) + 1,
(element) =>
maxBindingGap_simple(element, element.width, element.height, zoom),
);
};

View File

@@ -2142,7 +2142,7 @@ const pointDraggingUpdates = (
);
// Linear elements have no special logic
if (!isArrowElement(element) || isElbowArrow(element)) {
if (!isArrowElement(element)) {
return {
positions: naiveDraggingPoints,
};
@@ -2153,12 +2153,6 @@ const pointDraggingUpdates = (
element.points.length - 1,
);
if (startIsDragged === endIsDragged) {
return {
positions: naiveDraggingPoints,
};
}
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
element,
naiveDraggingPoints,
@@ -2172,6 +2166,25 @@ const pointDraggingUpdates = (
},
);
if (isElbowArrow(element)) {
return {
positions: naiveDraggingPoints,
updates: {
suggestedBinding: startIsDragged
? start.element
: endIsDragged
? end.element
: null,
},
};
}
if (startIsDragged === endIsDragged) {
return {
positions: naiveDraggingPoints,
};
}
// Generate the next bindings for the arrow
const updates: PointMoveOtherUpdates = {
suggestedBinding: null,