From 6c56972c96aaa3b46c691856df4e746f3f836810 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 29 Sep 2025 11:57:09 +0200 Subject: [PATCH] Fix ghost start binding Signed-off-by: Mark Tolmacs --- packages/excalidraw/components/App.tsx | 10 +- .../tests/__snapshots__/history.test.tsx.snap | 194 ++++++------------ packages/excalidraw/tests/history.test.tsx | 47 +---- 3 files changed, 79 insertions(+), 172 deletions(-) diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 2a3a2e359b..27411c013c 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1128,7 +1128,9 @@ class App extends React.Component { (otherBinding && arrow[otherBinding]?.mode === "inside" && arrow[otherBinding]?.elementId === hoveredElement?.id) || - (currentBinding && arrow[currentBinding]?.mode === "inside"); + (currentBinding && + arrow[currentBinding]?.mode === "inside" && + hoveredElement?.id === arrow[currentBinding]?.elementId); if ( currentBinding && @@ -8372,6 +8374,12 @@ class App extends React.Component { elementType: ExcalidrawLinearElement["type"], pointerDownState: PointerDownState, ): void => { + if (event.ctrlKey) { + flushSync(() => { + this.setState({ isBindingEnabled: false }); + }); + } + if (this.state.multiElement) { const { multiElement, selectedLinearElement } = this.state; diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 79245af873..6e71e99e20 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -120,12 +120,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl { "angle": 0, "backgroundColor": "transparent", - "boundElements": [ - { - "id": "id4", - "type": "arrow", - }, - ], + "boundElements": [], "customData": undefined, "fillStyle": "solid", "frameId": null, @@ -200,7 +195,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "112.78797", + "height": "112.79549", "id": "id4", "index": "a2", "isDeleted": false, @@ -214,8 +209,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - 94, - "112.78797", + "94.00000", + "112.79549", ], ], "roughness": 1, @@ -223,23 +218,16 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": 2, }, "startArrowhead": null, - "startBinding": { - "elementId": "id0", - "fixedPoint": [ - 1, - "0.50010", - ], - "mode": "inside", - }, + "startBinding": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", "updated": 1, - "version": 37, - "width": 94, + "version": 34, + "width": "94.00000", "x": 0, - "y": "0.01000", + "y": 0, } `; @@ -362,7 +350,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], "mode": "orbit", }, - "version": 36, + "version": 33, "width": 88, "y": "16.71973", }, @@ -394,7 +382,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], "mode": "orbit", }, - "version": 33, + "version": 30, "width": 88, "y": "10.00000", }, @@ -416,9 +404,16 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "updated": { "id0": { "deleted": { + "boundElements": [], "version": 13, }, "inserted": { + "boundElements": [ + { + "id": "id4", + "type": "arrow", + }, + ], "version": 12, }, }, @@ -432,29 +427,22 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "id4": { "deleted": { - "height": "112.78797", + "height": "112.79549", "points": [ [ 0, 0, ], [ - 94, - "112.78797", + "94.00000", + "112.79549", ], ], - "startBinding": { - "elementId": "id0", - "fixedPoint": [ - 1, - "0.50010", - ], - "mode": "inside", - }, - "version": 37, - "width": 94, + "startBinding": null, + "version": 34, + "width": "94.00000", "x": 0, - "y": "0.01000", + "y": 0, }, "inserted": { "height": "98.55605", @@ -476,7 +464,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], "mode": "orbit", }, - "version": 36, + "version": 33, "width": 88, "x": 6, "y": "16.71973", @@ -601,7 +589,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "0.01000", + "height": 0, "index": "a2", "isDeleted": false, "link": null, @@ -614,7 +602,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], [ 100, - "-0.01000", + 0, ], ], "roughness": 1, @@ -622,46 +610,23 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": 2, }, "startArrowhead": null, - "startBinding": { - "elementId": "id0", - "fixedPoint": [ - 1, - "0.50010", - ], - "mode": "inside", - }, + "startBinding": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "version": 6, + "version": 4, "width": 100, "x": 0, - "y": "0.01000", + "y": 0, }, "inserted": { "isDeleted": true, - "version": 5, - }, - }, - }, - "updated": { - "id0": { - "deleted": { - "boundElements": [ - { - "id": "id4", - "type": "arrow", - }, - ], "version": 3, }, - "inserted": { - "boundElements": [], - "version": 2, - }, }, }, + "updated": {}, }, "id": "id6", }, @@ -788,12 +753,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl { "angle": 0, "backgroundColor": "transparent", - "boundElements": [ - { - "id": "id4", - "type": "arrow", - }, - ], + "boundElements": [], "customData": undefined, "fillStyle": "solid", "frameId": null, @@ -861,7 +821,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "0.01000", + "height": 0, "id": "id4", "index": "a2", "isDeleted": false, @@ -875,8 +835,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ + 100, 0, - "-0.01000", ], ], "roughness": 1, @@ -884,23 +844,16 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": 2, }, "startArrowhead": null, - "startBinding": { - "elementId": "id0", - "fixedPoint": [ - 1, - "0.50010", - ], - "mode": "inside", - }, + "startBinding": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", "updated": 1, - "version": 34, - "width": 0, - "x": 250, - "y": "0.01000", + "version": 30, + "width": 100, + "x": 150, + "y": 0, } `; @@ -966,7 +919,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], "mode": "orbit", }, - "version": 33, + "version": 29, "width": "44.00000", "y": "2.93333", }, @@ -998,7 +951,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], "mode": "orbit", }, - "version": 31, + "version": 27, "width": "6.00000", "y": 10, }, @@ -1020,37 +973,37 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "updated": { "id0": { "deleted": { + "boundElements": [], "version": 14, }, "inserted": { + "boundElements": [ + { + "id": "id4", + "type": "arrow", + }, + ], "version": 13, }, }, "id4": { "deleted": { - "height": "0.01000", + "height": 0, "points": [ [ 0, 0, ], [ + 100, 0, - "-0.01000", ], ], - "startBinding": { - "elementId": "id0", - "fixedPoint": [ - 1, - "0.50010", - ], - "mode": "inside", - }, - "version": 34, - "width": 0, - "x": 250, - "y": "0.01000", + "startBinding": null, + "version": 30, + "width": 100, + "x": 150, + "y": 0, }, "inserted": { "height": "2.93333", @@ -1072,7 +1025,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], "mode": "orbit", }, - "version": 33, + "version": 29, "width": "44.00000", "x": 144, "y": "2.93333", @@ -1197,7 +1150,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "0.01000", + "height": 0, "index": "a2", "isDeleted": false, "link": null, @@ -1210,7 +1163,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], [ 100, - "-0.01000", + 0, ], ], "roughness": 1, @@ -1218,46 +1171,23 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": 2, }, "startArrowhead": null, - "startBinding": { - "elementId": "id0", - "fixedPoint": [ - 1, - "0.50010", - ], - "mode": "inside", - }, + "startBinding": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "version": 6, + "version": 4, "width": 100, "x": 0, - "y": "0.01000", + "y": 0, }, "inserted": { "isDeleted": true, - "version": 5, - }, - }, - }, - "updated": { - "id0": { - "deleted": { - "boundElements": [ - { - "id": "id4", - "type": "arrow", - }, - ], "version": 3, }, - "inserted": { - "boundElements": [], - "version": 2, - }, }, }, + "updated": {}, }, "id": "id6", }, diff --git a/packages/excalidraw/tests/history.test.tsx b/packages/excalidraw/tests/history.test.tsx index 64a6acb24f..4ac4289751 100644 --- a/packages/excalidraw/tests/history.test.tsx +++ b/packages/excalidraw/tests/history.test.tsx @@ -4571,16 +4571,12 @@ describe("history", () => { expect(h.elements).toEqual([ expect.objectContaining({ id: rect1.id, - boundElements: [{ id: arrowId, type: "arrow" }], + boundElements: [], }), expect.objectContaining({ id: rect2.id, boundElements: [] }), expect.objectContaining({ id: arrowId, - startBinding: expect.objectContaining({ - elementId: rect1.id, - fixedPoint: [1, 0.5001], - mode: "inside", - }), + startBinding: null, endBinding: null, }), ]); @@ -4644,21 +4640,12 @@ describe("history", () => { expect(h.elements).toEqual([ expect.objectContaining({ id: rect1.id, - boundElements: [ - expect.objectContaining({ - id: arrowId, - type: "arrow", - }), - ], + boundElements: [], }), expect.objectContaining({ id: rect2.id, boundElements: [] }), expect.objectContaining({ id: arrowId, - startBinding: expect.objectContaining({ - elementId: rect1.id, - fixedPoint: [1, 0.5001], - mode: "inside", - }), + startBinding: null, endBinding: null, }), ]); @@ -4724,21 +4711,12 @@ describe("history", () => { expect(h.elements).toEqual([ expect.objectContaining({ id: rect1.id, - boundElements: [ - expect.objectContaining({ - id: arrowId, - type: "arrow", - }), - ], + boundElements: [], }), expect.objectContaining({ id: rect2.id, boundElements: [] }), expect.objectContaining({ id: arrowId, - startBinding: expect.objectContaining({ - elementId: rect1.id, - fixedPoint: [1, 0.5001], - mode: "inside", - }), + startBinding: null, endBinding: null, }), ]); @@ -4811,12 +4789,7 @@ describe("history", () => { expect.arrayContaining([ expect.objectContaining({ id: rect1.id, - boundElements: [ - expect.objectContaining({ - id: arrowId, - type: "arrow", - }), - ], + boundElements: [], }), expect.objectContaining({ id: rect2.id, @@ -4824,11 +4797,7 @@ describe("history", () => { }), expect.objectContaining({ id: arrowId, - startBinding: expect.objectContaining({ - elementId: rect1.id, - fixedPoint: [1, 0.5001], - mode: "inside", - }), + startBinding: null, endBinding: expect.objectContaining({ // now we are back in the previous state! elementId: remoteContainer.id,