mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🐛 Fix removeChild crash on all portal components
The previous fix (80b64c440c) only addressed portal-on-document* but there were 6 additional components that portaled directly to document.body, causing the same race condition when React attempted to remove a node that had already been detached during concurrent state updates (e.g. navigating away while a context menu is open). Apply the dedicated-container pattern consistently to all portal sites: modal, context menus, combobox dropdown, theme selector, and tooltip. Each component now creates a dedicated <div> container appended to body on mount and removed on cleanup, giving React an exclusive containerInfo for each portal instance. Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
parent
43cdb91063
commit
ff60503ce6
@ -159,6 +159,8 @@
|
||||
|
||||
tooltip-ref (mf/use-ref nil)
|
||||
|
||||
container (mf/use-memo #(dom/create-element "div"))
|
||||
|
||||
id
|
||||
(d/nilv id internal-id)
|
||||
|
||||
@ -244,6 +246,11 @@
|
||||
content
|
||||
aria-label)})]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [body (dom/get-body)]
|
||||
(dom/append-child! body container)
|
||||
#(dom/remove-child! body container)))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps tooltip-id)
|
||||
(fn []
|
||||
@ -295,4 +302,4 @@
|
||||
[:div {:class (stl/css :tooltip-content)} content]
|
||||
[:div {:class (stl/css :tooltip-arrow)
|
||||
:id "tooltip-arrow"}]]])
|
||||
(.-body js/document)))]))
|
||||
container))]))
|
||||
|
||||
@ -83,7 +83,12 @@
|
||||
(mf/defc modal-container*
|
||||
{::mf/props :obj}
|
||||
[]
|
||||
(when-let [modal (mf/deref ref:modal)]
|
||||
(mf/portal
|
||||
(mf/html [:> modal-wrapper* {:data modal :key (dm/str (:id modal))}])
|
||||
(dom/get-body))))
|
||||
(let [container (mf/use-memo #(dom/create-element "div"))]
|
||||
(mf/with-effect []
|
||||
(let [body (dom/get-body)]
|
||||
(dom/append-child! body container)
|
||||
#(dom/remove-child! body container)))
|
||||
(when-let [modal (mf/deref ref:modal)]
|
||||
(mf/portal
|
||||
(mf/html [:> modal-wrapper* {:data modal :key (dm/str (:id modal))}])
|
||||
container))))
|
||||
|
||||
@ -515,7 +515,13 @@
|
||||
dropdown-direction (deref dropdown-direction*)
|
||||
dropdown-direction-change* (mf/use-ref 0)
|
||||
top (+ (get-in mdata [:position :y]) 5)
|
||||
left (+ (get-in mdata [:position :x]) 5)]
|
||||
left (+ (get-in mdata [:position :x]) 5)
|
||||
container (mf/use-memo #(dom/create-element "div"))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [body (dom/get-body)]
|
||||
(dom/append-child! body container)
|
||||
#(dom/remove-child! body container)))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps is-open?)
|
||||
@ -554,4 +560,4 @@
|
||||
:on-context-menu prevent-default}
|
||||
(when mdata
|
||||
[:& token-context-menu-tree (assoc mdata :width @width :on-delete-token on-delete-token)])]])
|
||||
(dom/get-body)))))
|
||||
container))))
|
||||
|
||||
@ -92,6 +92,8 @@
|
||||
icon-button-ref (mf/use-ref nil)
|
||||
ref (or ref internal-ref)
|
||||
|
||||
container (mf/use-memo #(dom/create-element "div"))
|
||||
|
||||
raw-tokens-by-type (mf/use-ctx muc/active-tokens-by-type)
|
||||
|
||||
filtered-tokens-by-type
|
||||
@ -267,6 +269,11 @@
|
||||
(mf/with-effect [dropdown-options]
|
||||
(mf/set-ref-val! options-ref dropdown-options))
|
||||
|
||||
(mf/with-effect []
|
||||
(let [body (dom/get-body)]
|
||||
(dom/append-child! body container)
|
||||
#(dom/remove-child! body container)))
|
||||
|
||||
(mf/with-effect [is-open* ref wrapper-ref]
|
||||
(when is-open
|
||||
(let [handler (fn [event]
|
||||
@ -305,4 +312,4 @@
|
||||
:empty-to-end empty-to-end
|
||||
:wrapper-ref dropdown-ref
|
||||
:ref set-option-ref}])
|
||||
(dom/get-body))))]))
|
||||
container)))]))
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
dropdown-direction-change* (mf/use-ref 0)
|
||||
top (+ (get-in mdata [:position :y]) 5)
|
||||
left (+ (get-in mdata [:position :x]) 5)
|
||||
container (mf/use-memo #(dom/create-element "div"))
|
||||
|
||||
delete-node (mf/use-fn
|
||||
(mf/deps mdata)
|
||||
@ -44,6 +45,11 @@
|
||||
(when node
|
||||
(on-delete-node node type)))))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [body (dom/get-body)]
|
||||
(dom/append-child! body container)
|
||||
#(dom/remove-child! body container)))
|
||||
|
||||
(mf/with-effect [is-open?]
|
||||
(when (and (not= 0 (mf/ref-val dropdown-direction-change*)) (= false is-open?))
|
||||
(reset! dropdown-direction* "down")
|
||||
@ -80,4 +86,4 @@
|
||||
:type "button"
|
||||
:on-click delete-node}
|
||||
(tr "labels.delete")]]])]])
|
||||
(dom/get-body)))))
|
||||
container))))
|
||||
|
||||
@ -111,7 +111,14 @@
|
||||
(let [rect (dom/get-bounding-rect node)]
|
||||
(swap! state* assoc
|
||||
:is-open? true
|
||||
:rect rect))))))]
|
||||
:rect rect))))))
|
||||
|
||||
container (mf/use-memo #(dom/create-element "div"))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [body (dom/get-body)]
|
||||
(dom/append-child! body container)
|
||||
#(dom/remove-child! body container)))
|
||||
|
||||
[:div {:on-click on-open-dropdown
|
||||
:disabled (not can-edit?)
|
||||
@ -140,4 +147,4 @@
|
||||
[:& theme-options {:active-theme-paths active-theme-paths
|
||||
:themes themes
|
||||
:on-close on-close-dropdown}]]])
|
||||
(dom/get-body)))]))
|
||||
container))]))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user