summaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/align.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/align.ts')
-rw-r--r--packages/excalidraw/align.ts75
1 files changed, 75 insertions, 0 deletions
diff --git a/packages/excalidraw/align.ts b/packages/excalidraw/align.ts
new file mode 100644
index 0000000..113ba27
--- /dev/null
+++ b/packages/excalidraw/align.ts
@@ -0,0 +1,75 @@
+import type { ElementsMap, ExcalidrawElement } from "./element/types";
+import { mutateElement } from "./element/mutateElement";
+import type { BoundingBox } from "./element/bounds";
+import { getCommonBoundingBox } from "./element/bounds";
+import { getMaximumGroups } from "./groups";
+import { updateBoundElements } from "./element/binding";
+import type Scene from "./scene/Scene";
+
+export interface Alignment {
+ position: "start" | "center" | "end";
+ axis: "x" | "y";
+}
+
+export const alignElements = (
+ selectedElements: ExcalidrawElement[],
+ elementsMap: ElementsMap,
+ alignment: Alignment,
+ scene: Scene,
+): ExcalidrawElement[] => {
+ const groups: ExcalidrawElement[][] = getMaximumGroups(
+ selectedElements,
+ elementsMap,
+ );
+ const selectionBoundingBox = getCommonBoundingBox(selectedElements);
+
+ return groups.flatMap((group) => {
+ const translation = calculateTranslation(
+ group,
+ selectionBoundingBox,
+ alignment,
+ );
+ return group.map((element) => {
+ // update element
+ const updatedEle = mutateElement(element, {
+ x: element.x + translation.x,
+ y: element.y + translation.y,
+ });
+ // update bound elements
+ updateBoundElements(element, scene.getNonDeletedElementsMap(), {
+ simultaneouslyUpdated: group,
+ });
+ return updatedEle;
+ });
+ });
+};
+
+const calculateTranslation = (
+ group: ExcalidrawElement[],
+ selectionBoundingBox: BoundingBox,
+ { axis, position }: Alignment,
+): { x: number; y: number } => {
+ const groupBoundingBox = getCommonBoundingBox(group);
+
+ const [min, max]: ["minX" | "minY", "maxX" | "maxY"] =
+ axis === "x" ? ["minX", "maxX"] : ["minY", "maxY"];
+
+ const noTranslation = { x: 0, y: 0 };
+ if (position === "start") {
+ return {
+ ...noTranslation,
+ [axis]: selectionBoundingBox[min] - groupBoundingBox[min],
+ };
+ } else if (position === "end") {
+ return {
+ ...noTranslation,
+ [axis]: selectionBoundingBox[max] - groupBoundingBox[max],
+ };
+ } // else if (position === "center") {
+ return {
+ ...noTranslation,
+ [axis]:
+ (selectionBoundingBox[min] + selectionBoundingBox[max]) / 2 -
+ (groupBoundingBox[min] + groupBoundingBox[max]) / 2,
+ };
+};