From dca9fbe306fea71a4e7f82ed9836308028a15b10 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 28 Feb 2025 22:07:47 +0100 Subject: [PATCH] Fixed gap binding --- excalidraw-app/components/DebugCanvas.tsx | 2 + packages/element/src/binding.ts | 74 ++----- packages/element/tests/elbowArrow.test.tsx | 31 ++- packages/element/tests/resize.test.tsx | 26 +-- .../data/__snapshots__/transform.test.ts.snap | 28 +-- packages/excalidraw/data/transform.test.ts | 6 +- .../tests/__snapshots__/history.test.tsx.snap | 184 +++++++++--------- .../tests/__snapshots__/move.test.tsx.snap | 16 +- packages/excalidraw/tests/history.test.tsx | 4 +- .../tests/linearElementEditor.test.tsx | 2 +- packages/excalidraw/tests/rotate.test.tsx | 10 +- packages/math/src/utils.ts | 2 +- 12 files changed, 169 insertions(+), 216 deletions(-) diff --git a/excalidraw-app/components/DebugCanvas.tsx b/excalidraw-app/components/DebugCanvas.tsx index e83a62647d..383ef889b1 100644 --- a/excalidraw-app/components/DebugCanvas.tsx +++ b/excalidraw-app/components/DebugCanvas.tsx @@ -13,6 +13,8 @@ import { useCallback, useImperativeHandle, useRef } from "react"; import { isLineSegment, + isCurve, + type Curve, type GlobalPoint, type LineSegment, } from "@excalidraw/math"; diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 3ff954fe50..9476edb3e1 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -463,23 +463,10 @@ export const maybeBindLinearElement = ( } }; -const normalizePointBinding = ( - binding: { focus: number; gap: number }, - hoveredElement: ExcalidrawBindableElement, -) => { - let gap = binding.gap; - const maxGap = maxBindingGap( - hoveredElement, - hoveredElement.width, - hoveredElement.height, - ); - - if (gap > maxGap) { - gap = BINDING_HIGHLIGHT_THICKNESS + BINDING_HIGHLIGHT_OFFSET; - } +const normalizePointBinding = (binding: { focus: number; gap: number }) => { return { ...binding, - gap, + gap: FIXED_BINDING_DISTANCE, }; }; @@ -729,7 +716,7 @@ const calculateFocusAndGap = ( return { focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint), - gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)), + gap: FIXED_BINDING_DISTANCE, }; }; @@ -747,7 +734,7 @@ export const updateBoundElements = ( changedElements?: Map; }, ) => { - const { newSize, simultaneouslyUpdated } = options ?? {}; + const { simultaneouslyUpdated } = options ?? {}; const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds( simultaneouslyUpdated, ); @@ -780,23 +767,13 @@ export const updateBoundElements = ( startBounds = getElementBounds(startBindingElement, elementsMap); endBounds = getElementBounds(endBindingElement, elementsMap); } - - const bindings = { - startBinding: maybeCalculateNewGapWhenScaling( - changedElement, - element.startBinding, - newSize, - ), - endBinding: maybeCalculateNewGapWhenScaling( - changedElement, - element.endBinding, - newSize, - ), - }; - // `linearElement` is being moved/scaled already, just update the binding if (simultaneouslyUpdatedElementIds.has(element.id)) { - mutateElement(element, bindings, true); + mutateElement( + element, + { startBinding: element.startBinding, endBinding: element.endBinding }, + true, + ); return; } @@ -818,7 +795,9 @@ export const updateBoundElements = ( const point = updateBoundPoint( element, bindingProp, - bindings[bindingProp], + bindingProp === "startBinding" + ? element.startBinding + : element.endBinding, bindableElement, elementsMap, ); @@ -1040,10 +1019,7 @@ export const avoidRectangularCorner = ( element: ExcalidrawBindableElement, p: GlobalPoint, ): GlobalPoint => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); const nonRotatedPoint = pointRotateRads(p, center, -element.angle as Radians); if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) { @@ -1226,7 +1202,6 @@ const updateBoundPoint = ( linearElement, bindableElement, startOrEnd === "startBinding" ? "start" : "end", - elementsMap, ).fixedPoint; const globalMidPoint = pointFrom( bindableElement.x + bindableElement.width / 2, @@ -1336,7 +1311,6 @@ export const calculateFixedPointForElbowArrowBinding = ( linearElement: NonDeleted, hoveredElement: ExcalidrawBindableElement, startOrEnd: "start" | "end", - elementsMap: ElementsMap, ): { fixedPoint: FixedPoint } => { const bounds = [ hoveredElement.x, @@ -1369,28 +1343,6 @@ export const calculateFixedPointForElbowArrowBinding = ( }; }; -const maybeCalculateNewGapWhenScaling = ( - changedElement: ExcalidrawBindableElement, - currentBinding: PointBinding | null | undefined, - newSize: { width: number; height: number } | undefined, -): PointBinding | null | undefined => { - if (currentBinding == null || newSize == null) { - return currentBinding; - } - const { width: newWidth, height: newHeight } = newSize; - const { width, height } = changedElement; - const newGap = Math.max( - 1, - Math.min( - maxBindingGap(changedElement, newWidth, newHeight), - currentBinding.gap * - (newWidth < newHeight ? newWidth / width : newHeight / height), - ), - ); - - return { ...currentBinding, gap: newGap }; -}; - const getElligibleElementForBindingElement = ( linearElement: NonDeleted, startOrEnd: "start" | "end", diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index b8b5a8b85d..f912e68b24 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -78,8 +78,8 @@ describe("elbow arrow segment move", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], [110, 0], - [110, 200], - [190, 200], + [110, 195.01], + [190, 195.01], ]); mouse.reset(); @@ -89,8 +89,8 @@ describe("elbow arrow segment move", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], [110, 0], - [110, 200], - [190, 200], + [110, 195.01], + [190, 195.01], ]); }); @@ -198,11 +198,11 @@ describe("elbow arrow routing", () => { points: [pointFrom(0, 0), pointFrom(90, 200)], }); - expect(arrow.points).toEqual([ + expect(arrow.points).toCloselyEqualPoints([ [0, 0], [45, 0], - [45, 200], - [90, 200], + [45, 199.07], + [90.07, 199.07], ]); }); }); @@ -241,9 +241,9 @@ describe("elbow arrow ui", () => { expect(h.state.currentItemArrowType).toBe(ARROW_TYPE.elbow); mouse.reset(); - mouse.moveTo(-43, -99); + mouse.moveTo(-50, -100); mouse.click(); - mouse.moveTo(43, 99); + mouse.moveTo(50, 100); mouse.click(); const arrow = h.scene.getSelectedElements( @@ -252,11 +252,11 @@ describe("elbow arrow ui", () => { expect(arrow.type).toBe("arrow"); expect(arrow.elbowed).toBe(true); - expect(arrow.points).toEqual([ + expect(arrow.points).toCloselyEqualPoints([ [0, 0], [45, 0], - [45, 200], - [90, 200], + [45, 195.01], + [90, 195.01], ]); }); @@ -293,12 +293,11 @@ describe("elbow arrow ui", () => { ".drag-input", ) as HTMLInputElement; UI.updateInput(inputAngle, String("40")); - +console.log(JSON.stringify(h.elements)) expect(arrow.points.map((point) => point.map(Math.round))).toEqual([ [0, 0], - [35, 0], - [35, 165], - [103, 165], + [109, 0], + [109, 152], ]); }); diff --git a/packages/element/tests/resize.test.tsx b/packages/element/tests/resize.test.tsx index f3804e2a22..1b1f2e876f 100644 --- a/packages/element/tests/resize.test.tsx +++ b/packages/element/tests/resize.test.tsx @@ -195,7 +195,7 @@ describe("generic element", () => { UI.resize(rectangle, "w", [50, 0]); expect(arrow.endBinding?.elementId).toEqual(rectangle.id); - expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(80, 0); + expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(80.62, 0); }); it("resizes with a label", async () => { @@ -510,13 +510,13 @@ describe("arrow element", () => { h.state, )[0] as ExcalidrawElbowArrowElement; - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); - expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); + expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.78); UI.resize(rectangle, "se", [-200, -150]); - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); - expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); + expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.78); }); it("flips the fixed point binding on negative resize for group selection", () => { @@ -538,8 +538,8 @@ describe("arrow element", () => { h.state, )[0] as ExcalidrawElbowArrowElement; - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); - expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); + expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.78); UI.resize([rectangle, arrow], "nw", [300, 350]); expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(0); @@ -809,7 +809,7 @@ describe("image element", () => { }); API.setElements([image]); const arrow = UI.createElement("arrow", { - x: -30, + x: -29, y: 50, width: 28, height: 5, @@ -819,14 +819,14 @@ describe("image element", () => { UI.resize(image, "ne", [40, 0]); - expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(31, 0); + expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(30, 0); const imageWidth = image.width; const scale = 20 / image.height; UI.resize(image, "nw", [50, 20]); expect(arrow.endBinding?.elementId).toEqual(image.id); - expect(Math.floor(arrow.width + arrow.endBinding!.gap)).toBeCloseTo( + expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo( 30 + imageWidth * scale, 0, ); @@ -1033,11 +1033,11 @@ describe("multiple selection", () => { expect(leftBoundArrow.x).toBeCloseTo(-110); expect(leftBoundArrow.y).toBeCloseTo(50); - expect(leftBoundArrow.width).toBeCloseTo(143, 0); + expect(leftBoundArrow.width).toBeCloseTo(146.46, 0); expect(leftBoundArrow.height).toBeCloseTo(7, 0); expect(leftBoundArrow.angle).toEqual(0); expect(leftBoundArrow.startBinding).toBeNull(); - expect(leftBoundArrow.endBinding?.gap).toBeCloseTo(10); + expect(leftBoundArrow.endBinding?.gap).toEqual(FIXED_BINDING_DISTANCE); expect(leftBoundArrow.endBinding?.elementId).toBe( leftArrowBinding.elementId, ); @@ -1051,7 +1051,7 @@ describe("multiple selection", () => { expect(rightBoundArrow.height).toBeCloseTo(0); expect(rightBoundArrow.angle).toEqual(0); expect(rightBoundArrow.startBinding).toBeNull(); - expect(rightBoundArrow.endBinding?.gap).toBeCloseTo(8.0952); + expect(rightBoundArrow.endBinding?.gap).toEqual(FIXED_BINDING_DISTANCE); expect(rightBoundArrow.endBinding?.elementId).toBe( rightArrowBinding.elementId, ); diff --git a/packages/excalidraw/data/__snapshots__/transform.test.ts.snap b/packages/excalidraw/data/__snapshots__/transform.test.ts.snap index 70f8daa313..00331bd11f 100644 --- a/packages/excalidraw/data/__snapshots__/transform.test.ts.snap +++ b/packages/excalidraw/data/__snapshots__/transform.test.ts.snap @@ -89,7 +89,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "endBinding": { "elementId": "ellipse-1", "focus": -0.007519379844961235, - "gap": 11.562288374879595, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -119,7 +119,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "startBinding": { "elementId": "id49", "focus": -0.0813953488372095, - "gap": 1, + "gap": 5, }, "strokeColor": "#1864ab", "strokeStyle": "solid", @@ -145,7 +145,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "endBinding": { "elementId": "ellipse-1", "focus": 0.10666666666666667, - "gap": 3.8343264684446097, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -175,7 +175,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "startBinding": { "elementId": "diamond-1", "focus": 0, - "gap": 4.545343408287929, + "gap": 5, }, "strokeColor": "#e67700", "strokeStyle": "solid", @@ -335,7 +335,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t "endBinding": { "elementId": "text-2", "focus": 0, - "gap": 14, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -365,7 +365,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t "startBinding": { "elementId": "text-1", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -437,7 +437,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe "endBinding": { "elementId": "id42", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -467,7 +467,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe "startBinding": { "elementId": "id41", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -613,7 +613,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when "endBinding": { "elementId": "id46", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -643,7 +643,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when "startBinding": { "elementId": "id45", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1475,7 +1475,7 @@ exports[`Test Transform > should transform the elements correctly when linear el "endBinding": { "elementId": "Alice", "focus": -0, - "gap": 5.299874999999986, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -1507,7 +1507,7 @@ exports[`Test Transform > should transform the elements correctly when linear el "startBinding": { "elementId": "Bob", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1538,7 +1538,7 @@ exports[`Test Transform > should transform the elements correctly when linear el "endBinding": { "elementId": "B", "focus": 0, - "gap": 14, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -1566,7 +1566,7 @@ exports[`Test Transform > should transform the elements correctly when linear el "startBinding": { "elementId": "Bob", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", diff --git a/packages/excalidraw/data/transform.test.ts b/packages/excalidraw/data/transform.test.ts index 0b0718e8e3..e9710499e1 100644 --- a/packages/excalidraw/data/transform.test.ts +++ b/packages/excalidraw/data/transform.test.ts @@ -433,7 +433,7 @@ describe("Test Transform", () => { startBinding: { elementId: rectangle.id, focus: 0, - gap: 1, + gap: FIXED_BINDING_DISTANCE, }, endBinding: { elementId: ellipse.id, @@ -518,7 +518,7 @@ describe("Test Transform", () => { startBinding: { elementId: text2.id, focus: 0, - gap: 1, + gap: FIXED_BINDING_DISTANCE, }, endBinding: { elementId: text3.id, @@ -781,7 +781,7 @@ describe("Test Transform", () => { expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({ elementId: "rect-1", focus: -0, - gap: 14, + gap: 5, }); expect(rect.boundElements).toStrictEqual([ { diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 9ffb97128a..bd37dc2c14 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -198,7 +198,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "102.35417", + "height": "99.23572", "id": "id172", "index": "a2", "isDeleted": false, @@ -212,8 +212,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "101.77517", - "102.35417", + "96.42891", + "99.23572", ], ], "roughness": 1, @@ -228,8 +228,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 40, - "width": "101.77517", - "x": "0.70711", + "width": "96.42891", + "x": "3.53553", "y": 0, } `; @@ -296,46 +296,46 @@ History { "endBinding": { "elementId": "id171", "focus": "0.00990", - "gap": 1, + "gap": 5, }, - "height": "0.98586", + "height": "0.92998", "points": [ [ 0, 0, ], [ - "98.58579", - "-0.98586", + "92.92893", + "-0.92998", ], ], "startBinding": { "elementId": "id170", "focus": "0.02970", - "gap": 1, + "gap": 5, }, }, "inserted": { "endBinding": { "elementId": "id171", "focus": "-0.02000", - "gap": 1, + "gap": 5, }, - "height": "0.00000", + "height": "0.00611", "points": [ [ 0, 0, ], [ - "98.58579", - "0.00000", + "92.92893", + "0.00611", ], ], "startBinding": { "elementId": "id170", "focus": "0.02000", - "gap": 1, + "gap": 5, }, }, }, @@ -390,15 +390,15 @@ History { "focus": 0, "gap": 1, }, - "height": "102.35417", + "height": "99.23572", "points": [ [ 0, 0, ], [ - "101.77517", - "102.35417", + "96.42891", + "99.23572", ], ], "startBinding": null, @@ -408,25 +408,25 @@ History { "endBinding": { "elementId": "id171", "focus": "0.00990", - "gap": 1, + "gap": 5, }, - "height": "0.98586", + "height": "0.93503", "points": [ [ 0, 0, ], [ - "98.58579", - "-0.98586", + "92.92893", + "-0.93503", ], ], "startBinding": { "elementId": "id170", "focus": "0.02970", - "gap": 1, + "gap": 5, }, - "y": "0.99364", + "y": "0.97365", }, }, "id175" => Delta { @@ -931,7 +931,7 @@ History { "endBinding": { "elementId": "id166", "focus": -0, - "gap": 1, + "gap": 5, }, "points": [ [ @@ -946,7 +946,7 @@ History { "startBinding": { "elementId": "id165", "focus": 0, - "gap": 1, + "gap": 5, }, }, }, @@ -1241,7 +1241,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "1.30038", + "height": "2.98409", "id": "id178", "index": "Zz", "isDeleted": false, @@ -1255,8 +1255,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "98.58579", - "1.30038", + "92.92893", + "-2.98409", ], ], "roughness": 1, @@ -1279,9 +1279,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 11, - "width": "98.58579", - "x": "0.70711", - "y": 0, + "width": "92.92893", + "x": "3.53553", + "y": "4.70319", } `; @@ -1613,7 +1613,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "1.30038", + "height": "2.98409", "id": "id181", "index": "a0", "isDeleted": false, @@ -1627,8 +1627,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "98.58579", - "1.30038", + "92.92893", + "-2.98409", ], ], "roughness": 1, @@ -1651,9 +1651,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 11, - "width": "98.58579", - "x": "0.70711", - "y": 0, + "width": "92.92893", + "x": "3.53553", + "y": "4.70319", } `; @@ -1771,7 +1771,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "11.27227", + "height": "22.46459", "index": "a0", "isDeleted": false, "lastCommittedPoint": null, @@ -1784,8 +1784,8 @@ History { 0, ], [ - "98.58579", - "11.27227", + "93.46683", + "-22.46459", ], ], "roughness": 1, @@ -1806,9 +1806,9 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "98.58579", - "x": "0.70711", - "y": 0, + "width": "93.46683", + "x": "2.99764", + "y": "35.33176", }, "inserted": { "isDeleted": true, @@ -2321,12 +2321,12 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "endBinding": { "elementId": "id185", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "374.05754", + "height": "408.02337", "id": "id186", "index": "a2", "isDeleted": false, @@ -2340,8 +2340,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "502.78936", - "-374.05754", + "495.48945", + "-408.02337", ], ], "roughness": 1, @@ -2352,7 +2352,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "startBinding": { "elementId": "id184", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -2360,9 +2360,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 10, - "width": "502.78936", - "x": "-0.83465", - "y": "-36.58211", + "width": "495.48945", + "x": "3.53553", + "y": 0, } `; @@ -2481,7 +2481,7 @@ History { "endBinding": { "elementId": "id185", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -2511,7 +2511,7 @@ History { "startBinding": { "elementId": "id184", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -15161,7 +15161,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "endBinding": { "elementId": "id58", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -15180,7 +15180,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + "92.92893", 0, ], ], @@ -15192,7 +15192,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "startBinding": { "elementId": "id56", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -15200,8 +15200,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": "92.92893", + "x": "3.53553", "y": 0, } `; @@ -15532,7 +15532,7 @@ History { "endBinding": { "elementId": "id58", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -15562,7 +15562,7 @@ History { "startBinding": { "elementId": "id56", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -15859,7 +15859,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "endBinding": { "elementId": "id52", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -15878,7 +15878,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + "92.92893", 0, ], ], @@ -15890,7 +15890,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "startBinding": { "elementId": "id50", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -15898,8 +15898,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": "92.92893", + "x": "3.53553", "y": 0, } `; @@ -16152,7 +16152,7 @@ History { "endBinding": { "elementId": "id52", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -16182,7 +16182,7 @@ History { "startBinding": { "elementId": "id50", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -16479,7 +16479,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "endBinding": { "elementId": "id64", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -16498,7 +16498,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + "92.92893", 0, ], ], @@ -16510,7 +16510,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "startBinding": { "elementId": "id62", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -16518,8 +16518,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": "92.92893", + "x": "3.53553", "y": 0, } `; @@ -16772,7 +16772,7 @@ History { "endBinding": { "elementId": "id64", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -16802,7 +16802,7 @@ History { "startBinding": { "elementId": "id62", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -17097,7 +17097,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "endBinding": { "elementId": "id70", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -17116,7 +17116,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + "92.92893", 0, ], ], @@ -17128,7 +17128,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "startBinding": { "elementId": "id68", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -17136,8 +17136,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": "92.92893", + "x": "3.53553", "y": 0, } `; @@ -17200,7 +17200,7 @@ History { "startBinding": { "elementId": "id68", "focus": 0, - "gap": 1, + "gap": 5, }, }, "inserted": { @@ -17460,7 +17460,7 @@ History { "endBinding": { "elementId": "id70", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -17490,7 +17490,7 @@ History { "startBinding": { "elementId": "id68", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -17811,7 +17811,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "endBinding": { "elementId": "id77", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -17830,7 +17830,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + "92.92893", 0, ], ], @@ -17842,7 +17842,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "startBinding": { "elementId": "id75", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -17850,8 +17850,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 11, - "width": "98.58579", - "x": "0.70711", + "width": "92.92893", + "x": "3.53553", "y": 0, } `; @@ -17913,7 +17913,7 @@ History { "endBinding": { "elementId": "id77", "focus": -0, - "gap": 1, + "gap": 5, }, "points": [ [ @@ -17928,7 +17928,7 @@ History { "startBinding": { "elementId": "id75", "focus": 0, - "gap": 1, + "gap": 5, }, }, "inserted": { @@ -18189,7 +18189,7 @@ History { "endBinding": { "elementId": "id77", "focus": -0, - "gap": 1, + "gap": 5, }, "fillStyle": "solid", "frameId": null, @@ -18219,7 +18219,7 @@ History { "startBinding": { "elementId": "id75", "focus": 0, - "gap": 1, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index 4b863d4e78..80db452185 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -191,12 +191,12 @@ exports[`move element > rectangles with binding arrow 7`] = ` "endBinding": { "elementId": "id1", "focus": "-0.46667", - "gap": 10, + "gap": 5, }, "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "87.29887", + "height": "87.97595", "id": "id2", "index": "a2", "isDeleted": false, @@ -210,8 +210,8 @@ exports[`move element > rectangles with binding arrow 7`] = ` 0, ], [ - "86.85786", - "87.29887", + "87.46447", + "87.97595", ], ], "roughness": 1, @@ -223,7 +223,7 @@ exports[`move element > rectangles with binding arrow 7`] = ` "startBinding": { "elementId": "id0", "focus": "-0.60000", - "gap": 10, + "gap": 5, }, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -232,8 +232,8 @@ exports[`move element > rectangles with binding arrow 7`] = ` "updated": 1, "version": 11, "versionNonce": 1051383431, - "width": "86.85786", - "x": "107.07107", - "y": "47.07107", + "width": "87.46447", + "x": 110, + "y": 50, } `; diff --git a/packages/excalidraw/tests/history.test.tsx b/packages/excalidraw/tests/history.test.tsx index 8dd65c7a5f..6a51daf1f4 100644 --- a/packages/excalidraw/tests/history.test.tsx +++ b/packages/excalidraw/tests/history.test.tsx @@ -4779,12 +4779,12 @@ describe("history", () => { startBinding: expect.objectContaining({ elementId: rect1.id, focus: 0, - gap: 1, + gap: FIXED_BINDING_DISTANCE, }), endBinding: expect.objectContaining({ elementId: rect2.id, focus: -0, - gap: 1, + gap: FIXED_BINDING_DISTANCE, }), isDeleted: true, }), diff --git a/packages/excalidraw/tests/linearElementEditor.test.tsx b/packages/excalidraw/tests/linearElementEditor.test.tsx index 8619985846..0a9b7503a8 100644 --- a/packages/excalidraw/tests/linearElementEditor.test.tsx +++ b/packages/excalidraw/tests/linearElementEditor.test.tsx @@ -1266,7 +1266,7 @@ describe("Test Linear Elements", () => { mouse.downAt(rect.x, rect.y); mouse.moveTo(200, 0); mouse.upAt(200, 0); - expect(arrow.width).toBeCloseTo(204, 0); + expect(arrow.width).toBeCloseTo(206.86, 0); expect(rect.x).toBe(200); expect(rect.y).toBe(0); expect(handleBindTextResizeSpy).toHaveBeenCalledWith( diff --git a/packages/excalidraw/tests/rotate.test.tsx b/packages/excalidraw/tests/rotate.test.tsx index 9687b08f25..d3809d5947 100644 --- a/packages/excalidraw/tests/rotate.test.tsx +++ b/packages/excalidraw/tests/rotate.test.tsx @@ -35,7 +35,7 @@ test("unselected bound arrow updates when rotating its target element", async () expect(arrow.endBinding?.elementId).toEqual(rectangle.id); expect(arrow.x).toBeCloseTo(-80); expect(arrow.y).toBeCloseTo(50); - expect(arrow.width).toBeCloseTo(116.7, 1); + expect(arrow.width).toBeCloseTo(119.58, 1); expect(arrow.height).toBeCloseTo(0); }); @@ -72,13 +72,13 @@ test("unselected bound arrows update when rotating their target elements", async expect(ellipseArrow.x).toEqual(0); expect(ellipseArrow.y).toEqual(0); expect(ellipseArrow.points[0]).toEqual([0, 0]); - expect(ellipseArrow.points[1][0]).toBeCloseTo(48.98, 1); - expect(ellipseArrow.points[1][1]).toBeCloseTo(125.79, 1); + expect(ellipseArrow.points[1][0]).toBeCloseTo(54.36, 1); + expect(ellipseArrow.points[1][1]).toBeCloseTo(139.61, 1); expect(textArrow.endBinding?.elementId).toEqual(text.id); expect(textArrow.x).toEqual(360); expect(textArrow.y).toEqual(300); expect(textArrow.points[0]).toEqual([0, 0]); - expect(textArrow.points[1][0]).toBeCloseTo(-94, 0); - expect(textArrow.points[1][1]).toBeCloseTo(-116.1, 0); + expect(textArrow.points[1][0]).toBeCloseTo(-100.12, 0); + expect(textArrow.points[1][1]).toBeCloseTo(-123.63, 0); }); diff --git a/packages/math/src/utils.ts b/packages/math/src/utils.ts index 8807c275e4..89765fa670 100644 --- a/packages/math/src/utils.ts +++ b/packages/math/src/utils.ts @@ -6,7 +6,7 @@ export const clamp = (value: number, min: number, max: number) => { export const round = ( value: number, - precision: number, + precision: number = (Math.log(1 / PRECISION) * Math.LOG10E + 1) | 0, func: "round" | "floor" | "ceil" = "round", ) => { const multiplier = Math.pow(10, precision);