fix: Remove duplicated grid snapping

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs
2026-03-17 20:21:45 +00:00
parent 7b2496bfd7
commit d9ab298526
2 changed files with 4 additions and 94 deletions
+4 -79
View File
@@ -1,7 +1,6 @@
import { import {
arrayToMap, arrayToMap,
getFeatureFlag, getFeatureFlag,
getGridPoint,
invariant, invariant,
isTransparent, isTransparent,
} from "@excalidraw/common"; } from "@excalidraw/common";
@@ -200,8 +199,6 @@ export const bindOrUnbindBindingElement = (
arrow.startBinding, arrow.startBinding,
start.element, start.element,
scene.getNonDeletedElementsMap(), scene.getNonDeletedElementsMap(),
undefined,
opts?.gridSize,
) || arrow.points[0], ) || arrow.points[0],
}); });
} }
@@ -215,8 +212,6 @@ export const bindOrUnbindBindingElement = (
arrow.endBinding, arrow.endBinding,
end.element, end.element,
scene.getNonDeletedElementsMap(), scene.getNonDeletedElementsMap(),
undefined,
opts?.gridSize,
) || arrow.points[arrow.points.length - 1], ) || arrow.points[arrow.points.length - 1],
}); });
} }
@@ -1752,54 +1747,6 @@ const extractBinding = (
const elementArea = (element: ExcalidrawBindableElement) => const elementArea = (element: ExcalidrawBindableElement) =>
element.width * element.height; element.width * element.height;
/**
* Snaps a bound arrow endpoint to the grid on the axis parallel to the
* bindable element's side, while preserving the binding gap distance on the
* perpendicular axis. In other words, the grid axis closest to the side's
* perpendicular (normal) is used as the snap axis and the other axis is kept at
* the binding gap distance.
*/
const snapBoundPointToGrid = (
outlinePoint: GlobalPoint,
bindableElement: ExcalidrawBindableElement,
elementsMap: ElementsMap,
gridSize: NullableGridSize,
): GlobalPoint => {
if (!gridSize) {
return outlinePoint;
}
const aabb = aabbForElement(bindableElement, elementsMap);
const heading = headingForPointFromElement(
bindableElement,
aabb,
outlinePoint,
);
const normalLocal = pointFrom<GlobalPoint>(heading[0], heading[1]);
const normalGlobal = pointRotateRads(
normalLocal,
pointFrom<GlobalPoint>(0, 0),
bindableElement.angle,
);
const absNX = Math.abs(normalGlobal[0]);
const absNY = Math.abs(normalGlobal[1]);
if (absNX >= absNY) {
// Global X is closest to the perpendicular → keep X, snap Y
const [, snappedY] = getGridPoint(
outlinePoint[0],
outlinePoint[1],
gridSize,
);
return pointFrom<GlobalPoint>(outlinePoint[0], snappedY);
}
// Global Y is closest to the perpendicular → keep Y, snap X
const [snappedX] = getGridPoint(outlinePoint[0], outlinePoint[1], gridSize);
return pointFrom<GlobalPoint>(snappedX, outlinePoint[1]);
};
export const updateBoundPoint = ( export const updateBoundPoint = (
arrow: NonDeleted<ExcalidrawArrowElement>, arrow: NonDeleted<ExcalidrawArrowElement>,
startOrEnd: "startBinding" | "endBinding", startOrEnd: "startBinding" | "endBinding",
@@ -1807,7 +1754,6 @@ export const updateBoundPoint = (
bindableElement: ExcalidrawBindableElement, bindableElement: ExcalidrawBindableElement,
elementsMap: ElementsMap, elementsMap: ElementsMap,
dragging?: boolean, dragging?: boolean,
gridSize?: NullableGridSize,
): LocalPoint | null => { ): LocalPoint | null => {
if ( if (
binding == null || binding == null ||
@@ -1927,24 +1873,11 @@ export const updateBoundPoint = (
// and short-circuit to the focus point if the arrow is too short to // and short-circuit to the focus point if the arrow is too short to
// avoid inversion // avoid inversion
if (!otherBindable) { if (!otherBindable) {
const snapped =
!arrowTooShort && outlinePoint
? snapBoundPointToGrid(
outlinePoint,
bindableElement,
elementsMap,
gridSize ?? null,
)
: null;
return LinearElementEditor.createPointAt( return LinearElementEditor.createPointAt(
arrow, arrow,
elementsMap, elementsMap,
arrowTooShort arrowTooShort ? focusPoint[0] : outlinePoint?.[0] ?? focusPoint[0],
? focusPoint[0] arrowTooShort ? focusPoint[1] : outlinePoint?.[1] ?? focusPoint[1],
: snapped?.[0] ?? outlinePoint?.[0] ?? focusPoint[0],
arrowTooShort
? focusPoint[1]
: snapped?.[1] ?? outlinePoint?.[1] ?? focusPoint[1],
null, null,
); );
} }
@@ -1963,19 +1896,11 @@ export const updateBoundPoint = (
} }
// 4. In the general case, snap to the outline if possible // 4. In the general case, snap to the outline if possible
const snappedOutline = outlinePoint
? snapBoundPointToGrid(
outlinePoint,
bindableElement,
elementsMap,
gridSize ?? null,
)
: null;
return LinearElementEditor.createPointAt( return LinearElementEditor.createPointAt(
arrow, arrow,
elementsMap, elementsMap,
snappedOutline?.[0] ?? outlinePoint?.[0] ?? focusPoint[0], outlinePoint?.[0] || focusPoint[0],
snappedOutline?.[1] ?? outlinePoint?.[1] ?? focusPoint[1], outlinePoint?.[1] || focusPoint[1],
null, null,
); );
}; };
@@ -344,9 +344,6 @@ export class LinearElementEditor {
// Apply the point movement if needed // Apply the point movement if needed
let suggestedBinding: AppState["suggestedBinding"] = null; let suggestedBinding: AppState["suggestedBinding"] = null;
const effectiveGridSize = event[KEYS.CTRL_OR_CMD]
? null
: app.getEffectiveGridSize();
const { positions, updates } = pointDraggingUpdates( const { positions, updates } = pointDraggingUpdates(
[idx], [idx],
deltaX, deltaX,
@@ -360,7 +357,6 @@ export class LinearElementEditor {
shouldRotateWithDiscreteAngle(event), shouldRotateWithDiscreteAngle(event),
event.altKey, event.altKey,
linearElementEditor, linearElementEditor,
effectiveGridSize,
); );
const angleLocked = shouldRotateWithDiscreteAngle(event); const angleLocked = shouldRotateWithDiscreteAngle(event);
@@ -543,9 +539,6 @@ export class LinearElementEditor {
// Apply the point movement if needed // Apply the point movement if needed
let suggestedBinding: AppState["suggestedBinding"] = null; let suggestedBinding: AppState["suggestedBinding"] = null;
const effectiveGridSize = event[KEYS.CTRL_OR_CMD]
? null
: app.getEffectiveGridSize();
const { positions, updates } = pointDraggingUpdates( const { positions, updates } = pointDraggingUpdates(
selectedPointsIndices, selectedPointsIndices,
deltaX, deltaX,
@@ -559,7 +552,6 @@ export class LinearElementEditor {
shouldRotateWithDiscreteAngle(event) && singlePointDragged, shouldRotateWithDiscreteAngle(event) && singlePointDragged,
event.altKey, event.altKey,
linearElementEditor, linearElementEditor,
effectiveGridSize,
); );
const angleLocked = const angleLocked =
@@ -2137,7 +2129,6 @@ const pointDraggingUpdates = (
angleLocked: boolean, angleLocked: boolean,
altKey: boolean, altKey: boolean,
linearElementEditor: LinearElementEditor, linearElementEditor: LinearElementEditor,
gridSize?: NullableGridSize,
): { ): {
positions: PointsPositionUpdates; positions: PointsPositionUpdates;
updates?: PointMoveOtherUpdates; updates?: PointMoveOtherUpdates;
@@ -2236,8 +2227,6 @@ const pointDraggingUpdates = (
element.startBinding, element.startBinding,
startBindable, startBindable,
elementsMap, elementsMap,
undefined,
gridSize,
) ?? null; ) ?? null;
if (startPoint) { if (startPoint) {
positions.set(0, { point: startPoint, isDragging: true }); positions.set(0, { point: startPoint, isDragging: true });
@@ -2257,8 +2246,6 @@ const pointDraggingUpdates = (
element.endBinding, element.endBinding,
endBindable, endBindable,
elementsMap, elementsMap,
undefined,
gridSize,
) ?? null; ) ?? null;
if (endPoint) { if (endPoint) {
positions.set(element.points.length - 1, { positions.set(element.points.length - 1, {
@@ -2432,7 +2419,6 @@ const pointDraggingUpdates = (
endBindable, endBindable,
elementsMap, elementsMap,
endIsDragged, endIsDragged,
endIsDragged ? gridSize : null,
) || nextArrow.points[nextArrow.points.length - 1] ) || nextArrow.points[nextArrow.points.length - 1]
: nextArrow.points[nextArrow.points.length - 1]; : nextArrow.points[nextArrow.points.length - 1];
@@ -2464,7 +2450,6 @@ const pointDraggingUpdates = (
startBindable, startBindable,
elementsMap, elementsMap,
startIsDragged, startIsDragged,
startIsDragged ? gridSize : null,
) || nextArrow.points[0] ) || nextArrow.points[0]
: nextArrow.points[0]; : nextArrow.points[0];