mirror of
https://github.com/penpot/penpot.git
synced 2026-07-20 21:17:44 +00:00
🐛 Fix problems with padding multiple values in plugins and UI (#10602)
* 🐛 Fix problem with padding types in plugins * 🐛 Fix problem with multiple selection paddings
This commit is contained in:
parent
508569437a
commit
3ee5b50007
@ -347,6 +347,22 @@
|
||||
(+ pad-top pad-top)
|
||||
(+ pad-top pad-bottom))))
|
||||
|
||||
(defn padding-type-for
|
||||
"`:simple` when top≈bottom and left≈right, `:multiple` otherwise (nil sides = 0)."
|
||||
[{:keys [p1 p2 p3 p4]}]
|
||||
(if (and (mth/close? (d/nilv p1 0) (d/nilv p3 0))
|
||||
(mth/close? (d/nilv p2 0) (d/nilv p4 0)))
|
||||
:simple
|
||||
:multiple))
|
||||
|
||||
(defn margin-type-for
|
||||
"`:simple` when top≈bottom and left≈right, `:multiple` otherwise (nil sides = 0)."
|
||||
[{:keys [m1 m2 m3 m4]}]
|
||||
(if (and (mth/close? (d/nilv m1 0) (d/nilv m3 0))
|
||||
(mth/close? (d/nilv m2 0) (d/nilv m4 0)))
|
||||
:simple
|
||||
:multiple))
|
||||
|
||||
(defn child-min-width
|
||||
[child]
|
||||
(if (and (fill-width? child)
|
||||
|
||||
@ -1213,6 +1213,10 @@
|
||||
(and (= type :simple) (or (= prop :p2) (= prop #{:p2 :p4})))
|
||||
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}}))
|
||||
|
||||
(and (= type :multiple) (some? prop))
|
||||
(st/emit! (dwsl/update-layout ids {:layout-padding-type :multiple
|
||||
:layout-padding {prop val}}))
|
||||
|
||||
(some? prop)
|
||||
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape.attrs :refer [editable-attrs]]
|
||||
@ -208,6 +209,59 @@
|
||||
[v]
|
||||
(when v (select-keys v blur-keys)))
|
||||
|
||||
(def layout-padding-attrs [:p1 :p2 :p3 :p4])
|
||||
|
||||
(defn- normalize-layout-padding
|
||||
[value]
|
||||
(if (= value :multiple)
|
||||
(zipmap layout-padding-attrs (repeat :multiple))
|
||||
value))
|
||||
|
||||
(defn- merge-layout-padding
|
||||
[values shape-values]
|
||||
(let [current (normalize-layout-padding (get values :layout-padding ::unset))
|
||||
next (normalize-layout-padding (get shape-values :layout-padding ::unset))]
|
||||
(cond
|
||||
(= current ::unset) next
|
||||
(= next ::unset) current
|
||||
|
||||
(and (map? current) (map? next))
|
||||
(attrs/get-attrs-multi [current next] layout-padding-attrs)
|
||||
|
||||
(= current next)
|
||||
current
|
||||
|
||||
:else
|
||||
:multiple)))
|
||||
|
||||
(defn- same-padding-value?
|
||||
[v1 v2]
|
||||
(if (and (number? v1) (number? v2))
|
||||
(mth/close? v1 v2)
|
||||
(= v1 v2)))
|
||||
|
||||
(defn- simple-layout-padding?
|
||||
[{:keys [p1 p2 p3 p4]}]
|
||||
(and (same-padding-value? p1 p3)
|
||||
(same-padding-value? p2 p4)))
|
||||
|
||||
(defn- promote-simple-layout-padding-type
|
||||
[{:keys [layout-padding layout-padding-type] :as values}]
|
||||
(cond-> values
|
||||
(and (= layout-padding-type :simple)
|
||||
(map? layout-padding)
|
||||
(not (simple-layout-padding? layout-padding)))
|
||||
(assoc :layout-padding-type :multiple)))
|
||||
|
||||
(defn- merge-layout-container-attrs
|
||||
[values shape-values attrs]
|
||||
(let [merged-values (attrs/get-attrs-multi [values shape-values] attrs)
|
||||
merged-values (cond-> merged-values
|
||||
(or (contains? values :layout-padding)
|
||||
(contains? shape-values :layout-padding))
|
||||
(assoc :layout-padding (merge-layout-padding values shape-values)))]
|
||||
(promote-simple-layout-padding-type merged-values)))
|
||||
|
||||
(defn get-attrs*
|
||||
"Given a group of attributes that we want to extract and the shapes to extract them from
|
||||
returns a list of tuples [id, values] with the extracted properties for the shapes that
|
||||
@ -217,9 +271,10 @@
|
||||
merge-attrs
|
||||
(fn [v1 v2]
|
||||
(cond
|
||||
(= attr-group :shadow) (attrs/get-attrs-multi [v1 v2] attrs shadow-eq shadow-sel)
|
||||
(= attr-group :blur) (attrs/get-attrs-multi [v1 v2] attrs blur-eq blur-sel)
|
||||
:else (attrs/get-attrs-multi [v1 v2] attrs)))
|
||||
(= attr-group :shadow) (attrs/get-attrs-multi [v1 v2] attrs shadow-eq shadow-sel)
|
||||
(= attr-group :blur) (attrs/get-attrs-multi [v1 v2] attrs blur-eq blur-sel)
|
||||
(= attr-group :layout-container) (merge-layout-container-attrs v1 v2 attrs)
|
||||
:else (attrs/get-attrs-multi [v1 v2] attrs)))
|
||||
|
||||
merge-attr
|
||||
(fn [acc applied-tokens t-attr]
|
||||
|
||||
@ -20,6 +20,20 @@
|
||||
;; Define in `app.plugins.shape` we do this way to prevent circular dependency
|
||||
(def shape-proxy? nil)
|
||||
|
||||
(defn- update-padding
|
||||
"Patch the layout padding and re-derive its `:simple`/`:multiple` type."
|
||||
[this id patch]
|
||||
(let [padding (merge (-> this u/proxy->shape :layout-padding) patch)]
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding patch
|
||||
:layout-padding-type (ctl/padding-type-for padding)}))))
|
||||
|
||||
(defn- update-margin
|
||||
"Patch the item margin and re-derive its `:simple`/`:multiple` type."
|
||||
[this id patch]
|
||||
(let [margin (merge (-> this u/proxy->shape :layout-item-margin) patch)]
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin patch
|
||||
:layout-item-margin-type (ctl/margin-type-for margin)}))))
|
||||
|
||||
(defn flex-layout-proxy? [p]
|
||||
(obj/type-of? p "FlexLayoutProxy"))
|
||||
|
||||
@ -185,7 +199,7 @@
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :verticalPadding value)
|
||||
@ -197,13 +211,13 @@
|
||||
(u/not-valid plugin-id :verticalPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}}))))}
|
||||
(update-padding this id {:p1 value :p3 value})))}
|
||||
|
||||
:horizontalPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :horizontalPadding value)
|
||||
@ -215,13 +229,13 @@
|
||||
(u/not-valid plugin-id :horizontalPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}}))))}
|
||||
(update-padding this id {:p2 value :p4 value})))}
|
||||
|
||||
:topPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :topPadding value)
|
||||
@ -233,13 +247,13 @@
|
||||
(u/not-valid plugin-id :topPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}}))))}
|
||||
(update-padding this id {:p1 value})))}
|
||||
|
||||
:rightPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rightPadding value)
|
||||
@ -251,13 +265,13 @@
|
||||
(u/not-valid plugin-id :rightPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}}))))}
|
||||
(update-padding this id {:p2 value})))}
|
||||
|
||||
:bottomPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :bottomPadding value)
|
||||
@ -269,13 +283,13 @@
|
||||
(u/not-valid plugin-id :bottomPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}}))))}
|
||||
(update-padding this id {:p3 value})))}
|
||||
|
||||
:leftPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :leftPadding value)
|
||||
@ -287,7 +301,27 @@
|
||||
(u/not-valid plugin-id :leftPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}}))))}
|
||||
(update-padding this id {:p4 value})))}
|
||||
|
||||
;; `:simple` mirrors vertical/horizontal padding; `:multiple` honours each side.
|
||||
:paddingType
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding-type (d/nilv :simple) d/name)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [value (keyword value)]
|
||||
(cond
|
||||
(not (contains? ctl/padding-type value))
|
||||
(u/not-valid plugin-id :paddingType value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :paddingType "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not (u/page-active? page-id))
|
||||
(u/not-valid plugin-id :paddingType "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding-type value})))))}
|
||||
|
||||
:remove
|
||||
(fn []
|
||||
@ -469,7 +503,7 @@
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :verticalMargin value)
|
||||
@ -481,13 +515,13 @@
|
||||
(u/not-valid plugin-id :verticalMargin "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value :m3 value}}))))}
|
||||
(update-margin this id {:m1 value :m3 value})))}
|
||||
|
||||
:horizontalMargin
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :horizontalMargin value)
|
||||
@ -499,13 +533,13 @@
|
||||
(u/not-valid plugin-id :horizontalMargin "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value :m4 value}}))))}
|
||||
(update-margin this id {:m2 value :m4 value})))}
|
||||
|
||||
:topMargin
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :topMargin value)
|
||||
@ -517,13 +551,13 @@
|
||||
(u/not-valid plugin-id :topMargin "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value}}))))}
|
||||
(update-margin this id {:m1 value})))}
|
||||
|
||||
:rightMargin
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rightMargin value)
|
||||
@ -535,13 +569,13 @@
|
||||
(u/not-valid plugin-id :rightMargin "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value}}))))}
|
||||
(update-margin this id {:m2 value})))}
|
||||
|
||||
:bottomMargin
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin :m3 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :bottomMargin value)
|
||||
@ -553,13 +587,13 @@
|
||||
(u/not-valid plugin-id :bottomMargin "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m3 value}}))))}
|
||||
(update-margin this id {:m3 value})))}
|
||||
|
||||
:leftMargin
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin :m4 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :leftMargin value)
|
||||
@ -571,7 +605,27 @@
|
||||
(u/not-valid plugin-id :leftMargin "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m4 value}}))))}
|
||||
(update-margin this id {:m4 value})))}
|
||||
|
||||
;; `:simple` mirrors vertical/horizontal margin; `:multiple` honours each side.
|
||||
:marginType
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-item-margin-type (d/nilv :simple) d/name)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [value (keyword value)]
|
||||
(cond
|
||||
(not (contains? ctl/item-margin-types value))
|
||||
(u/not-valid plugin-id :marginType value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :marginType "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not (u/page-active? page-id))
|
||||
(u/not-valid plugin-id :marginType "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin-type value})))))}
|
||||
|
||||
:maxWidth
|
||||
{:this true
|
||||
|
||||
@ -21,6 +21,13 @@
|
||||
;; Define in `app.plugins.shape` we do this way to prevent circular dependency
|
||||
(def shape-proxy? nil)
|
||||
|
||||
(defn- update-padding
|
||||
"Patch the layout padding and re-derive its `:simple`/`:multiple` type."
|
||||
[this id patch]
|
||||
(let [padding (merge (-> this u/proxy->shape :layout-padding) patch)]
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding patch
|
||||
:layout-padding-type (ctl/padding-type-for padding)}))))
|
||||
|
||||
(defn grid-layout-proxy? [p]
|
||||
(obj/type-of? p "GridLayoutProxy"))
|
||||
|
||||
@ -217,7 +224,7 @@
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :verticalPadding value)
|
||||
@ -229,13 +236,13 @@
|
||||
(u/not-valid plugin-id :verticalPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}}))))}
|
||||
(update-padding this id {:p1 value :p3 value})))}
|
||||
|
||||
:horizontalPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :horizontalPadding value)
|
||||
@ -247,13 +254,13 @@
|
||||
(u/not-valid plugin-id :horizontalPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}}))))}
|
||||
(update-padding this id {:p2 value :p4 value})))}
|
||||
|
||||
:topPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :topPadding value)
|
||||
@ -265,13 +272,13 @@
|
||||
(u/not-valid plugin-id :topPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}}))))}
|
||||
(update-padding this id {:p1 value})))}
|
||||
|
||||
:rightPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :rightPadding value)
|
||||
@ -283,13 +290,13 @@
|
||||
(u/not-valid plugin-id :rightPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}}))))}
|
||||
(update-padding this id {:p2 value})))}
|
||||
|
||||
:bottomPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :bottomPadding value)
|
||||
@ -301,13 +308,13 @@
|
||||
(u/not-valid plugin-id :bottomPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}}))))}
|
||||
(update-padding this id {:p3 value})))}
|
||||
|
||||
:leftPadding
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(fn [this value]
|
||||
(cond
|
||||
(not (sm/valid-safe-number? value))
|
||||
(u/not-valid plugin-id :leftPadding value)
|
||||
@ -319,7 +326,27 @@
|
||||
(u/not-valid plugin-id :leftPadding "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}}))))}
|
||||
(update-padding this id {:p4 value})))}
|
||||
|
||||
;; `:simple` mirrors vertical/horizontal padding; `:multiple` honours each side.
|
||||
:paddingType
|
||||
{:this true
|
||||
:get #(-> % u/proxy->shape :layout-padding-type (d/nilv :simple) d/name)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [value (keyword value)]
|
||||
(cond
|
||||
(not (contains? ctl/padding-type value))
|
||||
(u/not-valid plugin-id :paddingType value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :paddingType "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not (u/page-active? page-id))
|
||||
(u/not-valid plugin-id :paddingType "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding-type value})))))}
|
||||
|
||||
:addRow
|
||||
(fn [type value]
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
[frontend-tests.ui.comments-clustering-test]
|
||||
[frontend-tests.ui.comments-position-modifier-test]
|
||||
[frontend-tests.ui.ds-controls-numeric-input-test]
|
||||
[frontend-tests.ui.layout-container-multiple-test]
|
||||
[frontend-tests.ui.measures-menu-props-test]
|
||||
[frontend-tests.util-object-test]
|
||||
[frontend-tests.util-range-tree-test]
|
||||
@ -128,6 +129,7 @@
|
||||
'frontend-tests.ui.comments-clustering-test
|
||||
'frontend-tests.ui.comments-position-modifier-test
|
||||
'frontend-tests.ui.ds-controls-numeric-input-test
|
||||
'frontend-tests.ui.layout-container-multiple-test
|
||||
'frontend-tests.ui.measures-menu-props-test
|
||||
'frontend-tests.util-object-test
|
||||
'frontend-tests.util-range-tree-test
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns frontend-tests.ui.layout-container-multiple-test
|
||||
(:require
|
||||
[app.main.ui.workspace.sidebar.options.shapes.multiple :as multiple]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(defn- flex-frame
|
||||
[id padding-type padding]
|
||||
{:id id
|
||||
:type :frame
|
||||
:layout :flex
|
||||
:layout-flex-dir :row
|
||||
:layout-gap-type :multiple
|
||||
:layout-gap {:row-gap 0 :column-gap 0}
|
||||
:layout-align-items :start
|
||||
:layout-justify-content :start
|
||||
:layout-align-content :stretch
|
||||
:layout-wrap-type :nowrap
|
||||
:layout-padding-type padding-type
|
||||
:layout-padding padding})
|
||||
|
||||
(defn- layout-container-values
|
||||
[shapes]
|
||||
(let [[_ids values _tokens] (multiple/get-attrs* shapes {} :layout-container)]
|
||||
values))
|
||||
|
||||
(t/deftest multiple-selection-padding-keeps-matching-sides
|
||||
(let [values (layout-container-values
|
||||
[(flex-frame :frame-1 :simple {:p1 10 :p2 20 :p3 10 :p4 20})
|
||||
(flex-frame :frame-2 :simple {:p1 10 :p2 20 :p3 30 :p4 40})])]
|
||||
(t/is (= {:p1 10 :p2 20 :p3 :multiple :p4 :multiple}
|
||||
(:layout-padding values)))
|
||||
(t/is (= :multiple (:layout-padding-type values)))))
|
||||
|
||||
(t/deftest multiple-selection-padding-type-does-not-demote
|
||||
(let [values (layout-container-values
|
||||
[(flex-frame :frame-1 :multiple {:p1 10 :p2 20 :p3 10 :p4 20})
|
||||
(flex-frame :frame-2 :multiple {:p1 10 :p2 20 :p3 10 :p4 20})])]
|
||||
(t/is (= {:p1 10 :p2 20 :p3 10 :p4 20}
|
||||
(:layout-padding values)))
|
||||
(t/is (= :multiple (:layout-padding-type values)))))
|
||||
@ -1,4 +1,14 @@
|
||||
## 1.5.0 (Unreleased)
|
||||
## 1.6.0 (Unreleased)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **plugin-types:** Added `paddingType` (`'simple' | 'multiple'`) to flex and grid layouts and `marginType` (`'simple' | 'multiple'`) to layout children, exposing whether the four padding/margin sides are mirrored or honoured independently.
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **plugins-runtime**: Setting an individual padding/margin side (`leftPadding`, `topMargin`, …) now re-derives the padding/margin type, switching to `multiple` when the four sides stop being symmetric (so the value is actually painted) and back to `simple` once top/bottom and left/right are mirrored again.
|
||||
|
||||
## 1.5.0 (2026-07-08)
|
||||
|
||||
### 💣 Breaking changes & Deprecations
|
||||
|
||||
|
||||
@ -66,6 +66,7 @@
|
||||
"justifyContent",
|
||||
"justifyItems",
|
||||
"leftPadding",
|
||||
"paddingType",
|
||||
"remove",
|
||||
"rightPadding",
|
||||
"rowGap",
|
||||
@ -254,6 +255,7 @@
|
||||
"horizontalMargin",
|
||||
"horizontalSizing",
|
||||
"leftMargin",
|
||||
"marginType",
|
||||
"maxHeight",
|
||||
"maxWidth",
|
||||
"minHeight",
|
||||
@ -2032,6 +2034,12 @@
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"paddingType": {
|
||||
"decl": "CommonLayout",
|
||||
"kind": "getset",
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"horizontalSizing": {
|
||||
"decl": "CommonLayout",
|
||||
"kind": "getset",
|
||||
@ -3242,6 +3250,12 @@
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"paddingType": {
|
||||
"decl": "CommonLayout",
|
||||
"kind": "getset",
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"horizontalSizing": {
|
||||
"decl": "CommonLayout",
|
||||
"kind": "getset",
|
||||
@ -3590,6 +3604,12 @@
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"paddingType": {
|
||||
"decl": "CommonLayout",
|
||||
"kind": "getset",
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"horizontalSizing": {
|
||||
"decl": "CommonLayout",
|
||||
"kind": "getset",
|
||||
@ -4868,6 +4888,12 @@
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"marginType": {
|
||||
"decl": "LayoutChildProperties",
|
||||
"kind": "getset",
|
||||
"type": null,
|
||||
"array": false
|
||||
},
|
||||
"maxWidth": {
|
||||
"decl": "LayoutChildProperties",
|
||||
"kind": "getset",
|
||||
|
||||
@ -82,6 +82,34 @@ describe('Layout', () => {
|
||||
expect(flex.leftPadding).toBeCloseTo(4.5, 2);
|
||||
});
|
||||
|
||||
// paddingType is "simple" (sides mirrored) or "multiple" (each side independent).
|
||||
test('paddingType round-trips', (ctx) => {
|
||||
const flex = board(ctx).addFlexLayout();
|
||||
expect(flex.paddingType).toBe('simple');
|
||||
flex.paddingType = 'multiple';
|
||||
expect(flex.paddingType).toBe('multiple');
|
||||
flex.paddingType = 'simple';
|
||||
expect(flex.paddingType).toBe('simple');
|
||||
});
|
||||
|
||||
// Issue #10278: a single asymmetric side must switch paddingType to "multiple" so it paints.
|
||||
test('setting an individual padding side switches paddingType to multiple', (ctx) => {
|
||||
const flex = board(ctx).addFlexLayout();
|
||||
expect(flex.paddingType).toBe('simple');
|
||||
flex.leftPadding = 40;
|
||||
expect(flex.leftPadding).toBeCloseTo(40, 0);
|
||||
expect(flex.paddingType).toBe('multiple');
|
||||
});
|
||||
|
||||
// Re-derived from the sides: once symmetric again, the type collapses to "simple".
|
||||
test('padding collapsing back to symmetric restores simple type', (ctx) => {
|
||||
const flex = board(ctx).addFlexLayout();
|
||||
flex.leftPadding = 40;
|
||||
expect(flex.paddingType).toBe('multiple');
|
||||
flex.leftPadding = 0;
|
||||
expect(flex.paddingType).toBe('simple');
|
||||
});
|
||||
|
||||
test('sizing round-trips', (ctx) => {
|
||||
const flex = board(ctx).addFlexLayout();
|
||||
flex.horizontalSizing = 'fix';
|
||||
@ -210,6 +238,22 @@ describe('Layout', () => {
|
||||
expect(grid.leftPadding).toBeCloseTo(4.5, 2);
|
||||
});
|
||||
|
||||
// paddingType behaves the same as on flex layouts (see issue #10278).
|
||||
test('paddingType round-trips', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
expect(grid.paddingType).toBe('simple');
|
||||
grid.paddingType = 'multiple';
|
||||
expect(grid.paddingType).toBe('multiple');
|
||||
});
|
||||
|
||||
test('setting an individual padding side switches paddingType to multiple', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
expect(grid.paddingType).toBe('simple');
|
||||
grid.leftPadding = 40;
|
||||
expect(grid.leftPadding).toBeCloseTo(40, 0);
|
||||
expect(grid.paddingType).toBe('multiple');
|
||||
});
|
||||
|
||||
// Index boundaries — invalid indices must be rejected.
|
||||
test('addRowAtIndex with a negative index throws', (ctx) => {
|
||||
const grid = board(ctx).addGridLayout();
|
||||
@ -389,6 +433,55 @@ describe('Layout', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// marginType is the child-margin counterpart of a layout's paddingType.
|
||||
test('marginType round-trips', (ctx) => {
|
||||
const b = board(ctx);
|
||||
const flex = b.addFlexLayout();
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
flex.appendChild(rect);
|
||||
const child = rect.layoutChild;
|
||||
expect(child).toBeDefined();
|
||||
if (child) {
|
||||
expect(child.marginType).toBe('simple');
|
||||
child.marginType = 'multiple';
|
||||
expect(child.marginType).toBe('multiple');
|
||||
child.marginType = 'simple';
|
||||
expect(child.marginType).toBe('simple');
|
||||
}
|
||||
});
|
||||
|
||||
// Issue #10278 (margins): a single asymmetric side must switch marginType to "multiple".
|
||||
test('setting an individual margin side switches marginType to multiple', (ctx) => {
|
||||
const b = board(ctx);
|
||||
const flex = b.addFlexLayout();
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
flex.appendChild(rect);
|
||||
const child = rect.layoutChild;
|
||||
expect(child).toBeDefined();
|
||||
if (child) {
|
||||
expect(child.marginType).toBe('simple');
|
||||
child.leftMargin = 12;
|
||||
expect(child.leftMargin).toBeCloseTo(12, 0);
|
||||
expect(child.marginType).toBe('multiple');
|
||||
}
|
||||
});
|
||||
|
||||
// Symmetric margins collapse the child back to "simple", mirroring padding.
|
||||
test('margin collapsing back to symmetric restores simple type', (ctx) => {
|
||||
const b = board(ctx);
|
||||
const flex = b.addFlexLayout();
|
||||
const rect = ctx.penpot.createRectangle();
|
||||
flex.appendChild(rect);
|
||||
const child = rect.layoutChild;
|
||||
expect(child).toBeDefined();
|
||||
if (child) {
|
||||
child.leftMargin = 12;
|
||||
expect(child.marginType).toBe('multiple');
|
||||
child.leftMargin = 0;
|
||||
expect(child.marginType).toBe('simple');
|
||||
}
|
||||
});
|
||||
|
||||
// Community report (forum #10700, issue #12): layoutChild was said to be
|
||||
// null for children appended to a flex board that is re-found (fresh
|
||||
// proxy) instead of using the creation-time reference. Did not reproduce;
|
||||
|
||||
15
plugins/libs/plugin-types/index.d.ts
vendored
15
plugins/libs/plugin-types/index.d.ts
vendored
@ -747,6 +747,13 @@ export interface CommonLayout {
|
||||
* The `leftPadding` property specifies the padding at the left of the container.
|
||||
*/
|
||||
leftPadding: number;
|
||||
/**
|
||||
* The `paddingType` property specifies how the four padding values are applied.
|
||||
* It can be one of the following values:
|
||||
* - 'simple': the vertical and horizontal paddings are mirrored across both sides.
|
||||
* - 'multiple': each of the four sides (top, right, bottom, left) is honoured independently.
|
||||
*/
|
||||
paddingType: 'simple' | 'multiple';
|
||||
|
||||
/**
|
||||
* The `horizontalSizing` property specifies the horizontal sizing behavior of the container.
|
||||
@ -2576,6 +2583,14 @@ export interface LayoutChildProperties {
|
||||
*/
|
||||
leftMargin: number;
|
||||
|
||||
/**
|
||||
* The `marginType` property specifies how the four margin values are applied.
|
||||
* It can be one of the following values:
|
||||
* - 'simple': the vertical and horizontal margins are mirrored across both sides.
|
||||
* - 'multiple': each of the four sides (top, right, bottom, left) is honoured independently.
|
||||
*/
|
||||
marginType: 'simple' | 'multiple';
|
||||
|
||||
/**
|
||||
* Defines the maximum width of the child element.
|
||||
* If set to null, there is no maximum width constraint.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user