chore: Refactor binding gap calc

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs
2026-05-10 14:51:05 +00:00
parent 60f29dc188
commit 73c940bcf6
10 changed files with 255 additions and 300 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ export const isFocusPointVisible = (
element: bindableElement, element: bindableElement,
elementsMap, elementsMap,
point: focusPoint, point: focusPoint,
threshold: getBindingGap(bindableElement, arrow), threshold: getBindingGap(bindableElement),
overrideShouldTestInside: true, overrideShouldTestInside: true,
}) })
); );
+7 -12
View File
@@ -111,18 +111,13 @@ export type BindingStrategy =
* IMPORTANT: currently must be > 0 (this also applies to the computed gap) * IMPORTANT: currently must be > 0 (this also applies to the computed gap)
*/ */
export const BASE_BINDING_GAP = 5; export const BASE_BINDING_GAP = 5;
export const BASE_BINDING_GAP_ELBOW = 5;
export const BASE_ARROW_MIN_LENGTH = 10; export const BASE_ARROW_MIN_LENGTH = 10;
export const FOCUS_POINT_SIZE = 10 / 1.5; export const FOCUS_POINT_SIZE = 10 / 1.5;
export const getBindingGap = ( export const getBindingGap = (
bindTarget: ExcalidrawBindableElement, bindTarget: ExcalidrawBindableElement,
opts: Pick<ExcalidrawArrowElement, "elbowed">,
): number => { ): number => {
return ( return BASE_BINDING_GAP + bindTarget.strokeWidth / 2;
(opts.elbowed ? BASE_BINDING_GAP_ELBOW : BASE_BINDING_GAP) +
bindTarget.strokeWidth / 2
);
}; };
export const maxBindingDistance_simple = (zoom?: AppState["zoom"]): number => { export const maxBindingDistance_simple = (zoom?: AppState["zoom"]): number => {
@@ -1416,7 +1411,7 @@ export const bindPointToSnapToElementOutline = (
startOrEnd === "start" ? 1 : -2, startOrEnd === "start" ? 1 : -2,
elementsMap, elementsMap,
); );
const bindingGap = getBindingGap(bindableElement, arrowElement); const bindingGap = getBindingGap(bindableElement);
const aabb = aabbForElement(bindableElement, elementsMap); const aabb = aabbForElement(bindableElement, elementsMap);
const bindableCenter = getCenterForBounds(aabb); const bindableCenter = getCenterForBounds(aabb);
@@ -1477,7 +1472,7 @@ export const bindPointToSnapToElementOutline = (
bindableElement, bindableElement,
elementsMap, elementsMap,
anotherIntersector, anotherIntersector,
BASE_BINDING_GAP_ELBOW, BASE_BINDING_GAP,
).sort(pointDistanceSq)[0]; ).sort(pointDistanceSq)[0];
} }
} else { } else {
@@ -1536,7 +1531,7 @@ export const avoidRectangularCorner = (
-bindTarget.angle as Radians, -bindTarget.angle as Radians,
); );
const bindingGap = getBindingGap(bindTarget, arrowElement); const bindingGap = getBindingGap(bindTarget);
if (nonRotatedPoint[0] < bindTarget.x && nonRotatedPoint[1] < bindTarget.y) { if (nonRotatedPoint[0] < bindTarget.x && nonRotatedPoint[1] < bindTarget.y) {
// Top left // Top left
@@ -1714,7 +1709,7 @@ export const updateBoundPoint = (
otherBindable, otherBindable,
elementsMap, elementsMap,
intersector, intersector,
getBindingGap(otherBindable, arrow), getBindingGap(otherBindable),
).sort( ).sort(
(a, b) => pointDistanceSq(a, focusPoint) - pointDistanceSq(b, focusPoint), (a, b) => pointDistanceSq(a, focusPoint) - pointDistanceSq(b, focusPoint),
)[0]; )[0];
@@ -1724,7 +1719,7 @@ export const updateBoundPoint = (
bindableElement, bindableElement,
elementsMap, elementsMap,
intersector, intersector,
getBindingGap(bindableElement, arrow), getBindingGap(bindableElement),
).sort( ).sort(
(a, b) => (a, b) =>
pointDistanceSq(a, otherFocusPointOrArrowPoint) - pointDistanceSq(a, otherFocusPointOrArrowPoint) -
@@ -1752,7 +1747,7 @@ export const updateBoundPoint = (
element: otherBindable, element: otherBindable,
point: outlinePoint, point: outlinePoint,
elementsMap, elementsMap,
threshold: getBindingGap(otherBindable, arrow), threshold: getBindingGap(otherBindable),
overrideShouldTestInside: true, overrideShouldTestInside: true,
}) })
) { ) {
+1 -1
View File
@@ -316,7 +316,7 @@ export const getAllHoveredElementAtPoint = (
element, element,
point, point,
elementsMap, elementsMap,
threshold: tolerance ?? getBindingGap(element, arrow), threshold: tolerance ?? getBindingGap(element),
overrideShouldTestInside: true, overrideShouldTestInside: true,
}) })
) { ) {
+9 -9
View File
@@ -30,7 +30,7 @@ import {
getHeadingForElbowArrowSnap, getHeadingForElbowArrowSnap,
getGlobalFixedPointForBindableElement, getGlobalFixedPointForBindableElement,
getBindingGap, getBindingGap,
BASE_BINDING_GAP_ELBOW, BASE_BINDING_GAP,
} from "./binding"; } from "./binding";
import { distanceToElement } from "./distance"; import { distanceToElement } from "./distance";
import { import {
@@ -1311,8 +1311,8 @@ const getElbowArrowData = (
offsetFromHeading( offsetFromHeading(
startHeading, startHeading,
arrow.startArrowhead arrow.startArrowhead
? getBindingGap(hoveredStartElement, { elbowed: true }) * 6 ? getBindingGap(hoveredStartElement) * 6
: getBindingGap(hoveredStartElement, { elbowed: true }) * 2, : getBindingGap(hoveredStartElement) * 2,
1, 1,
), ),
) )
@@ -1324,8 +1324,8 @@ const getElbowArrowData = (
offsetFromHeading( offsetFromHeading(
endHeading, endHeading,
arrow.endArrowhead arrow.endArrowhead
? getBindingGap(hoveredEndElement, { elbowed: true }) * 6 ? getBindingGap(hoveredEndElement) * 6
: getBindingGap(hoveredEndElement, { elbowed: true }) * 2, : getBindingGap(hoveredEndElement) * 2,
1, 1,
), ),
) )
@@ -1372,8 +1372,8 @@ const getElbowArrowData = (
? 0 ? 0
: BASE_PADDING - : BASE_PADDING -
(arrow.startArrowhead (arrow.startArrowhead
? BASE_BINDING_GAP_ELBOW * 6 ? BASE_BINDING_GAP * 6
: BASE_BINDING_GAP_ELBOW * 2), : BASE_BINDING_GAP * 2),
BASE_PADDING, BASE_PADDING,
), ),
boundsOverlap boundsOverlap
@@ -1388,8 +1388,8 @@ const getElbowArrowData = (
? 0 ? 0
: BASE_PADDING - : BASE_PADDING -
(arrow.endArrowhead (arrow.endArrowhead
? BASE_BINDING_GAP_ELBOW * 6 ? BASE_BINDING_GAP * 6
: BASE_BINDING_GAP_ELBOW * 2), : BASE_BINDING_GAP * 2),
BASE_PADDING, BASE_PADDING,
), ),
boundsOverlap, boundsOverlap,
+137 -133
View File
@@ -588,104 +588,101 @@ const getDiagonalsForBindableElement = (
return [diagonalOne, diagonalTwo]; return [diagonalOne, diagonalTwo];
}; };
export const getHighlightedMidpointIndex = ( const getSnappedMidpointIndexForElbowArrow = (
point: GlobalPoint,
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
elementsMap: ElementsMap, point: GlobalPoint,
zoom: AppState["zoom"], center: GlobalPoint,
arrow: { elbowed: boolean }, horizontalThreshold: number,
): number => { verticalThreshold: number,
const center = elementCenterPoint(element, elementsMap); ) => {
const TOLERANCE = 0.05;
const maxDistance = maxBindingDistance_simple(zoom) + element.strokeWidth / 2;
const { x, y, width, height, angle } = element; const { x, y, width, height, angle } = element;
const nonRotated = pointRotateRads(point, center, -angle as Radians);
// snap-to-center point is adaptive to element size, but we don't want to go const bindingGap = getBindingGap(element);
// above and below certain px distance
const verticalThreshold = clamp(TOLERANCE * height, 5, maxDistance);
const horizontalThreshold = clamp(TOLERANCE * width, 5, maxDistance);
if (arrow.elbowed) {
const nonRotated = pointRotateRads(point, center, -angle as Radians);
const bindingGap = getBindingGap(element, arrow);
if (pointDistance(center, nonRotated) < bindingGap) {
return -1;
}
if (
nonRotated[0] <= x + width / 2 &&
nonRotated[1] > center[1] - verticalThreshold &&
nonRotated[1] < center[1] + verticalThreshold
) {
return 2;
} else if (
nonRotated[1] <= y + height / 2 &&
nonRotated[0] > center[0] - horizontalThreshold &&
nonRotated[0] < center[0] + horizontalThreshold
) {
return 3;
} else if (
nonRotated[0] >= x + width / 2 &&
nonRotated[1] > center[1] - verticalThreshold &&
nonRotated[1] < center[1] + verticalThreshold
) {
return 0;
} else if (
nonRotated[1] >= y + height / 2 &&
nonRotated[0] > center[0] - horizontalThreshold &&
nonRotated[0] < center[0] + horizontalThreshold
) {
return 1;
} else if (element.type === "diamond") {
const distance = bindingGap;
const topLeft = pointFrom<GlobalPoint>(
x + width / 4 - distance,
y + height / 4 - distance,
);
const topRight = pointFrom<GlobalPoint>(
x + (3 * width) / 4 + distance,
y + height / 4 - distance,
);
const bottomLeft = pointFrom<GlobalPoint>(
x + width / 4 - distance,
y + (3 * height) / 4 + distance,
);
const bottomRight = pointFrom<GlobalPoint>(
x + (3 * width) / 4 + distance,
y + (3 * height) / 4 + distance,
);
if (
pointDistance(bottomLeft, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 1;
}
if (
pointDistance(bottomRight, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 0;
}
if (
pointDistance(topLeft, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 2;
}
if (
pointDistance(topRight, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 3;
}
}
if (pointDistance(center, nonRotated) < bindingGap) {
return -1; return -1;
} }
if (
nonRotated[0] <= x + width / 2 &&
nonRotated[1] > center[1] - verticalThreshold &&
nonRotated[1] < center[1] + verticalThreshold
) {
return 2;
} else if (
nonRotated[1] <= y + height / 2 &&
nonRotated[0] > center[0] - horizontalThreshold &&
nonRotated[0] < center[0] + horizontalThreshold
) {
return 3;
} else if (
nonRotated[0] >= x + width / 2 &&
nonRotated[1] > center[1] - verticalThreshold &&
nonRotated[1] < center[1] + verticalThreshold
) {
return 0;
} else if (
nonRotated[1] >= y + height / 2 &&
nonRotated[0] > center[0] - horizontalThreshold &&
nonRotated[0] < center[0] + horizontalThreshold
) {
return 1;
} else if (element.type === "diamond") {
const distance = bindingGap;
const topLeft = pointFrom<GlobalPoint>(
x + width / 4 - distance,
y + height / 4 - distance,
);
const topRight = pointFrom<GlobalPoint>(
x + (3 * width) / 4 + distance,
y + height / 4 - distance,
);
const bottomLeft = pointFrom<GlobalPoint>(
x + width / 4 - distance,
y + (3 * height) / 4 + distance,
);
const bottomRight = pointFrom<GlobalPoint>(
x + (3 * width) / 4 + distance,
y + (3 * height) / 4 + distance,
);
if (
pointDistance(bottomLeft, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 1;
}
if (
pointDistance(bottomRight, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 0;
}
if (
pointDistance(topLeft, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 2;
}
if (
pointDistance(topRight, nonRotated) <
Math.max(horizontalThreshold, verticalThreshold)
) {
return 3;
}
}
return -1;
};
const getSnappedMidpointIndexForSimpleArrow = (
element: ExcalidrawBindableElement,
point: GlobalPoint,
elementsMap: ElementsMap,
horizontalThreshold: number,
verticalThreshold: number,
) => {
const baseMidpoints = getAllMidpoints(element, elementsMap); const baseMidpoints = getAllMidpoints(element, elementsMap);
for (let i = 0; i < baseMidpoints.length; i++) { for (let i = 0; i < baseMidpoints.length; i++) {
@@ -708,41 +705,6 @@ export const getHighlightedMidpointIndex = (
return -1; return -1;
}; };
export const getSnapOutlineMidPoint = (
point: GlobalPoint,
element: ExcalidrawBindableElement,
elementsMap: ElementsMap,
zoom: AppState["zoom"],
arrow: { elbowed: boolean },
): GlobalPoint | undefined => {
const center = elementCenterPoint(element, elementsMap);
const baseMidpoints = getAllMidpoints(element, elementsMap);
const sideMidpoints =
element.type === "diamond"
? baseMidpoints.map((midpoint) => {
return pointFrom<GlobalPoint>(
midpoint[0] + (midpoint[0] - center[0]) * 0.1,
midpoint[1] + (midpoint[1] - center[1]) * 0.1,
);
})
: baseMidpoints;
const idx = getHighlightedMidpointIndex(
point,
element,
elementsMap,
zoom,
arrow,
);
if (idx === -1) {
return undefined;
}
return sideMidpoints[idx];
};
export const getAllMidpoints = ( export const getAllMidpoints = (
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
elementsMap: ElementsMap, elementsMap: ElementsMap,
@@ -750,10 +712,9 @@ export const getAllMidpoints = (
const center = elementCenterPoint(element, elementsMap); const center = elementCenterPoint(element, elementsMap);
if (element.type === "diamond") { if (element.type === "diamond") {
return getDiamondBaseCorners(element).map((curve) => { return getDiamondBaseCorners(element).map((curve) =>
const point = bezierEquation(curve, 0.5); pointRotateRads(bezierEquation(curve, 0.5), center, element.angle),
return pointRotateRads(point, center, element.angle); );
});
} }
return [ return [
@@ -770,6 +731,52 @@ export const getAllMidpoints = (
); );
}; };
export const getSnapOutlineMidPoint = (
point: GlobalPoint,
element: ExcalidrawBindableElement,
elementsMap: ElementsMap,
zoom: AppState["zoom"],
arrow: { elbowed: boolean },
): GlobalPoint | undefined => {
const center = elementCenterPoint(element, elementsMap);
const baseMidpoints = getAllMidpoints(element, elementsMap);
const sideMidpoints =
element.type === "diamond"
? baseMidpoints.map((midpoint) => {
return pointFrom<GlobalPoint>(
midpoint[0] + (midpoint[0] - center[0]) * 0.1,
midpoint[1] + (midpoint[1] - center[1]) * 0.1,
);
})
: baseMidpoints;
const TOLERANCE = 0.05;
const maxDistance = maxBindingDistance_simple(zoom) + element.strokeWidth / 2;
const verticalThreshold = clamp(TOLERANCE * element.height, 5, maxDistance);
const horizontalThreshold = clamp(TOLERANCE * element.width, 5, maxDistance);
const idx = arrow.elbowed
? getSnappedMidpointIndexForElbowArrow(
element,
point,
center,
horizontalThreshold,
verticalThreshold,
)
: getSnappedMidpointIndexForSimpleArrow(
element,
point,
elementsMap,
horizontalThreshold,
verticalThreshold,
);
if (idx === -1) {
return undefined;
}
return sideMidpoints[idx];
};
export const projectFixedPointOntoDiagonal = ( export const projectFixedPointOntoDiagonal = (
arrow: ExcalidrawArrowElement, arrow: ExcalidrawArrowElement,
point: GlobalPoint, point: GlobalPoint,
@@ -780,9 +787,6 @@ export const projectFixedPointOntoDiagonal = (
isMidpointSnappingEnabled: boolean = true, isMidpointSnappingEnabled: boolean = true,
): GlobalPoint | null => { ): GlobalPoint | null => {
invariant(arrow.points.length >= 2, "Arrow must have at least two points"); invariant(arrow.points.length >= 2, "Arrow must have at least two points");
if (arrow.width < 3 && arrow.height < 3) {
return null;
}
if (isMidpointSnappingEnabled) { if (isMidpointSnappingEnabled) {
const sideMidPoint = getSnapOutlineMidPoint( const sideMidPoint = getSnapOutlineMidPoint(
@@ -24,13 +24,11 @@ import {
deconstructRectanguloidElement, deconstructRectanguloidElement,
elementCenterPoint, elementCenterPoint,
getAllMidpoints, getAllMidpoints,
getHighlightedMidpointIndex,
FOCUS_POINT_SIZE, FOCUS_POINT_SIZE,
getOmitSidesForEditorInterface, getOmitSidesForEditorInterface,
getTransformHandles, getTransformHandles,
getTransformHandlesFromCoords, getTransformHandlesFromCoords,
hasBoundingBox, hasBoundingBox,
hitElementItself,
isArrowElement, isArrowElement,
isBindableElement, isBindableElement,
isElbowArrow, isElbowArrow,
@@ -412,74 +410,42 @@ const renderBindingHighlightForBindableElement_simple = (
break; break;
} }
// Draw midpoint indicators
if ( if (
appState.isMidpointSnappingEnabled && appState.isMidpointSnappingEnabled &&
(isFrameLikeElement(suggestedBinding.element) || (isFrameLikeElement(suggestedBinding.element) ||
isBindableElement(suggestedBinding.element)) isBindableElement(suggestedBinding.element))
) { ) {
// Draw midpoint indicators context.save();
const linearElement = appState.selectedLinearElement;
const arrow =
linearElement?.elementId &&
LinearElementEditor.getElement(linearElement?.elementId, elementsMap);
const cursorIsInsideBindable =
pointerCoords &&
hitElementItself({
point: pointerCoords,
element: suggestedBinding.element,
elementsMap,
threshold: 0,
overrideShouldTestInside: true,
});
const isElbow = const midpointRadius = 4 / appState.zoom.value;
(arrow && isElbowArrow(arrow)) ||
(appState.activeTool.type === "arrow" &&
appState.currentItemArrowType === "elbow");
if (!cursorIsInsideBindable || isElbow) { // Render base midpoints
context.save(); const midpoints = getAllMidpoints(suggestedBinding.element, elementsMap);
for (const midpoint of midpoints) {
const midpoints = getAllMidpoints(suggestedBinding.element, elementsMap); context.fillStyle =
appState.theme === THEME.DARK
const highlightedIdx = pointerCoords ? `rgba(0, 0, 0, 0.8)`
? getHighlightedMidpointIndex( : `rgba(65, 65, 65, 0.5)`;
pointerCoords, context.beginPath();
suggestedBinding.element, context.arc(midpoint[0], midpoint[1], midpointRadius, 0, 2 * Math.PI);
elementsMap, context.fill();
appState.zoom,
{ elbowed: isElbow },
)
: -1;
const midpointRadius = 4 / appState.zoom.value;
midpoints.forEach((midpoint, idx) => {
const isHighlighted =
highlightedIdx === idx && (!cursorIsInsideBindable || isElbow);
if (isHighlighted) {
context.fillStyle =
appState.theme === THEME.DARK
? `rgba(3, 93, 161, 1)`
: `rgba(106, 189, 252, 1)`;
context.beginPath();
context.arc(midpoint[0], midpoint[1], midpointRadius, 0, 2 * Math.PI);
context.fill();
} else {
context.fillStyle =
appState.theme === THEME.DARK
? `rgba(0, 0, 0, 0.8)`
: `rgba(65, 65, 65, 0.5)`;
context.beginPath();
context.arc(midpoint[0], midpoint[1], midpointRadius, 0, 2 * Math.PI);
context.fill();
}
});
context.restore();
} }
// Render the highlighted midpoint if any
const midpoint = appState.suggestedBinding?.midPoint;
if (midpoint) {
context.fillStyle =
appState.theme === THEME.DARK
? `rgba(3, 93, 161, 1)`
: `rgba(106, 189, 252, 1)`;
context.beginPath();
context.arc(midpoint[0], midpoint[1], midpointRadius, 0, 2 * Math.PI);
context.fill();
}
context.restore();
} }
}; };
@@ -16703,7 +16703,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"id": "id13", "id": "id13",
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
@@ -16718,7 +16718,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -16729,8 +16729,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -16742,7 +16742,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 10, "version": 10,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
} }
`; `;
@@ -16787,8 +16787,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -16807,8 +16807,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -17121,7 +17121,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -17134,7 +17134,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -17145,8 +17145,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -17157,7 +17157,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 7, "version": 7,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
}, },
"inserted": { "inserted": {
"isDeleted": true, "isDeleted": true,
@@ -17451,7 +17451,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"id": "id13", "id": "id13",
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
@@ -17466,7 +17466,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -17477,8 +17477,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -17490,7 +17490,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 10, "version": 10,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
} }
`; `;
@@ -17759,7 +17759,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -17772,7 +17772,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -17783,8 +17783,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -17795,7 +17795,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 10, "version": 10,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
}, },
"inserted": { "inserted": {
"isDeleted": true, "isDeleted": true,
@@ -18097,7 +18097,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"id": "id13", "id": "id13",
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
@@ -18112,7 +18112,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -18123,8 +18123,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -18136,7 +18136,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 10, "version": 10,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
} }
`; `;
@@ -18405,7 +18405,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -18418,7 +18418,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -18429,8 +18429,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -18441,7 +18441,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 10, "version": 10,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
}, },
"inserted": { "inserted": {
"isDeleted": true, "isDeleted": true,
@@ -18741,7 +18741,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"id": "id13", "id": "id13",
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
@@ -18756,7 +18756,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -18767,8 +18767,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -18780,7 +18780,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 10, "version": 10,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
} }
`; `;
@@ -18841,8 +18841,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -19135,7 +19135,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -19148,7 +19148,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -19159,8 +19159,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -19171,7 +19171,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 7, "version": 7,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
}, },
"inserted": { "inserted": {
"isDeleted": true, "isDeleted": true,
@@ -19493,7 +19493,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"id": "id13", "id": "id13",
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
@@ -19508,7 +19508,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -19519,8 +19519,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -19532,7 +19532,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 11, "version": 11,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
} }
`; `;
@@ -19604,8 +19604,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -19883,7 +19883,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "0.00661", "height": 0,
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -19896,7 +19896,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
], ],
[ [
"88.00000", "88.00000",
"-0.00661", 0,
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -19907,8 +19907,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"startBinding": { "startBinding": {
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"0.50021", 1,
"0.50021", "0.50010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -19919,7 +19919,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
"version": 7, "version": 7,
"width": "88.00000", "width": "88.00000",
"x": 6, "x": 6,
"y": "0.01706", "y": "0.01000",
}, },
"inserted": { "inserted": {
"isDeleted": true, "isDeleted": true,
@@ -182,14 +182,14 @@ exports[`move element > rectangles with binding arrow 7`] = `
"elementId": "id3", "elementId": "id3",
"fixedPoint": [ "fixedPoint": [
"-0.02000", "-0.02000",
"0.47928", "0.48010",
], ],
"mode": "orbit", "mode": "orbit",
}, },
"fillStyle": "solid", "fillStyle": "solid",
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": "93.60377", "height": "90.01760",
"id": "id6", "id": "id6",
"index": "a2", "index": "a2",
"isDeleted": false, "isDeleted": false,
@@ -204,7 +204,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
], ],
[ [
89, 89,
"93.60377", "90.01760",
], ],
], ],
"roughness": 1, "roughness": 1,
@@ -217,7 +217,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
"elementId": "id0", "elementId": "id0",
"fixedPoint": [ "fixedPoint": [
"1.06000", "1.06000",
"0.52181", "0.56011",
], ],
"mode": "orbit", "mode": "orbit",
}, },
@@ -230,6 +230,6 @@ exports[`move element > rectangles with binding arrow 7`] = `
"versionNonce": 271613161, "versionNonce": 271613161,
"width": 89, "width": 89,
"x": 106, "x": 106,
"y": "52.18052", "y": "56.01120",
} }
`; `;
+5 -15
View File
@@ -1590,9 +1590,7 @@ describe("history", () => {
expect(API.getUndoStack().length).toBe(5); expect(API.getUndoStack().length).toBe(5);
expect(arrow.startBinding).toEqual({ expect(arrow.startBinding).toEqual({
elementId: rect1.id, elementId: rect1.id,
fixedPoint: expect.arrayContaining([ fixedPoint: expect.arrayContaining([1, 0.5001]),
0.5002127206977238, 0.5002127206977238,
]),
mode: "orbit", mode: "orbit",
}); });
expect(arrow.endBinding).toEqual({ expect(arrow.endBinding).toEqual({
@@ -1615,9 +1613,7 @@ describe("history", () => {
expect(API.getRedoStack().length).toBe(1); expect(API.getRedoStack().length).toBe(1);
expect(arrow.startBinding).toEqual({ expect(arrow.startBinding).toEqual({
elementId: rect1.id, elementId: rect1.id,
fixedPoint: expect.arrayContaining([ fixedPoint: expect.arrayContaining([1, 0.5001]),
0.5002127206977238, 0.5002127206977238,
]),
mode: "orbit", mode: "orbit",
}); });
expect(arrow.endBinding).toEqual({ expect(arrow.endBinding).toEqual({
@@ -1640,9 +1636,7 @@ describe("history", () => {
expect(API.getRedoStack().length).toBe(0); expect(API.getRedoStack().length).toBe(0);
expect(arrow.startBinding).toEqual({ expect(arrow.startBinding).toEqual({
elementId: rect1.id, elementId: rect1.id,
fixedPoint: expect.arrayContaining([ fixedPoint: expect.arrayContaining([1, 0.5001]),
0.5002127206977238, 0.5002127206977238,
]),
mode: "orbit", mode: "orbit",
}); });
expect(arrow.endBinding).toEqual({ expect(arrow.endBinding).toEqual({
@@ -1673,9 +1667,7 @@ describe("history", () => {
expect(API.getRedoStack().length).toBe(0); expect(API.getRedoStack().length).toBe(0);
expect(arrow.startBinding).toEqual({ expect(arrow.startBinding).toEqual({
elementId: rect1.id, elementId: rect1.id,
fixedPoint: expect.arrayContaining([ fixedPoint: expect.arrayContaining([1, 0.5001]),
0.5002127206977238, 0.5002127206977238,
]),
mode: "orbit", mode: "orbit",
}); });
expect(arrow.endBinding).toEqual({ expect(arrow.endBinding).toEqual({
@@ -1698,9 +1690,7 @@ describe("history", () => {
expect(API.getRedoStack().length).toBe(1); expect(API.getRedoStack().length).toBe(1);
expect(arrow.startBinding).toEqual({ expect(arrow.startBinding).toEqual({
elementId: rect1.id, elementId: rect1.id,
fixedPoint: expect.arrayContaining([ fixedPoint: expect.arrayContaining([1, 0.5001]),
0.5002127206977238, 0.5002127206977238,
]),
mode: "orbit", mode: "orbit",
}); });
expect(arrow.endBinding).toEqual({ expect(arrow.endBinding).toEqual({
+4 -4
View File
@@ -115,11 +115,11 @@ describe("move element", () => {
expect([rectA.x, rectA.y]).toEqual([0, 0]); expect([rectA.x, rectA.y]).toEqual([0, 0]);
expect([rectB.x, rectB.y]).toEqual([200, 0]); expect([rectB.x, rectB.y]).toEqual([200, 0]);
expect([[arrow.x, arrow.y]]).toCloselyEqualPoints( expect([[arrow.x, arrow.y]]).toCloselyEqualPoints(
[[106, 52.18052313249668]], [[106, 56.011199999998695]],
0, 0,
); );
expect([[arrow.width, arrow.height]]).toCloselyEqualPoints( expect([[arrow.width, arrow.height]]).toCloselyEqualPoints(
[[88, 91.60376557808824]], [[88, 88.01760000000121]],
0, 0,
); );
@@ -140,11 +140,11 @@ describe("move element", () => {
expect([rectA.x, rectA.y]).toEqual([0, 0]); expect([rectA.x, rectA.y]).toEqual([0, 0]);
expect([rectB.x, rectB.y]).toEqual([201, 2]); expect([rectB.x, rectB.y]).toEqual([201, 2]);
expect([[arrow.x, arrow.y]]).toCloselyEqualPoints( expect([[arrow.x, arrow.y]]).toCloselyEqualPoints(
[[106, 52.18052313249668]], [[106, 56.011199999998695]],
0, 0,
); );
expect([[arrow.width, arrow.height]]).toCloselyEqualPoints( expect([[arrow.width, arrow.height]]).toCloselyEqualPoints(
[[89, 93.60376557808823]], [[89, 90.01760000000121]],
0, 0,
); );