From 996ef6796c1d22cb5285e3631d82fbfb00ec3e9d 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 be559d20ad..adb2f8927a 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 a2a8da1b9a..64f28648d9 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -2441,11 +2441,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]