|  | @@ -17,12 +17,15 @@ import { isLinearElement } from "./typeChecks";
 | 
	
		
			
				|  |  |  import { mutateElement } from "./mutateElement";
 | 
	
		
			
				|  |  |  import { getPerfectElementSize } from "./sizeHelpers";
 | 
	
		
			
				|  |  |  import {
 | 
	
		
			
				|  |  | -  resizeTest,
 | 
	
		
			
				|  |  |    getCursorForResizingElement,
 | 
	
		
			
				|  |  | -  normalizeResizeHandle,
 | 
	
		
			
				|  |  | +  normalizeTransformHandleType,
 | 
	
		
			
				|  |  |  } from "./resizeTest";
 | 
	
		
			
				|  |  |  import { measureText, getFontString } from "../utils";
 | 
	
		
			
				|  |  |  import { updateBoundElements } from "./binding";
 | 
	
		
			
				|  |  | +import {
 | 
	
		
			
				|  |  | +  TransformHandleType,
 | 
	
		
			
				|  |  | +  MaybeTransformHandleType,
 | 
	
		
			
				|  |  | +} from "./transformHandles";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const normalizeAngle = (angle: number): number => {
 | 
	
		
			
				|  |  |    if (angle >= 2 * Math.PI) {
 | 
	
	
		
			
				|  | @@ -31,12 +34,10 @@ const normalizeAngle = (angle: number): number => {
 | 
	
		
			
				|  |  |    return angle;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -type ResizeTestType = ReturnType<typeof resizeTest>;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  // Returns true when a resize (scaling/rotation) happened
 | 
	
		
			
				|  |  |  export const resizeElements = (
 | 
	
		
			
				|  |  | -  resizeHandle: ResizeTestType,
 | 
	
		
			
				|  |  | -  setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
 | 
	
		
			
				|  |  | +  transformHandleType: MaybeTransformHandleType,
 | 
	
		
			
				|  |  | +  setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
 | 
	
		
			
				|  |  |    selectedElements: readonly NonDeletedExcalidrawElement[],
 | 
	
		
			
				|  |  |    resizeArrowDirection: "origin" | "end",
 | 
	
		
			
				|  |  |    isRotateWithDiscreteAngle: boolean,
 | 
	
	
		
			
				|  | @@ -50,7 +51,7 @@ export const resizeElements = (
 | 
	
		
			
				|  |  |  ) => {
 | 
	
		
			
				|  |  |    if (selectedElements.length === 1) {
 | 
	
		
			
				|  |  |      const [element] = selectedElements;
 | 
	
		
			
				|  |  | -    if (resizeHandle === "rotation") {
 | 
	
		
			
				|  |  | +    if (transformHandleType === "rotation") {
 | 
	
		
			
				|  |  |        rotateSingleElement(
 | 
	
		
			
				|  |  |          element,
 | 
	
		
			
				|  |  |          pointerX,
 | 
	
	
		
			
				|  | @@ -61,10 +62,10 @@ export const resizeElements = (
 | 
	
		
			
				|  |  |      } else if (
 | 
	
		
			
				|  |  |        isLinearElement(element) &&
 | 
	
		
			
				|  |  |        element.points.length === 2 &&
 | 
	
		
			
				|  |  | -      (resizeHandle === "nw" ||
 | 
	
		
			
				|  |  | -        resizeHandle === "ne" ||
 | 
	
		
			
				|  |  | -        resizeHandle === "sw" ||
 | 
	
		
			
				|  |  | -        resizeHandle === "se")
 | 
	
		
			
				|  |  | +      (transformHandleType === "nw" ||
 | 
	
		
			
				|  |  | +        transformHandleType === "ne" ||
 | 
	
		
			
				|  |  | +        transformHandleType === "sw" ||
 | 
	
		
			
				|  |  | +        transformHandleType === "se")
 | 
	
		
			
				|  |  |      ) {
 | 
	
		
			
				|  |  |        resizeSingleTwoPointElement(
 | 
	
		
			
				|  |  |          element,
 | 
	
	
		
			
				|  | @@ -75,28 +76,30 @@ export const resizeElements = (
 | 
	
		
			
				|  |  |        );
 | 
	
		
			
				|  |  |      } else if (
 | 
	
		
			
				|  |  |        element.type === "text" &&
 | 
	
		
			
				|  |  | -      (resizeHandle === "nw" ||
 | 
	
		
			
				|  |  | -        resizeHandle === "ne" ||
 | 
	
		
			
				|  |  | -        resizeHandle === "sw" ||
 | 
	
		
			
				|  |  | -        resizeHandle === "se")
 | 
	
		
			
				|  |  | +      (transformHandleType === "nw" ||
 | 
	
		
			
				|  |  | +        transformHandleType === "ne" ||
 | 
	
		
			
				|  |  | +        transformHandleType === "sw" ||
 | 
	
		
			
				|  |  | +        transformHandleType === "se")
 | 
	
		
			
				|  |  |      ) {
 | 
	
		
			
				|  |  |        resizeSingleTextElement(
 | 
	
		
			
				|  |  |          element,
 | 
	
		
			
				|  |  | -        resizeHandle,
 | 
	
		
			
				|  |  | +        transformHandleType,
 | 
	
		
			
				|  |  |          isResizeCenterPoint,
 | 
	
		
			
				|  |  |          pointerX,
 | 
	
		
			
				|  |  |          pointerY,
 | 
	
		
			
				|  |  |        );
 | 
	
		
			
				|  |  | -    } else if (resizeHandle) {
 | 
	
		
			
				|  |  | +    } else if (transformHandleType) {
 | 
	
		
			
				|  |  |        resizeSingleElement(
 | 
	
		
			
				|  |  |          element,
 | 
	
		
			
				|  |  | -        resizeHandle,
 | 
	
		
			
				|  |  | +        transformHandleType,
 | 
	
		
			
				|  |  |          isResizeWithSidesSameLength,
 | 
	
		
			
				|  |  |          isResizeCenterPoint,
 | 
	
		
			
				|  |  |          pointerX,
 | 
	
		
			
				|  |  |          pointerY,
 | 
	
		
			
				|  |  |        );
 | 
	
		
			
				|  |  | -      setResizeHandle(normalizeResizeHandle(element, resizeHandle));
 | 
	
		
			
				|  |  | +      setTransformHandle(
 | 
	
		
			
				|  |  | +        normalizeTransformHandleType(element, transformHandleType),
 | 
	
		
			
				|  |  | +      );
 | 
	
		
			
				|  |  |        if (element.width < 0) {
 | 
	
		
			
				|  |  |          mutateElement(element, { width: -element.width });
 | 
	
		
			
				|  |  |        }
 | 
	
	
		
			
				|  | @@ -109,12 +112,12 @@ export const resizeElements = (
 | 
	
		
			
				|  |  |      // FIXME it is not very nice to have this here
 | 
	
		
			
				|  |  |      document.documentElement.style.cursor = getCursorForResizingElement({
 | 
	
		
			
				|  |  |        element,
 | 
	
		
			
				|  |  | -      resizeHandle,
 | 
	
		
			
				|  |  | +      transformHandleType,
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return true;
 | 
	
		
			
				|  |  |    } else if (selectedElements.length > 1) {
 | 
	
		
			
				|  |  | -    if (resizeHandle === "rotation") {
 | 
	
		
			
				|  |  | +    if (transformHandleType === "rotation") {
 | 
	
		
			
				|  |  |        rotateMultipleElements(
 | 
	
		
			
				|  |  |          selectedElements,
 | 
	
		
			
				|  |  |          pointerX,
 | 
	
	
		
			
				|  | @@ -126,14 +129,14 @@ export const resizeElements = (
 | 
	
		
			
				|  |  |        );
 | 
	
		
			
				|  |  |        return true;
 | 
	
		
			
				|  |  |      } else if (
 | 
	
		
			
				|  |  | -      resizeHandle === "nw" ||
 | 
	
		
			
				|  |  | -      resizeHandle === "ne" ||
 | 
	
		
			
				|  |  | -      resizeHandle === "sw" ||
 | 
	
		
			
				|  |  | -      resizeHandle === "se"
 | 
	
		
			
				|  |  | +      transformHandleType === "nw" ||
 | 
	
		
			
				|  |  | +      transformHandleType === "ne" ||
 | 
	
		
			
				|  |  | +      transformHandleType === "sw" ||
 | 
	
		
			
				|  |  | +      transformHandleType === "se"
 | 
	
		
			
				|  |  |      ) {
 | 
	
		
			
				|  |  |        resizeMultipleElements(
 | 
	
		
			
				|  |  |          selectedElements,
 | 
	
		
			
				|  |  | -        resizeHandle,
 | 
	
		
			
				|  |  | +        transformHandleType,
 | 
	
		
			
				|  |  |          pointerX,
 | 
	
		
			
				|  |  |          pointerY,
 | 
	
		
			
				|  |  |        );
 | 
	
	
		
			
				|  | @@ -257,29 +260,29 @@ const measureFontSizeFromWH = (
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const getSidesForResizeHandle = (
 | 
	
		
			
				|  |  | -  resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  | +const getSidesForTransformHandle = (
 | 
	
		
			
				|  |  | +  transformHandleType: TransformHandleType,
 | 
	
		
			
				|  |  |    isResizeFromCenter: boolean,
 | 
	
		
			
				|  |  |  ) => {
 | 
	
		
			
				|  |  |    return {
 | 
	
		
			
				|  |  |      n:
 | 
	
		
			
				|  |  | -      /^(n|ne|nw)$/.test(resizeHandle) ||
 | 
	
		
			
				|  |  | -      (isResizeFromCenter && /^(s|se|sw)$/.test(resizeHandle)),
 | 
	
		
			
				|  |  | +      /^(n|ne|nw)$/.test(transformHandleType) ||
 | 
	
		
			
				|  |  | +      (isResizeFromCenter && /^(s|se|sw)$/.test(transformHandleType)),
 | 
	
		
			
				|  |  |      s:
 | 
	
		
			
				|  |  | -      /^(s|se|sw)$/.test(resizeHandle) ||
 | 
	
		
			
				|  |  | -      (isResizeFromCenter && /^(n|ne|nw)$/.test(resizeHandle)),
 | 
	
		
			
				|  |  | +      /^(s|se|sw)$/.test(transformHandleType) ||
 | 
	
		
			
				|  |  | +      (isResizeFromCenter && /^(n|ne|nw)$/.test(transformHandleType)),
 | 
	
		
			
				|  |  |      w:
 | 
	
		
			
				|  |  | -      /^(w|nw|sw)$/.test(resizeHandle) ||
 | 
	
		
			
				|  |  | -      (isResizeFromCenter && /^(e|ne|se)$/.test(resizeHandle)),
 | 
	
		
			
				|  |  | +      /^(w|nw|sw)$/.test(transformHandleType) ||
 | 
	
		
			
				|  |  | +      (isResizeFromCenter && /^(e|ne|se)$/.test(transformHandleType)),
 | 
	
		
			
				|  |  |      e:
 | 
	
		
			
				|  |  | -      /^(e|ne|se)$/.test(resizeHandle) ||
 | 
	
		
			
				|  |  | -      (isResizeFromCenter && /^(w|nw|sw)$/.test(resizeHandle)),
 | 
	
		
			
				|  |  | +      /^(e|ne|se)$/.test(transformHandleType) ||
 | 
	
		
			
				|  |  | +      (isResizeFromCenter && /^(w|nw|sw)$/.test(transformHandleType)),
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const resizeSingleTextElement = (
 | 
	
		
			
				|  |  |    element: NonDeleted<ExcalidrawTextElement>,
 | 
	
		
			
				|  |  | -  resizeHandle: "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  | +  transformHandleType: "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  |    isResizeFromCenter: boolean,
 | 
	
		
			
				|  |  |    pointerX: number,
 | 
	
		
			
				|  |  |    pointerY: number,
 | 
	
	
		
			
				|  | @@ -296,7 +299,7 @@ const resizeSingleTextElement = (
 | 
	
		
			
				|  |  |      -element.angle,
 | 
	
		
			
				|  |  |    );
 | 
	
		
			
				|  |  |    let scale;
 | 
	
		
			
				|  |  | -  switch (resizeHandle) {
 | 
	
		
			
				|  |  | +  switch (transformHandleType) {
 | 
	
		
			
				|  |  |      case "se":
 | 
	
		
			
				|  |  |        scale = Math.max(
 | 
	
		
			
				|  |  |          (rotatedX - x1) / (x2 - x1),
 | 
	
	
		
			
				|  | @@ -339,7 +342,7 @@ const resizeSingleTextElement = (
 | 
	
		
			
				|  |  |      const deltaX2 = (x2 - nextX2) / 2;
 | 
	
		
			
				|  |  |      const deltaY2 = (y2 - nextY2) / 2;
 | 
	
		
			
				|  |  |      const [nextElementX, nextElementY] = adjustXYWithRotation(
 | 
	
		
			
				|  |  | -      getSidesForResizeHandle(resizeHandle, isResizeFromCenter),
 | 
	
		
			
				|  |  | +      getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
 | 
	
		
			
				|  |  |        element.x,
 | 
	
		
			
				|  |  |        element.y,
 | 
	
		
			
				|  |  |        element.angle,
 | 
	
	
		
			
				|  | @@ -361,7 +364,7 @@ const resizeSingleTextElement = (
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const resizeSingleElement = (
 | 
	
		
			
				|  |  |    element: NonDeletedExcalidrawElement,
 | 
	
		
			
				|  |  | -  resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  | +  transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  |    sidesWithSameLength: boolean,
 | 
	
		
			
				|  |  |    isResizeFromCenter: boolean,
 | 
	
		
			
				|  |  |    pointerX: number,
 | 
	
	
		
			
				|  | @@ -380,16 +383,32 @@ const resizeSingleElement = (
 | 
	
		
			
				|  |  |    );
 | 
	
		
			
				|  |  |    let scaleX = 1;
 | 
	
		
			
				|  |  |    let scaleY = 1;
 | 
	
		
			
				|  |  | -  if (resizeHandle === "e" || resizeHandle === "ne" || resizeHandle === "se") {
 | 
	
		
			
				|  |  | +  if (
 | 
	
		
			
				|  |  | +    transformHandleType === "e" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "ne" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "se"
 | 
	
		
			
				|  |  | +  ) {
 | 
	
		
			
				|  |  |      scaleX = (rotatedX - x1) / (x2 - x1);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (resizeHandle === "s" || resizeHandle === "sw" || resizeHandle === "se") {
 | 
	
		
			
				|  |  | +  if (
 | 
	
		
			
				|  |  | +    transformHandleType === "s" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "sw" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "se"
 | 
	
		
			
				|  |  | +  ) {
 | 
	
		
			
				|  |  |      scaleY = (rotatedY - y1) / (y2 - y1);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (resizeHandle === "w" || resizeHandle === "nw" || resizeHandle === "sw") {
 | 
	
		
			
				|  |  | +  if (
 | 
	
		
			
				|  |  | +    transformHandleType === "w" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "nw" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "sw"
 | 
	
		
			
				|  |  | +  ) {
 | 
	
		
			
				|  |  |      scaleX = (x2 - rotatedX) / (x2 - x1);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (resizeHandle === "n" || resizeHandle === "nw" || resizeHandle === "ne") {
 | 
	
		
			
				|  |  | +  if (
 | 
	
		
			
				|  |  | +    transformHandleType === "n" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "nw" ||
 | 
	
		
			
				|  |  | +    transformHandleType === "ne"
 | 
	
		
			
				|  |  | +  ) {
 | 
	
		
			
				|  |  |      scaleY = (y2 - rotatedY) / (y2 - y1);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    let nextWidth = element.width * scaleX;
 | 
	
	
		
			
				|  | @@ -419,7 +438,7 @@ const resizeSingleElement = (
 | 
	
		
			
				|  |  |      Math.abs(nextHeight),
 | 
	
		
			
				|  |  |    );
 | 
	
		
			
				|  |  |    const [flipDiffX, flipDiffY] = getFlipAdjustment(
 | 
	
		
			
				|  |  | -    resizeHandle,
 | 
	
		
			
				|  |  | +    transformHandleType,
 | 
	
		
			
				|  |  |      nextWidth,
 | 
	
		
			
				|  |  |      nextHeight,
 | 
	
		
			
				|  |  |      nextX1,
 | 
	
	
		
			
				|  | @@ -434,7 +453,7 @@ const resizeSingleElement = (
 | 
	
		
			
				|  |  |      element.angle,
 | 
	
		
			
				|  |  |    );
 | 
	
		
			
				|  |  |    const [nextElementX, nextElementY] = adjustXYWithRotation(
 | 
	
		
			
				|  |  | -    getSidesForResizeHandle(resizeHandle, isResizeFromCenter),
 | 
	
		
			
				|  |  | +    getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
 | 
	
		
			
				|  |  |      element.x - flipDiffX,
 | 
	
		
			
				|  |  |      element.y - flipDiffY,
 | 
	
		
			
				|  |  |      element.angle,
 | 
	
	
		
			
				|  | @@ -461,7 +480,7 @@ const resizeSingleElement = (
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const resizeMultipleElements = (
 | 
	
		
			
				|  |  |    elements: readonly NonDeletedExcalidrawElement[],
 | 
	
		
			
				|  |  | -  resizeHandle: "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  | +  transformHandleType: "nw" | "ne" | "sw" | "se",
 | 
	
		
			
				|  |  |    pointerX: number,
 | 
	
		
			
				|  |  |    pointerY: number,
 | 
	
		
			
				|  |  |  ) => {
 | 
	
	
		
			
				|  | @@ -472,7 +491,7 @@ const resizeMultipleElements = (
 | 
	
		
			
				|  |  |      origCoords: readonly [number, number, number, number],
 | 
	
		
			
				|  |  |      finalCoords: readonly [number, number, number, number],
 | 
	
		
			
				|  |  |    ) => { x: number; y: number };
 | 
	
		
			
				|  |  | -  switch (resizeHandle) {
 | 
	
		
			
				|  |  | +  switch (transformHandleType) {
 | 
	
		
			
				|  |  |      case "se":
 | 
	
		
			
				|  |  |        scale = Math.max(
 | 
	
		
			
				|  |  |          (pointerX - x1) / (x2 - x1),
 | 
	
	
		
			
				|  | @@ -651,7 +670,7 @@ const rotateMultipleElements = (
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  export const getResizeOffsetXY = (
 | 
	
		
			
				|  |  | -  resizeHandle: ResizeTestType,
 | 
	
		
			
				|  |  | +  transformHandleType: MaybeTransformHandleType,
 | 
	
		
			
				|  |  |    selectedElements: NonDeletedExcalidrawElement[],
 | 
	
		
			
				|  |  |    x: number,
 | 
	
		
			
				|  |  |    y: number,
 | 
	
	
		
			
				|  | @@ -664,7 +683,7 @@ export const getResizeOffsetXY = (
 | 
	
		
			
				|  |  |    const cy = (y1 + y2) / 2;
 | 
	
		
			
				|  |  |    const angle = selectedElements.length === 1 ? selectedElements[0].angle : 0;
 | 
	
		
			
				|  |  |    [x, y] = rotate(x, y, cx, cy, -angle);
 | 
	
		
			
				|  |  | -  switch (resizeHandle) {
 | 
	
		
			
				|  |  | +  switch (transformHandleType) {
 | 
	
		
			
				|  |  |      case "n":
 | 
	
		
			
				|  |  |        return rotate(x - (x1 + x2) / 2, y - y1, 0, 0, angle);
 | 
	
		
			
				|  |  |      case "s":
 | 
	
	
		
			
				|  | @@ -687,14 +706,14 @@ export const getResizeOffsetXY = (
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  export const getResizeArrowDirection = (
 | 
	
		
			
				|  |  | -  resizeHandle: ResizeTestType,
 | 
	
		
			
				|  |  | +  transformHandleType: MaybeTransformHandleType,
 | 
	
		
			
				|  |  |    element: NonDeleted<ExcalidrawLinearElement>,
 | 
	
		
			
				|  |  |  ): "origin" | "end" => {
 | 
	
		
			
				|  |  |    const [, [px, py]] = element.points;
 | 
	
		
			
				|  |  |    const isResizeEnd =
 | 
	
		
			
				|  |  | -    (resizeHandle === "nw" && (px < 0 || py < 0)) ||
 | 
	
		
			
				|  |  | -    (resizeHandle === "ne" && px >= 0) ||
 | 
	
		
			
				|  |  | -    (resizeHandle === "sw" && px <= 0) ||
 | 
	
		
			
				|  |  | -    (resizeHandle === "se" && (px > 0 || py > 0));
 | 
	
		
			
				|  |  | +    (transformHandleType === "nw" && (px < 0 || py < 0)) ||
 | 
	
		
			
				|  |  | +    (transformHandleType === "ne" && px >= 0) ||
 | 
	
		
			
				|  |  | +    (transformHandleType === "sw" && px <= 0) ||
 | 
	
		
			
				|  |  | +    (transformHandleType === "se" && (px > 0 || py > 0));
 | 
	
		
			
				|  |  |    return isResizeEnd ? "end" : "origin";
 | 
	
		
			
				|  |  |  };
 |