diff --git a/CHANGES.md b/CHANGES.md index ee8f56c3a..8c92a234c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ - Removed sizing variables from radius (by @ondrejkonec) [Github #3184](https://github.com/penpot/penpot/pull/3184) - Dashboard search, set focus after shortcut (by @akshay-gupta7) [Github #3196](https://github.com/penpot/penpot/pull/3196) - Library name dropdown arrow is overlapped by library name (by @ondrejkonec) [Taiga #5200](https://tree.taiga.io/project/penpot/issue/5200) +- Reorder shadows (by @akshay-gupta7) [Github #3236](https://github.com/penpot/penpot/pull/3236) ## 1.18.4 diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 45196fc41..2a662526f 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -786,7 +786,8 @@ } } -.grid-option { +.grid-option, +.shadow-option { margin-bottom: 0.5rem; .advanced-options { .row-flex { @@ -797,6 +798,15 @@ position: absolute; top: 12px; } + .element-set-actions-button { + min-width: auto; + min-height: auto; + padding-right: 10px; + svg { + width: 12px; + height: 12px; + } + } } } @@ -807,7 +817,8 @@ margin-left: 0.25rem; } -.element-set-content .grid-option-main { +.element-set-content .grid-option-main, +.element-set-content .shadow-option-main { align-items: center; display: flex; padding: 0.3rem 0; @@ -857,11 +868,13 @@ } } -.grid-option-main-actions { +.grid-option-main-actions, +.shadow-option-main-actions { display: flex; visibility: hidden; - .grid-option:hover & { + .grid-option:hover &, + .shadow-option:hover & { visibility: visible; } } diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 04abaad50..53471eb08 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] + [app.common.schema :as sm] [app.main.broadcast :as mbc] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] @@ -200,7 +201,6 @@ (if (= (:type shape) :frame) (d/merge shape attrs) shape)))))))) - (defn change-stroke [ids attrs index] (ptk/reify ::change-stroke @@ -224,70 +224,93 @@ attrs (merge attrs color-attrs)] - (rx/of (dch/update-shapes ids (fn [shape] - (let [new-attrs (merge (get-in shape [:strokes index]) attrs) - new-attrs (cond-> new-attrs - (not (contains? new-attrs :stroke-width)) - (assoc :stroke-width 1) + (rx/of (dch/update-shapes + ids + (fn [shape] + (let [new-attrs (merge (get-in shape [:strokes index]) attrs) + new-attrs (cond-> new-attrs + (not (contains? new-attrs :stroke-width)) + (assoc :stroke-width 1) - (not (contains? new-attrs :stroke-style)) - (assoc :stroke-style :solid) + (not (contains? new-attrs :stroke-style)) + (assoc :stroke-style :solid) - (not (contains? new-attrs :stroke-alignment)) - (assoc :stroke-alignment :center) + (not (contains? new-attrs :stroke-alignment)) + (assoc :stroke-alignment :center) - :always - (d/without-nils))] - (-> shape - (cond-> (not (contains? shape :strokes)) - (assoc :strokes [])) - (assoc-in [:strokes index] new-attrs)))))))))) + :always + (d/without-nils))] + (cond-> shape + (not (contains? shape :strokes)) + (assoc :strokes []) + + :always + (assoc-in [:strokes index] new-attrs)))))))))) (defn change-shadow [ids attrs index] (ptk/reify ::change-shadow ptk/WatchEvent (watch [_ _ _] - (rx/of (dch/update-shapes ids (fn [shape] - (let [;; If we try to set a gradient to a shadow (for example using the color selection from multiple shapes) let's use the first stop color - attrs (cond-> attrs - (:gradient attrs) (get-in [:gradient :stops 0])) - new-attrs (merge (get-in shape [:shadow index :color]) attrs)] - (assoc-in shape [:shadow index :color] new-attrs)))))))) + (rx/of (dch/update-shapes + ids + (fn [shape] + (let [;; If we try to set a gradient to a shadow (for + ;; example using the color selection from + ;; multiple shapes) let's use the first stop + ;; color + attrs (cond-> attrs + (:gradient attrs) (get-in [:gradient :stops 0])) + new-attrs (merge (get-in shape [:shadow index :color]) attrs)] + (assoc-in shape [:shadow index :color] new-attrs)))))))) + +(defn add-shadow + [ids shadow] + (dm/assert! (sm/coll-of-uuid? ids)) + (ptk/reify ::add-shadow + ptk/WatchEvent + (watch [_ _ _] + (let [add-shadow (fn [shape] + (update shape :shadow #(into [shadow] %)))] + (rx/of (dch/update-shapes ids add-shadow)))))) (defn add-stroke [ids stroke] (ptk/reify ::add-stroke ptk/WatchEvent (watch [_ _ _] - (let [add (fn [shape attrs] (assoc shape :strokes (into [attrs] (:strokes shape))))] - (rx/of (dch/update-shapes - ids - #(add % stroke))))))) + (let [add-stroke (fn [shape] (update shape :strokes #(into [stroke] %)))] + (rx/of (dch/update-shapes ids add-stroke)))))) (defn remove-stroke [ids position] (ptk/reify ::remove-stroke ptk/WatchEvent (watch [_ _ _] - (let [remove-fill-by-index (fn [values index] (->> (d/enumerate values) - (filterv (fn [[idx _]] (not= idx index))) - (mapv second))) - - remove (fn [shape] (update shape :strokes remove-fill-by-index position))] - (rx/of (dch/update-shapes - ids - #(remove %))))))) + (letfn [(remove-fill-by-index [values index] + (->> (d/enumerate values) + (filterv (fn [[idx _]] (not= idx index))) + (mapv second))) + (remove-stroke [shape] + (update shape :strokes remove-fill-by-index position))] + (rx/of (dch/update-shapes ids remove-stroke)))))) (defn remove-all-strokes [ids] (ptk/reify ::remove-all-strokes ptk/WatchEvent (watch [_ _ _] - (let [remove-all (fn [shape] (assoc shape :strokes []))] - (rx/of (dch/update-shapes - ids - #(remove-all %))))))) + (let [remove-all #(assoc % :strokes [])] + (rx/of (dch/update-shapes ids remove-all)))))) + +(defn reorder-shadows + [ids index new-index] + (ptk/reify ::reorder-shadow + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dch/update-shapes + ids + #(swap-attrs % :shadow index new-index)))))) (defn reorder-strokes [ids index new-index] diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 9b9034979..4218a6b37 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -42,7 +42,7 @@ ;; We need to store the handle-blur ref so we can call it on unmount handle-blur-ref (mf/use-ref nil) - dirty-ref (mf/use-ref false) + dirty-ref (mf/use-ref false) ;; This `value` represents the previous value and is used as ;; initil value for the simple math expression evaluation. @@ -106,10 +106,11 @@ apply-value (mf/use-callback (mf/deps on-change update-input value) - (fn [new-value] + (fn [new-value event] (mf/set-ref-val! dirty-ref false) - (when (and (not= new-value value) (some? on-change)) - (on-change new-value)) + (when (and (not= new-value value) + (fn? on-change)) + (on-change new-value event)) (update-input new-value))) set-delta @@ -146,7 +147,7 @@ :else new-value)] - (apply-value new-value)))))) + (apply-value new-value event)))))) handle-key-down (mf/use-callback @@ -180,12 +181,12 @@ handle-blur (mf/use-callback (mf/deps parse-value apply-value update-input on-blur) - (fn [_] + (fn [event] (let [new-value (or (parse-value) default-val)] (if (or nillable new-value) - (apply-value new-value) + (apply-value new-value event) (update-input new-value))) - (when on-blur (on-blur)))) + (when on-blur (on-blur event)))) on-click (mf/use-callback @@ -236,7 +237,7 @@ (events/listen globals/window EventType.CLICK on-click)]] #(doseq [key keys] (events/unlistenByKey key))))) - + (mf/use-layout-effect (mf/deps handle-mouse-wheel) (fn [] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index b518d9763..83cc9a414 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -8,74 +8,108 @@ (:require [app.common.colors :as clr] [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.math :as mth] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.colors :as dc] [app.main.data.workspace.undo :as dwu] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] + [okulary.core :as l] [rumext.v2 :as mf])) (def shadow-attrs [:shadow]) -(defn create-shadow [] - (let [id (uuid/next)] - {:id id - :style :drop-shadow - :color {:color clr/black :opacity 0.2} - :offset-x 4 - :offset-y 4 - :blur 4 - :spread 0 - :hidden false})) +(defn- create-shadow + [] + {:id (uuid/next) + :style :drop-shadow + :color {:color clr/black + :opacity 0.2} + :offset-x 4 + :offset-y 4 + :blur 4 + :spread 0 + :hidden false}) -(defn valid-number? [value] - (or (number? value) (not (js/isNaN (js/parseInt value))))) +(defn- remove-shadow-by-index + [values index] + (->> (d/enumerate values) + (filterv (fn [[idx _]] (not= idx index))) + (mapv second))) (mf/defc shadow-entry - [{:keys [ids index value]}] - (let [open-shadow (mf/use-state false) - - basic-offset-x-ref (mf/use-ref nil) + [{:keys [ids index value on-reorder disable-drag? on-blur open-state-ref]}] + (let [basic-offset-x-ref (mf/use-ref nil) basic-offset-y-ref (mf/use-ref nil) - basic-blur-ref (mf/use-ref nil) + basic-blur-ref (mf/use-ref nil) - adv-offset-x-ref (mf/use-ref nil) - adv-offset-y-ref (mf/use-ref nil) - adv-blur-ref (mf/use-ref nil) - adv-spread-ref (mf/use-ref nil) + adv-offset-x-ref (mf/use-ref nil) + adv-offset-y-ref (mf/use-ref nil) + adv-blur-ref (mf/use-ref nil) + adv-spread-ref (mf/use-ref nil) - shadow-style (str (:style value)) + shadow-style (dm/str (:style value)) + shadow-id (:id value) - remove-shadow-by-index - (fn [values index] (->> (d/enumerate values) - (filterv (fn [[idx _]] (not= idx index))) - (mapv second))) + open-status-ref (mf/with-memo [open-state-ref shadow-id] + (-> (l/key shadow-id) + (l/derived open-state-ref))) + open-shadow (mf/deref open-status-ref) on-remove-shadow - (fn [index] - (fn [] - (st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index))))) + (mf/use-fn + (mf/deps ids) + (fn [event] + (let [index (-> (dom/get-current-target event) + (dom/get-data "index") + (parse-long))] + (st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index)))))) - select-text - (fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref)))) + on-drop + (mf/use-fn + (mf/deps on-reorder index) + (fn [_ data] + (on-reorder index (:index data)))) + [dprops dref] + (h/use-sortable + :data-type "penpot/shadow-entry" + :on-drop on-drop + :disabled disable-drag? + :detect-center? false + :data {:id (dm/str "shadow-" index) + :index index + :name (dm/str "Border row" index)}) + + ;; FIXME: this function causes the numeric-input rerender + ;; ALWAYS, this is causes because numeric-input design makes + ;; imposible implement efficiently any component that uses it; + ;; it should be refactored update-attr (fn update-attr - ([index attr valid?] - (update-attr index attr valid? nil)) - - ([index attr valid? update-ref] + ([index attr] + (update-attr index attr nil)) + ([index attr update-ref] (fn [value] - (when (or (not valid?) (valid? value)) - (do (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value))) - (let [update-node (and update-ref (mf/ref-val update-ref))] - (when update-node - (dom/set-value! update-node value)))))))) + (when (mth/finite? value) + (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value))) + (when-let [update-node (and update-ref (mf/ref-val update-ref))] + (dom/set-value! update-node value)))))) + + ;; FIXME: the same as previous function, imposible to + ;; implement efficiently because of numeric-input component + ;; and probably this affects all callbacks that that component + ;; receives + select-text + (fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref)))) update-color (fn [index] @@ -96,113 +130,147 @@ toggle-visibility (fn [index] (fn [] - (st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))] + (st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not))))) + + on-toggle-open-shadow + (fn [] + (swap! open-state-ref update shadow-id not))] [:* - [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} - [:div.element-set-actions-button - {:on-click #(reset! open-shadow true)} - i/actions] - - ;; [:> numeric-input {:ref basic-offset-x-ref - ;; :on-change (update-attr index :offset-x valid-number?) - ;; :on-click (select-text basic-offset-x-ref) - ;; :value (:offset-x value)}] - ;; [:> numeric-input {:ref basic-offset-y-ref - ;; :on-change (update-attr index :offset-y valid-number?) - ;; :on-click (select-text basic-offset-y-ref) - ;; :value (:offset-y value)}] - ;; [:> numeric-input {:ref basic-blur-ref - ;; :on-click (select-text basic-blur-ref) - ;; :on-change (update-attr index :blur valid-number?) - ;; :min 0 - ;; :value (:blur value)}] - - [:select.input-select - {:default-value shadow-style - :on-change (fn [event] - (let [value (-> event dom/get-target dom/get-value d/read-string)] - (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} - [:option {:value ":drop-shadow" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")] - [:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]] - - [:div.element-set-actions - [:div.element-set-actions-button {:on-click (toggle-visibility index)} - (if (:hidden value) i/eye-closed i/eye)] - [:div.element-set-actions-button {:on-click (on-remove-shadow index)} - i/minus]]] - - [:& advanced-options {:visible? @open-shadow - :on-close #(reset! open-shadow false)} - [:div.color-data + [:div.shadow-option {:class (dom/classnames + :dnd-over-top (= (:over dprops) :top) + :dnd-over-bot (= (:over dprops) :bot)) + :ref dref} + [:div.shadow-option-main {:style {:display (when open-shadow "none")}} [:div.element-set-actions-button - {:on-click #(reset! open-shadow false)} + {:on-click on-toggle-open-shadow} i/actions] + [:select.input-select - {:default-value (str (:style value)) + {:default-value shadow-style :on-change (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} - [:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")] - [:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]]] + [:option {:value ":drop-shadow" + :selected (when (= shadow-style ":drop-shadow") "selected")} + (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow" + :selected (when (= shadow-style ":inner-shadow") "selected")} + (tr "workspace.options.shadow-options.inner-shadow")]] - [:div.row-grid-2 - [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} - [:> numeric-input {:ref adv-offset-x-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-offset-x-ref) - :on-change (update-attr index :offset-x valid-number? basic-offset-x-ref) - :value (:offset-x value)}] - [:span.after (tr "workspace.options.shadow-options.offsetx")]] + [:div.shadow-option-main-actions + [:div.element-set-actions-button {:on-click (toggle-visibility index)} + (if (:hidden value) i/eye-closed i/eye)] + [:div.element-set-actions-button + {:data-index index + :on-click on-remove-shadow} + i/minus]]] - [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} - [:> numeric-input {:ref adv-offset-y-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-offset-y-ref) - :on-change (update-attr index :offset-y valid-number? basic-offset-y-ref) - :value (:offset-y value)}] - [:span.after (tr "workspace.options.shadow-options.offsety")]]] + [:& advanced-options {:visible? open-shadow + :on-close on-toggle-open-shadow} + [:div.color-data + [:div.element-set-actions-button + {:on-click on-toggle-open-shadow} + i/actions] + [:select.input-select + {:default-value shadow-style + :on-change (fn [event] + (let [value (-> event dom/get-target dom/get-value d/read-string)] + (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} + [:option {:value ":drop-shadow" + :selected (when (= shadow-style ":drop-shadow") "selected")} + (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow" + :selected (when (= shadow-style ":inner-shadow") "selected")} + (tr "workspace.options.shadow-options.inner-shadow")]]] - [:div.row-grid-2 - [:div.input-element {:title (tr "workspace.options.shadow-options.blur")} - [:> numeric-input {:ref adv-blur-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-blur-ref) - :on-change (update-attr index :blur valid-number? basic-blur-ref) - :min 0 - :value (:blur value)}] - [:span.after (tr "workspace.options.shadow-options.blur")]] + [:div.row-grid-2 + [:div.input-element {:title (tr "workspace.options.shadow-options.offsetx")} + [:> numeric-input {:ref adv-offset-x-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-offset-x-ref) + :on-change (update-attr index :offset-x basic-offset-x-ref) + :on-blur on-blur + :value (:offset-x value)}] + [:span.after (tr "workspace.options.shadow-options.offsetx")]] - [:div.input-element {:title (tr "workspace.options.shadow-options.spread")} - [:> numeric-input {:ref adv-spread-ref - :no-validate true - :placeholder "--" - :on-focus (select-text adv-spread-ref) - :on-change (update-attr index :spread valid-number?) - :value (:spread value)}] - [:span.after (tr "workspace.options.shadow-options.spread")]]] + [:div.input-element {:title (tr "workspace.options.shadow-options.offsety")} + [:> numeric-input {:ref adv-offset-y-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-offset-y-ref) + :on-change (update-attr index :offset-y basic-offset-y-ref) + :on-blur on-blur + :value (:offset-y value)}] + [:span.after (tr "workspace.options.shadow-options.offsety")]]] - [:div.color-row-wrap - [:& color-row {:color (if (string? (:color value)) - ;; Support for old format colors - {:color (:color value) :opacity (:opacity value)} - (:color value)) - :title (tr "workspace.options.shadow-options.color") - :disable-gradient true - :on-change (update-color index) - :on-detach (detach-color index) - :on-open #(st/emit! (dwu/start-undo-transaction :color-row)) - :on-close #(st/emit! (dwu/commit-undo-transaction :color-row))}]]]])) + [:div.row-grid-2 + [:div.input-element {:title (tr "workspace.options.shadow-options.blur")} + [:> numeric-input {:ref adv-blur-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-blur-ref) + :on-change (update-attr index :blur basic-blur-ref) + :on-blur on-blur + :min 0 + :value (:blur value)}] + [:span.after (tr "workspace.options.shadow-options.blur")]] + + [:div.input-element {:title (tr "workspace.options.shadow-options.spread")} + [:> numeric-input {:ref adv-spread-ref + :no-validate true + :placeholder "--" + :on-focus (select-text adv-spread-ref) + :on-change (update-attr index :spread) + :on-blur on-blur + :value (:spread value)}] + [:span.after (tr "workspace.options.shadow-options.spread")]]] + + [:div.color-row-wrap + [:& color-row {:color (if (string? (:color value)) + ;; Support for old format colors + {:color (:color value) :opacity (:opacity value)} + (:color value)) + :title (tr "workspace.options.shadow-options.color") + :disable-gradient true + :on-change (update-color index) + :on-detach (detach-color index) + :on-open #(st/emit! (dwu/start-undo-transaction :color-row)) + :on-close #(st/emit! (dwu/commit-undo-transaction :color-row))}]]]]])) (mf/defc shadow-menu - [{:keys [ids type values] :as props}] - (let [on-remove-all-shadows - (fn [_] (st/emit! (dch/update-shapes ids #(dissoc % :shadow)))) + {::mf/wrap-props false} + [props] + (let [ids (unchecked-get props "ids") + type (unchecked-get props "type") + values (unchecked-get props "values") + + shadows (:shadow values []) + open-state-ref (mf/with-memo [] (l/atom {})) + + disable-drag* (mf/use-state false) + disable-drag? (deref disable-drag*) + + on-remove-all + (mf/use-fn + (mf/deps ids) + (fn [] + (st/emit! (dch/update-shapes ids #(dissoc % :shadow))))) + + handle-reorder + (mf/use-fn + (mf/deps ids) + (fn [new-index index] + (st/emit! (dc/reorder-shadows ids index new-index)))) + + on-blur + (mf/use-fn + #(reset! disable-drag* false)) on-add-shadow - (fn [] - (st/emit! (dch/update-shapes ids #(update % :shadow (fnil conj []) (create-shadow)) )))] + (mf/use-fn + (mf/deps ids) + #(st/emit! (dc/add-shadow ids (create-shadow))))] + [:div.element-set.shadow-options [:div.element-set-title [:span @@ -211,22 +279,28 @@ :group (tr "workspace.options.shadow-options.title.group") (tr "workspace.options.shadow-options.title"))] - (when-not (= :multiple (:shadow values)) + (when-not (= :multiple shadows) [:div.add-page {:on-click on-add-shadow} i/close])] (cond - (= :multiple (:shadow values)) + (= :multiple shadows) [:div.element-set-content [:div.element-set-options-group [:div.element-set-label (tr "settings.multiple")] [:div.element-set-actions - [:div.element-set-actions-button {:on-click on-remove-all-shadows} + [:div.element-set-actions-button {:on-click on-remove-all} i/minus]]]] - (seq (:shadow values)) - [:div.element-set-content - (for [[index value] (d/enumerate (:shadow values []))] - [:& shadow-entry {:key (str "shadow-" index) - :ids ids - :value value - :index index}])])])) + (seq shadows) + [:& h/sortable-container {} + [:div.element-set-content + (for [[index value] (d/enumerate shadows)] + [:& shadow-entry + {:key (dm/str "shadow-" index) + :ids ids + :value value + :on-reorder handle-reorder + :disable-drag? disable-drag? + :on-blur on-blur + :index index + :open-state-ref open-state-ref}])]])]))