feat: support unbinding bound text (#4686)
* feat: support unbinding text * fix unbound text * move the unbind option next to group action * use boundTextElement.id when unbinding * update original text so it takes same bounding box when unbind * Add spec * recompute measurements when unbinding
This commit is contained in:
parent
719ae7b72f
commit
edfbac9d7d
44
src/actions/actionUnbindText.tsx
Normal file
44
src/actions/actionUnbindText.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { getBoundTextElement, measureText } from "../element/textElement";
|
||||
import { ExcalidrawTextElement } from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { getFontString } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
export const actionUnbindText = register({
|
||||
name: "unbindText",
|
||||
contextItemLabel: "labels.unbindText",
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
selectedElements.forEach((element) => {
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
const { width, height, baseline } = measureText(
|
||||
boundTextElement.originalText,
|
||||
getFontString(boundTextElement),
|
||||
);
|
||||
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
||||
containerId: null,
|
||||
width,
|
||||
height,
|
||||
baseline,
|
||||
text: boundTextElement.originalText,
|
||||
});
|
||||
mutateElement(element, {
|
||||
boundElements: element.boundElements?.filter(
|
||||
(ele) => ele.id !== boundTextElement.id,
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
elements,
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
});
|
@ -80,3 +80,4 @@ export { actionToggleGridMode } from "./actionToggleGridMode";
|
||||
export { actionToggleZenMode } from "./actionToggleZenMode";
|
||||
|
||||
export { actionToggleStats } from "./actionToggleStats";
|
||||
export { actionUnbindText } from "./actionUnbindText";
|
||||
|
@ -103,7 +103,8 @@ export type ActionName =
|
||||
| "exportWithDarkMode"
|
||||
| "toggleTheme"
|
||||
| "increaseFontSize"
|
||||
| "decreaseFontSize";
|
||||
| "decreaseFontSize"
|
||||
| "unbindText";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
actionToggleGridMode,
|
||||
actionToggleStats,
|
||||
actionToggleZenMode,
|
||||
actionUnbindText,
|
||||
actionUngroup,
|
||||
} from "../actions";
|
||||
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
||||
@ -5031,6 +5032,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
}
|
||||
} else if (type === "element") {
|
||||
const elementsWithUnbindedText = getSelectedElements(
|
||||
elements,
|
||||
this.state,
|
||||
).some((element) => !hasBoundTextElement(element));
|
||||
if (this.state.viewModeEnabled) {
|
||||
ContextMenu.push({
|
||||
options: [navigator.clipboard && actionCopy, ...options],
|
||||
@ -5064,6 +5069,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
actionPasteStyles,
|
||||
separator,
|
||||
maybeGroupAction && actionGroup,
|
||||
!elementsWithUnbindedText && actionUnbindText,
|
||||
maybeUngroupAction && actionUngroup,
|
||||
(maybeGroupAction || maybeUngroupAction) && separator,
|
||||
actionAddToLibrary,
|
||||
|
@ -175,7 +175,6 @@ export const measureText = (
|
||||
container.style.whiteSpace = "pre";
|
||||
container.style.font = font;
|
||||
container.style.minHeight = "1em";
|
||||
|
||||
if (maxWidth) {
|
||||
const lineHeight = getApproxLineHeight(font);
|
||||
container.style.width = `${String(maxWidth)}px`;
|
||||
|
@ -1,9 +1,11 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { render, screen } from "../tests/test-utils";
|
||||
import { GlobalTestState, render, screen } from "../tests/test-utils";
|
||||
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { fireEvent } from "../tests/test-utils";
|
||||
import { queryByText } from "@testing-library/react";
|
||||
|
||||
import { BOUND_TEXT_PADDING, FONT_FAMILY } from "../constants";
|
||||
import {
|
||||
ExcalidrawTextElement,
|
||||
@ -472,5 +474,47 @@ describe("textWysiwyg", () => {
|
||||
expect(text.height).toBe(APPROX_LINE_HEIGHT);
|
||||
expect(text.width).toBe(rectangle.width - BOUND_TEXT_PADDING * 2);
|
||||
});
|
||||
|
||||
it("should unbind bound text when unbind action from context menu is triggred", async () => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].id).toBe(rectangle.id);
|
||||
|
||||
Keyboard.withModifierKeys({}, () => {
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
});
|
||||
|
||||
expect(h.elements.length).toBe(2);
|
||||
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
fireEvent.change(editor, { target: { value: "Hello World!" } });
|
||||
editor.blur();
|
||||
expect(rectangle.boundElements).toStrictEqual([
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
mouse.reset();
|
||||
UI.clickTool("selection");
|
||||
mouse.clickAt(10, 20);
|
||||
mouse.down();
|
||||
mouse.up();
|
||||
fireEvent.contextMenu(GlobalTestState.canvas, {
|
||||
button: 2,
|
||||
clientX: 20,
|
||||
clientY: 30,
|
||||
});
|
||||
const contextMenu = document.querySelector(".context-menu");
|
||||
fireEvent.click(queryByText(contextMenu as HTMLElement, "Unbind text")!);
|
||||
expect(h.elements[0].boundElements).toEqual([]);
|
||||
expect((h.elements[1] as ExcalidrawTextElement).containerId).toEqual(
|
||||
null,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -104,7 +104,8 @@
|
||||
"personalLib": "Personal Library",
|
||||
"excalidrawLib": "Excalidraw Library",
|
||||
"decreaseFontSize": "Decrease font size",
|
||||
"increaseFontSize": "Increase font size"
|
||||
"increaseFontSize": "Increase font size",
|
||||
"unbindText": "Unbind text"
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Reset the canvas",
|
||||
|
Loading…
x
Reference in New Issue
Block a user