From d63d692d345efd24e1c0d1c18b2e7c6b0ee472d0 Mon Sep 17 00:00:00 2001 From: Dalai Felinto Date: Sun, 30 Nov 2025 13:50:09 +0100 Subject: [PATCH] :bug: Fix mask issues with component swap #7675 The logic to swap a component would delete the swapped out component first before bringing in the new one. In the process of doing so, the sanitization code would unmask the group, now orphan of its mask shape component, when it was the first element of the group. The fix was to pass an optional argument to the generate-delete-shapes function to ignore mask in special cases like this. Signed-off-by: Dalai Felinto --- CHANGES.md | 2 ++ common/src/app/common/logic/libraries.cljc | 4 ++- common/src/app/common/logic/shapes.cljc | 32 ++++++++++++---------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 18f2224ed6..e694ae53cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ ### :heart: Community contributions (Thank you!) +- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675) + ### :sparkles: New features & Enhancements ### :bug: Bugs fixed diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 2844a6b1af..364099b37b 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -2439,11 +2439,13 @@ (ctk/get-swap-slot)) (constantly false)) + ;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the + ;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this [all-parents changes] (-> changes (cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) - {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn})) + {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true})) [new-shape changes] (-> changes (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc index d0958ccacd..6fa3030d6e 100644 --- a/common/src/app/common/logic/shapes.cljc +++ b/common/src/app/common/logic/shapes.cljc @@ -123,8 +123,10 @@ ;; ignore-children-fn is used to ignore some descendants ;; on the deletion process. It should receive a shape and ;; return a boolean - ignore-children-fn] - :or {ignore-children-fn (constantly false)}}] + ignore-children-fn + ignore-mask] + :or {ignore-children-fn (constantly false) + ignore-mask false}}] (let [objects (pcb/get-objects changes) data (pcb/get-library-data changes) page-id (pcb/get-page-id changes) @@ -162,18 +164,20 @@ lookup (d/getf objects) groups-to-unmask - (reduce (fn [group-ids id] - ;; When the shape to delete is the mask of a masked group, - ;; the mask condition must be removed, and it must be - ;; converted to a normal group. - (let [obj (lookup id) - parent (lookup (:parent-id obj))] - (if (and (:masked-group parent) - (= id (first (:shapes parent)))) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids-to-delete) + (when-not ignore-mask + (reduce (fn [group-ids id] + ;; When the shape to delete is the mask of a masked group, + ;; the mask condition must be removed, and it must be + ;; converted to a normal group. + (let [obj (lookup id) + parent (lookup (:parent-id obj))] + (if (and (:masked-group parent) + (= id (first (:shapes parent)))) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids-to-delete) + []) interacting-shapes (filter (fn [shape]