mirror of
https://github.com/penpot/penpot.git
synced 2026-03-30 00:00:45 +02:00
🐛 Fix repeateable keys triggering an infinite React loop in text editor v2
This commit is contained in:
@@ -55,6 +55,7 @@
|
||||
|
||||
(declare v2-update-text-shape-content)
|
||||
(declare v2-update-text-editor-styles)
|
||||
(declare v2-sync-wasm-text-layout)
|
||||
|
||||
;; -- Content helpers
|
||||
|
||||
@@ -907,10 +908,43 @@
|
||||
(ptk/reify ::v2-update-text-editor-styles
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; `stylechange` can fire on every `selectionchange` while typing.
|
||||
;; Avoid swapping the global store when the computed styles are unchanged,
|
||||
;; otherwise we can end up in store->rerender->selectionchange loops.
|
||||
(let [merged-styles (merge (txt/get-default-text-attrs)
|
||||
(get-in state [:workspace-global :default-font])
|
||||
new-styles)]
|
||||
(update-in state [:workspace-v2-editor-state id] (fnil merge {}) merged-styles)))))
|
||||
new-styles)
|
||||
prev (get-in state [:workspace-v2-editor-state id])]
|
||||
(if (= merged-styles prev)
|
||||
state
|
||||
(assoc-in state [:workspace-v2-editor-state id] merged-styles))))))
|
||||
|
||||
(defn v2-sync-wasm-text-layout
|
||||
"Live-sync WASM text layout from the DOM editor without writing shape :content.
|
||||
Intended to be called from Text Editor v2 `needslayout` events (coalesced)."
|
||||
[id content]
|
||||
(ptk/reify ::v2-sync-wasm-text-layout
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shape (get objects id)]
|
||||
(if-not (and (some? shape) (cfh/text-shape? shape))
|
||||
(rx/empty)
|
||||
(let [new-size (dwwt/get-wasm-text-new-size shape content)
|
||||
modifiers (when (and (some? new-size)
|
||||
(not= :fixed (:grow-type shape)))
|
||||
(dwwt/resize-wasm-text-modifiers shape content))]
|
||||
;; `get-wasm-text-new-size` has the side effect of syncing WASM's internal text
|
||||
;; content/layout. Only non-fixed grow-types need geometry modifiers updates.
|
||||
(if (some? modifiers)
|
||||
(rx/of (dwm/set-wasm-modifiers modifiers))
|
||||
(rx/empty))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
;; While typing, v2 only commits shape :content on debounced `change`.
|
||||
;; We still need to repaint the WASM canvas for live preview.
|
||||
(wasm.api/request-render "text-editor-v2-needslayout"))))
|
||||
|
||||
(defn v2-update-text-shape-position-data
|
||||
[shape-id position-data]
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
[app.util.object :as obj]
|
||||
[app.util.text.content :as content]
|
||||
[app.util.text.content.styles :as styles]
|
||||
[app.util.timers :as ts]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
@@ -48,6 +49,20 @@
|
||||
result (.-textContent editor-root)]
|
||||
(when (not= result "") result))))
|
||||
|
||||
(defn coalesce-per-tick
|
||||
"Return a function that runs `f` at most once per JS tick.
|
||||
Rationale: `needslayout` can fire on every input (including key repeat). We want
|
||||
to break nested store update loops."
|
||||
[f]
|
||||
(let [scheduled?* (atom false)]
|
||||
(fn []
|
||||
(when-not @scheduled?*
|
||||
(reset! scheduled?* true)
|
||||
(ts/asap
|
||||
(fn []
|
||||
(reset! scheduled?* false)
|
||||
(f)))))))
|
||||
|
||||
(defn- get-fonts
|
||||
[content]
|
||||
(let [extract-fn (juxt :font-id :font-variant-id)
|
||||
@@ -136,14 +151,16 @@
|
||||
(st/emit! (dwt/v2-update-text-editor-styles shape-id styles))))
|
||||
|
||||
on-needs-layout
|
||||
(fn []
|
||||
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
||||
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
||||
:update-name? true
|
||||
:save-undo? false)))
|
||||
;; FIXME: We need to find a better way to trigger layout changes.
|
||||
#_(st/emit!
|
||||
(dwt/v2-update-text-shape-position-data shape-id [])))
|
||||
(coalesce-per-tick
|
||||
(fn []
|
||||
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
||||
;; For WASM renderer, use the dedicated layout sync to avoid touching shape content
|
||||
;; during `needslayout` bursts. For non-wasm, keep existing behavior.
|
||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||
(st/emit! (dwt/v2-sync-wasm-text-layout shape-id content))
|
||||
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
||||
:update-name? true
|
||||
:save-undo? false))))))
|
||||
|
||||
on-change
|
||||
(fn []
|
||||
|
||||
Reference in New Issue
Block a user