Browse Source

fix: Elbow arrow orthogonality (#9073)

Márk Tolmács 6 months ago
parent
commit
9b6edc767a
2 changed files with 52 additions and 10 deletions
  1. 24 1
      packages/excalidraw/element/elbowArrow.ts
  2. 28 9
      packages/excalidraw/element/flowchart.ts

+ 24 - 1
packages/excalidraw/element/elbowArrow.ts

@@ -244,6 +244,12 @@ const handleSegmentRenormalization = (
       );
     }
 
+    import.meta.env.DEV &&
+      invariant(
+        validateElbowPoints(nextPoints),
+        "Invalid elbow points with fixed segments",
+      );
+
     return normalizeArrowElementUpdate(
       nextPoints,
       filteredNextFixedSegments,
@@ -912,7 +918,11 @@ export const updateElbowArrowPoints = (
   // 0. During all element replacement in the scene, we just need to renormalize
   // the arrow
   // TODO (dwelle,mtolmacs): Remove this once Scene.getScene() is removed
-  if (elementsMap.size === 0 && updates.points) {
+  if (
+    elementsMap.size === 0 &&
+    updates.points &&
+    validateElbowPoints(updates.points)
+  ) {
     return normalizeArrowElementUpdate(
       updates.points.map((p) =>
         pointFrom<GlobalPoint>(arrow.x + p[0], arrow.y + p[1]),
@@ -2123,3 +2133,16 @@ const getHoveredElements = (
 
 const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean =>
   a[0] === b[0] && a[1] === b[1];
+
+const validateElbowPoints = <P extends GlobalPoint | LocalPoint>(
+  points: readonly P[],
+  tolerance: number = DEDUP_TRESHOLD,
+) =>
+  points
+    .slice(1)
+    .map(
+      (p, i) =>
+        Math.abs(p[0] - points[i][0]) < tolerance ||
+        Math.abs(p[1] - points[i][1]) < tolerance,
+    )
+    .every(Boolean);

+ 28 - 9
packages/excalidraw/element/flowchart.ts

@@ -10,13 +10,15 @@ import {
 import { bindLinearElement } from "./binding";
 import { LinearElementEditor } from "./linearElementEditor";
 import { newArrowElement, newElement } from "./newElement";
-import type {
-  ElementsMap,
-  ExcalidrawBindableElement,
-  ExcalidrawElement,
-  ExcalidrawFlowchartNodeElement,
-  NonDeletedSceneElementsMap,
-  OrderedExcalidrawElement,
+import type { SceneElementsMap } from "./types";
+import {
+  type ElementsMap,
+  type ExcalidrawBindableElement,
+  type ExcalidrawElement,
+  type ExcalidrawFlowchartNodeElement,
+  type NonDeletedSceneElementsMap,
+  type Ordered,
+  type OrderedExcalidrawElement,
 } from "./types";
 import { KEYS } from "../keys";
 import type { AppState, PendingExcalidrawElements } from "../types";
@@ -28,9 +30,10 @@ import {
   isFrameElement,
   isFlowchartNodeElement,
 } from "./typeChecks";
-import { invariant } from "../utils";
+import { invariant, toBrandedType } from "../utils";
 import { pointFrom, type LocalPoint } from "../../math";
 import { aabbForElement } from "../shapes";
+import { updateElbowArrowPoints } from "./elbowArrow";
 
 type LinkDirection = "up" | "right" | "down" | "left";
 
@@ -467,7 +470,23 @@ const createBindingArrow = (
     },
   ]);
 
-  return bindingArrow;
+  const update = updateElbowArrowPoints(
+    bindingArrow,
+    toBrandedType<SceneElementsMap>(
+      new Map([
+        ...elementsMap.entries(),
+        [startBindingElement.id, startBindingElement],
+        [endBindingElement.id, endBindingElement],
+        [bindingArrow.id, bindingArrow],
+      ] as [string, Ordered<ExcalidrawElement>][]),
+    ),
+    { points: bindingArrow.points },
+  );
+
+  return {
+    ...bindingArrow,
+    ...update,
+  };
 };
 
 export class FlowChartNavigator {