Browse Source

[skip ci] First iteration of bringing over previous changes

Mark Tolmacs 4 months ago
parent
commit
b6dea75d57

+ 4 - 0
packages/common/src/utils.ts

@@ -2,6 +2,7 @@ import { average } from "@excalidraw/math";
 
 import type {
   ExcalidrawBindableElement,
+  ExcalidrawElement,
   FontFamilyValues,
   FontString,
 } from "@excalidraw/element/types";
@@ -1200,3 +1201,6 @@ export const escapeDoubleQuotes = (str: string) => {
 
 export const castArray = <T>(value: T | T[]): T[] =>
   Array.isArray(value) ? value : [value];
+
+export const toLocalPoint = (p: GlobalPoint, element: ExcalidrawElement) =>
+  pointTranslate<GlobalPoint, LocalPoint>(p, vector(-element.x, -element.y));

+ 180 - 185
packages/element/src/binding.ts

@@ -80,11 +80,10 @@ import type {
   NonDeletedSceneElementsMap,
   ExcalidrawTextElement,
   ExcalidrawArrowElement,
-  OrderedExcalidrawElement,
-  ExcalidrawElbowArrowElement,
   FixedPoint,
   SceneElementsMap,
   FixedPointBinding,
+  ExcalidrawElbowArrowElement,
 } from "./types";
 
 export type SuggestedBinding =
@@ -107,6 +106,7 @@ export const isBindingEnabled = (appState: AppState): boolean => {
   return appState.isBindingEnabled;
 };
 
+export const INSIDE_BINDING_BAND_PERCENT = 0.1;
 export const FIXED_BINDING_DISTANCE = 5;
 export const BINDING_HIGHLIGHT_THICKNESS = 10;
 export const BINDING_HIGHLIGHT_OFFSET = 4;
@@ -462,26 +462,6 @@ 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;
-  }
-  return {
-    ...binding,
-    gap,
-  };
-};
-
 export const bindLinearElement = (
   linearElement: NonDeleted<ExcalidrawLinearElement>,
   hoveredElement: ExcalidrawBindableElement,
@@ -492,17 +472,25 @@ export const bindLinearElement = (
     return;
   }
 
+  const direction = startOrEnd === "start" ? -1 : 1;
+  const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
+  const adjacentPointIndex = edgePointIndex - direction;
+
+  const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
+    linearElement,
+    edgePointIndex,
+    elementsMap,
+  );
+  const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
+    linearElement,
+    adjacentPointIndex,
+    elementsMap,
+  );
+
   let binding: PointBinding | FixedPointBinding = {
     elementId: hoveredElement.id,
-    ...normalizePointBinding(
-      calculateFocusAndGap(
-        linearElement,
-        hoveredElement,
-        startOrEnd,
-        elementsMap,
-      ),
-      hoveredElement,
-    ),
+    focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
+    gap: FIXED_BINDING_DISTANCE,
   };
 
   if (isElbowArrow(linearElement)) {
@@ -705,33 +693,6 @@ const getAllElementsAtPositionForBinding = (
   return elementsAtPosition;
 };
 
-const calculateFocusAndGap = (
-  linearElement: NonDeleted<ExcalidrawLinearElement>,
-  hoveredElement: ExcalidrawBindableElement,
-  startOrEnd: "start" | "end",
-  elementsMap: NonDeletedSceneElementsMap,
-): { focus: number; gap: number } => {
-  const direction = startOrEnd === "start" ? -1 : 1;
-  const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
-  const adjacentPointIndex = edgePointIndex - direction;
-
-  const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
-    linearElement,
-    edgePointIndex,
-    elementsMap,
-  );
-  const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
-    linearElement,
-    adjacentPointIndex,
-    elementsMap,
-  );
-
-  return {
-    focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
-    gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
-  };
-};
-
 // Supports translating, rotating and scaling `changedElement` with bound
 // linear elements.
 // Because scaling involves moving the focus points as well, it is
@@ -742,11 +703,9 @@ export const updateBoundElements = (
   elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
   options?: {
     simultaneouslyUpdated?: readonly ExcalidrawElement[];
-    newSize?: { width: number; height: number };
-    changedElements?: Map<string, OrderedExcalidrawElement>;
   },
 ) => {
-  const { newSize, simultaneouslyUpdated } = options ?? {};
+  const { simultaneouslyUpdated } = options ?? {};
   const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
     simultaneouslyUpdated,
   );
@@ -780,22 +739,13 @@ export const updateBoundElements = (
       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;
     }
 
