From a846ebb6e720cc5830adcb1a39d56dbe2eddcf0e Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Thu, 31 Jul 2025 20:23:26 +0200 Subject: [PATCH] Fix arrows --- packages/element/src/binding.ts | 47 +++------ packages/excalidraw/components/App.tsx | 128 +++++++++++++++---------- 2 files changed, 94 insertions(+), 81 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 1cd9af2c64..e524c0e396 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -422,6 +422,7 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = ( }; let current: BindingStrategy; + // We are hovering another element with the end point if (hovered) { const isInsideBinding = globalBindMode === "inside" || isAlwaysInsideBinding(hovered); @@ -447,9 +448,18 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = ( // No start binding if (!arrow.startBinding) { - end = hovered - ? { element: hovered, mode: "orbit", focusPoint: point } - : { mode: null }; + if (hovered) { + const isInsideBinding = + globalBindMode === "inside" || isAlwaysInsideBinding(hovered); + + end = { + mode: isInsideBinding ? "inside" : "orbit", + element: hovered, + focusPoint: point, + }; + } else { + end = { mode: null }; + } return { start, end }; } @@ -500,33 +510,6 @@ const bindingStrategyForSimpleArrowEndpointDragging = ( return { current, other }; } - // Update the start point on new arrows - if ( - opts?.newArrow && - opts?.appState?.selectedLinearElement && - oppositeBinding - ) { - const oppositeBindingElement = elementsMap.get( - oppositeBinding?.elementId, - ) as ExcalidrawBindableElement; - - if (oppositeBinding.elementId !== hovered?.id) { - other = { - element: oppositeBindingElement, - mode: "orbit", - focusPoint: elementCenterPoint(oppositeBindingElement, elementsMap), - }; - } else { - other = { - element: oppositeBindingElement, - mode: "inside", - focusPoint: - opts.appState.selectedLinearElement.pointerDownState - .arrowOriginalStartPoint ?? point, - }; - } - } - // Dragged point is outside of any bindable element // so we break any existing binding if (!hovered) { @@ -1378,10 +1361,10 @@ export const snapToCenter = ( element: ExcalidrawBindableElement, elementsMap: ElementsMap, p: GlobalPoint, - tolerance: number = 0.05, ) => { + const extent = Math.min(element.width, element.height); const center = elementCenterPoint(element, elementsMap); - if (pointDistance(p, center) < tolerance) { + if (pointDistance(p, center) < extent * 0.05) { return pointFrom(center[0], center[1]); } return p; diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 3c332045fd..0f538c8ee9 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -8584,63 +8584,93 @@ class App extends React.Component { return; } - const hoveredElement = getHoveredElementForBinding( - pointFrom(pointerCoords.x, pointerCoords.y), - this.scene.getNonDeletedElements(), + const element = LinearElementEditor.getElement( + linearElementEditor.elementId, elementsMap, - this.state.zoom, ); + let [x, y] = [pointerCoords.x, pointerCoords.y]; - // Timed bind mode handler for arrow elements - if (this.state.bindMode === "orbit") { - if (this.bindModeHandler && !hoveredElement) { - clearTimeout(this.bindModeHandler); - this.bindModeHandler = null; - } else if (!this.bindModeHandler && hoveredElement) { - this.bindModeHandler = setTimeout(() => { - if (hoveredElement) { - flushSync(() => { - this.setState({ - bindMode: "inside", + if (isBindingElement(element)) { + const hoveredElement = getHoveredElementForBinding( + pointFrom(pointerCoords.x, pointerCoords.y), + this.scene.getNonDeletedElements(), + elementsMap, + this.state.zoom, + ); + + // Timed bind mode handler for arrow elements + if (this.state.bindMode === "orbit") { + if (this.bindModeHandler && !hoveredElement) { + clearTimeout(this.bindModeHandler); + this.bindModeHandler = null; + } else if (!this.bindModeHandler && hoveredElement) { + this.bindModeHandler = setTimeout(() => { + if (hoveredElement) { + flushSync(() => { + this.setState({ + bindMode: "inside", + }); }); - }); - const newState = LinearElementEditor.handlePointDragging( - event, - this, - this.lastPointerMoveCoords?.x ?? pointerDownState.origin.x, - this.lastPointerMoveCoords?.y ?? pointerDownState.origin.y, - linearElementEditor, - ); - if (newState) { - pointerDownState.lastCoords.x = - this.lastPointerMoveCoords?.x ?? pointerDownState.origin.x; - pointerDownState.lastCoords.y = - this.lastPointerMoveCoords?.y ?? pointerDownState.origin.y; - pointerDownState.drag.hasOccurred = true; - this.setState(newState); + const [lastX, lastY] = + hoveredElement && element.startBinding?.mode !== "inside" + ? snapToCenter( + hoveredElement, + elementsMap, + pointFrom( + this.lastPointerMoveCoords?.x ?? + pointerDownState.origin.x, + this.lastPointerMoveCoords?.y ?? + pointerDownState.origin.y, + ), + ) + : [ + this.lastPointerMoveCoords?.x ?? + pointerDownState.origin.x, + this.lastPointerMoveCoords?.y ?? + pointerDownState.origin.y, + ]; + + const newState = LinearElementEditor.handlePointDragging( + event, + this, + lastX, + lastY, + linearElementEditor, + ); + if (newState) { + pointerDownState.lastCoords.x = + this.lastPointerMoveCoords?.x ?? + pointerDownState.origin.x; + pointerDownState.lastCoords.y = + this.lastPointerMoveCoords?.y ?? + pointerDownState.origin.y; + pointerDownState.drag.hasOccurred = true; + + this.setState(newState); + } + } else { + this.bindModeHandler = null; } - } else { - this.bindModeHandler = null; - } - }, BIND_MODE_TIMEOUT); - } - } else if (!hoveredElement) { - flushSync(() => { - this.setState({ - bindMode: "orbit", + }, BIND_MODE_TIMEOUT); + } + } else if (!hoveredElement) { + flushSync(() => { + this.setState({ + bindMode: "orbit", + }); }); - }); - } + } - const [x, y] = hoveredElement - ? snapToCenter( - hoveredElement, - elementsMap, - pointFrom(pointerCoords.x, pointerCoords.y), - 10, - ) - : [pointerCoords.x, pointerCoords.y]; + [x, y] = + hoveredElement && element.startBinding?.mode !== "inside" + ? snapToCenter( + hoveredElement, + elementsMap, + pointFrom(pointerCoords.x, pointerCoords.y), + ) + : [pointerCoords.x, pointerCoords.y]; + } const newState = LinearElementEditor.handlePointDragging( event,