diff --git a/src/appState.ts b/src/appState.ts index 703cad4bfd..5087a3523c 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -8,7 +8,6 @@ export const DEFAULT_TEXT_ALIGN = "left"; export function getDefaultAppState(): AppState { return { - wysiwygElement: null, isLoading: false, errorMessage: null, draggingElement: null, diff --git a/src/components/App.tsx b/src/components/App.tsx index 6829bed6ca..ed34cf856f 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1228,7 +1228,8 @@ class App extends React.Component { ]); }; - const wysiwygElement = textWysiwyg({ + textWysiwyg({ + id: element.id, x, y, initText: element.text, @@ -1248,7 +1249,6 @@ class App extends React.Component { onSubmit: withBatchedUpdates((text) => { updateElement(text); this.setState((prevState) => ({ - wysiwygElement: null, selectedElementIds: { ...prevState.selectedElementIds, [element.id]: true, @@ -1269,7 +1269,7 @@ class App extends React.Component { }), }); // deselect all other elements when inserting text - this.setState({ selectedElementIds: {}, wysiwygElement }); + this.setState({ selectedElementIds: {} }); // do an initial update to re-initialize element position since we were // modifying element's x/y for sake of editor (case: syncing to remote) @@ -1579,9 +1579,6 @@ class App extends React.Component { private handleCanvasPointerDown = ( event: React.PointerEvent, ) => { - if (this.state.wysiwygElement && this.state.wysiwygElement.submit) { - this.state.wysiwygElement.submit(); - } if (lastPointerUp !== null) { // Unfortunately, sometimes we don't get a pointerup after a pointerdown, // this can happen when a contextual menu or alert is triggered. In order to avoid diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 0fe0295424..114e7139be 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -26,6 +26,7 @@ import { ErrorDialog } from "./ErrorDialog"; import { ShortcutsDialog } from "./ShortcutsDialog"; import { LoadingMessage } from "./LoadingMessage"; import { GitHubCorner } from "./GitHubCorner"; +import { CLASSES } from "../constants"; interface LayerUIProps { actionManager: ActionManager; @@ -146,7 +147,7 @@ export const LayerUI = React.memo( {showSelectedShapeActions(appState, elements) && (
- + { + window.removeEventListener("pointerup", rebindBlur); + // deferred to guard against focus traps on various UIs that steal focus + // upon pointerUp + setTimeout(() => { + editable.onblur = handleSubmit; + // case: clicking on the same property → no change → no update → no focus + editable.focus(); + }); + }; + + // prevent blur when changing properties from the menu + const onPointerDown = (event: MouseEvent) => { + if ( + event.target instanceof HTMLElement && + event.target.closest(CLASSES.SHAPE_ACTIONS_MENU) && + !isWritableElement(event.target) + ) { + editable.onblur = null; + window.addEventListener("pointerup", rebindBlur); + // handle edge-case where pointerup doesn't fire e.g. due to user + // alt-tabbing away + window.addEventListener("blur", handleSubmit); + } + }; + + // handle updates of textElement properties of editing element + const unbindUpdate = globalSceneState.addCallback(() => { + const editingElement = globalSceneState + .getElementsIncludingDeleted() + .find((element) => element.id === id); + if (editingElement && isTextElement(editingElement)) { + Object.assign(editable.style, { + font: editingElement.font, + textAlign: editingElement.textAlign, + color: editingElement.strokeColor, + opacity: editingElement.opacity / 100, + }); + } + editable.focus(); + }); + + let isDestroyed = false; + + editable.onblur = handleSubmit; + window.addEventListener("pointerdown", onPointerDown); window.addEventListener("wheel", stopEvent, true); document.body.appendChild(editable); editable.focus(); selectNode(editable); - - return { - submit: handleSubmit, - changeStyle: (style: any) => { - Object.assign(editable.style, style); - editable.focus(); - }, - }; } diff --git a/src/element/types.ts b/src/element/types.ts index ef78b06f48..1b1add6cc2 100644 --- a/src/element/types.ts +++ b/src/element/types.ts @@ -68,8 +68,3 @@ export type ResizeArrowFnType = ( pointerY: number, perfect: boolean, ) => void; - -export type WysiwigElement = { - submit: () => void; - changeStyle: (style: Record) => void; -}; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 3cbac058f2..3ff3f4e6a5 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -14,7 +14,6 @@ import { handlerRectangles, getCommonBounds, canResizeMutlipleElements, - isTextElement, } from "../element"; import { roundRect } from "./roundRect"; @@ -104,18 +103,6 @@ export function renderScene( return { atLeastOneVisibleElement: false }; } - if ( - appState.wysiwygElement?.changeStyle && - isTextElement(appState.editingElement) - ) { - appState.wysiwygElement.changeStyle({ - font: appState.editingElement.font, - textAlign: appState.editingElement.textAlign, - color: appState.editingElement.strokeColor, - opacity: appState.editingElement.opacity, - }); - } - const context = canvas.getContext("2d")!; context.scale(scale, scale); diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 899867c4fe..9b52a0bf12 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -41,7 +41,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -241,7 +240,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -360,7 +358,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -636,7 +633,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -797,7 +793,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -998,7 +993,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -1258,7 +1252,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -1658,7 +1651,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -2283,7 +2275,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -2402,7 +2393,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -2521,7 +2511,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -2640,7 +2629,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -2781,7 +2769,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -2922,7 +2909,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -3063,7 +3049,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -3182,7 +3167,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -3301,7 +3285,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -3442,7 +3425,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -3561,7 +3543,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -3634,7 +3615,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -4520,7 +4500,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -4945,7 +4924,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -5277,7 +5255,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -5520,7 +5497,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -5694,7 +5670,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -6531,7 +6506,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -7259,7 +7233,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -7882,7 +7855,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -8405,7 +8377,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -8878,7 +8849,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -9256,7 +9226,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -9543,7 +9512,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -9759,7 +9727,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -10652,7 +10619,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -11434,7 +11400,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -12109,7 +12074,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -12677,7 +12641,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -13056,7 +13019,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -13113,7 +13075,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -13170,7 +13131,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; @@ -13467,7 +13427,6 @@ Object { "showShortcutsDialog": false, "username": "", "viewBackgroundColor": "#ffffff", - "wysiwygElement": null, "zoom": 1, } `; diff --git a/src/types.ts b/src/types.ts index 968ea24168..3f9d39db55 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,6 @@ import { NonDeletedExcalidrawElement, NonDeleted, TextAlign, - WysiwigElement, } from "./element/types"; import { SHAPES } from "./shapes"; import { Point as RoughPoint } from "roughjs/bin/geometry"; @@ -14,7 +13,6 @@ export type FlooredNumber = number & { _brand: "FlooredNumber" }; export type Point = Readonly; export type AppState = { - wysiwygElement: WysiwigElement | null; isLoading: boolean; errorMessage: string | null; draggingElement: NonDeletedExcalidrawElement | null;