From b9ba407f96fc4305443a4a3c0816b692a053378c Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Thu, 16 Feb 2023 20:46:51 +0530 Subject: [PATCH] feat: Bind text to container if double clicked on filled shape or stroke (#6250) * feat: bind text to container when clicked on filled shape or element stroke * Bind if double clicked on stroke as well * remove * specs * remove * shuffle * fix * back to normal --- src/components/App.tsx | 11 ++++- src/element/textWysiwyg.test.tsx | 75 ++++++++++++++++++++++++++++---- src/element/typeChecks.ts | 2 +- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 1fab2a2c..a48510bf 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -226,6 +226,7 @@ import { setEraserCursor, updateActiveTool, getShortcutKey, + isTransparent, } from "../utils"; import { ContextMenu, @@ -2762,7 +2763,15 @@ class App extends React.Component { sceneY, ); if (container) { - if (isArrowElement(container) || hasBoundTextElement(container)) { + if ( + isArrowElement(container) || + hasBoundTextElement(container) || + !isTransparent(container.backgroundColor) || + isHittingElementNotConsideringBoundingBox(container, this.state, [ + sceneX, + sceneY, + ]) + ) { const midPoint = getContainerCenter(container, this.state); sceneX = midPoint.x; diff --git a/src/element/textWysiwyg.test.tsx b/src/element/textWysiwyg.test.tsx index 83b3251d..c61d4e68 100644 --- a/src/element/textWysiwyg.test.tsx +++ b/src/element/textWysiwyg.test.tsx @@ -463,14 +463,21 @@ describe("textWysiwyg", () => { }); }); - it("should bind text to container when double clicked on center of filled container", async () => { + it("should bind text to container when double clicked inside filled container", async () => { + const rectangle = API.createElement({ + type: "rectangle", + x: 10, + y: 20, + width: 90, + height: 75, + backgroundColor: "red", + }); + h.elements = [rectangle]; + expect(h.elements.length).toBe(1); expect(h.elements[0].id).toBe(rectangle.id); - mouse.doubleClickAt( - rectangle.x + rectangle.width / 2, - rectangle.y + rectangle.height / 2, - ); + mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10); expect(h.elements.length).toBe(2); const text = h.elements[1] as ExcalidrawTextElementWithContainer; @@ -504,24 +511,37 @@ describe("textWysiwyg", () => { }); h.elements = [rectangle]; + mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10); + expect(h.elements.length).toBe(2); + let text = h.elements[1] as ExcalidrawTextElementWithContainer; + expect(text.type).toBe("text"); + expect(text.containerId).toBe(null); + mouse.down(); + let editor = document.querySelector( + ".excalidraw-textEditorContainer > textarea", + ) as HTMLTextAreaElement; + await new Promise((r) => setTimeout(r, 0)); + editor.blur(); + mouse.doubleClickAt( rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2, ); - expect(h.elements.length).toBe(2); + expect(h.elements.length).toBe(3); - const text = h.elements[1] as ExcalidrawTextElementWithContainer; + text = h.elements[1] as ExcalidrawTextElementWithContainer; expect(text.type).toBe("text"); expect(text.containerId).toBe(rectangle.id); + mouse.down(); - const editor = document.querySelector( + editor = document.querySelector( ".excalidraw-textEditorContainer > textarea", ) as HTMLTextAreaElement; fireEvent.change(editor, { target: { value: "Hello World!" } }); - await new Promise((r) => setTimeout(r, 0)); editor.blur(); + expect(rectangle.boundElements).toStrictEqual([ { id: text.id, type: "text" }, ]); @@ -551,6 +571,43 @@ describe("textWysiwyg", () => { ]); }); + it("should bind text to container when double clicked on container stroke", async () => { + const rectangle = API.createElement({ + type: "rectangle", + x: 10, + y: 20, + width: 90, + height: 75, + strokeWidth: 4, + }); + h.elements = [rectangle]; + + expect(h.elements.length).toBe(1); + expect(h.elements[0].id).toBe(rectangle.id); + + mouse.doubleClickAt(rectangle.x + 2, rectangle.y + 2); + expect(h.elements.length).toBe(2); + + const text = h.elements[1] as ExcalidrawTextElementWithContainer; + expect(text.type).toBe("text"); + expect(text.containerId).toBe(rectangle.id); + expect(rectangle.boundElements).toStrictEqual([ + { id: text.id, type: "text" }, + ]); + mouse.down(); + const editor = document.querySelector( + ".excalidraw-textEditorContainer > textarea", + ) as HTMLTextAreaElement; + + fireEvent.change(editor, { target: { value: "Hello World!" } }); + + await new Promise((r) => setTimeout(r, 0)); + editor.blur(); + expect(rectangle.boundElements).toStrictEqual([ + { id: text.id, type: "text" }, + ]); + }); + it("shouldn't bind to non-text-bindable containers", async () => { const freedraw = API.createElement({ type: "freedraw", diff --git a/src/element/typeChecks.ts b/src/element/typeChecks.ts index 72088e72..a6b6cb2d 100644 --- a/src/element/typeChecks.ts +++ b/src/element/typeChecks.ts @@ -137,7 +137,7 @@ export const isExcalidrawElement = (element: any): boolean => { export const hasBoundTextElement = ( element: ExcalidrawElement | null, -): element is ExcalidrawBindableElement => { +): element is MarkNonNullable => { return ( isBindableElement(element) && !!element.boundElements?.some(({ type }) => type === "text")