fix: update coords when text unbinded from its container (#6445)

* fix: update coords when text unbinded from its container

* Add specs
This commit is contained in:
Aakansha Doshi 2023-04-13 11:45:58 +05:30 committed by GitHub
parent 372743f59f
commit 13b27afe0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 3 deletions

View File

@ -2,6 +2,7 @@ import { BOUND_TEXT_PADDING, ROUNDNESS, VERTICAL_ALIGN } from "../constants";
import { getNonDeletedElements, isTextElement, newElement } from "../element"; import { getNonDeletedElements, isTextElement, newElement } from "../element";
import { mutateElement } from "../element/mutateElement"; import { mutateElement } from "../element/mutateElement";
import { import {
computeBoundTextPosition,
computeContainerDimensionForBoundText, computeContainerDimensionForBoundText,
getBoundTextElement, getBoundTextElement,
measureText, measureText,
@ -33,6 +34,7 @@ export const actionUnbindText = register({
trackEvent: { category: "element" }, trackEvent: { category: "element" },
predicate: (elements, appState) => { predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState); const selectedElements = getSelectedElements(elements, appState);
return selectedElements.some((element) => hasBoundTextElement(element)); return selectedElements.some((element) => hasBoundTextElement(element));
}, },
perform: (elements, appState) => { perform: (elements, appState) => {
@ -52,13 +54,15 @@ export const actionUnbindText = register({
element.id, element.id,
); );
resetOriginalContainerCache(element.id); resetOriginalContainerCache(element.id);
const { x, y } = computeBoundTextPosition(element, boundTextElement);
mutateElement(boundTextElement as ExcalidrawTextElement, { mutateElement(boundTextElement as ExcalidrawTextElement, {
containerId: null, containerId: null,
width, width,
height, height,
baseline, baseline,
text: boundTextElement.originalText, text: boundTextElement.originalText,
x,
y,
}); });
mutateElement(element, { mutateElement(element, {
boundElements: element.boundElements?.filter( boundElements: element.boundElements?.filter(

View File

@ -245,10 +245,16 @@ export const handleBindTextResize = (
} }
}; };
const computeBoundTextPosition = ( export const computeBoundTextPosition = (
container: ExcalidrawElement, container: ExcalidrawElement,
boundTextElement: ExcalidrawTextElementWithContainer, boundTextElement: ExcalidrawTextElementWithContainer,
) => { ) => {
if (isArrowElement(container)) {
return LinearElementEditor.getBoundTextElementPosition(
container,
boundTextElement,
);
}
const containerCoords = getContainerCoords(container); const containerCoords = getContainerCoords(container);
const maxContainerHeight = getMaxContainerHeight(container); const maxContainerHeight = getMaxContainerHeight(container);
const maxContainerWidth = getMaxContainerWidth(container); const maxContainerWidth = getMaxContainerWidth(container);

View File

@ -740,6 +740,45 @@ describe("textWysiwyg", () => {
expect(rectangle.boundElements).toBe(null); expect(rectangle.boundElements).toBe(null);
}); });
it("should bind text to container when triggered via context menu", async () => {
expect(h.elements.length).toBe(1);
expect(h.elements[0].id).toBe(rectangle.id);
UI.clickTool("text");
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",
},
});
editor.dispatchEvent(new Event("input"));
await new Promise((cb) => setTimeout(cb, 0));
expect(h.elements.length).toBe(2);
expect(h.elements[1].type).toBe("text");
API.setSelectedElements([h.elements[0], h.elements[1]]);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 20,
clientY: 30,
});
const contextMenu = document.querySelector(".context-menu");
fireEvent.click(
queryByText(contextMenu as HTMLElement, "Bind text to the container")!,
);
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(rectangle.boundElements).toStrictEqual([
{ id: h.elements[1].id, type: "text" },
]);
expect(text.containerId).toBe(rectangle.id);
expect(text.verticalAlign).toBe(VERTICAL_ALIGN.MIDDLE);
});
it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => { it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => {
expect(h.elements.length).toBe(1); expect(h.elements.length).toBe(1);

View File

@ -23,7 +23,7 @@ import {
getMaxContainerWidth, getMaxContainerWidth,
} from "../element/textElement"; } from "../element/textElement";
import * as textElementUtils from "../element/textElement"; import * as textElementUtils from "../element/textElement";
import { ROUNDNESS } from "../constants"; import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
const renderScene = jest.spyOn(Renderer, "renderScene"); const renderScene = jest.spyOn(Renderer, "renderScene");
@ -1191,5 +1191,62 @@ describe("Test Linear Elements", () => {
expect(queryByTestId(container, "align-horizontal-center")).toBeNull(); expect(queryByTestId(container, "align-horizontal-center")).toBeNull();
expect(queryByTestId(container, "align-right")).toBeNull(); expect(queryByTestId(container, "align-right")).toBeNull();
}); });
it("should update label coords when a label binded via context menu is unbinded", async () => {
createTwoPointerLinearElement("arrow");
const text = API.createElement({
type: "text",
text: "Hello Excalidraw",
});
expect(text.x).toBe(0);
expect(text.y).toBe(0);
h.elements = [h.elements[0], text];
const container = h.elements[0];
API.setSelectedElements([container, text]);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 20,
clientY: 30,
});
let contextMenu = document.querySelector(".context-menu");
fireEvent.click(
queryByText(contextMenu as HTMLElement, "Bind text to the container")!,
);
expect(container.boundElements).toStrictEqual([
{ id: h.elements[1].id, type: "text" },
]);
expect(text.containerId).toBe(container.id);
expect(text.verticalAlign).toBe(VERTICAL_ALIGN.MIDDLE);
mouse.reset();
mouse.clickAt(
container.x + container.width / 2,
container.y + container.height / 2,
);
mouse.down();
mouse.up();
API.setSelectedElements([h.elements[0], h.elements[1]]);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 20,
clientY: 30,
});
contextMenu = document.querySelector(".context-menu");
fireEvent.click(queryByText(contextMenu as HTMLElement, "Unbind text")!);
expect(container.boundElements).toEqual([]);
expect(text).toEqual(
expect.objectContaining({
containerId: null,
width: 160,
height: 25,
x: -40,
y: 7.5,
}),
);
});
}); });
}); });