diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 86041b4aba..ae84623c3b 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -2447,21 +2447,37 @@ export const getArrowLocalFixedPoints = ( ]; }; -export const normalizeFixedPoint = ( +export const isFixedPoint = ( + fixedPoint: any, +): fixedPoint is FixedPointBinding["fixedPoint"] => { + return ( + Array.isArray(fixedPoint) && + fixedPoint.length === 2 && + fixedPoint.every((coord) => Number.isFinite(coord)) + ); +}; + +export const normalizeFixedPoint = ( fixedPoint: T, -): T extends null ? null : FixedPoint => { +): FixedPoint => { + if (!isFixedPoint(fixedPoint)) { + return [0.5001, 0.5001]; + } + + const EPSILON = 0.0001; + // Do not allow a precise 0.5 for fixed point ratio // to avoid jumping arrow heading due to floating point imprecision if ( - fixedPoint && - (Math.abs(fixedPoint[0] - 0.5) < 0.0001 || - Math.abs(fixedPoint[1] - 0.5) < 0.0001) + Math.abs(fixedPoint[0] - 0.5) < EPSILON || + Math.abs(fixedPoint[1] - 0.5) < EPSILON ) { return fixedPoint.map((ratio) => - Math.abs(ratio - 0.5) < 0.0001 ? 0.5001 : ratio, - ) as T extends null ? null : FixedPoint; + Math.abs(ratio - 0.5) < EPSILON ? 0.5001 : ratio, + ) as FixedPoint; } - return fixedPoint as any as T extends null ? null : FixedPoint; + + return fixedPoint; }; type Side = diff --git a/packages/excalidraw/data/restore.ts b/packages/excalidraw/data/restore.ts index 5c81c657f3..b77f010bd8 100644 --- a/packages/excalidraw/data/restore.ts +++ b/packages/excalidraw/data/restore.ts @@ -155,7 +155,7 @@ const repairBinding = ( | ExcalidrawElbowArrowElement["startBinding"] | ExcalidrawElbowArrowElement["endBinding"] = { ...binding, - fixedPoint: normalizeFixedPoint(binding.fixedPoint ?? [0, 0]), + fixedPoint: normalizeFixedPoint(binding.fixedPoint), mode: binding.mode || "orbit", }; @@ -176,7 +176,7 @@ const repairBinding = ( return { elementId: binding.elementId, mode: binding.mode, - fixedPoint: normalizeFixedPoint(binding.fixedPoint || [0.5, 0.5]), + fixedPoint: normalizeFixedPoint(binding.fixedPoint), } as FixedPointBinding | null; } return null; @@ -185,15 +185,14 @@ const repairBinding = ( // binding schema v1 (legacy) -> attempt to migrate to v2 // --------------------------------------------------------------------------- - const targetBoundElement = - (targetElementsMap.get(binding.elementId) as ExcalidrawBindableElement) || - undefined; + const targetBoundElement = targetElementsMap.get(binding.elementId) as + | ExcalidrawBindableElement + | undefined; const boundElement = targetBoundElement || - (existingElementsMap?.get( - binding.elementId, - ) as ExcalidrawBindableElement) || - undefined; + (existingElementsMap?.get(binding.elementId) as + | ExcalidrawBindableElement + | undefined); const elementsMap = targetBoundElement ? targetElementsMap : existingElementsMap; @@ -208,11 +207,28 @@ const repairBinding = ( const mode = isPointInElement(p, boundElement, elementsMap) ? "inside" : "orbit"; + const safeElement = { + ...element, + startBinding: element.startBinding?.elementId + ? { + ...element.startBinding, + mode, + fixedPoint: normalizeFixedPoint(element.startBinding.fixedPoint), + } + : null, + endBinding: element.endBinding?.elementId + ? { + ...element.endBinding, + mode, + fixedPoint: normalizeFixedPoint(element.endBinding.fixedPoint), + } + : null, + }; const focusPoint = mode === "inside" ? p : projectFixedPointOntoDiagonal( - element, + safeElement, p, boundElement, startOrEnd, @@ -220,7 +236,7 @@ const repairBinding = ( { value: 1 as NormalizedZoomValue }, ) || p; const { fixedPoint } = calculateFixedPointForNonElbowArrowBinding( - element, + safeElement, boundElement, startOrEnd, elementsMap,