@@ -817,7 +767,9 @@ export const updateBoundElements = (
           const point = updateBoundPoint(
             element,
             bindingProp,
-            bindings[bindingProp],
+            bindingProp === "startBinding"
+              ? element.startBinding
+              : element.endBinding,
             bindableElement,
             elementsMap,
           );
@@ -847,10 +799,10 @@ export const updateBoundElements = (
       updates,
       {
         ...(changedElement.id === element.startBinding?.elementId
-          ? { startBinding: bindings.startBinding }
+          ? { startBinding: element.startBinding }
           : {}),
         ...(changedElement.id === element.endBinding?.elementId
-          ? { endBinding: bindings.endBinding }
+          ? { endBinding: element.endBinding }
           : {}),
       },
       elementsMap as NonDeletedSceneElementsMap,
@@ -884,7 +836,6 @@ export const getHeadingForElbowArrowSnap = (
   otherPoint: Readonly<GlobalPoint>,
   bindableElement: ExcalidrawBindableElement | undefined | null,
   aabb: Bounds | undefined | null,
-  elementsMap: ElementsMap,
   origPoint: GlobalPoint,
   zoom?: AppState["zoom"],
 ): Heading => {
@@ -894,12 +845,7 @@ export const getHeadingForElbowArrowSnap = (
     return otherPointHeading;
   }
 
-  const distance = getDistanceForBinding(
-    origPoint,
-    bindableElement,
-    elementsMap,
-    zoom,
-  );
+  const distance = getDistanceForBinding(origPoint, bindableElement, zoom);
 
   if (!distance) {
     return vectorToHeading(
@@ -919,7 +865,6 @@ export const getHeadingForElbowArrowSnap = (
 const getDistanceForBinding = (
   point: Readonly<GlobalPoint>,
   bindableElement: ExcalidrawBindableElement,
-  elementsMap: ElementsMap,
   zoom?: AppState["zoom"],
 ) => {
   const distance = distanceToBindableElement(bindableElement, point);
@@ -934,40 +879,47 @@ const getDistanceForBinding = (
 };
 
 export const bindPointToSnapToElementOutline = (
-  arrow: ExcalidrawElbowArrowElement,
+  linearElement: ExcalidrawLinearElement,
   bindableElement: ExcalidrawBindableElement,
   startOrEnd: "start" | "end",
+  elementsMap: ElementsMap,
 ): GlobalPoint => {
   if (isDevEnv() || isTestEnv()) {
-    invariant(arrow.points.length > 1, "Arrow should have at least 2 points");
+    invariant(
+      linearElement.points.length > 0,
+      "Arrow should have at least 1 point",
+    );
   }
 
+  const elbowed = isElbowArrow(linearElement);
   const aabb = aabbForElement(bindableElement);
-  const localP =
-    arrow.points[startOrEnd === "start" ? 0 : arrow.points.length - 1];
-  const globalP = pointFrom<GlobalPoint>(
-    arrow.x + localP[0],
-    arrow.y + localP[1],
-  );
-  const edgePoint = isRectanguloidElement(bindableElement)
-    ? avoidRectangularCorner(bindableElement, globalP)
-    : globalP;
-  const elbowed = isElbowArrow(arrow);
   const center = getCenterForBounds(aabb);
-  const adjacentPointIdx = startOrEnd === "start" ? 1 : arrow.points.length - 2;
-  const adjacentPoint = pointRotateRads(
-    pointFrom<GlobalPoint>(
-      arrow.x + arrow.points[adjacentPointIdx][0],
-      arrow.y + arrow.points[adjacentPointIdx][1],
-    ),
-    center,
-    arrow.angle ?? 0,
+
+  const pointIdx = startOrEnd === "start" ? 0 : linearElement.points.length - 1;
+  const p = pointFrom<GlobalPoint>(
+    linearElement.x + linearElement.points[pointIdx][0],
+    linearElement.y + linearElement.points[pointIdx][1],
   );
+  const edgePoint = avoidRectangularCorner(bindableElement, p);
+
+  const adjacentPointIdx =
+    startOrEnd === "start" ? 1 : linearElement.points.length - 2;
+  const adjacentPoint =
+    linearElement.points.length === 1
+      ? center
+      : pointRotateRads(
+          pointFrom<GlobalPoint>(
+            linearElement.x + linearElement.points[adjacentPointIdx][0],
+            linearElement.y + linearElement.points[adjacentPointIdx][1],
+          ),
+          center,
+          linearElement.angle ?? 0,
+        );
 
   let intersection: GlobalPoint | null = null;
   if (elbowed) {
     const isHorizontal = headingIsHorizontal(
-      headingForPointFromElement(bindableElement, aabb, globalP),
+      headingForPointFromElement(bindableElement, aabb, p),
     );
     const otherPoint = pointFrom<GlobalPoint>(
       isHorizontal ? center[0] : edgePoint[0],
@@ -1032,6 +984,28 @@ export const bindPointToSnapToElementOutline = (
     );
   }
 
+  const isInside = isPointInShape(
+    edgePoint,
+    getElementShape(
+      {
+        ...bindableElement,
+        x:
+          bindableElement.x +
+          bindableElement.width * INSIDE_BINDING_BAND_PERCENT,
+        y:
+          bindableElement.y +
+          bindableElement.height * INSIDE_BINDING_BAND_PERCENT,
+        width: bindableElement.width * (1 - INSIDE_BINDING_BAND_PERCENT * 2),
+        height: bindableElement.height * (1 - INSIDE_BINDING_BAND_PERCENT * 2),
+      } as ExcalidrawBindableElement,
+      elementsMap,
+    ),
+  );
+
+  if (!isInside) {
+    return intersection;
+  }
+
   return edgePoint;
 };
 
@@ -1039,6 +1013,10 @@ export const avoidRectangularCorner = (
   element: ExcalidrawBindableElement,
   p: GlobalPoint,
 ): GlobalPoint => {
+  if (!isRectanguloidElement(element)) {
+    return p;
+  }
+
   const center = pointFrom<GlobalPoint>(
     element.x + element.width / 2,
     element.y + element.height / 2,
@@ -1199,6 +1177,45 @@ export const snapToMid = (
   return p;
 };
 
+export const getOutlineAvoidingPoint = (
+  element: NonDeleted<ExcalidrawLinearElement>,
+  coords: GlobalPoint,
+  pointIndex: number,
+  scene: Scene,
+  zoom: AppState["zoom"],
+  fallback?: GlobalPoint,
+): GlobalPoint => {
+  const elementsMap = scene.getNonDeletedElementsMap();
+  const hoveredElement = getHoveredElementForBinding(
+    { x: coords[0], y: coords[1] },
+    scene.getNonDeletedElements(),
+    elementsMap,
+    zoom,
+    true,
+    isElbowArrow(element),
+  );
+
+  if (hoveredElement) {
+    const newPoints = Array.from(element.points);
+    newPoints[pointIndex] = pointFrom<LocalPoint>(
+      coords[0] - element.x,
+      coords[1] - element.y,
+    );
+
+    return bindPointToSnapToElementOutline(
+      {
+        ...element,
+        points: newPoints,
+      },
+      hoveredElement,
+      pointIndex === 0 ? "start" : "end",
+      elementsMap,
+    );
+  }
+
+  return fallback ?? coords;
+};
+
 const updateBoundPoint = (
   linearElement: NonDeleted<ExcalidrawLinearElement>,
   startOrEnd: "startBinding" | "endBinding",
@@ -1262,66 +1279,65 @@ const updateBoundPoint = (
 
   let newEdgePoint: GlobalPoint;
 
-  // The linear element was not originally pointing inside the bound shape,
-  // we can point directly at the focus point
-  if (binding.gap === 0) {
-    newEdgePoint = focusPointAbsolute;
-  } else {
-    const edgePointAbsolute =
-      LinearElementEditor.getPointAtIndexGlobalCoordinates(
-        linearElement,
-        edgePointIndex,
-        elementsMap,
-      );
-
-    const center = pointFrom<GlobalPoint>(
-      bindableElement.x + bindableElement.width / 2,
-      bindableElement.y + bindableElement.height / 2,
+  // // The linear element was not originally pointing inside the bound shape,
+  // // we can point directly at the focus point
+  // if (binding.gap === 0) {
+  //   newEdgePoint = focusPointAbsolute;
+  // } else {
+  //  ...
+  // }
+  const edgePointAbsolute =
+    LinearElementEditor.getPointAtIndexGlobalCoordinates(
+      linearElement,
+      edgePointIndex,
+      elementsMap,
     );
-    const interceptorLength =
-      pointDistance(adjacentPoint, edgePointAbsolute) +
-      pointDistance(adjacentPoint, center) +
-      Math.max(bindableElement.width, bindableElement.height) * 2;
-    const intersections = [
-      ...intersectElementWithLineSegment(
-        bindableElement,
-        lineSegment<GlobalPoint>(
-          adjacentPoint,
-          pointFromVector(
-            vectorScale(
-              vectorNormalize(
-                vectorFromPoint(focusPointAbsolute, adjacentPoint),
-              ),
-              interceptorLength,
-            ),
-            adjacentPoint,
+
+  const center = pointFrom<GlobalPoint>(
+    bindableElement.x + bindableElement.width / 2,
+    bindableElement.y + bindableElement.height / 2,
+  );
+  const interceptorLength =
+    pointDistance(adjacentPoint, edgePointAbsolute) +
+    pointDistance(adjacentPoint, center) +
+    Math.max(bindableElement.width, bindableElement.height) * 2;
+  const intersections = [
+    ...intersectElementWithLineSegment(
+      bindableElement,
+      lineSegment<GlobalPoint>(
+        adjacentPoint,
+        pointFromVector(
+          vectorScale(
+            vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)),
+            interceptorLength,
           ),
+          adjacentPoint,
         ),
-        binding.gap,
-      ).sort(
-        (g, h) =>
-          pointDistanceSq(g, adjacentPoint) - pointDistanceSq(h, adjacentPoint),
       ),
-      // Fallback when arrow doesn't point to the shape
-      pointFromVector(
-        vectorScale(
-          vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)),
-          pointDistance(adjacentPoint, edgePointAbsolute),
-        ),
-        adjacentPoint,
+      FIXED_BINDING_DISTANCE,
+    ).sort(
+      (g, h) =>
+        pointDistanceSq(g, adjacentPoint) - pointDistanceSq(h, adjacentPoint),
+    ),
+    // Fallback when arrow doesn't point to the shape
+    pointFromVector(
+      vectorScale(
+        vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)),
+        pointDistance(adjacentPoint, edgePointAbsolute),
       ),
-    ];
-
-    if (intersections.length > 1) {
-      // The adjacent point is outside the shape (+ gap)
-      newEdgePoint = intersections[0];
-    } else if (intersections.length === 1) {
-      // The adjacent point is inside the shape (+ gap)
-      newEdgePoint = focusPointAbsolute;
-    } else {
-      // Shouldn't happend, but just in case
-      newEdgePoint = edgePointAbsolute;
-    }
+      adjacentPoint,
+    ),
+  ];
+
+  if (intersections.length > 1) {
+    // The adjacent point is outside the shape (+ gap)
+    newEdgePoint = intersections[0];
+  } else if (intersections.length === 1) {
+    // The adjacent point is inside the shape (+ gap)
+    newEdgePoint = focusPointAbsolute;
+  } else {
+    // Shouldn't happend, but just in case
+    newEdgePoint = edgePointAbsolute;
   }
 
   return LinearElementEditor.pointFromAbsoluteCoords(
@@ -1332,7 +1348,7 @@ const updateBoundPoint = (
 };
 
 export const calculateFixedPointForElbowArrowBinding = (
-  linearElement: NonDeleted<ExcalidrawElbowArrowElement>,
+  linearElement: NonDeleted<ExcalidrawArrowElement>,
   hoveredElement: ExcalidrawBindableElement,
   startOrEnd: "start" | "end",
   elementsMap: ElementsMap,
@@ -1347,6 +1363,7 @@ export const calculateFixedPointForElbowArrowBinding = (
     linearElement,
     hoveredElement,
     startOrEnd,
+    elementsMap,
   );
   const globalMidPoint = pointFrom(
     bounds[0] + (bounds[2] - bounds[0]) / 2,
@@ -1368,28 +1385,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<ExcalidrawLinearElement>,
   startOrEnd: "start" | "end",

+ 5 - 2
packages/element/src/elbowArrow.ts

@@ -1254,6 +1254,7 @@ const getElbowArrowData = (
     "start",
     arrow.startBinding?.fixedPoint,
     origStartGlobalPoint,
+    elementsMap,
     hoveredStartElement,
     options?.isDragging,
   );
@@ -1267,6 +1268,7 @@ const getElbowArrowData = (
     "end",
     arrow.endBinding?.fixedPoint,
     origEndGlobalPoint,
+    elementsMap,
     hoveredEndElement,
     options?.isDragging,
   );
@@ -2212,6 +2214,7 @@ const getGlobalPoint = (
   startOrEnd: "start" | "end",
   fixedPointRatio: [number, number] | undefined | null,
   initialPoint: GlobalPoint,
+  elementsMap: NonDeletedSceneElementsMap,
   element?: ExcalidrawBindableElement | null,
   isDragging?: boolean,
 ): GlobalPoint => {
@@ -2221,6 +2224,7 @@ const getGlobalPoint = (
         arrow,
         element,
         startOrEnd,
+        elementsMap,
       );
 
       return snapToMid(element, snapPoint);
@@ -2240,7 +2244,7 @@ const getGlobalPoint = (
       distanceToBindableElement(element, fixedGlobalPoint) -
         FIXED_BINDING_DISTANCE,
     ) > 0.01
-      ? bindPointToSnapToElementOutline(arrow, element, startOrEnd)
+      ? bindPointToSnapToElementOutline(arrow, element, startOrEnd, elementsMap)
       : fixedGlobalPoint;
   }
 
@@ -2268,7 +2272,6 @@ const getBindPointHeading = (
           number,
         ],
       ),
-    elementsMap,
     origPoint,
   );
 

+ 44 - 18
packages/element/src/linearElementEditor.ts

@@ -42,6 +42,7 @@ import type { Mutable } from "@excalidraw/common/utility-types";
 import {
   bindOrUnbindLinearElement,
   getHoveredElementForBinding,
+  getOutlineAvoidingPoint,
   isBindingEnabled,
 } from "./binding";
 import {
@@ -251,27 +252,28 @@ export class LinearElementEditor {
       pointSceneCoords: { x: number; y: number }[],
     ) => void,
     linearElementEditor: LinearElementEditor,
-    scene: Scene,
   ): LinearElementEditor | null {
     if (!linearElementEditor) {
       return null;
     }
     const { elementId } = linearElementEditor;
-    const elementsMap = scene.getNonDeletedElementsMap();
+    const elementsMap = app.scene.getNonDeletedElementsMap();
     const element = LinearElementEditor.getElement(elementId, elementsMap);
     if (!element) {
       return null;
     }
 
+    const elbowed = isElbowArrow(element);
+
     if (
-      isElbowArrow(element) &&
+      elbowed &&
       !linearElementEditor.pointerDownState.lastClickedIsEndPoint &&
       linearElementEditor.pointerDownState.lastClickedPoint !== 0
     ) {
       return null;
     }
 
-    const selectedPointsIndices = isElbowArrow(element)
+    const selectedPointsIndices = elbowed
       ? [
           !!linearElementEditor.selectedPointsIndices?.includes(0)
             ? 0
@@ -281,7 +283,7 @@ export class LinearElementEditor {
             : undefined,
         ].filter((idx): idx is number => idx !== undefined)
       : linearElementEditor.selectedPointsIndices;
-    const lastClickedPoint = isElbowArrow(element)
+    const lastClickedPoint = elbowed
       ? linearElementEditor.pointerDownState.lastClickedPoint > 0
         ? element.points.length - 1
         : 0
@@ -333,19 +335,43 @@ export class LinearElementEditor {
         LinearElementEditor.movePoints(
           element,
           selectedPointsIndices.map((pointIndex) => {
-            const newPointPosition: LocalPoint =
-              pointIndex === lastClickedPoint
-                ? LinearElementEditor.createPointAt(
-                    element,
-                    elementsMap,
-                    scenePointerX - linearElementEditor.pointerOffset.x,
-                    scenePointerY - linearElementEditor.pointerOffset.y,
-                    event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
-                  )
-                : pointFrom(
-                    element.points[pointIndex][0] + deltaX,
-                    element.points[pointIndex][1] + deltaY,
-                  );
+            let newPointPosition = pointFrom<LocalPoint>(
+              element.points[pointIndex][0] + deltaX,
+              element.points[pointIndex][1] + deltaY,
+            );
+
+            // Check if point dragging is happening
+            if (pointIndex === lastClickedPoint) {
+              let globalNewPointPosition = pointFrom<GlobalPoint>(
+                scenePointerX - linearElementEditor.pointerOffset.x,
+                scenePointerY - linearElementEditor.pointerOffset.y,
+              );
+
+              if (
+                pointIndex === 0 ||
+                pointIndex === element.points.length - 1
+              ) {
+                globalNewPointPosition = getOutlineAvoidingPoint(
+                  element,
+                  pointFrom<GlobalPoint>(
+                    element.x + element.points[pointIndex][0] + deltaX,
+                    element.y + element.points[pointIndex][1] + deltaY,
+                  ),
+                  pointIndex,
+                  app.scene,
+                  app.state.zoom,
+                );
+              }
+
+              newPointPosition = LinearElementEditor.createPointAt(
+                element,
+                elementsMap,
+                globalNewPointPosition[0],
+                globalNewPointPosition[1],
+                event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
+              );
+            }
+
             return {
               index: pointIndex,
               point: newPointPosition,

+ 2 - 6
packages/element/src/resizeElements.ts

@@ -969,10 +969,7 @@ export const resizeSingleElement = (
 
     mutateElement(latestElement, updates, shouldInformMutation);
 
-    updateBoundElements(latestElement, elementsMap as SceneElementsMap, {
-      // TODO: confirm with MARK if this actually makes sense
-      newSize: { width: nextWidth, height: nextHeight },
-    });
+    updateBoundElements(latestElement, elementsMap as SceneElementsMap);
 
     if (boundTextElement && boundTextFont != null) {
       mutateElement(boundTextElement, {
@@ -1525,7 +1522,7 @@ export const resizeMultipleElements = (
       element,
       update: { boundTextFontSize, ...update },
     } of elementsAndUpdates) {
-      const { width, height, angle } = update;
+      const { angle } = update;
 
       mutateElement(element, update, false, {
         // needed for the fixed binding point udpate to take effect
@@ -1534,7 +1531,6 @@ export const resizeMultipleElements = (
 
       updateBoundElements(element, elementsMap as SceneElementsMap, {
         simultaneouslyUpdated: elementsToUpdate,
-        newSize: { width, height },
       });
 
       const boundTextElement = getBoundTextElement(element, elementsMap);

+ 12 - 1
packages/element/tests/binding.test.tsx

@@ -190,7 +190,18 @@ describe("element binding", () => {
 
     // Sever connection
     expect(API.getSelectedElement().type).toBe("arrow");
-    Keyboard.keyPress(KEYS.ARROW_LEFT);
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      // We have to move a significant distance to get out of the binding zone
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+    });
     expect(arrow.endBinding).toBe(null);
     Keyboard.keyPress(KEYS.ARROW_RIGHT);
     expect(arrow.endBinding).toBe(null);

+ 6 - 5
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(81, 0);
   });
 
   it("resizes with a label", async () => {
@@ -826,8 +826,9 @@ describe("image element", () => {
     UI.resize(image, "nw", [50, 20]);
 
     expect(arrow.endBinding?.elementId).toEqual(image.id);
-    expect(Math.floor(arrow.width + arrow.endBinding!.gap)).toBeCloseTo(
-      30 + imageWidth * scale,
+
+    expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(
+      30 + imageWidth * scale + 1,
       0,
     );
   });
@@ -1033,11 +1034,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, 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).toBeCloseTo(5);
     expect(leftBoundArrow.endBinding?.elementId).toBe(
       leftArrowBinding.elementId,
     );

+ 20 - 4
packages/excalidraw/actions/actionFinalize.tsx

@@ -1,4 +1,4 @@
-import { pointFrom } from "@excalidraw/math";
+import { type GlobalPoint, pointFrom } from "@excalidraw/math";
 
 import {
   maybeBindLinearElement,
@@ -91,10 +91,26 @@ export const actionFinalize = register({
         multiPointElement.type !== "freedraw" &&
         appState.lastPointerDownWith !== "touch"
       ) {
-        const { points, lastCommittedPoint } = multiPointElement;
+        const { x: rx, y: ry, points, lastCommittedPoint } = multiPointElement;
+        const lastGlobalPoint = pointFrom<GlobalPoint>(
+          rx + points[points.length - 1][0],
+          ry + points[points.length - 1][1],
+        );
+        const hoveredElementForBinding = getHoveredElementForBinding(
+          {
+            x: lastGlobalPoint[0],
+            y: lastGlobalPoint[1],
+          },
+          elements,
+          elementsMap,
+          app.state.zoom,
+          true,
+          isElbowArrow(multiPointElement),
+        );
         if (
-          !lastCommittedPoint ||
-          points[points.length - 1] !== lastCommittedPoint
+          !hoveredElementForBinding &&
+          (!lastCommittedPoint ||
+            points[points.length - 1] !== lastCommittedPoint)
         ) {
           mutateElement(multiPointElement, {
             points: multiPointElement.points.slice(0, -1),

+ 2 - 0
packages/excalidraw/actions/actionProperties.tsx

@@ -1655,6 +1655,7 @@ export const actionChangeArrowType = register({
               newElement,
               startHoveredElement,
               "start",
+              elementsMap,
             )
           : startGlobalPoint;
         const finalEndPoint = endHoveredElement
@@ -1662,6 +1663,7 @@ export const actionChangeArrowType = register({
               newElement,
               endHoveredElement,
               "end",
+              elementsMap,
             )
           : endGlobalPoint;
 

+ 1 - 3
packages/excalidraw/change.ts

@@ -1508,9 +1508,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
   ) {
     for (const element of changed.values()) {
       if (!element.isDeleted && isBindableElement(element)) {
-        updateBoundElements(element, elements, {
-          changedElements: changed,
-        });
+        updateBoundElements(element, elements);
       }
     }
   }

+ 165 - 75
packages/excalidraw/components/App.tsx

@@ -16,6 +16,7 @@ import {
   vectorSubtract,
   vectorDot,
   vectorNormalize,
+  lineSegment,
 } from "@excalidraw/math";
 import { isPointInShape } from "@excalidraw/utils/collision";
 import { getSelectionBoxShape } from "@excalidraw/utils/shape";
@@ -301,7 +302,7 @@ import {
 
 import { isNonDeletedElement } from "@excalidraw/element";
 
-import type { LocalPoint, Radians } from "@excalidraw/math";
+import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
 
 import type {
   ExcalidrawBindableElement,
@@ -5971,9 +5972,19 @@ class App extends React.Component<AppProps, AppState> {
           {
             points: [
               ...points.slice(0, -1),
-              pointFrom<LocalPoint>(
-                lastCommittedX + dxFromLastCommitted,
-                lastCommittedY + dyFromLastCommitted,
+              toLocalPoint(
+                getOutlineAvoidingPoint(
+                  multiElement,
+                  pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
+                  multiElement.points.length - 1,
+                  this.scene,
+                  this.state.zoom,
+                  pointFrom<GlobalPoint>(
+                    multiElement.x + lastCommittedX + dxFromLastCommitted,
+                    multiElement.y + lastCommittedY + dyFromLastCommitted,
+                  ),
+                ),
+                multiElement,
               ),
             ],
           },
@@ -7715,18 +7726,34 @@ class App extends React.Component<AppProps, AppState> {
       }
 
       const { x: rx, y: ry, lastCommittedPoint } = multiElement;
+      const lastGlobalPoint = pointFrom<GlobalPoint>(
+        rx + multiElement.points[multiElement.points.length - 1][0],
+        ry + multiElement.points[multiElement.points.length - 1][1],
+      );
+      const hoveredElementForBinding = getHoveredElementForBinding(
+        {
+          x: lastGlobalPoint[0],
+          y: lastGlobalPoint[1],
+        },
+        this.scene.getNonDeletedElements(),
+        this.scene.getNonDeletedElementsMap(),
+        this.state.zoom,
+        true,
+        isElbowArrow(multiElement),
+      );
 
       // clicking inside commit zone → finalize arrow
       if (
-        multiElement.points.length > 1 &&
-        lastCommittedPoint &&
-        pointDistance(
-          pointFrom(
-            pointerDownState.origin.x - rx,
-            pointerDownState.origin.y - ry,
-          ),
-          lastCommittedPoint,
-        ) < LINE_CONFIRM_THRESHOLD
+        hoveredElementForBinding ||
+        (multiElement.points.length > 1 &&
+          lastCommittedPoint &&
+          pointDistance(
+            pointFrom(
+              pointerDownState.origin.x - rx,
+              pointerDownState.origin.y - ry,
+            ),
+            lastCommittedPoint,
+          ) < LINE_CONFIRM_THRESHOLD)
       ) {
         this.actionManager.executeAction(actionFinalize);
         return;
@@ -7770,53 +7797,92 @@ class App extends React.Component<AppProps, AppState> {
           ? [currentItemStartArrowhead, currentItemEndArrowhead]
           : [null, null];
 
-      const element =
-        elementType === "arrow"
-          ? newArrowElement({
-              type: elementType,
-              x: gridX,
-              y: gridY,
-              strokeColor: this.state.currentItemStrokeColor,
-              backgroundColor: this.state.currentItemBackgroundColor,
-              fillStyle: this.state.currentItemFillStyle,
-              strokeWidth: this.state.currentItemStrokeWidth,
-              strokeStyle: this.state.currentItemStrokeStyle,
-              roughness: this.state.currentItemRoughness,
-              opacity: this.state.currentItemOpacity,
-              roundness:
-                this.state.currentItemArrowType === ARROW_TYPE.round
-                  ? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
-                  : // note, roundness doesn't have any effect for elbow arrows,
-                    // but it's best to set it to null as well
-                    null,
-              startArrowhead,
-              endArrowhead,
-              locked: false,
-              frameId: topLayerFrame ? topLayerFrame.id : null,
-              elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
-              fixedSegments:
-                this.state.currentItemArrowType === ARROW_TYPE.elbow
-                  ? []
-                  : null,
-            })
-          : newLinearElement({
-              type: elementType,
-              x: gridX,
-              y: gridY,
-              strokeColor: this.state.currentItemStrokeColor,
-              backgroundColor: this.state.currentItemBackgroundColor,
-              fillStyle: this.state.currentItemFillStyle,
-              strokeWidth: this.state.currentItemStrokeWidth,
-              strokeStyle: this.state.currentItemStrokeStyle,
-              roughness: this.state.currentItemRoughness,
-              opacity: this.state.currentItemOpacity,
-              roundness:
-                this.state.currentItemRoundness === "round"
-                  ? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
-                  : null,
-              locked: false,
-              frameId: topLayerFrame ? topLayerFrame.id : null,
-            });
+      let element: NonDeleted<ExcalidrawLinearElement>;
+      if (elementType === "arrow") {
+        const arrow: Mutable<NonDeleted<ExcalidrawArrowElement>> =
+          newArrowElement({
+            type: "arrow",
+            x: gridX,
+            y: gridY,
+            strokeColor: this.state.currentItemStrokeColor,
+            backgroundColor: this.state.currentItemBackgroundColor,
+            fillStyle: this.state.currentItemFillStyle,
+            strokeWidth: this.state.currentItemStrokeWidth,
+            strokeStyle: this.state.currentItemStrokeStyle,
+            roughness: this.state.currentItemRoughness,
+            opacity: this.state.currentItemOpacity,
+            roundness:
+              this.state.currentItemArrowType === ARROW_TYPE.round
+                ? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
+                : // note, roundness doesn't have any effect for elbow arrows,
+                  // but it's best to set it to null as well
+                  null,
+            startArrowhead,
+            endArrowhead,
+            locked: false,
+            frameId: topLayerFrame ? topLayerFrame.id : null,
+            elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
+            fixedSegments:
+              this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null,
+          });
+
+        const hoveredElement = getHoveredElementForBinding(
+          { x: gridX, y: gridY },
+          this.scene.getNonDeletedElements(),
+          this.scene.getNonDeletedElementsMap(),
+          this.state.zoom,
+          true,
+          this.state.currentItemArrowType === ARROW_TYPE.elbow,
+        );
+
+        if (hoveredElement) {
+          [arrow.x, arrow.y] =
+            intersectElementWithLineSegment(
+              hoveredElement,
+              lineSegment(
+                pointFrom<GlobalPoint>(gridX, gridY),
+                pointFrom<GlobalPoint>(
+                  gridX,
+                  hoveredElement.y + hoveredElement.height / 2,
+                ),
+              ),
+              2 * FIXED_BINDING_DISTANCE,
+            )[0] ??
+            intersectElementWithLineSegment(
+              hoveredElement,
+              lineSegment(
+                pointFrom<GlobalPoint>(gridX, gridY),
+                pointFrom<GlobalPoint>(
+                  hoveredElement.x + hoveredElement.width / 2,
+                  gridY,
+                ),
+              ),
+              2 * FIXED_BINDING_DISTANCE,
+            )[0] ??
+            pointFrom<GlobalPoint>(gridX, gridY);
+        }
+
+        element = arrow;
+      } else {
+        element = newLinearElement({
+          type: elementType,
+          x: gridX,
+          y: gridY,
+          strokeColor: this.state.currentItemStrokeColor,
+          backgroundColor: this.state.currentItemBackgroundColor,
+          fillStyle: this.state.currentItemFillStyle,
+          strokeWidth: this.state.currentItemStrokeWidth,
+          strokeStyle: this.state.currentItemStrokeStyle,
+          roughness: this.state.currentItemRoughness,
+          opacity: this.state.currentItemOpacity,
+          roundness:
+            this.state.currentItemRoundness === "round"
+              ? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
+              : null,
+          locked: false,
+          frameId: topLayerFrame ? topLayerFrame.id : null,
+        });
+      }
       this.setState((prevState) => {
         const nextSelectedElementIds = {
           ...prevState.selectedElementIds,
@@ -8131,12 +8197,6 @@ class App extends React.Component<AppProps, AppState> {
         this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
       }
 
-      const [gridX, gridY] = getGridPoint(
-        pointerCoords.x,
-        pointerCoords.y,
-        event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
-      );
-
       // for arrows/lines, don't start dragging until a given threshold
       // to ensure we don't create a 2-point arrow by mistake when
       // user clicks mouse in a way that it moves a tiny bit (thus
@@ -8237,7 +8297,6 @@ class App extends React.Component<AppProps, AppState> {
             );
           },
           linearElementEditor,
-          this.scene,
         );
         if (newLinearElementEditor) {
           pointerDownState.lastCoords.x = pointerCoords.x;
@@ -8579,6 +8638,11 @@ class App extends React.Component<AppProps, AppState> {
         } else if (isLinearElement(newElement)) {
           pointerDownState.drag.hasOccurred = true;
           const points = newElement.points;
+          const [gridX, gridY] = getGridPoint(
+            pointerCoords.x,
+            pointerCoords.y,
+            event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
+          );
           let dx = gridX - newElement.x;
           let dy = gridY - newElement.y;
 
@@ -8595,7 +8659,23 @@ class App extends React.Component<AppProps, AppState> {
             mutateElement(
               newElement,
               {
-                points: [...points, pointFrom<LocalPoint>(dx, dy)],
+                points: [
+                  ...points,
+                  toLocalPoint(
+                    getOutlineAvoidingPoint(
+                      newElement,
+                      pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
+                      newElement.points.length - 1,
+                      this.scene,
+                      this.state.zoom,
+                      pointFrom<GlobalPoint>(
+                        newElement.x + dx,
+                        newElement.y + dy,
+                      ),
+                    ),
+                    newElement,
+                  ),
+                ],
               },
               false,
             );
@@ -8606,7 +8686,23 @@ class App extends React.Component<AppProps, AppState> {
             mutateElement(
               newElement,
               {
-                points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
+                points: [
+                  ...points.slice(0, -1),
+                  toLocalPoint(
+                    getOutlineAvoidingPoint(
+                      newElement,
+                      pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
+                      newElement.points.length - 1,
+                      this.scene,
+                      this.state.zoom,
+                      pointFrom<GlobalPoint>(
+                        newElement.x + dx,
+                        newElement.y + dy,
+                      ),
+                    ),
+                    newElement,
+                  ),
+                ],
               },
               false,
               { isDragging: true },
@@ -10634,12 +10730,6 @@ class App extends React.Component<AppProps, AppState> {
         updateBoundElements(
           croppingElement,
           this.scene.getNonDeletedElementsMap(),
-          {
-            newSize: {
-              width: croppingElement.width,
-              height: croppingElement.height,
-            },
-          },
         );
 
         this.setState({

+ 1 - 3
packages/excalidraw/components/Stats/MultiDimension.tsx

@@ -87,9 +87,7 @@ const resizeElementInGroup = (
   );
   if (boundTextElement) {
     const newFontSize = boundTextElement.fontSize * scale;
-    updateBoundElements(latestElement, elementsMap, {
-      newSize: { width: updates.width, height: updates.height },
-    });
+    updateBoundElements(latestElement, elementsMap);
     const latestBoundTextElement = elementsMap.get(boundTextElement.id);
     if (latestBoundTextElement && isTextElement(latestBoundTextElement)) {
       mutateElement(

+ 14 - 14
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",

+ 3 - 3
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: FIXED_BINDING_DISTANCE,
       });
       expect(rect.boundElements).toStrictEqual([
         {

+ 132 - 124
packages/excalidraw/tests/__snapshots__/history.test.tsx.snap

@@ -197,7 +197,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
   "fillStyle": "solid",
   "frameId": null,
   "groupIds": [],
-  "height": "102.35417",
+  "height": "99.58947",
   "id": "id172",
   "index": "a2",
   "isDeleted": false,
@@ -211,8 +211,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
       0,
     ],
     [
-      "101.77517",
-      "102.35417",
+      "99.58947",
+      "99.58947",
     ],
   ],
   "roughness": 1,
@@ -227,8 +227,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": "99.58947",
+  "x": 0,
   "y": 0,
 }
 `;
@@ -294,47 +294,47 @@ History {
             "deleted": {
               "endBinding": {
                 "elementId": "id171",
-                "focus": "0.00990",
-                "gap": 1,
+                "focus": "0.01099",
+                "gap": 5,
               },
-              "height": "0.98586",
+              "height": "0.96335",
               "points": [
                 [
                   0,
                   0,
                 ],
                 [
-                  "98.58579",
-                  "-0.98586",
+                  "92.92893",
+                  "-0.96335",
                 ],
               ],
               "startBinding": {
                 "elementId": "id170",
-                "focus": "0.02970",
-                "gap": 1,
+                "focus": "0.03005",
+                "gap": 5,
               },
             },
             "inserted": {
               "endBinding": {
                 "elementId": "id171",
-                "focus": "-0.02000",
-                "gap": 1,
+                "focus": "-0.02041",
+                "gap": 5,
               },
-              "height": "0.00000",
+              "height": "0.03665",
               "points": [
                 [
                   0,
                   0,
                 ],
                 [
-                  "98.58579",
-                  "0.00000",
+                  "92.92893",
+                  "0.03665",
                 ],
               ],
               "startBinding": {
                 "elementId": "id170",
-                "focus": "0.02000",
-                "gap": 1,
+                "focus": "0.01884",
+                "gap": 5,
               },
             },
           },
@@ -389,43 +389,47 @@ History {
                 "focus": 0,
                 "gap": 1,
               },
-              "height": "102.35417",
+              "height": "99.58947",
               "points": [
                 [
                   0,
                   0,
                 ],
                 [
-                  "101.77517",
-                  "102.35417",
+                  "99.58947",
+                  "99.58947",
                 ],
               ],
               "startBinding": null,
+              "width": "99.58947",
+              "x": 0,
               "y": 0,
             },
             "inserted": {
               "endBinding": {
                 "elementId": "id171",
-                "focus": "0.00990",
-                "gap": 1,
+                "focus": "0.01099",
+                "gap": 5,
               },
-              "height": "0.98586",
+              "height": "0.96335",
               "points": [
                 [
                   0,
                   0,
                 ],
                 [
-                  "98.58579",
-                  "-0.98586",
+                  "92.92893",
+                  "-0.96335",
                 ],
               ],
               "startBinding": {
                 "elementId": "id170",
-                "focus": "0.02970",
-                "gap": 1,
+                "focus": "0.03005",
+                "gap": 5,
               },
-              "y": "0.99364",
+              "width": "92.92893",
+              "x": "3.53553",
+              "y": "0.96335",
             },
           },
           "id175" => Delta {
@@ -565,7 +569,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -579,7 +583,7 @@ History {
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -802,7 +806,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
       0,
     ],
     [
-      100,
+      "96.46447",
       0,
     ],
   ],
@@ -818,8 +822,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
   "type": "arrow",
   "updated": 1,
   "version": 30,
-  "width": 0,
-  "x": "149.29289",
+  "width": "96.46447",
+  "x": 150,
   "y": 0,
 }
 `;
@@ -852,7 +856,7 @@ History {
                   0,
                 ],
                 [
-                  0,
+                  "0.00000",
                   0,
                 ],
               ],
@@ -864,7 +868,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "92.92893",
                   0,
                 ],
               ],
@@ -919,17 +923,19 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
               "startBinding": null,
+              "width": "96.46447",
+              "x": 150,
             },
             "inserted": {
               "endBinding": {
                 "elementId": "id166",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "points": [
                 [
@@ -937,15 +943,17 @@ History {
                   0,
                 ],
                 [
-                  0,
+                  "0.00000",
                   0,
                 ],
               ],
               "startBinding": {
                 "elementId": "id165",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
+              "width": "0.00000",
+              "x": "146.46447",
             },
           },
         },
@@ -1072,7 +1080,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -1086,7 +1094,7 @@ History {
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -1238,7 +1246,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
   "fillStyle": "solid",
   "frameId": null,
   "groupIds": [],
-  "height": "1.30038",
+  "height": "1.71911",
   "id": "id178",
   "index": "Zz",
   "isDeleted": false,
@@ -1252,8 +1260,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
       0,
     ],
     [
-      "98.58579",
-      "1.30038",
+      "92.92893",
+      "1.71911",
     ],
   ],
   "roughness": 1,
@@ -1276,8 +1284,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
   "type": "arrow",
   "updated": 1,
   "version": 11,
-  "width": "98.58579",
-  "x": "0.70711",
+  "width": "92.92893",
+  "x": "3.53553",
   "y": 0,
 }
 `;
@@ -1609,7 +1617,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
   "fillStyle": "solid",
   "frameId": null,
   "groupIds": [],
-  "height": "1.30038",
+  "height": "1.71911",
   "id": "id181",
   "index": "a0",
   "isDeleted": false,
@@ -1623,8 +1631,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
       0,
     ],
     [
-      "98.58579",
-      "1.30038",
+      "92.92893",
+      "1.71911",
     ],
   ],
   "roughness": 1,
@@ -1647,8 +1655,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
   "type": "arrow",
   "updated": 1,
   "version": 11,
-  "width": "98.58579",
-  "x": "0.70711",
+  "width": "92.92893",
+  "x": "3.53553",
   "y": 0,
 }
 `;
@@ -1767,7 +1775,7 @@ History {
               "fillStyle": "solid",
               "frameId": null,
               "groupIds": [],
-              "height": "11.27227",
+              "height": "12.86717",
               "index": "a0",
               "isDeleted": false,
               "lastCommittedPoint": null,
@@ -1780,8 +1788,8 @@ History {
                   0,
                 ],
                 [
-                  "98.58579",
-                  "11.27227",
+                  "92.92893",
+                  "12.86717",
                 ],
               ],
               "roughness": 1,
@@ -1802,8 +1810,8 @@ History {
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": "98.58579",
-              "x": "0.70711",
+              "width": "92.92893",
+              "x": "3.53553",
               "y": 0,
             },
             "inserted": {
@@ -2315,12 +2323,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": "369.21589",
   "id": "id186",
   "index": "a2",
   "isDeleted": false,
@@ -2334,8 +2342,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
       0,
     ],
     [
-      "502.78936",
-      "-374.05754",
+      "496.84035",
+      "-369.21589",
     ],
   ],
   "roughness": 1,
@@ -2346,7 +2354,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",
@@ -2354,9 +2362,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": "496.84035",
+  "x": "2.18463",
+  "y": "-38.80748",
 }
 `;
 
@@ -2475,7 +2483,7 @@ History {
               "endBinding": {
                 "elementId": "id185",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "fillStyle": "solid",
               "frameId": null,
@@ -2493,7 +2501,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -2505,13 +2513,13 @@ History {
               "startBinding": {
                 "elementId": "id184",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
               "strokeColor": "#1e1e1e",
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -15111,7 +15119,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "endBinding": {
     "elementId": "id58",
     "focus": -0,
-    "gap": 1,
+    "gap": 5,
   },
   "fillStyle": "solid",
   "frameId": null,
@@ -15130,7 +15138,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
       0,
     ],
     [
-      "98.58579",
+      "92.92893",
       0,
     ],
   ],
@@ -15142,7 +15150,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "startBinding": {
     "elementId": "id56",
     "focus": 0,
-    "gap": 1,
+    "gap": 5,
   },
   "strokeColor": "#1e1e1e",
   "strokeStyle": "solid",
@@ -15150,8 +15158,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,
 }
 `;
@@ -15192,7 +15200,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -15205,7 +15213,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -15482,7 +15490,7 @@ History {
               "endBinding": {
                 "elementId": "id58",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "fillStyle": "solid",
               "frameId": null,
@@ -15500,7 +15508,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -15512,13 +15520,13 @@ History {
               "startBinding": {
                 "elementId": "id56",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
               "strokeColor": "#1e1e1e",
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -15808,7 +15816,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "endBinding": {
     "elementId": "id52",
     "focus": -0,
-    "gap": 1,
+    "gap": 5,
   },
   "fillStyle": "solid",
   "frameId": null,
@@ -15827,7 +15835,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
       0,
     ],
     [
-      "98.58579",
+      "92.92893",
       0,
     ],
   ],
@@ -15839,7 +15847,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "startBinding": {
     "elementId": "id50",
     "focus": 0,
-    "gap": 1,
+    "gap": 5,
   },
   "strokeColor": "#1e1e1e",
   "strokeStyle": "solid",
@@ -15847,8 +15855,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,
 }
 `;
@@ -16101,7 +16109,7 @@ History {
               "endBinding": {
                 "elementId": "id52",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "fillStyle": "solid",
               "frameId": null,
@@ -16119,7 +16127,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -16131,13 +16139,13 @@ History {
               "startBinding": {
                 "elementId": "id50",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
               "strokeColor": "#1e1e1e",
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -16427,7 +16435,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "endBinding": {
     "elementId": "id64",
     "focus": -0,
-    "gap": 1,
+    "gap": 5,
   },
   "fillStyle": "solid",
   "frameId": null,
@@ -16446,7 +16454,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
       0,
     ],
     [
-      "98.58579",
+      "92.92893",
       0,
     ],
   ],
@@ -16458,7 +16466,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "startBinding": {
     "elementId": "id62",
     "focus": 0,
-    "gap": 1,
+    "gap": 5,
   },
   "strokeColor": "#1e1e1e",
   "strokeStyle": "solid",
@@ -16466,8 +16474,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,
 }
 `;
@@ -16720,7 +16728,7 @@ History {
               "endBinding": {
                 "elementId": "id64",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "fillStyle": "solid",
               "frameId": null,
@@ -16738,7 +16746,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -16750,13 +16758,13 @@ History {
               "startBinding": {
                 "elementId": "id62",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
               "strokeColor": "#1e1e1e",
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -17044,7 +17052,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "endBinding": {
     "elementId": "id70",
     "focus": -0,
-    "gap": 1,
+    "gap": 5,
   },
   "fillStyle": "solid",
   "frameId": null,
@@ -17063,7 +17071,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
       0,
     ],
     [
-      "98.58579",
+      "92.92893",
       0,
     ],
   ],
@@ -17075,7 +17083,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "startBinding": {
     "elementId": "id68",
     "focus": 0,
-    "gap": 1,
+    "gap": 5,
   },
   "strokeColor": "#1e1e1e",
   "strokeStyle": "solid",
@@ -17083,8 +17091,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,
 }
 `;
@@ -17140,14 +17148,14 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
               "startBinding": {
                 "elementId": "id68",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
             },
             "inserted": {
@@ -17157,7 +17165,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -17407,7 +17415,7 @@ History {
               "endBinding": {
                 "elementId": "id70",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "fillStyle": "solid",
               "frameId": null,
@@ -17425,7 +17433,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -17437,13 +17445,13 @@ History {
               "startBinding": {
                 "elementId": "id68",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
               "strokeColor": "#1e1e1e",
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },
@@ -17757,7 +17765,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "endBinding": {
     "elementId": "id77",
     "focus": -0,
-    "gap": 1,
+    "gap": 5,
   },
   "fillStyle": "solid",
   "frameId": null,
@@ -17776,7 +17784,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
       0,
     ],
     [
-      "98.58579",
+      "92.92893",
       0,
     ],
   ],
@@ -17788,7 +17796,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
   "startBinding": {
     "elementId": "id75",
     "focus": 0,
-    "gap": 1,
+    "gap": 5,
   },
   "strokeColor": "#1e1e1e",
   "strokeStyle": "solid",
@@ -17796,8 +17804,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,
 }
 `;
@@ -17859,7 +17867,7 @@ History {
               "endBinding": {
                 "elementId": "id77",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "points": [
                 [
@@ -17867,14 +17875,14 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
               "startBinding": {
                 "elementId": "id75",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
             },
             "inserted": {
@@ -17885,7 +17893,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -18135,7 +18143,7 @@ History {
               "endBinding": {
                 "elementId": "id77",
                 "focus": -0,
-                "gap": 1,
+                "gap": 5,
               },
               "fillStyle": "solid",
               "frameId": null,
@@ -18153,7 +18161,7 @@ History {
                   0,
                 ],
                 [
-                  100,
+                  "96.46447",
                   0,
                 ],
               ],
@@ -18165,13 +18173,13 @@ History {
               "startBinding": {
                 "elementId": "id75",
                 "focus": 0,
-                "gap": 1,
+                "gap": 5,
               },
               "strokeColor": "#1e1e1e",
               "strokeStyle": "solid",
               "strokeWidth": 2,
               "type": "arrow",
-              "width": 100,
+              "width": "96.46447",
               "x": 0,
               "y": 0,
             },

+ 0 - 11
packages/excalidraw/tests/__snapshots__/linearElementEditor.test.tsx.snap

@@ -44,14 +44,3 @@ exports[`Test Linear Elements > Test bound text element > should resize and posi
 "Online whiteboard
 collaboration made easy"
 `;
-
-exports[`Test Linear Elements > Test bound text element > should wrap the bound text when arrow bound container moves 1`] = `
-"Online whiteboard
-collaboration made easy"
-`;
-
-exports[`Test Linear Elements > Test bound text element > should wrap the bound text when arrow bound container moves 2`] = `
-"Online whiteboard
-collaboration made
-easy"
-`;

+ 0 - 136
packages/excalidraw/tests/__snapshots__/move.test.tsx.snap

@@ -101,139 +101,3 @@ exports[`move element > rectangle 5`] = `
   "y": 40,
 }
 `;
-
-exports[`move element > rectangles with binding arrow 5`] = `
-{
-  "angle": 0,
-  "backgroundColor": "transparent",
-  "boundElements": [
-    {
-      "id": "id2",
-      "type": "arrow",
-    },
-  ],
-  "customData": undefined,
-  "fillStyle": "solid",
-  "frameId": null,
-  "groupIds": [],
-  "height": 100,
-  "id": "id0",
-  "index": "a0",
-  "isDeleted": false,
-  "link": null,
-  "locked": false,
-  "opacity": 100,
-  "roughness": 1,
-  "roundness": {
-    "type": 3,
-  },
-  "seed": 1278240551,
-  "strokeColor": "#1e1e1e",
-  "strokeStyle": "solid",
-  "strokeWidth": 2,
-  "type": "rectangle",
-  "updated": 1,
-  "version": 4,
-  "versionNonce": 1723083209,
-  "width": 100,
-  "x": 0,
-  "y": 0,
-}
-`;
-
-exports[`move element > rectangles with binding arrow 6`] = `
-{
-  "angle": 0,
-  "backgroundColor": "transparent",
-  "boundElements": [
-    {
-      "id": "id2",
-      "type": "arrow",
-    },
-  ],
-  "customData": undefined,
-  "fillStyle": "solid",
-  "frameId": null,
-  "groupIds": [],
-  "height": 300,
-  "id": "id1",
-  "index": "a1",
-  "isDeleted": false,
-  "link": null,
-  "locked": false,
-  "opacity": 100,
-  "roughness": 1,
-  "roundness": {
-    "type": 3,
-  },
-  "seed": 1150084233,
-  "strokeColor": "#1e1e1e",
-  "strokeStyle": "solid",
-  "strokeWidth": 2,
-  "type": "rectangle",
-  "updated": 1,
-  "version": 7,
-  "versionNonce": 745419401,
-  "width": 300,
-  "x": 201,
-  "y": 2,
-}
-`;
-
-exports[`move element > rectangles with binding arrow 7`] = `
-{
-  "angle": 0,
-  "backgroundColor": "transparent",
-  "boundElements": null,
-  "customData": undefined,
-  "elbowed": false,
-  "endArrowhead": "arrow",
-  "endBinding": {
-    "elementId": "id1",
-    "focus": "-0.46667",
-    "gap": 10,
-  },
-  "fillStyle": "solid",
-  "frameId": null,
-  "groupIds": [],
-  "height": "87.29887",
-  "id": "id2",
-  "index": "a2",
-  "isDeleted": false,
-  "lastCommittedPoint": null,
-  "link": null,
-  "locked": false,
-  "opacity": 100,
-  "points": [
-    [
-      0,
-      0,
-    ],
-    [
-      "86.85786",
-      "87.29887",
-    ],
-  ],
-  "roughness": 1,
-  "roundness": {
-    "type": 2,
-  },
-  "seed": 1604849351,
-  "startArrowhead": null,
-  "startBinding": {
-    "elementId": "id0",
-    "focus": "-0.60000",
-    "gap": 10,
-  },
-  "strokeColor": "#1e1e1e",
-  "strokeStyle": "solid",
-  "strokeWidth": 2,
-  "type": "arrow",
-  "updated": 1,
-  "version": 11,
-  "versionNonce": 1051383431,
-  "width": "86.85786",
-  "x": "107.07107",
-  "y": "47.07107",
-}
-`;

+ 4 - 2
packages/excalidraw/tests/history.test.tsx

@@ -52,6 +52,8 @@ import * as StaticScene from "../renderer/staticScene";
 import { Snapshot, CaptureUpdateAction } from "../store";
 import { AppStateChange, ElementsChange } from "../change";
 
+import { FIXED_BINDING_DISTANCE } from "../element/binding.js";
+
 import { API } from "./helpers/api";
 import { Keyboard, Pointer, UI } from "./helpers/ui";
 import {
@@ -4779,12 +4781,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,
             }),

+ 1 - 1
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.6, 1);
   expect(arrow.height).toBeCloseTo(0);
 });