From 7f56cc0cf3458e9ef6e5071b4594740cae36a0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20Tolm=C3=A1cs?= Date: Wed, 6 May 2026 16:39:45 +0200 Subject: [PATCH] fix: Speculative fixes for arrow invariant failures (#11241) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- packages/element/src/binding.ts | 11 +++++----- packages/element/src/linearElementEditor.ts | 10 ++++----- packages/excalidraw/components/App.tsx | 24 ++++++++++++++++++--- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 3f80ffbae2..e21542ebb8 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -734,12 +734,11 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( }); // Handle outside-outside binding to the same element - if (otherBinding && otherBinding.elementId === hit?.id) { - invariant( - !opts?.newArrow || appState.selectedLinearElement?.initialState.origin, - "appState.selectedLinearElement.initialState.origin must be defined for new arrows", - ); - + if ( + otherBinding && + otherBinding.elementId === hit?.id && + (!opts?.newArrow || appState.selectedLinearElement?.initialState.origin) + ) { return { start: { mode: "inside", diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 8af8ecd858..d2f80a50b3 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -486,7 +486,7 @@ export class LinearElementEditor { selectedPointsIndices, )}) points(0..${ element.points.length - 1 - }) lastClickedPoint(${lastClickedPoint})`, + }) lastClickedPoint(${lastClickedPoint}) isElbowArrow: ${elbowed}`, ); // Fall back to the actual last point as a last resort. @@ -2139,13 +2139,13 @@ const pointDraggingUpdates = ( } => { const naiveDraggingPoints = new Map( selectedPointsIndices.map((pointIndex) => { + // NOTE: Avoid stale point index issue potentially caused by elbow + // arrows unpredictably changing the number of points during dragging + const point = element.points[pointIndex] ?? element.points.at(-1); return [ pointIndex, { - point: pointFrom( - element.points[pointIndex][0] + deltaX, - element.points[pointIndex][1] + deltaY, - ), + point: pointFrom(point[0] + deltaX, point[1] + deltaY), isDragging: true, }, ]; diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 4601808950..118147fafa 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -10408,20 +10408,38 @@ class App extends React.Component { ); let linearElementEditor = this.state.selectedLinearElement; - if (!linearElementEditor) { + + if ( + !linearElementEditor || + linearElementEditor.elementId !== newElement.id + ) { linearElementEditor = new LinearElementEditor( newElement, this.scene.getNonDeletedElementsMap(), ); + } + + const lastClickedPointOutOfBounds = + linearElementEditor && + (linearElementEditor.initialState.lastClickedPoint < 0 || + linearElementEditor.initialState.lastClickedPoint >= + points.length); + if (lastClickedPointOutOfBounds) { + console.warn( + "Last clicked point is out of bounds. Attempting to fix it.", + ); linearElementEditor = { ...linearElementEditor, - selectedPointsIndices: [1], + selectedPointsIndices: [points.length - 1], initialState: { ...linearElementEditor.initialState, - lastClickedPoint: 1, + prevSelectedPointsIndices: null, + lastClickedPoint: points.length - 1, }, + hoverPointIndex: points.length - 1, }; } + this.setState({ newElement, ...LinearElementEditor.handlePointDragging(