From bf8c66e94220f694f4dad653d4b52aeb69e93d7e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 22 Oct 2026 12:25:48 +0200 Subject: [PATCH] :bug: Keep the workspace open when closing the v2 text editor Closing the v2 text editor with Escape could crash the workspace. The text options panel renders once more with the editor it saw last, after closing has disposed it, and reading the style of a disposed editor throws. The panel now takes the values from the shape when the editor is gone. --- .../src/app/main/data/workspace/texts.cljs | 15 +++++++++-- .../data/workspace_texts_test.cljs | 27 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 1fe143dc45..4b533c8fa5 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -449,6 +449,17 @@ result (if (empty? result) txt/default-text-attrs result)] result)) +(defn- v2-editor-live? + "False for a missing or disposed v2 editor. A component can still hold + the instance for one render after closing the editor disposed it, and + reading its style then throws." + [editor] + (and (some? editor) + (try + (.-currentStyle ^js editor) + true + (catch :default _ false)))) + (defn- styled-runs "The text runs of `content` with their styles, without node ids or the style keys set to nil." @@ -481,7 +492,7 @@ [{:keys [editor-styles editor-state editor-instance attrs shape] :as options}] (cond (some? editor-styles) (v3-current-text-values options) - (some? editor-instance) (v2-current-text-values options) + (v2-editor-live? editor-instance) (v2-current-text-values options) (some? editor-state) (v1-current-paragraph-values options) :else (shape-current-values shape txt/is-paragraph-node? attrs))) @@ -498,7 +509,7 @@ [{:keys [editor-styles editor-state editor-instance attrs shape] :as options}] (cond (some? editor-styles) (v3-current-text-values options) - (some? editor-instance) (v2-current-text-values options) + (v2-editor-live? editor-instance) (v2-current-text-values options) (some? editor-state) (v1-current-text-values options) :else (shape-current-values shape txt/is-text-node? attrs))) diff --git a/frontend/test/frontend_tests/data/workspace_texts_test.cljs b/frontend/test/frontend_tests/data/workspace_texts_test.cljs index 82dbb709e8..d2772ebd36 100644 --- a/frontend/test/frontend_tests/data/workspace_texts_test.cljs +++ b/frontend/test/frontend_tests/data/workspace_texts_test.cljs @@ -673,6 +673,33 @@ (t/testing "a copy without geometry gives a payload without it" (t/is (not (contains? (#'dwt/copied-shapes->payload data) :geometry)))))) +;; --------------------------------------------------------------------------- +;; Tests: values read from a disposed v2 editor +;; --------------------------------------------------------------------------- + +(defn- disposed-v2-editor + "A v2 editor after `dispose!`: reading its style throws." + [] + (let [editor #js {}] + (js/Object.defineProperty editor "currentStyle" + #js {:get (fn [] (throw (js/TypeError. "Cannot read properties of null (reading 'currentStyle')")))}) + editor)) + +;; Scenario: the sidebar renders once more with the editor it saw last, +;; after closing the text editor disposed it. Proves: the values come from +;; the shape instead of crashing the workspace. +(t/deftest current-values-of-a-disposed-v2-editor-come-from-the-shape + (let [shape (make-text-shape) + editor (disposed-v2-editor)] + (t/is (= (dwt/current-text-values {:shape shape :attrs txt/text-node-attrs}) + (dwt/current-text-values {:editor-instance editor + :shape shape + :attrs txt/text-node-attrs}))) + (t/is (= (dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs}) + (dwt/current-paragraph-values {:editor-instance editor + :shape shape + :attrs txt/paragraph-attrs}))))) + ;; --------------------------------------------------------------------------- ;; same-styled-text? ;; ---------------------------------------------------------------------------