M2: Remove all auto layout takes it off a frame and every frame nested in it (#1833)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Trevor Brown
2026-11-01 23:49:53 -05:00
committed by GitHub
co-authored by Claude Fable 5.1
parent 3b8ded69c0
commit 2abd8c5f52
5 changed files with 58 additions and 1 deletions
+27
View File
@@ -318,3 +318,30 @@ fn fill_text_in_a_hugging_card_sets_the_height_in_one_step() {
// Whatever the fonts, the card hugs the wrapped text exactly.
assert!((cb.height() - (tb.height() + 20.0)).abs() < 1e-6, "card {cb:?} text {tb:?}");
}
/// "To remove auto layout from a frame and all of its nested objects at once, right-click on the
/// frame and go to More layout options > Remove all auto layout." (Toggle on auto layout in designs)
#[test]
fn remove_all_auto_layout_strips_the_frame_and_every_frame_nested_in_it() {
let mut s = Session::new();
let outer = frame(&mut s, None, 0.0, 0.0, 300.0, 100.0);
let inner = frame(&mut s, Some(outer), 0.0, 0.0, 100.0, 50.0);
let deep = frame(&mut s, Some(inner), 0.0, 0.0, 40.0, 20.0);
rect(&mut s, Some(deep), 0.0, 0.0, 10.0, 10.0);
for (f, dir) in [(outer, "horizontal"), (inner, "vertical"), (deep, "horizontal")] {
s.execute("node.set", &json!({"id": f, "props": {"layout": dir}})).unwrap();
}
let laid_out = |s: &Session| [outer, inner, deep].map(|n| s.doc().unwrap().node(n).unwrap().layout.is_some());
// Remove auto layout takes it off the frame alone.
s.execute("layout.remove", &json!({"ids": [outer]})).unwrap();
assert_eq!(laid_out(&s), [false, true, true]);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(laid_out(&s), [true, true, true]);
let steps = s.ds().unwrap().history.undo.len();
let r = s.execute("layout.removeAll", &json!({"ids": [outer]})).unwrap();
assert_eq!(laid_out(&s), [false, false, false], "every nested frame too");
assert_eq!(r["removed"].as_array().map(Vec::len), Some(3));
assert_eq!(s.ds().unwrap().history.undo.len(), steps + 1, "one undo step");
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(laid_out(&s), [true, true, true]);
}
+26
View File
@@ -83,6 +83,32 @@ pub fn specs() -> Vec<CommandSpec> {
}
ok()
}),
spec!(
"layout.removeAll",
"Remove all auto layout",
"{ids?} takes the auto layout off the frames and every frame nested in them at once (More layout options ▸ Remove all auto layout); instances and the layers inside them keep theirs",
true,
|s, p| {
let ids = req_targets("layout.removeAll", s, p)?;
let doc = s.doc_mut()?;
let mut removed = Vec::new();
for root in ids {
for i in doc.subtree(root) {
let strip = doc.node(i).is_some_and(|n| n.layout.is_some() && !matches!(n.kind, figcraft_doc::NodeKind::Instance { .. }))
&& !crate::assets::components::inside_instance(doc, i);
if !strip {
continue;
}
let n = doc.get_mut(i)?;
n.layout = None;
super::props::drop_layout_bindings(n);
figcraft_layout::coherent_sizing(doc, i);
removed.push(i);
}
}
Ok(json!({"removed": removed}))
}
),
spec!("layout.suggest", "Suggest auto layout", "{ids?, direction? (horizontal|vertical|grid)}", true, layout_add),
spec!("object.moveToPage", "Move to page", "{ids?, page}", true, move_to_page),
spec!("object.setThumbnail", "Set as thumbnail", "{id?}", true, |s, p| {
+1
View File
@@ -1187,6 +1187,7 @@ name = "English"
"Remove" = "Remove"
"Remove all" = "Remove all"
"Remove annotation" = "Remove annotation"
"Remove all auto layout" = "Remove all auto layout"
"Remove auto layout" = "Remove auto layout"
"Remove column" = "Remove column"
"Remove dev resource" = "Remove dev resource"
+1
View File
@@ -502,6 +502,7 @@ pub fn popups(app: &mut FigApp, ctx: &egui::Context) {
(tr("Use as mask"), "object.mask"),
("-", ""),
(tr("Add auto layout"), "layout.add"),
(tr("Remove all auto layout"), "layout.removeAll"),
(tr("Create component"), "component.create"),
(tr("Detach instance"), "component.detach"),
("-", ""),
+3 -1
View File
@@ -1,6 +1,6 @@
# Auto layout and constraints parity
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-30 · **Change:** minor (grid auto-placement looks for free cells a 64-bit word at a time and skips what blocks it, with the same placements (#1083); Layout sidebar: Add auto layout (+) is available for groups and converts a single group to a frame in place, with mask order, flow-only inference and Fill sizing preserved; adding auto layout to an empty frame keeps its size (#369); `node.set` applies auto layout keys only to frames, with warnings (#603); core editor leftovers: auto gap handles, wrapped-line alignment; behaviour suite written from the public documentation, every rule's status; fixes and the missing settings it found) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** minor (Remove all auto layout, as the help center's More layout options has it: `layout.removeAll` takes the auto layout off a frame and every frame nested in it in one undo step; grid auto-placement looks for free cells a 64-bit word at a time and skips what blocks it, with the same placements (#1083); Layout sidebar: Add auto layout (+) is available for groups and converts a single group to a frame in place, with mask order, flow-only inference and Fill sizing preserved; adding auto layout to an empty frame keeps its size (#369); `node.set` applies auto layout keys only to frames, with warnings (#603); core editor leftovers: auto gap handles, wrapped-line alignment; behaviour suite written from the public documentation, every rule's status; fixes and the missing settings it found) · **Target:** Figma Design (desktop app 126.10.7)
Auto layout is the most used layout feature in Figma and the one where small differences (a
hugging frame one pixel off, text wrapping at another width) break real files. **Breadth ~98%,
@@ -42,6 +42,7 @@ Status: **pass** (a case in the suite holds), **fixed** (the suite found a diver
| Hug only on auto layout frames (and text); Fill only in an auto layout flow, never on a top-level frame | pass | `al_behaviour_tests::hug_and_fill_only_where_they_apply` |
| Min/max width and height bound Hug, Fill and Fixed sizes, on frames and children | pass | `sizing_rules`; a fill child at its max leaves the rest to the other fill children (silent on redistribution: CSS flexbox) |
| A hugging text wider than its max width wraps at the max | pass | `sizing_rules` |
| Remove all auto layout: a frame and all of its nested objects lose their auto layout at once (More layout options) | **added** | `layout.removeAll {ids?}`, one undo step, in the canvas context menu beside Add auto layout; instances and the layers inside them keep theirs; `al_nesting_tests::remove_all_auto_layout_strips_the_frame_and_every_frame_nested_in_it` |
| Text filling a width wraps and its hugging frame grows with it | pass | `sizing_rules` |
| A hugging frame follows a child that grows, through nested levels, in one edit and one undo step | pass | `al_behaviour_tests::a_hugging_frame_follows_its_children_through_nested_levels` |
| Padding per side (uniform, vertical/horizontal, individual) | pass | `padding_and_stroke_rules` |
@@ -95,6 +96,7 @@ Status: **pass** (a case in the suite holds), **fixed** (the suite found a diver
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | M2: Remove all auto layout ("To remove auto layout from a frame and all of its nested objects at once, right-click on the frame and go to More layout options > Remove all auto layout", Toggle on auto layout in designs): `layout.removeAll {ids?}` strips the frames and every frame nested in them in one undo step, leaving instances and their layers alone; in the canvas context menu beside Add auto layout and in the Actions palette. Found by a documentation audit of this page. Test: `al_nesting_tests::remove_all_auto_layout_strips_the_frame_and_every_frame_nested_in_it`. Numbers unchanged |
| 2026-10-30 | minor | M8, #1083: grid placement finds free cells on a bitmap, a word at a time, with the same placements; crafted grids of wide children no longer stall a layout pass. Numbers unchanged. |
| 2026-10-29 | minor | M2 review fixes: conversion preserves mask runs and Fill sizing per axis; layout inference uses visible, non-absolute flow children; the sidebar hides Add for mixed selections containing existing auto layout. Regression coverage includes rendered mask pixels, visible-child positions, parent layout stability and the sidebar accessibility tree. Numbers unchanged |
| 2026-10-29 | minor | M2: the Layout sidebar exposes Add auto layout (+) for groups. `layout.add` converts one selected group into a frame in place rather than wrapping it: same id, name and direct children; the layout arranges those children, and one undo restores the group. Sidebar, menu, shortcut and API calls all use the corrected command. This follows the contributor’s observed Figma behavior and the workflow familiar to users of similar design tools. Tests cover direct-child reflow, stable hierarchy, undo/redo and wrapping a multi-group selection. Numbers unchanged |