Skip to content

Commit

Permalink
feat: Bind text to container if double clicked on filled shape or str…
Browse files Browse the repository at this point in the history
…oke (excalidraw#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
  • Loading branch information
ad1992 authored Feb 16, 2023
1 parent 5acb997 commit b9ba407
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 11 deletions.
11 changes: 10 additions & 1 deletion src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ import {
setEraserCursor,
updateActiveTool,
getShortcutKey,
isTransparent,
} from "../utils";
import {
ContextMenu,
Expand Down Expand Up @@ -2762,7 +2763,15 @@ class App extends React.Component<AppProps, AppState> {
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;
Expand Down
75 changes: 66 additions & 9 deletions src/element/textWysiwyg.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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" },
]);
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/element/typeChecks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const isExcalidrawElement = (element: any): boolean => {

export const hasBoundTextElement = (
element: ExcalidrawElement | null,
): element is ExcalidrawBindableElement => {
): element is MarkNonNullable<ExcalidrawBindableElement, "boundElements"> => {
return (
isBindableElement(element) &&
!!element.boundElements?.some(({ type }) => type === "text")
Expand Down

0 comments on commit b9ba407

Please sign in to comment.