diff --git a/packages/excalidraw/element/binding.ts b/packages/excalidraw/element/binding.ts index d72b846d9..b679ae9d0 100644 --- a/packages/excalidraw/element/binding.ts +++ b/packages/excalidraw/element/binding.ts @@ -17,7 +17,11 @@ import type { } from "./types"; import type { Bounds } from "./bounds"; -import { getCenterForBounds } from "./bounds"; +import { + getCenterForBounds, + getElementBounds, + doBoundsIntersect, +} from "./bounds"; import type { AppState } from "../types"; import { isPointOnShape } from "@excalidraw/utils/collision"; import { @@ -743,6 +747,21 @@ export const updateBoundElements = ( return; } + // Check for intersections before updating bound elements incase connected elements overlap + const startBindingElement = element.startBinding + ? elementsMap.get(element.startBinding.elementId) + : null; + const endBindingElement = element.endBinding + ? elementsMap.get(element.endBinding.elementId) + : null; + + let startBounds: Bounds | null = null; + let endBounds: Bounds | null = null; + if (startBindingElement && endBindingElement) { + startBounds = getElementBounds(startBindingElement, elementsMap); + endBounds = getElementBounds(endBindingElement, elementsMap); + } + const bindings = { startBinding: maybeCalculateNewGapWhenScaling( changedElement, @@ -770,7 +789,12 @@ export const updateBoundElements = ( bindableElement && isBindableElement(bindableElement) && (bindingProp === "startBinding" || bindingProp === "endBinding") && - changedElement.id === element[bindingProp]?.elementId + (changedElement.id === element[bindingProp]?.elementId || + (changedElement.id === + element[ + bindingProp === "startBinding" ? "endBinding" : "startBinding" + ]?.elementId && + !doBoundsIntersect(startBounds, endBounds))) ) { const point = updateBoundPoint( element, diff --git a/packages/excalidraw/element/bounds.ts b/packages/excalidraw/element/bounds.ts index 6b5010370..e4be1dfbd 100644 --- a/packages/excalidraw/element/bounds.ts +++ b/packages/excalidraw/element/bounds.ts @@ -1013,3 +1013,17 @@ export const getCenterForBounds = (bounds: Bounds): GlobalPoint => bounds[0] + (bounds[2] - bounds[0]) / 2, bounds[1] + (bounds[3] - bounds[1]) / 2, ); + +export const doBoundsIntersect = ( + bounds1: Bounds | null, + bounds2: Bounds | null, +): boolean => { + if (bounds1 == null || bounds2 == null) { + return false; + } + + const [minX1, minY1, maxX1, maxY1] = bounds1; + const [minX2, minY2, maxX2, maxY2] = bounds2; + + return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2; +}; diff --git a/packages/excalidraw/element/linearElementEditor.ts b/packages/excalidraw/element/linearElementEditor.ts index 1fcbe5fde..4bf1e988b 100644 --- a/packages/excalidraw/element/linearElementEditor.ts +++ b/packages/excalidraw/element/linearElementEditor.ts @@ -219,7 +219,9 @@ export class LinearElementEditor { }); } - /** @returns whether point was dragged */ + /** + * @returns whether point was dragged + */ static handlePointDragging( event: PointerEvent, app: AppClassProperties, diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index d8cf4ec2d..165c135fe 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -297,7 +297,7 @@ History { "focus": "0.00990", "gap": 1, }, - "height": "0.98597", + "height": "0.98586", "points": [ [ 0, @@ -305,7 +305,7 @@ History { ], [ "98.58579", - "-0.98597", + "-0.98586", ], ], "startBinding": { @@ -320,7 +320,7 @@ History { "focus": "-0.02000", "gap": 1, }, - "height": "0.00119", + "height": "0.00000", "points": [ [ 0, @@ -328,7 +328,7 @@ History { ], [ "98.58579", - "0.00119", + "0.00000", ], ], "startBinding": { @@ -409,7 +409,7 @@ History { "focus": "0.00990", "gap": 1, }, - "height": "0.98700", + "height": "0.98586", "points": [ [ 0, @@ -417,7 +417,7 @@ History { ], [ "98.58579", - "-0.98700", + "-0.98586", ], ], "startBinding": { @@ -425,7 +425,7 @@ History { "focus": "0.02970", "gap": 1, }, - "y": "0.99465", + "y": "0.99364", }, }, "id175" => Delta { @@ -1238,7 +1238,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "2.52823", + "height": "1.30038", "id": "id178", "index": "Zz", "isDeleted": false, @@ -1253,7 +1253,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], [ "98.58579", - "-2.52823", + "1.30038", ], ], "roughness": 1, @@ -1278,7 +1278,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "version": 11, "width": "98.58579", "x": "0.70711", - "y": "3.82861", + "y": 0, } `; @@ -1609,7 +1609,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "2.52823", + "height": "1.30038", "id": "id181", "index": "a0", "isDeleted": false, @@ -1624,7 +1624,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl ], [ "98.58579", - "-2.52823", + "1.30038", ], ], "roughness": 1, @@ -1649,7 +1649,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "version": 11, "width": "98.58579", "x": "0.70711", - "y": "3.82861", + "y": 0, } `; @@ -1767,7 +1767,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "22.07000", + "height": "11.27227", "index": "a0", "isDeleted": false, "lastCommittedPoint": null, @@ -1780,8 +1780,8 @@ History { 0, ], [ - "99.27949", - "-22.07000", + "98.58579", + "11.27227", ], ], "roughness": 1, @@ -1802,9 +1802,9 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "99.27949", - "x": "0.01341", - "y": "33.34227", + "width": "98.58579", + "x": "0.70711", + "y": 0, }, "inserted": { "isDeleted": true, @@ -2320,7 +2320,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "410.63965", + "height": "374.05754", "id": "id186", "index": "a2", "isDeleted": false, @@ -2334,8 +2334,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "501.24760", - "-410.63965", + "502.78936", + "-374.05754", ], ], "roughness": 1, @@ -2354,9 +2354,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 10, - "width": "501.24760", - "x": "0.70711", - "y": 0, + "width": "502.78936", + "x": "-0.83465", + "y": "-36.58211", } `; diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index eb5f14498..90236a4dd 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -196,7 +196,7 @@ exports[`move element > rectangles with binding arrow 7`] = ` "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "84.41974", + "height": "87.29887", "id": "id2", "index": "a2", "isDeleted": false, @@ -210,8 +210,8 @@ exports[`move element > rectangles with binding arrow 7`] = ` 0, ], [ - "83.92893", - "84.41974", + "86.85786", + "87.29887", ], ], "roughness": 1, @@ -232,8 +232,8 @@ exports[`move element > rectangles with binding arrow 7`] = ` "updated": 1, "version": 11, "versionNonce": 1051383431, - "width": "83.92893", - "x": 110, - "y": 50, + "width": "86.85786", + "x": "107.07107", + "y": "47.07107", } `; diff --git a/packages/excalidraw/tests/move.test.tsx b/packages/excalidraw/tests/move.test.tsx index 17de52247..528f9554d 100644 --- a/packages/excalidraw/tests/move.test.tsx +++ b/packages/excalidraw/tests/move.test.tsx @@ -1,4 +1,5 @@ import React from "react"; +import "../../utils/test-utils"; import { render, fireEvent, act, unmountComponent } from "./test-utils"; import { Excalidraw } from "../index"; import * as StaticScene from "../renderer/staticScene"; @@ -121,10 +122,8 @@ describe("move element", () => { expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); expect([rectA.x, rectA.y]).toEqual([0, 0]); expect([rectB.x, rectB.y]).toEqual([201, 2]); - expect([Math.round(arrow.x), Math.round(arrow.y)]).toEqual([110, 50]); - expect([Math.round(arrow.width), Math.round(arrow.height)]).toEqual([ - 84, 84, - ]); + expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[107.07, 47.07]]); + expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([[86.86, 87.3]]); h.elements.forEach((element) => expect(element).toMatchSnapshot()); });