[skip ci] Change flipping

This commit is contained in:
Mark Tolmacs 2025-03-29 16:52:45 +01:00
parent db9e501d35
commit bcbd418154

View file

@ -1,6 +1,6 @@
import { getNonDeletedElements } from "@excalidraw/element"; import { getNonDeletedElements } from "@excalidraw/element";
import { import {
bindOrUnbindLinearElements, bindOrUnbindLinearElement,
isBindingEnabled, isBindingEnabled,
} from "@excalidraw/element/binding"; } from "@excalidraw/element/binding";
import { getCommonBoundingBox } from "@excalidraw/element/bounds"; import { getCommonBoundingBox } from "@excalidraw/element/bounds";
@ -12,15 +12,15 @@ import { deepCopyElement } from "@excalidraw/element/duplicate";
import { resizeMultipleElements } from "@excalidraw/element/resizeElements"; import { resizeMultipleElements } from "@excalidraw/element/resizeElements";
import { import {
isArrowElement, isArrowElement,
isElbowArrow, isBindableElement,
isLinearElement, isBindingElement,
} from "@excalidraw/element/typeChecks"; } from "@excalidraw/element/typeChecks";
import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element/frame"; import { updateFrameMembershipOfSelectedElements } from "@excalidraw/element/frame";
import { CODES, KEYS, arrayToMap } from "@excalidraw/common"; import { CODES, KEYS, arrayToMap } from "@excalidraw/common";
import type { import type {
ExcalidrawArrowElement, ExcalidrawArrowElement,
ExcalidrawElbowArrowElement, ExcalidrawBindableElement,
ExcalidrawElement, ExcalidrawElement,
NonDeleted, NonDeleted,
NonDeletedSceneElementsMap, NonDeletedSceneElementsMap,
@ -160,52 +160,54 @@ const flipElements = (
}, },
); );
bindOrUnbindLinearElements( const selectedBindables = selectedElements.filter(
selectedElements.filter(isLinearElement), (e): e is ExcalidrawBindableElement => isBindableElement(e),
elementsMap,
app.scene.getNonDeletedElements(),
app.scene,
isBindingEnabled(appState),
[],
appState.zoom,
); );
// ---------------------------------------------------------------------------
// flipping arrow elements (and potentially other) makes the selection group
// "move" across the canvas because of how arrows can bump against the "wall"
// of the selection, so we need to center the group back to the original
// position so that repeated flips don't accumulate the offset
const { elbowArrows, otherElements } = selectedElements.reduce(
(
acc: {
elbowArrows: ExcalidrawElbowArrowElement[];
otherElements: ExcalidrawElement[];
},
element,
) =>
isElbowArrow(element)
? { ...acc, elbowArrows: acc.elbowArrows.concat(element) }
: { ...acc, otherElements: acc.otherElements.concat(element) },
{ elbowArrows: [], otherElements: [] },
);
const { midX: newMidX, midY: newMidY } = const { midX: newMidX, midY: newMidY } =
getCommonBoundingBox(selectedElements); getCommonBoundingBox(selectedElements);
const [diffX, diffY] = [midX - newMidX, midY - newMidY]; const [diffX, diffY] = [midX - newMidX, midY - newMidY];
otherElements.forEach((element) =>
selectedElements.forEach((element) => {
fixBindings(element, selectedBindables, app, elementsMap);
mutateElement(element, { mutateElement(element, {
x: element.x + diffX, x: element.x + diffX,
y: element.y + diffY, y: element.y + diffY,
}), });
); });
elbowArrows.forEach((element) =>
mutateElement(element, {
x: element.x + diffX,
y: element.y + diffY,
}),
);
// ---------------------------------------------------------------------------
return selectedElements; return selectedElements;
}; };
// BEHAVIOR: If you flip a binding element along with its bound elements,
// the binding should be preserved. If your selected elements doesn't contain
// the bound element(s), then remove the binding. Also do not "magically"
// re-bind a binable just because the arrow endpoint is flipped into the
// binding range. Rationale being the consistency with the fact that arrows
// don't bind when the arrow is moved into the binding range by its shaft.
const fixBindings = (
element: ExcalidrawElement,
selectedBindables: ExcalidrawBindableElement[],
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
) => {
if (isBindingElement(element)) {
let start = null;
let end = null;
if (isBindingEnabled(app.state)) {
start = element.startBinding
? selectedBindables.find(
(e) => element.startBinding!.elementId === e.id,
) ?? null
: null;
end = element.endBinding
? selectedBindables.find(
(e) => element.endBinding!.elementId === e.id,
) ?? null
: null;
}
bindOrUnbindLinearElement(element, start, end, elementsMap, app.scene);
}
};