From bd1e0fb23f881f7f0c77f32d02d9949efff4a67c Mon Sep 17 00:00:00 2001 From: Milos Milic <124778054+MilosM348@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:36:15 +0200 Subject: [PATCH] :sparkles: Add Alt+click to expand a layer subtree in the Layers sidebar (#9179) Closes #7736. The Layers sidebar offered no way to expand every nested level of a single subtree at once. Unfolding a layer that wraps a deep tree required clicking each disclosure indicator one level at a time - O(siblings * depth) clicks. The asymmetry was particularly visible next to the existing Shift+click gesture, which collapses every layer in the panel in a single action via `dwc/collapse-all`, with no expand counterpart for either a single subtree or the whole tree. Add a new `dwc/expand-subtree` event in `app.main.data.workspace.collapse` that uses `cfh/get-children-ids-with-self` to gather the shape's id together with every descendant id, then merges `{descendant-id true}` entries into `[:workspace-local :expanded]` so the entire subtree opens in one update. Existing expansion state on unrelated branches is left untouched (`merge`, not `assoc`), matching the per-key shape used by `toggle-collapse` and `expand-collapse`. Wire the gesture into `layer_item.cljs` `toggle-collapse` callback as a third branch: - Shift+click while expanded - collapse every layer (existing). - Alt+click while collapsed - expand the entire subtree (new). - Otherwise - toggle this single level (existing). Alt is chosen instead of Shift to avoid the ambiguity the issue author flagged: "for a layer of middle depth it is unclear whether [Shift+click] should fold all (up to the topmost parent) or expand all (only the current subtree)". Alt is a common platform convention for "do this recursively" (Finder, file managers, several IDEs), so the asymmetric mapping matches user expectations. The callback's `mf/deps` vector is extended with `id` and `objects` so the closure refreshes when the shape tree changes. CHANGES.md entry added under the 2.17.0 New features section. --- CHANGES.md | 1 + .../src/app/main/data/workspace/collapse.cljs | 16 ++++++++++++++++ .../main/ui/workspace/sidebar/layer_item.cljs | 12 ++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e5b9dd1bb0..2518f1b0cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ ### :sparkles: New features & Enhancements +- Add `Alt+click` on a layer's disclosure arrow to recursively expand the entire subtree rooted at that layer in the Layers sidebar; symmetric with the existing `Shift+click` collapse-all gesture, and removes the O(siblings × depth) click cost of unfolding a deep subtree one level at a time [Github #7736](https://github.com/penpot/penpot/issues/7736) - Show alpha percentage next to library color values to distinguish colors that differ only in opacity (by @rockchris099) [Github #6328](https://github.com/penpot/penpot/issues/6328) - Add "Clear artboard guides" option to right-click context menu for frames (by @eureka0928) [Github #6987](https://github.com/penpot/penpot/issues/6987) - Add loader feedback while importing and exporting files [Github #9020](https://github.com/penpot/penpot/issues/9020) diff --git a/frontend/src/app/main/data/workspace/collapse.cljs b/frontend/src/app/main/data/workspace/collapse.cljs index 1143a6f4d8..b5b4998c6b 100644 --- a/frontend/src/app/main/data/workspace/collapse.cljs +++ b/frontend/src/app/main/data/workspace/collapse.cljs @@ -49,3 +49,19 @@ (update [_ state] (update state :workspace-local dissoc :expanded)))) +(defn expand-subtree + "Recursively expand the layer subtree rooted at `id`, marking the shape + and all of its descendants as expanded in the Layers sidebar. + + Closes the gap with `collapse-all`: there was no symmetric way to + open every nested level of a single subtree, so unfolding a deep + shape required clicking each disclosure indicator one by one + (O(siblings × depth) clicks)." + [id objects] + (ptk/reify ::expand-subtree + ptk/UpdateEvent + (update [_ state] + (let [ids (cfh/get-children-ids-with-self objects id) + expansions (into {} (map (fn [descendant-id] [descendant-id true])) ids)] + (update-in state [:workspace-local :expanded] merge expansions))))) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs index b17698d659..d31351adc8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs @@ -275,11 +275,19 @@ toggle-collapse (mf/use-fn - (mf/deps is-expanded) + (mf/deps is-expanded id objects) (fn [event] (dom/stop-propagation event) - (if (and is-expanded (kbd/shift? event)) + (cond + ;; Shift+click while expanded collapses every layer in the sidebar + (and is-expanded (kbd/shift? event)) (st/emit! (dwc/collapse-all)) + + ;; Alt+click while collapsed expands the entire subtree rooted at this id + (and (not is-expanded) (kbd/alt? event)) + (st/emit! (dwc/expand-subtree id objects)) + + :else (st/emit! (dwc/toggle-collapse id))))) toggle-blocking