Fixed gap binding

This commit is contained in:
Mark Tolmacs 2025-02-28 22:07:47 +01:00
parent ca5e9c3ad9
commit fbc5e4a03d
12 changed files with 169 additions and 216 deletions

View file

@ -13,6 +13,8 @@ import { useCallback, useImperativeHandle, useRef } from "react";
import {
isLineSegment,
isCurve,
type Curve,
type GlobalPoint,
type LineSegment,
} from "@excalidraw/math";

View file

@ -462,23 +462,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,
};
};
@ -728,7 +715,7 @@ const calculateFocusAndGap = (
return {
focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
gap: FIXED_BINDING_DISTANCE,
};
};
@ -746,7 +733,7 @@ export const updateBoundElements = (
changedElements?: Map<string, OrderedExcalidrawElement>;
},
) => {
const { newSize, simultaneouslyUpdated } = options ?? {};
const { simultaneouslyUpdated } = options ?? {};
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
simultaneouslyUpdated,
);
@ -779,23 +766,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;
}
@ -817,7 +794,9 @@ export const updateBoundElements = (
const point = updateBoundPoint(
element,
bindingProp,
bindings[bindingProp],
bindingProp === "startBinding"
? element.startBinding
: element.endBinding,
bindableElement,
elementsMap,
);
@ -1039,10 +1018,7 @@ export const avoidRectangularCorner = (
element: ExcalidrawBindableElement,
p: GlobalPoint,
): GlobalPoint => {
const center = pointFrom<GlobalPoint>(
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) {
@ -1225,7 +1201,6 @@ const updateBoundPoint = (
linearElement,
bindableElement,
startOrEnd === "startBinding" ? "start" : "end",
elementsMap,
).fixedPoint;
const globalMidPoint = pointFrom<GlobalPoint>(
bindableElement.x + bindableElement.width / 2,
@ -1335,7 +1310,6 @@ export const calculateFixedPointForElbowArrowBinding = (
linearElement: NonDeleted<ExcalidrawElbowArrowElement>,
hoveredElement: ExcalidrawBindableElement,
startOrEnd: "start" | "end",
elementsMap: ElementsMap,
): { fixedPoint: FixedPoint } => {
const bounds = [
hoveredElement.x,
@ -1368,28 +1342,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",

View file

@ -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<LocalPoint>(0, 0), pointFrom<LocalPoint>(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],
]);
});

View file

@ -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,
);

View file

@ -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",

View file

@ -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([
{

View file

@ -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.23572",
"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",
"96.42891",
"99.23572",
],
],
"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": "96.42891",
"x": "3.53553",
"y": 0,
}
`;
@ -295,46 +295,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,
},
},
},
@ -389,15 +389,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,
@ -407,25 +407,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 {
@ -929,7 +929,7 @@ History {
"endBinding": {
"elementId": "id166",
"focus": -0,
"gap": 1,
"gap": 5,
},
"points": [
[
@ -944,7 +944,7 @@ History {
"startBinding": {
"elementId": "id165",
"focus": 0,
"gap": 1,
"gap": 5,
},
},
},
@ -1238,7 +1238,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,
@ -1252,8 +1252,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
0,
],
[
"98.58579",
"1.30038",
"92.92893",
"-2.98409",
],
],
"roughness": 1,
@ -1276,9 +1276,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",
}
`;
@ -1609,7 +1609,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,
@ -1623,8 +1623,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
0,
],
[
"98.58579",
"1.30038",
"92.92893",
"-2.98409",
],
],
"roughness": 1,
@ -1647,9 +1647,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",
}
`;
@ -1767,7 +1767,7 @@ History {
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": "11.27227",
"height": "22.46459",
"index": "a0",
"isDeleted": false,
"lastCommittedPoint": null,
@ -1780,8 +1780,8 @@ History {
0,
],
[
"98.58579",
"11.27227",
"93.46683",
"-22.46459",
],
],
"roughness": 1,
@ -1802,9 +1802,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,
@ -2315,12 +2315,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,
@ -2334,8 +2334,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
0,
],
[
"502.78936",
"-374.05754",
"495.48945",
"-408.02337",
],
],
"roughness": 1,
@ -2346,7 +2346,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 +2354,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,
}
`;
@ -2475,7 +2475,7 @@ History {
"endBinding": {
"elementId": "id185",
"focus": -0,
"gap": 1,
"gap": 5,
},
"fillStyle": "solid",
"frameId": null,
@ -2505,7 +2505,7 @@ History {
"startBinding": {
"elementId": "id184",
"focus": 0,
"gap": 1,
"gap": 5,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
@ -15111,7 +15111,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 +15130,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
0,
],
[
"98.58579",
"92.92893",
0,
],
],
@ -15142,7 +15142,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 +15150,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,
}
`;
@ -15482,7 +15482,7 @@ History {
"endBinding": {
"elementId": "id58",
"focus": -0,
"gap": 1,
"gap": 5,
},
"fillStyle": "solid",
"frameId": null,
@ -15512,7 +15512,7 @@ History {
"startBinding": {
"elementId": "id56",
"focus": 0,
"gap": 1,
"gap": 5,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
@ -15808,7 +15808,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 +15827,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
0,
],
[
"98.58579",
"92.92893",
0,
],
],
@ -15839,7 +15839,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 +15847,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 +16101,7 @@ History {
"endBinding": {
"elementId": "id52",
"focus": -0,
"gap": 1,
"gap": 5,
},
"fillStyle": "solid",
"frameId": null,
@ -16131,7 +16131,7 @@ History {
"startBinding": {
"elementId": "id50",
"focus": 0,
"gap": 1,
"gap": 5,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
@ -16427,7 +16427,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 +16446,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
0,
],
[
"98.58579",
"92.92893",
0,
],
],
@ -16458,7 +16458,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 +16466,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 +16720,7 @@ History {
"endBinding": {
"elementId": "id64",
"focus": -0,
"gap": 1,
"gap": 5,
},
"fillStyle": "solid",
"frameId": null,
@ -16750,7 +16750,7 @@ History {
"startBinding": {
"elementId": "id62",
"focus": 0,
"gap": 1,
"gap": 5,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
@ -17044,7 +17044,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 +17063,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
0,
],
[
"98.58579",
"92.92893",
0,
],
],
@ -17075,7 +17075,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 +17083,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,
}
`;
@ -17147,7 +17147,7 @@ History {
"startBinding": {
"elementId": "id68",
"focus": 0,
"gap": 1,
"gap": 5,
},
},
"inserted": {
@ -17407,7 +17407,7 @@ History {
"endBinding": {
"elementId": "id70",
"focus": -0,
"gap": 1,
"gap": 5,
},
"fillStyle": "solid",
"frameId": null,
@ -17437,7 +17437,7 @@ History {
"startBinding": {
"elementId": "id68",
"focus": 0,
"gap": 1,
"gap": 5,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
@ -17757,7 +17757,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 +17776,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
0,
],
[
"98.58579",
"92.92893",
0,
],
],
@ -17788,7 +17788,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 +17796,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 +17859,7 @@ History {
"endBinding": {
"elementId": "id77",
"focus": -0,
"gap": 1,
"gap": 5,
},
"points": [
[
@ -17874,7 +17874,7 @@ History {
"startBinding": {
"elementId": "id75",
"focus": 0,
"gap": 1,
"gap": 5,
},
},
"inserted": {
@ -18135,7 +18135,7 @@ History {
"endBinding": {
"elementId": "id77",
"focus": -0,
"gap": 1,
"gap": 5,
},
"fillStyle": "solid",
"frameId": null,
@ -18165,7 +18165,7 @@ History {
"startBinding": {
"elementId": "id75",
"focus": 0,
"gap": 1,
"gap": 5,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",

View file

@ -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,
}
`;

View file

@ -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,
}),

View file

@ -1246,7 +1246,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(

View file

@ -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);
});

View file

@ -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);