Browse Source

fix: creating text while color picker open (#6651)

Co-authored-by: Aakansha Doshi <[email protected]>
David Luzar 2 years ago
parent
commit
84bd9bd4ff
4 changed files with 111 additions and 184 deletions
  1. 15 2
      src/components/App.tsx
  2. 7 4
      src/components/ColorPicker/ColorPicker.tsx
  3. 82 178
      src/element/textWysiwyg.test.tsx
  4. 7 0
      src/utils.ts

+ 15 - 2
src/components/App.tsx

@@ -825,6 +825,14 @@ class App extends React.Component<AppProps, AppState> {
         if (typeof this.props.name !== "undefined") {
         if (typeof this.props.name !== "undefined") {
           name = this.props.name;
           name = this.props.name;
         }
         }
+
+        editingElement =
+          editingElement || actionResult.appState?.editingElement || null;
+
+        if (editingElement?.isDeleted) {
+          editingElement = null;
+        }
+
         this.setState(
         this.setState(
           (state) => {
           (state) => {
             // using Object.assign instead of spread to fool TS 4.2.2+ into
             // using Object.assign instead of spread to fool TS 4.2.2+ into
@@ -835,8 +843,7 @@ class App extends React.Component<AppProps, AppState> {
               // or programmatically from the host, so it will need to be
               // or programmatically from the host, so it will need to be
               // rewritten later
               // rewritten later
               contextMenu: null,
               contextMenu: null,
-              editingElement:
-                editingElement || actionResult.appState?.editingElement || null,
+              editingElement,
               viewModeEnabled,
               viewModeEnabled,
               zenModeEnabled,
               zenModeEnabled,
               gridSize,
               gridSize,
@@ -1347,6 +1354,12 @@ class App extends React.Component<AppProps, AppState> {
       });
       });
     }
     }
 
 
+    // failsafe in case the state is being updated in incorrect order resulting
+    // in the editingElement being now a deleted element
+    if (this.state.editingElement?.isDeleted) {
+      this.setState({ editingElement: null });
+    }
+
     if (
     if (
       this.state.selectedLinearElement &&
       this.state.selectedLinearElement &&
       !this.state.selectedElementIds[this.state.selectedLinearElement.elementId]
       !this.state.selectedElementIds[this.state.selectedLinearElement.elementId]

+ 7 - 4
src/components/ColorPicker/ColorPicker.tsx

@@ -1,4 +1,4 @@
-import { isTransparent, isWritableElement } from "../../utils";
+import { isInteractive, isTransparent, isWritableElement } from "../../utils";
 import { ExcalidrawElement } from "../../element/types";
 import { ExcalidrawElement } from "../../element/types";
 import { AppState } from "../../types";
 import { AppState } from "../../types";
 import { TopPicks } from "./TopPicks";
 import { TopPicks } from "./TopPicks";
@@ -121,11 +121,14 @@ const ColorPickerPopupContent = ({
           }
           }
         }}
         }}
         onCloseAutoFocus={(e) => {
         onCloseAutoFocus={(e) => {
-          e.preventDefault();
           e.stopPropagation();
           e.stopPropagation();
+          // prevents focusing the trigger
+          e.preventDefault();
 
 
-          // return focus to excalidraw container
-          if (container) {
+          // return focus to excalidraw container unless
+          // user focuses an interactive element, such as a button, or
+          // enters the text editor by clicking on canvas with the text tool
+          if (container && !isInteractive(document.activeElement)) {
             container.focus();
             container.focus();
           }
           }
 
 

+ 82 - 178
src/element/textWysiwyg.test.tsx

@@ -26,6 +26,17 @@ ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
 const tab = "    ";
 const tab = "    ";
 const mouse = new Pointer("mouse");
 const mouse = new Pointer("mouse");
 
 
+const getTextEditor = () => {
+  return document.querySelector(
+    ".excalidraw-textEditorContainer > textarea",
+  ) as HTMLTextAreaElement;
+};
+
+const updateTextEditor = (editor: HTMLTextAreaElement, value: string) => {
+  fireEvent.change(editor, { target: { value } });
+  editor.dispatchEvent(new Event("input"));
+};
+
 describe("textWysiwyg", () => {
 describe("textWysiwyg", () => {
   describe("start text editing", () => {
   describe("start text editing", () => {
     const { h } = window;
     const { h } = window;
@@ -190,9 +201,7 @@ describe("textWysiwyg", () => {
 
 
       mouse.clickAt(text.x + 50, text.y + 50);
       mouse.clickAt(text.x + 50, text.y + 50);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
       expect(editor).not.toBe(null);
       expect(editor).not.toBe(null);
       expect(h.state.editingElement?.id).toBe(text.id);
       expect(h.state.editingElement?.id).toBe(text.id);
@@ -214,9 +223,7 @@ describe("textWysiwyg", () => {
 
 
       mouse.doubleClickAt(text.x + 50, text.y + 50);
       mouse.doubleClickAt(text.x + 50, text.y + 50);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
       expect(editor).not.toBe(null);
       expect(editor).not.toBe(null);
       expect(h.state.editingElement?.id).toBe(text.id);
       expect(h.state.editingElement?.id).toBe(text.id);
@@ -243,9 +250,7 @@ describe("textWysiwyg", () => {
       textElement = UI.createElement("text");
       textElement = UI.createElement("text");
 
 
       mouse.clickOn(textElement);
       mouse.clickOn(textElement);
-      textarea = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      )!;
+      textarea = getTextEditor();
     });
     });
 
 
     afterAll(() => {
     afterAll(() => {
@@ -455,17 +460,11 @@ describe("textWysiwyg", () => {
       UI.clickTool("text");
       UI.clickTool("text");
       mouse.clickAt(750, 300);
       mouse.clickAt(750, 300);
 
 
-      textarea = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      )!;
-      fireEvent.change(textarea, {
-        target: {
-          value:
-            "Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
-        },
-      });
-
-      textarea.dispatchEvent(new Event("input"));
+      textarea = getTextEditor();
+      updateTextEditor(
+        textarea,
+        "Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
+      );
       await new Promise((cb) => setTimeout(cb, 0));
       await new Promise((cb) => setTimeout(cb, 0));
       textarea.blur();
       textarea.blur();
       expect(textarea.style.width).toBe("792px");
       expect(textarea.style.width).toBe("792px");
@@ -513,11 +512,9 @@ describe("textWysiwyg", () => {
         { id: text.id, type: "text" },
         { id: text.id, type: "text" },
       ]);
       ]);
       mouse.down();
       mouse.down();
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
@@ -543,11 +540,9 @@ describe("textWysiwyg", () => {
       ]);
       ]);
       expect(text.angle).toBe(rectangle.angle);
       expect(text.angle).toBe(rectangle.angle);
       mouse.down();
       mouse.down();
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
@@ -572,9 +567,7 @@ describe("textWysiwyg", () => {
       API.setSelectedElements([diamond]);
       API.setSelectedElements([diamond]);
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       const value = new Array(1000).fill("1").join("\n");
       const value = new Array(1000).fill("1").join("\n");
@@ -587,9 +580,7 @@ describe("textWysiwyg", () => {
       expect(diamond.height).toBe(50020);
       expect(diamond.height).toBe(50020);
 
 
       // Clearing text to simulate height decrease
       // Clearing text to simulate height decrease
-      expect(() =>
-        fireEvent.input(editor, { target: { value: "" } }),
-      ).not.toThrow();
+      expect(() => updateTextEditor(editor, "")).not.toThrow();
 
 
       expect(diamond.height).toBe(70);
       expect(diamond.height).toBe(70);
     });
     });
@@ -611,9 +602,7 @@ describe("textWysiwyg", () => {
       expect(text.type).toBe("text");
       expect(text.type).toBe("text");
       expect(text.containerId).toBe(null);
       expect(text.containerId).toBe(null);
       mouse.down();
       mouse.down();
-      let editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      let editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
 
 
@@ -628,11 +617,9 @@ describe("textWysiwyg", () => {
       expect(text.containerId).toBe(rectangle.id);
       expect(text.containerId).toBe(rectangle.id);
 
 
       mouse.down();
       mouse.down();
-      editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      editor = getTextEditor();
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
 
 
@@ -652,13 +639,11 @@ describe("textWysiwyg", () => {
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       expect(text.type).toBe("text");
       expect(text.type).toBe("text");
       expect(text.containerId).toBe(rectangle.id);
       expect(text.containerId).toBe(rectangle.id);
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
       editor.blur();
       editor.blur();
       expect(rectangle.boundElements).toStrictEqual([
       expect(rectangle.boundElements).toStrictEqual([
         { id: text.id, type: "text" },
         { id: text.id, type: "text" },
@@ -689,11 +674,8 @@ describe("textWysiwyg", () => {
         { id: text.id, type: "text" },
         { id: text.id, type: "text" },
       ]);
       ]);
       mouse.down();
       mouse.down();
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      const editor = getTextEditor();
+      updateTextEditor(editor, "Hello World!");
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
@@ -717,17 +699,9 @@ describe("textWysiwyg", () => {
         freedraw.y + freedraw.height / 2,
         freedraw.y + freedraw.height / 2,
       );
       );
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      fireEvent.change(editor, {
-        target: {
-          value: "Hello World!",
-        },
-      });
+      const editor = getTextEditor();
+      updateTextEditor(editor, "Hello World!");
       fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
       fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
-      editor.dispatchEvent(new Event("input"));
 
 
       expect(freedraw.boundElements).toBe(null);
       expect(freedraw.boundElements).toBe(null);
       expect(h.elements[1].type).toBe("text");
       expect(h.elements[1].type).toBe("text");
@@ -759,11 +733,9 @@ describe("textWysiwyg", () => {
       expect(text.type).toBe("text");
       expect(text.type).toBe("text");
       expect(text.containerId).toBe(null);
       expect(text.containerId).toBe(null);
       mouse.down();
       mouse.down();
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
@@ -776,17 +748,12 @@ describe("textWysiwyg", () => {
 
 
       UI.clickTool("text");
       UI.clickTool("text");
       mouse.clickAt(20, 30);
       mouse.clickAt(20, 30);
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      fireEvent.change(editor, {
-        target: {
-          value: "Excalidraw is an opensource virtual collaborative whiteboard",
-        },
-      });
+      const editor = getTextEditor();
 
 
-      editor.dispatchEvent(new Event("input"));
+      updateTextEditor(
+        editor,
+        "Excalidraw is an opensource virtual collaborative whiteboard",
+      );
       await new Promise((cb) => setTimeout(cb, 0));
       await new Promise((cb) => setTimeout(cb, 0));
       expect(h.elements.length).toBe(2);
       expect(h.elements.length).toBe(2);
       expect(h.elements[1].type).toBe("text");
       expect(h.elements[1].type).toBe("text");
@@ -826,12 +793,10 @@ describe("textWysiwyg", () => {
       mouse.down();
       mouse.down();
 
 
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
-      let editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      let editor = getTextEditor();
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
       editor.blur();
       editor.blur();
       expect(text.fontFamily).toEqual(FONT_FAMILY.Virgil);
       expect(text.fontFamily).toEqual(FONT_FAMILY.Virgil);
       UI.clickTool("text");
       UI.clickTool("text");
@@ -841,9 +806,7 @@ describe("textWysiwyg", () => {
         rectangle.y + rectangle.height / 2,
         rectangle.y + rectangle.height / 2,
       );
       );
       mouse.down();
       mouse.down();
-      editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      editor = getTextEditor();
 
 
       editor.select();
       editor.select();
       fireEvent.click(screen.getByTitle(/code/i));
       fireEvent.click(screen.getByTitle(/code/i));
@@ -876,17 +839,9 @@ describe("textWysiwyg", () => {
 
 
       Keyboard.keyDown(KEYS.ENTER);
       Keyboard.keyDown(KEYS.ENTER);
       let text = h.elements[1] as ExcalidrawTextElementWithContainer;
       let text = h.elements[1] as ExcalidrawTextElementWithContainer;
-      let editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      fireEvent.change(editor, {
-        target: {
-          value: "Hello World!",
-        },
-      });
+      let editor = getTextEditor();
 
 
-      editor.dispatchEvent(new Event("input"));
+      updateTextEditor(editor, "Hello World!");
 
 
       await new Promise((cb) => setTimeout(cb, 0));
       await new Promise((cb) => setTimeout(cb, 0));
       editor.blur();
       editor.blur();
@@ -905,17 +860,8 @@ describe("textWysiwyg", () => {
       mouse.select(rectangle);
       mouse.select(rectangle);
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      fireEvent.change(editor, {
-        target: {
-          value: "Hello",
-        },
-      });
-
-      editor.dispatchEvent(new Event("input"));
+      editor = getTextEditor();
+      updateTextEditor(editor, "Hello");
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
 
 
@@ -943,13 +889,11 @@ describe("textWysiwyg", () => {
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       expect(text.containerId).toBe(rectangle.id);
       expect(text.containerId).toBe(rectangle.id);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
       editor.blur();
       editor.blur();
       expect(rectangle.boundElements).toStrictEqual([
       expect(rectangle.boundElements).toStrictEqual([
         { id: text.id, type: "text" },
         { id: text.id, type: "text" },
@@ -982,11 +926,9 @@ describe("textWysiwyg", () => {
       // Bind first text
       // Bind first text
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       const text = h.elements[1] as ExcalidrawTextElementWithContainer;
       expect(text.containerId).toBe(rectangle.id);
       expect(text.containerId).toBe(rectangle.id);
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
       editor.blur();
       editor.blur();
       expect(rectangle.boundElements).toStrictEqual([
       expect(rectangle.boundElements).toStrictEqual([
         { id: text.id, type: "text" },
         { id: text.id, type: "text" },
@@ -1005,11 +947,9 @@ describe("textWysiwyg", () => {
     it("should respect text alignment when resizing", async () => {
     it("should respect text alignment when resizing", async () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      let editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      let editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello" } });
+      updateTextEditor(editor, "Hello");
       editor.blur();
       editor.blur();
 
 
       // should center align horizontally and vertically by default
       // should center align horizontally and vertically by default
@@ -1024,9 +964,7 @@ describe("textWysiwyg", () => {
       mouse.select(rectangle);
       mouse.select(rectangle);
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      editor = getTextEditor();
 
 
       editor.select();
       editor.select();
 
 
@@ -1049,9 +987,7 @@ describe("textWysiwyg", () => {
 
 
       mouse.select(rectangle);
       mouse.select(rectangle);
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
-      editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      editor = getTextEditor();
 
 
       editor.select();
       editor.select();
 
 
@@ -1089,11 +1025,9 @@ describe("textWysiwyg", () => {
       expect(text.type).toBe("text");
       expect(text.type).toBe("text");
       expect(text.containerId).toBe(rectangle.id);
       expect(text.containerId).toBe(rectangle.id);
       mouse.down();
       mouse.down();
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
 
 
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      updateTextEditor(editor, "Hello World!");
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
@@ -1106,11 +1040,9 @@ describe("textWysiwyg", () => {
     it("should scale font size correctly when resizing using shift", async () => {
     it("should scale font size correctly when resizing using shift", async () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello" } });
+      updateTextEditor(editor, "Hello");
       editor.blur();
       editor.blur();
       const textElement = h.elements[1] as ExcalidrawTextElement;
       const textElement = h.elements[1] as ExcalidrawTextElement;
       expect(rectangle.width).toBe(90);
       expect(rectangle.width).toBe(90);
@@ -1128,11 +1060,9 @@ describe("textWysiwyg", () => {
     it("should bind text correctly when container duplicated with alt-drag", async () => {
     it("should bind text correctly when container duplicated with alt-drag", async () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello" } });
+      updateTextEditor(editor, "Hello");
       editor.blur();
       editor.blur();
       expect(h.elements.length).toBe(2);
       expect(h.elements.length).toBe(2);
 
 
@@ -1162,11 +1092,9 @@ describe("textWysiwyg", () => {
 
 
     it("undo should work", async () => {
     it("undo should work", async () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello" } });
+      updateTextEditor(editor, "Hello");
       editor.blur();
       editor.blur();
       expect(rectangle.boundElements).toStrictEqual([
       expect(rectangle.boundElements).toStrictEqual([
         { id: h.elements[1].id, type: "text" },
         { id: h.elements[1].id, type: "text" },
@@ -1201,12 +1129,10 @@ describe("textWysiwyg", () => {
 
 
     it("should not allow bound text with only whitespaces", async () => {
     it("should not allow bound text with only whitespaces", async () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      const editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
 
 
-      fireEvent.change(editor, { target: { value: "   " } });
+      updateTextEditor(editor, "   ");
       editor.blur();
       editor.blur();
       expect(rectangle.boundElements).toStrictEqual([]);
       expect(rectangle.boundElements).toStrictEqual([]);
       expect(h.elements[1].isDeleted).toBe(true);
       expect(h.elements[1].isDeleted).toBe(true);
@@ -1225,9 +1151,9 @@ describe("textWysiwyg", () => {
         type: "text",
         type: "text",
         text: "Online whiteboard collaboration made easy",
         text: "Online whiteboard collaboration made easy",
       });
       });
+
       h.elements = [container, text];
       h.elements = [container, text];
       API.setSelectedElements([container, text]);
       API.setSelectedElements([container, text]);
-
       fireEvent.contextMenu(GlobalTestState.canvas, {
       fireEvent.contextMenu(GlobalTestState.canvas, {
         button: 2,
         button: 2,
         clientX: 20,
         clientX: 20,
@@ -1258,11 +1184,9 @@ describe("textWysiwyg", () => {
     it("should reset the container height cache when resizing", async () => {
     it("should reset the container height cache when resizing", async () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
       expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
       expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
-      let editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      let editor = getTextEditor();
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello" } });
+      updateTextEditor(editor, "Hello");
       editor.blur();
       editor.blur();
 
 
       resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
       resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
@@ -1272,9 +1196,7 @@ describe("textWysiwyg", () => {
       mouse.select(rectangle);
       mouse.select(rectangle);
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
 
 
-      editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
+      editor = getTextEditor();
 
 
       await new Promise((r) => setTimeout(r, 0));
       await new Promise((r) => setTimeout(r, 0));
       editor.blur();
       editor.blur();
@@ -1287,12 +1209,8 @@ describe("textWysiwyg", () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
       expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
       expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      const editor = getTextEditor();
+      updateTextEditor(editor, "Hello World!");
       editor.blur();
       editor.blur();
 
 
       mouse.select(rectangle);
       mouse.select(rectangle);
@@ -1316,12 +1234,8 @@ describe("textWysiwyg", () => {
       Keyboard.keyPress(KEYS.ENTER);
       Keyboard.keyPress(KEYS.ENTER);
       expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
       expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
 
 
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      await new Promise((r) => setTimeout(r, 0));
-      fireEvent.change(editor, { target: { value: "Hello World!" } });
+      const editor = getTextEditor();
+      updateTextEditor(editor, "Hello World!");
       editor.blur();
       editor.blur();
       expect(
       expect(
         (h.elements[1] as ExcalidrawTextElementWithContainer).lineHeight,
         (h.elements[1] as ExcalidrawTextElementWithContainer).lineHeight,
@@ -1352,17 +1266,12 @@ describe("textWysiwyg", () => {
 
 
       beforeEach(async () => {
       beforeEach(async () => {
         Keyboard.keyPress(KEYS.ENTER);
         Keyboard.keyPress(KEYS.ENTER);
-        editor = document.querySelector(
-          ".excalidraw-textEditorContainer > textarea",
-        ) as HTMLTextAreaElement;
-        await new Promise((r) => setTimeout(r, 0));
-        fireEvent.change(editor, { target: { value: "Hello" } });
+        editor = getTextEditor();
+        updateTextEditor(editor, "Hello");
         editor.blur();
         editor.blur();
         mouse.select(rectangle);
         mouse.select(rectangle);
         Keyboard.keyPress(KEYS.ENTER);
         Keyboard.keyPress(KEYS.ENTER);
-        editor = document.querySelector(
-          ".excalidraw-textEditorContainer > textarea",
-        ) as HTMLTextAreaElement;
+        editor = getTextEditor();
         editor.select();
         editor.select();
       });
       });
 
 
@@ -1473,17 +1382,12 @@ describe("textWysiwyg", () => {
     it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
     it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
       UI.clickTool("text");
       UI.clickTool("text");
       mouse.clickAt(20, 30);
       mouse.clickAt(20, 30);
-      const editor = document.querySelector(
-        ".excalidraw-textEditorContainer > textarea",
-      ) as HTMLTextAreaElement;
-
-      fireEvent.change(editor, {
-        target: {
-          value: "Excalidraw is an opensource virtual collaborative whiteboard",
-        },
-      });
+      const editor = getTextEditor();
 
 
-      editor.dispatchEvent(new Event("input"));
+      updateTextEditor(
+        editor,
+        "Excalidraw is an opensource virtual collaborative whiteboard",
+      );
       await new Promise((cb) => setTimeout(cb, 0));
       await new Promise((cb) => setTimeout(cb, 0));
 
 
       editor.select();
       editor.select();

+ 7 - 0
src/utils.ts

@@ -60,6 +60,13 @@ export const isInputLike = (
   target instanceof HTMLTextAreaElement ||
   target instanceof HTMLTextAreaElement ||
   target instanceof HTMLSelectElement;
   target instanceof HTMLSelectElement;
 
 
+export const isInteractive = (target: Element | EventTarget | null) => {
+  return (
+    isInputLike(target) ||
+    (target instanceof Element && !!target.closest("label, button"))
+  );
+};
+
 export const isWritableElement = (
 export const isWritableElement = (
   target: Element | EventTarget | null,
   target: Element | EventTarget | null,
 ): target is
 ): target is