From c99980327fd7630a143b82006f9f5736e5d9ceb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 19 Oct 2026 15:06:56 +0200 Subject: [PATCH] :bug: Resize fit-content flex boards while typing in v3 text editor (#12330) * :bug: Resize fit-content flex boards while typing in v3 text editor Typing in an auto-width text did not resize its fit-content layout parent until the editor closed. The v3 editor now previews the live text size in the layout on each keystroke, also when the text sits in a group. * :bug: Preview text layout when typing with a pending caret style Both v3 editor commit paths share v3-update-editor-content. * :bug: Skip the layout preview for absolute texts Absolute layout items do not affect their board. * :zap: Preview text layout only when the text size changes Each preview propagates modifiers, so skip unchanged sizes. * :bug: Leave interactive render mode when the v3 editor closes Finalize only ends it when it applies geometry. --- .../data/text-editor/get-file-12325.json | 727 ++++++++++++++++++ .../ui/specs/text-editor-v3.spec.js | 74 +- .../src/app/main/data/workspace/texts.cljs | 73 +- .../ui/workspace/shapes/text/v3_editor.cljs | 9 +- 4 files changed, 866 insertions(+), 17 deletions(-) create mode 100644 frontend/playwright/data/text-editor/get-file-12325.json diff --git a/frontend/playwright/data/text-editor/get-file-12325.json b/frontend/playwright/data/text-editor/get-file-12325.json new file mode 100644 index 0000000000..2c039f5bb8 --- /dev/null +++ b/frontend/playwright/data/text-editor/get-file-12325.json @@ -0,0 +1,727 @@ +{ + "~:features": { + "~#set": [ + "fdata/path-data", + "plugins/runtime", + "design-tokens/v1", + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type", + "text-editor/v2" + ] + }, + "~:team-id": "~u9e6e22b2-db76-81d6-8006-75d7cdbb8bad", + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "Bug 12325", + "~:revn": 3, + "~:modified-at": "~m1753957736516", + "~:vern": 0, + "~:id": "~u238a17e0-75ff-8075-8006-934586ea2230", + "~:is-shared": false, + "~:migrations": { + "~#ordered-set": [ + "legacy-2", + "legacy-3", + "legacy-5", + "legacy-6", + "legacy-7", + "legacy-8", + "legacy-9", + "legacy-10", + "legacy-11", + "legacy-12", + "legacy-13", + "legacy-14", + "legacy-16", + "legacy-17", + "legacy-18", + "legacy-19", + "legacy-25", + "legacy-26", + "legacy-27", + "legacy-28", + "legacy-29", + "legacy-31", + "legacy-32", + "legacy-33", + "legacy-34", + "legacy-36", + "legacy-37", + "legacy-38", + "legacy-39", + "legacy-40", + "legacy-41", + "legacy-42", + "legacy-43", + "legacy-44", + "legacy-45", + "legacy-46", + "legacy-47", + "legacy-48", + "legacy-49", + "legacy-50", + "legacy-51", + "legacy-52", + "legacy-53", + "legacy-54", + "legacy-55", + "legacy-56", + "legacy-57", + "legacy-59", + "legacy-62", + "legacy-65", + "legacy-66", + "legacy-67", + "0001-remove-tokens-from-groups", + "0002-normalize-bool-content-v2", + "0002-clean-shape-interactions", + "0003-fix-root-shape", + "0003-convert-path-content-v2", + "0004-clean-shadow-color", + "0005-deprecate-image-type", + "0006-fix-old-texts-fills", + "0007-clear-invalid-strokes-and-fills-v2", + "0008-fix-library-colors-v4", + "0009-clean-library-colors", + "0009-add-partial-text-touched-flags" + ] + }, + "~:version": 67, + "~:project-id": "~u9e6e22b2-db76-81d6-8006-75d7cdc30669", + "~:created-at": "~m1753957644225", + "~:data": { + "~:pages": ["~u238a17e0-75ff-8075-8006-934586ea2231"], + "~:pages-index": { + "~u238a17e0-75ff-8075-8006-934586ea2231": { + "~:objects": { + "~u00000000-0000-0000-0000-000000000000": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:name": "Root Frame", + "~:width": 0.01, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 0.0, + "~:y": 0.0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0.0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0.01 + } + }, + { + "~#point": { + "~:x": 0.0, + "~:y": 0.01 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:r3": 0, + "~:r1": 0, + "~:id": "~u00000000-0000-0000-0000-000000000000", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 0, + "~:proportion": 1.0, + "~:r4": 0, + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 0.01, + "~:height": 0.01, + "~:x1": 0, + "~:y1": 0, + "~:x2": 0.01, + "~:y2": 0.01 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 0.01, + "~:flip-y": null, + "~:shapes": [ + "~uc0a701d6-7df9-80ac-8008-c9e6c5b7f7f4", + "~uc0a701d6-7df9-80ac-8008-c9e6c8b2a9e2" + ] + } + }, + "~uc0a701d6-7df9-80ac-8008-c9e6c5b7f7f4": { + "~#shape": { + "~:id": "~uc0a701d6-7df9-80ac-8008-c9e6c5b7f7f4", + "~:name": "Text board", + "~:type": "~:frame", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:shapes": ["~uc0a701d6-7df9-80ac-8008-c9e6c47fbce4"], + "~:fills": [ + { + "~:fill-color": "#B1B2B5", + "~:fill-opacity": 1 + } + ], + "~:strokes": [], + "~:hide-fill-on-export": false, + "~:show-content": true, + "~:proportion-lock": false, + "~:proportion": 1, + "~:r1": 0, + "~:r2": 0, + "~:r3": 0, + "~:r4": 0, + "~:layout": "~:flex", + "~:layout-flex-dir": "~:row", + "~:layout-wrap-type": "~:nowrap", + "~:layout-gap-type": "~:multiple", + "~:layout-gap": { + "~:row-gap": 0, + "~:column-gap": 0 + }, + "~:layout-padding-type": "~:simple", + "~:layout-padding": { + "~:p1": 0, + "~:p2": 0, + "~:p3": 0, + "~:p4": 0 + }, + "~:layout-align-items": "~:start", + "~:layout-justify-content": "~:start", + "~:layout-align-content": "~:stretch", + "~:layout-item-h-sizing": "~:auto", + "~:layout-item-v-sizing": "~:auto", + "~:x": 0, + "~:y": 0, + "~:width": 99.99999904632568, + "~:height": 17, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 99.99999904632568, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 99.99999904632568, + "~:y": 17 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 17 + } + } + ], + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 99.99999904632568, + "~:height": 17, + "~:x1": 0, + "~:y1": 0, + "~:x2": 99.99999904632568, + "~:y2": 17 + } + } + } + }, + "~uc0a701d6-7df9-80ac-8008-c9e6c47fbce4": { + "~#shape": { + "~:id": "~uc0a701d6-7df9-80ac-8008-c9e6c47fbce4", + "~:name": "Hello wide world", + "~:type": "~:text", + "~:grow-type": "~:auto-width", + "~:parent-id": "~uc0a701d6-7df9-80ac-8008-c9e6c5b7f7f4", + "~:frame-id": "~uc0a701d6-7df9-80ac-8008-c9e6c5b7f7f4", + "~:content": { + "~:type": "root", + "~:children": [ + { + "~:type": "paragraph-set", + "~:children": [ + { + "~:type": "paragraph", + "~:children": [ + { + "~:line-height": "1.2", + "~:font-style": "normal", + "~:typography-ref-id": null, + "~:text-transform": "none", + "~:text-align": "left", + "~:font-id": "sourcesanspro", + "~:font-size": "14", + "~:font-weight": "400", + "~:typography-ref-file": null, + "~:text-direction": "ltr", + "~:font-variant-id": "regular", + "~:text-decoration": "none", + "~:letter-spacing": "0", + "~:fills": [ + { + "~:fill-color": "#000000", + "~:fill-opacity": 1 + } + ], + "~:font-family": "sourcesanspro", + "~:text": "Hello wide world" + } + ] + } + ] + } + ] + }, + "~:x": 0, + "~:y": 0, + "~:width": 99.99999904632568, + "~:height": 17, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 99.99999904632568, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 99.99999904632568, + "~:y": 17 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 17 + } + } + ], + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 99.99999904632568, + "~:height": 17, + "~:x1": 0, + "~:y1": 0, + "~:x2": 99.99999904632568, + "~:y2": 17 + } + } + } + }, + "~uc0a701d6-7df9-80ac-8008-c9e6c8b2a9e2": { + "~#shape": { + "~:id": "~uc0a701d6-7df9-80ac-8008-c9e6c8b2a9e2", + "~:name": "Group board", + "~:type": "~:frame", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:shapes": ["~uc0a701d6-7df9-80ac-8008-c9e6ca7a304a"], + "~:fills": [ + { + "~:fill-color": "#B1B2B5", + "~:fill-opacity": 1 + } + ], + "~:strokes": [], + "~:hide-fill-on-export": false, + "~:show-content": true, + "~:proportion-lock": false, + "~:proportion": 1, + "~:r1": 0, + "~:r2": 0, + "~:r3": 0, + "~:r4": 0, + "~:layout": "~:flex", + "~:layout-flex-dir": "~:row", + "~:layout-wrap-type": "~:nowrap", + "~:layout-gap-type": "~:multiple", + "~:layout-gap": { + "~:row-gap": 0, + "~:column-gap": 0 + }, + "~:layout-padding-type": "~:simple", + "~:layout-padding": { + "~:p1": 0, + "~:p2": 0, + "~:p3": 0, + "~:p4": 0 + }, + "~:layout-align-items": "~:start", + "~:layout-justify-content": "~:start", + "~:layout-align-content": "~:stretch", + "~:layout-item-h-sizing": "~:auto", + "~:layout-item-v-sizing": "~:auto", + "~:x": 0, + "~:y": 100, + "~:width": 120.99999821186066, + "~:height": 17, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 100 + } + }, + { + "~#point": { + "~:x": 120.99999821186066, + "~:y": 100 + } + }, + { + "~#point": { + "~:x": 120.99999821186066, + "~:y": 117 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 117 + } + } + ], + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 100, + "~:width": 120.99999821186066, + "~:height": 17, + "~:x1": 0, + "~:y1": 100, + "~:x2": 120.99999821186066, + "~:y2": 117 + } + } + } + }, + "~uc0a701d6-7df9-80ac-8008-c9e6ca7a304a": { + "~#shape": { + "~:id": "~uc0a701d6-7df9-80ac-8008-c9e6ca7a304a", + "~:name": "Group", + "~:type": "~:group", + "~:parent-id": "~uc0a701d6-7df9-80ac-8008-c9e6c8b2a9e2", + "~:frame-id": "~uc0a701d6-7df9-80ac-8008-c9e6c8b2a9e2", + "~:shapes": ["~uc0a701d6-7df9-80ac-8008-c9e6c781a50f"], + "~:fills": [], + "~:strokes": [], + "~:proportion-lock": false, + "~:proportion": 1, + "~:x": 0, + "~:y": 100, + "~:width": 120.99999821186066, + "~:height": 17, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 100 + } + }, + { + "~#point": { + "~:x": 120.99999821186066, + "~:y": 100 + } + }, + { + "~#point": { + "~:x": 120.99999821186066, + "~:y": 117 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 117 + } + } + ], + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 100, + "~:width": 120.99999821186066, + "~:height": 17, + "~:x1": 0, + "~:y1": 100, + "~:x2": 120.99999821186066, + "~:y2": 117 + } + } + } + }, + "~uc0a701d6-7df9-80ac-8008-c9e6c781a50f": { + "~#shape": { + "~:id": "~uc0a701d6-7df9-80ac-8008-c9e6c781a50f", + "~:name": "Hello grouped world", + "~:type": "~:text", + "~:grow-type": "~:auto-width", + "~:parent-id": "~uc0a701d6-7df9-80ac-8008-c9e6ca7a304a", + "~:frame-id": "~uc0a701d6-7df9-80ac-8008-c9e6c8b2a9e2", + "~:content": { + "~:type": "root", + "~:children": [ + { + "~:type": "paragraph-set", + "~:children": [ + { + "~:type": "paragraph", + "~:children": [ + { + "~:line-height": "1.2", + "~:font-style": "normal", + "~:typography-ref-id": null, + "~:text-transform": "none", + "~:text-align": "left", + "~:font-id": "sourcesanspro", + "~:font-size": "14", + "~:font-weight": "400", + "~:typography-ref-file": null, + "~:text-direction": "ltr", + "~:font-variant-id": "regular", + "~:text-decoration": "none", + "~:letter-spacing": "0", + "~:fills": [ + { + "~:fill-color": "#000000", + "~:fill-opacity": 1 + } + ], + "~:font-family": "sourcesanspro", + "~:text": "Hello grouped world" + } + ] + } + ] + } + ] + }, + "~:x": 0, + "~:y": 100, + "~:width": 120.99999821186066, + "~:height": 17, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 100 + } + }, + { + "~#point": { + "~:x": 120.99999821186066, + "~:y": 100 + } + }, + { + "~#point": { + "~:x": 120.99999821186066, + "~:y": 117 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 117 + } + } + ], + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 100, + "~:width": 120.99999821186066, + "~:height": 17, + "~:x1": 0, + "~:y1": 100, + "~:x2": 120.99999821186066, + "~:y2": 117 + } + } + } + } + }, + "~:id": "~u238a17e0-75ff-8075-8006-934586ea2231", + "~:name": "Page 1" + } + }, + "~:id": "~u238a17e0-75ff-8075-8006-934586ea2230", + "~:options": { + "~:components-v2": true, + "~:base-font-size": "16px" + } + } +} diff --git a/frontend/playwright/ui/specs/text-editor-v3.spec.js b/frontend/playwright/ui/specs/text-editor-v3.spec.js index fa49a0704a..894640a028 100644 --- a/frontend/playwright/ui/specs/text-editor-v3.spec.js +++ b/frontend/playwright/ui/specs/text-editor-v3.spec.js @@ -23,7 +23,6 @@ async function openEditorAndSelectAll(workspace) { await workspace.page.keyboard.press("ControlOrMeta+a"); } - test("Typography at a collapsed caret only styles newly typed text", async ({ page, }) => { @@ -218,8 +217,6 @@ test.describe("BUG 11083 - Changing typography must not quit the editor", () => }); }); - - test("BUG 10467 - Auto-width text captures every typed character", async ({ page, }) => { @@ -681,3 +678,74 @@ test.describe("Text Editor context menu", () => { await expect(menuItem(page, "Select all")).not.toHaveAttribute("disabled"); }); }); + +test.describe("BUG 12325 - Fit-content flex board follows an auto-width text while typing", () => { + // Two grey fit-content flex boards, one per test. The content + // (0, 0)-(121, 117) loads centred in the viewport at zoom 1. + const CONTENT_CENTER = { x: 60.5, y: 58.5 }; + + // Screenshot of a small canvas patch centred on (x, y), in canvas + // coordinates. + async function patchAt(workspace, x, y) { + const viewport = await workspace.viewport.boundingBox(); + const px = viewport.x + viewport.width / 2 + x - CONTENT_CENTER.x; + const py = viewport.y + viewport.height / 2 + y - CONTENT_CENTER.y; + return workspace.page.screenshot({ + clip: { x: px - 2, y: py - 2, width: 4, height: 4 }, + }); + } + + async function openFile(page) { + const workspace = new WasmWorkspacePage(page, { textEditor: true }); + await workspace.setupEmptyFile(); + await workspace.mockGetFile("text-editor/get-file-12325.json"); + await workspace.goToWorkspace(); + await workspace.waitForFirstRender(); + return workspace; + } + + // Shrinks `text` to its first character while editing and checks that a spot + // in the right part of its board, at `boardY`, shows the empty canvas. + async function expectBoardShrinksWhileTyping(workspace, text, boardY) { + const x = 80; + const inside = boardY + 8; + const outside = boardY + 50; + expect(await patchAt(workspace, x, inside)).not.toEqual( + await patchAt(workspace, x, outside), + ); + + await workspace.textEditor.startEditing(); + await workspace.page.keyboard.press("End"); + for (let i = 0; i < text.length - 1; i++) { + await workspace.page.keyboard.press("Backspace"); + } + + // Without leaving the editor, the board already shrank around the text, so + // the spot shows the empty canvas. + await expect + .poll(async () => + (await patchAt(workspace, x, inside)).equals( + await patchAt(workspace, x, outside), + ), + ) + .toBe(true); + } + + test("The board shrinks with the text", async ({ page }) => { + const workspace = await openFile(page); + await workspace.clickToggableLayer("Text board"); + await workspace.clickLeafLayer("Hello wide world"); + + await expectBoardShrinksWhileTyping(workspace, "Hello wide world", 0); + }); + + test("The board shrinks with a text inside a group", async ({ page }) => { + const workspace = await openFile(page); + await workspace.clickToggableLayer("Group board"); + // The first "Group" match is the board row. + await workspace.clickToggableLayer("Group", {}, 1); + await workspace.clickLeafLayer("Hello grouped world"); + + await expectBoardShrinksWhileTyping(workspace, "Hello grouped world", 100); + }); +}); diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 1eb94ca1c0..f737947e3a 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -1453,18 +1453,77 @@ (gsh/transform-shape (ctm/change-size shape width height)))))) {:undo-group (when new-shape? id)})))))))) +(defn- layout-item + "The ancestor of `shape` (or `shape` itself) that is a direct child of its board." + [objects shape] + (loop [item shape] + (let [parent-id (:parent-id item)] + (if (or (= parent-id (:frame-id shape)) + (nil? (get objects parent-id))) + item + (recur (get objects parent-id)))))) + +(defn- v3-sync-text-layout + "Previews an auto-grow text's live size in its layout, so a fit-content parent + follows it while typing. Geometry is committed on finalize." + [id] + (ptk/reify ::v3-sync-text-layout + ptk/WatchEvent + (watch [_ state _] + (let [objects (dsh/lookup-page-objects state) + shape (get objects id) + preview? (and (cfh/text-shape? shape) + (not= :fixed (:grow-type shape)) + (ctl/any-layout-descent? objects shape) + (not (ctl/position-absolute? (layout-item objects shape)))) + size (when preview? + (dwwt/get-wasm-text-new-size shape)) + previous (or (get-in state [::layout-preview id]) + (select-keys (:selrect shape) [:width :height]))] + ;; Modifiers are costly, so skip keystrokes that keep the size. + (if (and (some? size) + (not (and (mth/close? (:width size) (:width previous)) + (mth/close? (:height size) (:height previous))))) + (rx/of #(assoc-in % [::layout-preview id] size) + (dwm/set-wasm-modifiers (dwwt/resize-wasm-text-modifiers shape))) + (rx/empty)))))) + +(defn v3-stop-text-layout-preview + "Drops the layout preview of the text `id` and leaves interactive render mode." + [id] + (ptk/reify ::v3-stop-text-layout-preview + ptk/WatchEvent + (watch [_ state _] + (if (contains? (get state ::layout-preview) id) + (rx/of #(update % ::layout-preview dissoc id) + (dwm/clear-local-transform)) + (rx/empty))))) + +(defn v3-update-editor-content + "Commits `content` from the WASM editor to the text `shape-id` and, while + editing, previews its new size in the layout." + [shape-id content & {:keys [finalize?]}] + (let [text (txt/content->text content) + name (when (not= text "") + (txt/generate-shape-name text)) + update-event (v2-update-text-shape-content shape-id content + :update-name? true + :name name + :finalize? finalize?)] + (if finalize? + update-event + (ptk/reify ::v3-update-editor-content + ptk/WatchEvent + (watch [_ _ _] + (rx/of update-event + (v3-sync-text-layout shape-id))))))) + (defn v3-sync-editor-content "Event pushing the WASM editor content back into the shape, or nil when there is nothing to sync. Every text edit commits through it, menu or keystroke alike." [& {:keys [finalize?]}] (when-let [{:keys [shape-id content]} (wasm.text-editor/text-editor-sync-content)] - (let [text (txt/content->text content) - name (when (not= text "") - (txt/generate-shape-name text))] - (v2-update-text-shape-content shape-id content - :update-name? true - :name name - :finalize? finalize?)))) + (v3-update-editor-content shape-id content :finalize? finalize?))) (defn- sync-editor-content-stream "Stream of the sync event for `reason`, after asking WASM to repaint." diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs index ca486c0fe1..74f95eea13 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs @@ -9,7 +9,6 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] - [app.common.types.text :as txt] [app.config :as cf] [app.main.data.helpers :as dsh] [app.main.data.workspace :as dw] @@ -87,12 +86,7 @@ ;; Sync first so the cached content stays index-aligned with WASM. (text-editor/text-editor-sync-content) (if-let [{:keys [content]} (wasm.api/apply-pending-caret-styles! shape-id range)] - (let [text (txt/content->text content) - name (when (not= text "") (txt/generate-shape-name text))] - (st/emit! (dwt/v2-update-text-shape-content - shape-id content - :update-name? true - :name name))) + (st/emit! (dwt/v3-update-editor-content shape-id content)) (sync-wasm-text-editor-content!)))) (defn- collapse-input-caret @@ -663,6 +657,7 @@ (fn [] (on-blur) (st/emit! dw/hide-context-menu + (dwt/v3-stop-text-layout-preview shape-id) (dwu/commit-undo-transaction shape-id)) (text-editor/text-editor-dispose) (wasm.api/request-render-preserving-target "text-editor-dispose"))))