From 1fd2fe56ee7d30bffe8bdb9994e442d6cd56db99 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Thu, 13 Jan 2022 19:53:22 +0100 Subject: [PATCH] fix: cmd/ctrl native browser behavior blocked in inputs (#4589) * fix: cmd/ctrl native browser behavior blocked in inputs * add basic test for fontSize increase/decrease via keyboard * add tests for fontSize resizing via keyboard outside wysiwyg * Update src/element/textWysiwyg.test.tsx Co-authored-by: Aakansha Doshi * Update src/tests/resize.test.tsx Co-authored-by: Aakansha Doshi * Update src/tests/resize.test.tsx Co-authored-by: Aakansha Doshi Co-authored-by: Aakansha Doshi --- src/actions/actionClipboard.tsx | 2 +- src/actions/register.ts | 6 ++++-- src/components/App.tsx | 6 ++---- src/element/textWysiwyg.test.tsx | 33 +++++++++++++++++++++++++++++--- src/element/textWysiwyg.tsx | 20 +++++++++++++------ src/tests/resize.test.tsx | 33 +++++++++++++++++++++++++++++++- 6 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/actions/actionClipboard.tsx b/src/actions/actionClipboard.tsx index 1212e394..836dedf6 100644 --- a/src/actions/actionClipboard.tsx +++ b/src/actions/actionClipboard.tsx @@ -25,7 +25,7 @@ export const actionCut = register({ name: "cut", perform: (elements, appState, data, app) => { actionCopy.perform(elements, appState, data, app); - return actionDeleteSelected.perform(elements, appState, data, app); + return actionDeleteSelected.perform(elements, appState); }, contextItemLabel: "labels.cut", keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.X, diff --git a/src/actions/register.ts b/src/actions/register.ts index 59d7aad2..ccc9cdbf 100644 --- a/src/actions/register.ts +++ b/src/actions/register.ts @@ -2,7 +2,9 @@ import { Action } from "./types"; export let actions: readonly Action[] = []; -export const register = (action: Action): Action => { +export const register = (action: T) => { actions = actions.concat(action); - return action; + return action as T & { + keyTest?: unknown extends T["keyTest"] ? never : T["keyTest"]; + }; }; diff --git a/src/components/App.tsx b/src/components/App.tsx index a582e26d..c578c77d 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1649,10 +1649,7 @@ class App extends React.Component { } if ( - (isWritableElement(event.target) && - event.key !== KEYS.ESCAPE && - // handle cmd/ctrl-modifier shortcuts even inside inputs - !event[KEYS.CTRL_OR_CMD]) || + (isWritableElement(event.target) && event.key !== KEYS.ESCAPE) || // case: using arrows to move between buttons (isArrowKey(event.key) && isInputLike(event.target)) ) { @@ -1993,6 +1990,7 @@ class App extends React.Component { }), element, excalidrawContainer: this.excalidrawContainerRef.current, + app: this, }); // deselect all other elements when inserting text this.deselectElements(); diff --git a/src/element/textWysiwyg.test.tsx b/src/element/textWysiwyg.test.tsx index 3a880e43..126650cb 100644 --- a/src/element/textWysiwyg.test.tsx +++ b/src/element/textWysiwyg.test.tsx @@ -5,7 +5,10 @@ import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { KEYS } from "../keys"; import { fireEvent } from "../tests/test-utils"; import { BOUND_TEXT_PADDING, FONT_FAMILY } from "../constants"; -import { ExcalidrawTextElementWithContainer } from "./types"; +import { + ExcalidrawTextElement, + ExcalidrawTextElementWithContainer, +} from "./types"; import * as textElementUtils from "./textElement"; // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); @@ -16,12 +19,13 @@ const mouse = new Pointer("mouse"); describe("textWysiwyg", () => { describe("Test unbounded text", () => { let textarea: HTMLTextAreaElement; + let textElement: ExcalidrawTextElement; beforeEach(async () => { await render(); - const element = UI.createElement("text"); + textElement = UI.createElement("text"); - mouse.clickOn(element); + mouse.clickOn(textElement); textarea = document.querySelector( ".excalidraw-textEditorContainer > textarea", )!; @@ -171,7 +175,30 @@ describe("textWysiwyg", () => { expect(textarea.value).toEqual(`Line#1\nLine#2`); }); + + it("should resize text via shortcuts while in wysiwyg", () => { + textarea.value = "abc def"; + const origFontSize = textElement.fontSize; + textarea.dispatchEvent( + new KeyboardEvent("keydown", { + key: KEYS.CHEVRON_RIGHT, + ctrlKey: true, + shiftKey: true, + }), + ); + expect(textElement.fontSize).toBe(origFontSize * 1.1); + + textarea.dispatchEvent( + new KeyboardEvent("keydown", { + key: KEYS.CHEVRON_LEFT, + ctrlKey: true, + shiftKey: true, + }), + ); + expect(textElement.fontSize).toBe(origFontSize); + }); }); + describe("Test bounded text", () => { let rectangle: any; const { diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index c07302bc..45997aa7 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -17,6 +17,11 @@ import { getContainerElement, wrapText, } from "./textElement"; +import { + actionDecreaseFontSize, + actionIncreaseFontSize, +} from "../actions/actionProperties"; +import App from "../components/App"; const normalizeText = (text: string) => { return ( @@ -60,6 +65,7 @@ export const textWysiwyg = ({ element, canvas, excalidrawContainer, + app, }: { id: ExcalidrawElement["id"]; appState: AppState; @@ -73,6 +79,7 @@ export const textWysiwyg = ({ element: ExcalidrawTextElement; canvas: HTMLCanvasElement | null; excalidrawContainer: HTMLDivElement | null; + app: App; }) => { const textPropertiesUpdated = ( updatedElement: ExcalidrawTextElement, @@ -293,16 +300,18 @@ export const textWysiwyg = ({ } editable.onkeydown = (event) => { - if (!event[KEYS.CTRL_OR_CMD]) { - event.stopPropagation(); - } - if (event.key === KEYS.ESCAPE) { + event.stopPropagation(); + + if (actionDecreaseFontSize.keyTest(event)) { + app.actionManager.executeAction(actionDecreaseFontSize); + } else if (actionIncreaseFontSize.keyTest(event)) { + app.actionManager.executeAction(actionIncreaseFontSize); + } else if (event.key === KEYS.ESCAPE) { event.preventDefault(); submittedViaKeyboard = true; handleSubmit(); } else if (event.key === KEYS.ENTER && event[KEYS.CTRL_OR_CMD]) { event.preventDefault(); - event.stopPropagation(); if (event.isComposing || event.keyCode === 229) { return; } @@ -315,7 +324,6 @@ export const textWysiwyg = ({ event.code === CODES.BRACKET_RIGHT)) ) { event.preventDefault(); - event.stopPropagation(); if (event.shiftKey || event.code === CODES.BRACKET_LEFT) { outdent(); } else { diff --git a/src/tests/resize.test.tsx b/src/tests/resize.test.tsx index b77c039f..4e553825 100644 --- a/src/tests/resize.test.tsx +++ b/src/tests/resize.test.tsx @@ -8,7 +8,10 @@ import { getTransformHandles, TransformHandleDirection, } from "../element/transformHandles"; -import { ExcalidrawElement } from "../element/types"; +import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types"; +import ExcalidrawApp from "../excalidraw-app"; +import { API } from "./helpers/api"; +import { KEYS } from "../keys"; const mouse = new Pointer("mouse"); @@ -143,3 +146,31 @@ const resize = ( mouse.up(); }); }; + +describe("Test text element", () => { + it("should update font size via keyboard", async () => { + await render(); + + const textElement = API.createElement({ + type: "text", + text: "abc", + }); + + window.h.elements = [textElement]; + + API.setSelectedElements([textElement]); + + const origFontSize = textElement.fontSize; + + Keyboard.withModifierKeys({ shift: true, ctrl: true }, () => { + Keyboard.keyDown(KEYS.CHEVRON_RIGHT); + expect((window.h.elements[0] as ExcalidrawTextElement).fontSize).toBe( + origFontSize * 1.1, + ); + Keyboard.keyDown(KEYS.CHEVRON_LEFT); + expect((window.h.elements[0] as ExcalidrawTextElement).fontSize).toBe( + origFontSize, + ); + }); + }); +});