Преглед на файлове

feat: create new text with width (#8038)

Co-authored-by: dwelle <[email protected]>
Ryan Di преди 1 година
родител
ревизия
860308eb27

+ 40 - 8
packages/excalidraw/components/App.tsx

@@ -330,6 +330,7 @@ import {
   getContainerElement,
   getContainerElement,
   getDefaultLineHeight,
   getDefaultLineHeight,
   getLineHeightInPx,
   getLineHeightInPx,
+  getMinTextElementWidth,
   isMeasureTextSupported,
   isMeasureTextSupported,
   isValidTextContainer,
   isValidTextContainer,
   measureText,
   measureText,
@@ -1696,6 +1697,7 @@ class App extends React.Component<AppProps, AppState> {
                           canvas={this.interactiveCanvas}
                           canvas={this.interactiveCanvas}
                           elementsMap={elementsMap}
                           elementsMap={elementsMap}
                           visibleElements={visibleElements}
                           visibleElements={visibleElements}
+                          allElementsMap={allElementsMap}
                           selectedElements={selectedElements}
                           selectedElements={selectedElements}
                           sceneNonce={sceneNonce}
                           sceneNonce={sceneNonce}
                           selectionNonce={
                           selectionNonce={
@@ -4718,6 +4720,7 @@ class App extends React.Component<AppProps, AppState> {
     sceneY,
     sceneY,
     insertAtParentCenter = true,
     insertAtParentCenter = true,
     container,
     container,
+    autoEdit = true,
   }: {
   }: {
     /** X position to insert text at */
     /** X position to insert text at */
     sceneX: number;
     sceneX: number;
@@ -4726,6 +4729,7 @@ class App extends React.Component<AppProps, AppState> {
     /** whether to attempt to insert at element center if applicable */
     /** whether to attempt to insert at element center if applicable */
     insertAtParentCenter?: boolean;
     insertAtParentCenter?: boolean;
     container?: ExcalidrawTextContainer | null;
     container?: ExcalidrawTextContainer | null;
+    autoEdit?: boolean;
   }) => {
   }) => {
     let shouldBindToContainer = false;
     let shouldBindToContainer = false;
 
 
@@ -4858,13 +4862,16 @@ class App extends React.Component<AppProps, AppState> {
       }
       }
     }
     }
 
 
-    this.setState({
-      editingElement: element,
-    });
-
-    this.handleTextWysiwyg(element, {
-      isExistingElement: !!existingTextElement,
-    });
+    if (autoEdit || existingTextElement || container) {
+      this.handleTextWysiwyg(element, {
+        isExistingElement: !!existingTextElement,
+      });
+    } else {
+      this.setState({
+        draggingElement: element,
+        multiElement: null,
+      });
+    }
   };
   };
 
 
   private handleCanvasDoubleClick = (
   private handleCanvasDoubleClick = (
@@ -5899,7 +5906,6 @@ class App extends React.Component<AppProps, AppState> {
 
 
     if (this.state.activeTool.type === "text") {
     if (this.state.activeTool.type === "text") {
       this.handleTextOnPointerDown(event, pointerDownState);
       this.handleTextOnPointerDown(event, pointerDownState);
-      return;
     } else if (
     } else if (
       this.state.activeTool.type === "arrow" ||
       this.state.activeTool.type === "arrow" ||
       this.state.activeTool.type === "line"
       this.state.activeTool.type === "line"
@@ -6020,6 +6026,7 @@ class App extends React.Component<AppProps, AppState> {
     );
     );
     const clicklength =
     const clicklength =
       event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
       event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
+
     if (this.device.editor.isMobile && clicklength < 300) {
     if (this.device.editor.isMobile && clicklength < 300) {
       const hitElement = this.getElementAtPosition(
       const hitElement = this.getElementAtPosition(
         scenePointer.x,
         scenePointer.x,
@@ -6693,6 +6700,7 @@ class App extends React.Component<AppProps, AppState> {
       sceneY,
       sceneY,
       insertAtParentCenter: !event.altKey,
       insertAtParentCenter: !event.altKey,
       container,
       container,
+      autoEdit: false,
     });
     });
 
 
     resetCursor(this.interactiveCanvas);
     resetCursor(this.interactiveCanvas);
@@ -8043,6 +8051,28 @@ class App extends React.Component<AppProps, AppState> {
         return;
         return;
       }
       }
 
 
+      if (isTextElement(draggingElement)) {
+        const minWidth = getMinTextElementWidth(
+          getFontString({
+            fontSize: draggingElement.fontSize,
+            fontFamily: draggingElement.fontFamily,
+          }),
+          draggingElement.lineHeight,
+        );
+
+        if (draggingElement.width < minWidth) {
+          mutateElement(draggingElement, {
+            autoResize: true,
+          });
+        }
+
+        this.resetCursor();
+
+        this.handleTextWysiwyg(draggingElement, {
+          isExistingElement: true,
+        });
+      }
+
       if (
       if (
         activeTool.type !== "selection" &&
         activeTool.type !== "selection" &&
         draggingElement &&
         draggingElement &&
@@ -9410,6 +9440,7 @@ class App extends React.Component<AppProps, AppState> {
         distance(pointerDownState.origin.y, pointerCoords.y),
         distance(pointerDownState.origin.y, pointerCoords.y),
         shouldMaintainAspectRatio(event),
         shouldMaintainAspectRatio(event),
         shouldResizeFromCenter(event),
         shouldResizeFromCenter(event),
+        this.state.zoom.value,
       );
       );
     } else {
     } else {
       let [gridX, gridY] = getGridPoint(
       let [gridX, gridY] = getGridPoint(
@@ -9467,6 +9498,7 @@ class App extends React.Component<AppProps, AppState> {
           ? !shouldMaintainAspectRatio(event)
           ? !shouldMaintainAspectRatio(event)
           : shouldMaintainAspectRatio(event),
           : shouldMaintainAspectRatio(event),
         shouldResizeFromCenter(event),
         shouldResizeFromCenter(event),
+        this.state.zoom.value,
         aspectRatio,
         aspectRatio,
         this.state.originSnapOffset,
         this.state.originSnapOffset,
       );
       );

+ 7 - 1
packages/excalidraw/components/canvases/InteractiveCanvas.tsx

@@ -9,7 +9,10 @@ import type {
   RenderableElementsMap,
   RenderableElementsMap,
   RenderInteractiveSceneCallback,
   RenderInteractiveSceneCallback,
 } from "../../scene/types";
 } from "../../scene/types";
-import type { NonDeletedExcalidrawElement } from "../../element/types";
+import type {
+  NonDeletedExcalidrawElement,
+  NonDeletedSceneElementsMap,
+} from "../../element/types";
 import { isRenderThrottlingEnabled } from "../../reactUtils";
 import { isRenderThrottlingEnabled } from "../../reactUtils";
 import { renderInteractiveScene } from "../../renderer/interactiveScene";
 import { renderInteractiveScene } from "../../renderer/interactiveScene";
 
 
@@ -19,6 +22,7 @@ type InteractiveCanvasProps = {
   elementsMap: RenderableElementsMap;
   elementsMap: RenderableElementsMap;
   visibleElements: readonly NonDeletedExcalidrawElement[];
   visibleElements: readonly NonDeletedExcalidrawElement[];
   selectedElements: readonly NonDeletedExcalidrawElement[];
   selectedElements: readonly NonDeletedExcalidrawElement[];
+  allElementsMap: NonDeletedSceneElementsMap;
   sceneNonce: number | undefined;
   sceneNonce: number | undefined;
   selectionNonce: number | undefined;
   selectionNonce: number | undefined;
   scale: number;
   scale: number;
@@ -122,6 +126,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
         elementsMap: props.elementsMap,
         elementsMap: props.elementsMap,
         visibleElements: props.visibleElements,
         visibleElements: props.visibleElements,
         selectedElements: props.selectedElements,
         selectedElements: props.selectedElements,
+        allElementsMap: props.allElementsMap,
         scale: window.devicePixelRatio,
         scale: window.devicePixelRatio,
         appState: props.appState,
         appState: props.appState,
         renderConfig: {
         renderConfig: {
@@ -197,6 +202,7 @@ const getRelevantAppStateProps = (
   activeEmbeddable: appState.activeEmbeddable,
   activeEmbeddable: appState.activeEmbeddable,
   snapLines: appState.snapLines,
   snapLines: appState.snapLines,
   zenModeEnabled: appState.zenModeEnabled,
   zenModeEnabled: appState.zenModeEnabled,
+  editingElement: appState.editingElement,
 });
 });
 
 
 const areEqual = (
 const areEqual = (

+ 5 - 0
packages/excalidraw/constants.ts

@@ -25,6 +25,11 @@ export const supportsResizeObserver =
 
 
 export const APP_NAME = "Excalidraw";
 export const APP_NAME = "Excalidraw";
 
 
+// distance when creating text before it's considered `autoResize: false`
+// we're using higher threshold so that clicks that end up being drags
+// don't unintentionally create text elements that are wrapped to a few chars
+// (happens a lot with fast clicks with the text tool)
+export const TEXT_AUTOWRAP_THRESHOLD = 36; // px
 export const DRAGGING_THRESHOLD = 10; // px
 export const DRAGGING_THRESHOLD = 10; // px
 export const LINE_CONFIRM_THRESHOLD = 8; // px
 export const LINE_CONFIRM_THRESHOLD = 8; // px
 export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
 export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;

+ 39 - 3
packages/excalidraw/element/dragElements.ts

@@ -4,11 +4,17 @@ import { getCommonBounds } from "./bounds";
 import { mutateElement } from "./mutateElement";
 import { mutateElement } from "./mutateElement";
 import { getPerfectElementSize } from "./sizeHelpers";
 import { getPerfectElementSize } from "./sizeHelpers";
 import type { NonDeletedExcalidrawElement } from "./types";
 import type { NonDeletedExcalidrawElement } from "./types";
-import type { AppState, PointerDownState } from "../types";
-import { getBoundTextElement } from "./textElement";
+import type { AppState, NormalizedZoomValue, PointerDownState } from "../types";
+import { getBoundTextElement, getMinTextElementWidth } from "./textElement";
 import { getGridPoint } from "../math";
 import { getGridPoint } from "../math";
 import type Scene from "../scene/Scene";
 import type Scene from "../scene/Scene";
-import { isArrowElement, isFrameLikeElement } from "./typeChecks";
+import {
+  isArrowElement,
+  isFrameLikeElement,
+  isTextElement,
+} from "./typeChecks";
+import { getFontString } from "../utils";
+import { TEXT_AUTOWRAP_THRESHOLD } from "../constants";
 
 
 export const dragSelectedElements = (
 export const dragSelectedElements = (
   pointerDownState: PointerDownState,
   pointerDownState: PointerDownState,
@@ -140,6 +146,7 @@ export const dragNewElement = (
   height: number,
   height: number,
   shouldMaintainAspectRatio: boolean,
   shouldMaintainAspectRatio: boolean,
   shouldResizeFromCenter: boolean,
   shouldResizeFromCenter: boolean,
+  zoom: NormalizedZoomValue,
   /** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
   /** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
       true */
       true */
   widthAspectRatio?: number | null,
   widthAspectRatio?: number | null,
@@ -185,12 +192,41 @@ export const dragNewElement = (
     newY = originY - height / 2;
     newY = originY - height / 2;
   }
   }
 
 
+  let textAutoResize = null;
+
+  // NOTE this should apply only to creating text elements, not existing
+  // (once we rewrite appState.draggingElement to actually mean dragging
+  // elements)
+  if (isTextElement(draggingElement)) {
+    height = draggingElement.height;
+    const minWidth = getMinTextElementWidth(
+      getFontString({
+        fontSize: draggingElement.fontSize,
+        fontFamily: draggingElement.fontFamily,
+      }),
+      draggingElement.lineHeight,
+    );
+    width = Math.max(width, minWidth);
+
+    if (Math.abs(x - originX) > TEXT_AUTOWRAP_THRESHOLD / zoom) {
+      textAutoResize = {
+        autoResize: false,
+      };
+    }
+
+    newY = originY;
+    if (shouldResizeFromCenter) {
+      newX = originX - width / 2;
+    }
+  }
+
   if (width !== 0 && height !== 0) {
   if (width !== 0 && height !== 0) {
     mutateElement(draggingElement, {
     mutateElement(draggingElement, {
       x: newX + (originOffset?.x ?? 0),
       x: newX + (originOffset?.x ?? 0),
       y: newY + (originOffset?.y ?? 0),
       y: newY + (originOffset?.y ?? 0),
       width,
       width,
       height,
       height,
+      ...textAutoResize,
     });
     });
   }
   }
 };
 };

+ 9 - 8
packages/excalidraw/element/resizeElements.ts

@@ -1,8 +1,4 @@
-import {
-  BOUND_TEXT_PADDING,
-  MIN_FONT_SIZE,
-  SHIFT_LOCKING_ANGLE,
-} from "../constants";
+import { MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE } from "../constants";
 import { rescalePoints } from "../points";
 import { rescalePoints } from "../points";
 
 
 import { rotate, centerPoint, rotatePoint } from "../math";
 import { rotate, centerPoint, rotatePoint } from "../math";
@@ -51,7 +47,7 @@ import {
   getApproxMinLineHeight,
   getApproxMinLineHeight,
   wrapText,
   wrapText,
   measureText,
   measureText,
-  getMinCharWidth,
+  getMinTextElementWidth,
 } from "./textElement";
 } from "./textElement";
 import { LinearElementEditor } from "./linearElementEditor";
 import { LinearElementEditor } from "./linearElementEditor";
 import { isInGroup } from "../groups";
 import { isInGroup } from "../groups";
@@ -352,8 +348,13 @@ const resizeSingleTextElement = (
     const boundsCurrentWidth = esx2 - esx1;
     const boundsCurrentWidth = esx2 - esx1;
 
 
     const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
     const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
-    const minWidth =
-      getMinCharWidth(getFontString(element)) + BOUND_TEXT_PADDING * 2;
+    const minWidth = getMinTextElementWidth(
+      getFontString({
+        fontSize: element.fontSize,
+        fontFamily: element.fontFamily,
+      }),
+      element.lineHeight,
+    );
 
 
     let scaleX = atStartBoundsWidth / boundsCurrentWidth;
     let scaleX = atStartBoundsWidth / boundsCurrentWidth;
 
 

+ 7 - 0
packages/excalidraw/element/textElement.ts

@@ -938,3 +938,10 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
   }
   }
   return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
   return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
 };
 };
+
+export const getMinTextElementWidth = (
+  font: FontString,
+  lineHeight: ExcalidrawTextElement["lineHeight"],
+) => {
+  return measureText("", font, lineHeight).width + BOUND_TEXT_PADDING * 2;
+};

+ 1 - 1
packages/excalidraw/element/textWysiwyg.test.tsx

@@ -576,7 +576,7 @@ describe("textWysiwyg", () => {
 
 
     it("text should never go beyond max width", async () => {
     it("text should never go beyond max width", async () => {
       UI.clickTool("text");
       UI.clickTool("text");
-      mouse.clickAt(750, 300);
+      mouse.click(0, 0);
 
 
       textarea = await getTextEditor(textEditorSelector, true);
       textarea = await getTextEditor(textEditorSelector, true);
       updateTextEditor(
       updateTextEditor(

+ 57 - 4
packages/excalidraw/renderer/interactiveScene.ts

@@ -47,13 +47,18 @@ import {
   getNormalizedCanvasDimensions,
   getNormalizedCanvasDimensions,
 } from "./helpers";
 } from "./helpers";
 import oc from "open-color";
 import oc from "open-color";
-import { isFrameLikeElement, isLinearElement } from "../element/typeChecks";
+import {
+  isFrameLikeElement,
+  isLinearElement,
+  isTextElement,
+} from "../element/typeChecks";
 import type {
 import type {
   ElementsMap,
   ElementsMap,
   ExcalidrawBindableElement,
   ExcalidrawBindableElement,
   ExcalidrawElement,
   ExcalidrawElement,
   ExcalidrawFrameLikeElement,
   ExcalidrawFrameLikeElement,
   ExcalidrawLinearElement,
   ExcalidrawLinearElement,
+  ExcalidrawTextElement,
   GroupId,
   GroupId,
   NonDeleted,
   NonDeleted,
 } from "../element/types";
 } from "../element/types";
@@ -303,7 +308,6 @@ const renderSelectionBorder = (
     cy: number;
     cy: number;
     activeEmbeddable: boolean;
     activeEmbeddable: boolean;
   },
   },
-  padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2,
 ) => {
 ) => {
   const {
   const {
     angle,
     angle,
@@ -320,6 +324,8 @@ const renderSelectionBorder = (
   const elementWidth = elementX2 - elementX1;
   const elementWidth = elementX2 - elementX1;
   const elementHeight = elementY2 - elementY1;
   const elementHeight = elementY2 - elementY1;
 
 
+  const padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
+
   const linePadding = padding / appState.zoom.value;
   const linePadding = padding / appState.zoom.value;
   const lineWidth = 8 / appState.zoom.value;
   const lineWidth = 8 / appState.zoom.value;
   const spaceWidth = 4 / appState.zoom.value;
   const spaceWidth = 4 / appState.zoom.value;
@@ -570,11 +576,34 @@ const renderTransformHandles = (
   });
   });
 };
 };
 
 
+const renderTextBox = (
+  text: NonDeleted<ExcalidrawTextElement>,
+  context: CanvasRenderingContext2D,
+  appState: InteractiveCanvasAppState,
+  selectionColor: InteractiveCanvasRenderConfig["selectionColor"],
+) => {
+  context.save();
+  const padding = (DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
+  const width = text.width + padding * 2;
+  const height = text.height + padding * 2;
+  const cx = text.x + width / 2;
+  const cy = text.y + height / 2;
+  const shiftX = -(width / 2 + padding);
+  const shiftY = -(height / 2 + padding);
+  context.translate(cx + appState.scrollX, cy + appState.scrollY);
+  context.rotate(text.angle);
+  context.lineWidth = 1 / appState.zoom.value;
+  context.strokeStyle = selectionColor;
+  context.strokeRect(shiftX, shiftY, width, height);
+  context.restore();
+};
+
 const _renderInteractiveScene = ({
 const _renderInteractiveScene = ({
   canvas,
   canvas,
   elementsMap,
   elementsMap,
   visibleElements,
   visibleElements,
   selectedElements,
   selectedElements,
+  allElementsMap,
   scale,
   scale,
   appState,
   appState,
   renderConfig,
   renderConfig,
@@ -626,12 +655,31 @@ const _renderInteractiveScene = ({
   // Paint selection element
   // Paint selection element
   if (appState.selectionElement) {
   if (appState.selectionElement) {
     try {
     try {
-      renderSelectionElement(appState.selectionElement, context, appState);
+      renderSelectionElement(
+        appState.selectionElement,
+        context,
+        appState,
+        renderConfig.selectionColor,
+      );
     } catch (error: any) {
     } catch (error: any) {
       console.error(error);
       console.error(error);
     }
     }
   }
   }
 
 
+  if (appState.editingElement && isTextElement(appState.editingElement)) {
+    const textElement = allElementsMap.get(appState.editingElement.id) as
+      | ExcalidrawTextElement
+      | undefined;
+    if (textElement && !textElement.autoResize) {
+      renderTextBox(
+        textElement,
+        context,
+        appState,
+        renderConfig.selectionColor,
+      );
+    }
+  }
+
   if (appState.isBindingEnabled) {
   if (appState.isBindingEnabled) {
     appState.suggestedBindings
     appState.suggestedBindings
       .filter((binding) => binding != null)
       .filter((binding) => binding != null)
@@ -810,7 +858,12 @@ const _renderInteractiveScene = ({
         "mouse", // when we render we don't know which pointer type so use mouse,
         "mouse", // when we render we don't know which pointer type so use mouse,
         getOmitSidesForDevice(device),
         getOmitSidesForDevice(device),
       );
       );
-      if (!appState.viewModeEnabled && showBoundingBox) {
+      if (
+        !appState.viewModeEnabled &&
+        showBoundingBox &&
+        // do not show transform handles when text is being edited
+        !isTextElement(appState.editingElement)
+      ) {
         renderTransformHandles(
         renderTransformHandles(
           context,
           context,
           renderConfig,
           renderConfig,

+ 3 - 1
packages/excalidraw/renderer/renderElement.ts

@@ -24,6 +24,7 @@ import type { RoughCanvas } from "roughjs/bin/canvas";
 import type {
 import type {
   StaticCanvasRenderConfig,
   StaticCanvasRenderConfig,
   RenderableElementsMap,
   RenderableElementsMap,
+  InteractiveCanvasRenderConfig,
 } from "../scene/types";
 } from "../scene/types";
 import { distance, getFontString, isRTL } from "../utils";
 import { distance, getFontString, isRTL } from "../utils";
 import { getCornerRadius, isRightAngle } from "../math";
 import { getCornerRadius, isRightAngle } from "../math";
@@ -618,6 +619,7 @@ export const renderSelectionElement = (
   element: NonDeletedExcalidrawElement,
   element: NonDeletedExcalidrawElement,
   context: CanvasRenderingContext2D,
   context: CanvasRenderingContext2D,
   appState: InteractiveCanvasAppState,
   appState: InteractiveCanvasAppState,
+  selectionColor: InteractiveCanvasRenderConfig["selectionColor"],
 ) => {
 ) => {
   context.save();
   context.save();
   context.translate(element.x + appState.scrollX, element.y + appState.scrollY);
   context.translate(element.x + appState.scrollX, element.y + appState.scrollY);
@@ -631,7 +633,7 @@ export const renderSelectionElement = (
 
 
   context.fillRect(offset, offset, element.width, element.height);
   context.fillRect(offset, offset, element.width, element.height);
   context.lineWidth = 1 / appState.zoom.value;
   context.lineWidth = 1 / appState.zoom.value;
-  context.strokeStyle = " rgb(105, 101, 219)";
+  context.strokeStyle = selectionColor;
   context.strokeRect(offset, offset, element.width, element.height);
   context.strokeRect(offset, offset, element.width, element.height);
 
 
   context.restore();
   context.restore();

+ 2 - 1
packages/excalidraw/scene/types.ts

@@ -55,7 +55,7 @@ export type InteractiveCanvasRenderConfig = {
   remotePointerUserStates: Map<SocketId, UserIdleState>;
   remotePointerUserStates: Map<SocketId, UserIdleState>;
   remotePointerUsernames: Map<SocketId, string>;
   remotePointerUsernames: Map<SocketId, string>;
   remotePointerButton: Map<SocketId, string | undefined>;
   remotePointerButton: Map<SocketId, string | undefined>;
-  selectionColor?: string;
+  selectionColor: string;
   // extra options passed to the renderer
   // extra options passed to the renderer
   // ---------------------------------------------------------------------------
   // ---------------------------------------------------------------------------
   renderScrollbars?: boolean;
   renderScrollbars?: boolean;
@@ -83,6 +83,7 @@ export type InteractiveSceneRenderConfig = {
   elementsMap: RenderableElementsMap;
   elementsMap: RenderableElementsMap;
   visibleElements: readonly NonDeletedExcalidrawElement[];
   visibleElements: readonly NonDeletedExcalidrawElement[];
   selectedElements: readonly NonDeletedExcalidrawElement[];
   selectedElements: readonly NonDeletedExcalidrawElement[];
+  allElementsMap: NonDeletedSceneElementsMap;
   scale: number;
   scale: number;
   appState: InteractiveCanvasAppState;
   appState: InteractiveCanvasAppState;
   renderConfig: InteractiveCanvasRenderConfig;
   renderConfig: InteractiveCanvasRenderConfig;

+ 2 - 1
packages/excalidraw/snapping.ts

@@ -1375,6 +1375,7 @@ export const isActiveToolNonLinearSnappable = (
     activeToolType === TOOL_TYPE.diamond ||
     activeToolType === TOOL_TYPE.diamond ||
     activeToolType === TOOL_TYPE.frame ||
     activeToolType === TOOL_TYPE.frame ||
     activeToolType === TOOL_TYPE.magicframe ||
     activeToolType === TOOL_TYPE.magicframe ||
-    activeToolType === TOOL_TYPE.image
+    activeToolType === TOOL_TYPE.image ||
+    activeToolType === TOOL_TYPE.text
   );
   );
 };
 };

+ 5 - 5
packages/excalidraw/tests/linearElementEditor.test.tsx

@@ -1051,11 +1051,11 @@ describe("Test Linear Elements", () => {
           arrayToMap(h.elements),
           arrayToMap(h.elements),
         ),
         ),
       ).toMatchInlineSnapshot(`
       ).toMatchInlineSnapshot(`
-          {
-            "x": 75,
-            "y": 60,
-          }
-        `);
+        {
+          "x": 75,
+          "y": 60,
+        }
+      `);
       expect(textElement.text).toMatchInlineSnapshot(`
       expect(textElement.text).toMatchInlineSnapshot(`
         "Online whiteboard 
         "Online whiteboard 
         collaboration made 
         collaboration made 

+ 1 - 0
packages/excalidraw/types.ts

@@ -197,6 +197,7 @@ export type InteractiveCanvasAppState = Readonly<
     // SnapLines
     // SnapLines
     snapLines: AppState["snapLines"];
     snapLines: AppState["snapLines"];
     zenModeEnabled: AppState["zenModeEnabled"];
     zenModeEnabled: AppState["zenModeEnabled"];
+    editingElement: AppState["editingElement"];
   }
   }
 >;
 >;