Add helper function PenpotUtils.addFlexLayout for adding a layout without changing relative child positions

Resolves #33

Additional changes: Improve prompts pertaining to layouts
This commit is contained in:
Dominik Jain 2026-01-29 14:18:42 +01:00
parent d3e531ec7c
commit cbb0863746
2 changed files with 40 additions and 6 deletions

View File

@ -88,11 +88,10 @@ initial_instructions: |
Boards can have layout systems that automatically control the positioning and spacing of their children: Boards can have layout systems that automatically control the positioning and spacing of their children:
* **Flex Layout**: A flexbox-style layout system * **Flex Layout**: A flexbox-style layout system
- Add to a board with `board.addFlexLayout(): FlexLayout`; instance then accessibly via `board.flex`;
Check with: `if (board.flex) { ... }`
- Properties: `dir`, `rowGap`, `columnGap`, `alignItems`, `justifyContent`; - Properties: `dir`, `rowGap`, `columnGap`, `alignItems`, `justifyContent`;
- `dir`: "row" | "column" | "row-reverse" | "column-reverse" - `dir`: "row" | "column" | "row-reverse" | "column-reverse"
- Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding` - Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding`
- To modify spacing: adjust `rowGap` and `columnGap` properties, not individual child positions
- When a board has flex layout, - When a board has flex layout,
- child positions are controlled by the layout system, not by individual x/y coordinates; - child positions are controlled by the layout system, not by individual x/y coordinates;
appending or inserting children automatically positions them according to the layout rules. appending or inserting children automatically positions them according to the layout rules.
@ -104,10 +103,14 @@ initial_instructions: |
of the `children` array for dir="column" or dir="row", which is what you want. So call it in the order of visual appearance. of the `children` array for dir="column" or dir="row", which is what you want. So call it in the order of visual appearance.
To insert at a specific index, use `board.insertChild(index, shape)`, bearing in mind the reversed order for dir="column" To insert at a specific index, use `board.insertChild(index, shape)`, bearing in mind the reversed order for dir="column"
or dir="row". or dir="row".
- To modify spacing: adjust `rowGap` and `columnGap` properties, not individual child positions - Add to a board with `board.addFlexLayout(): FlexLayout`; instance then accessible via `board.flex`.
IMPORTANT: When adding a flex layout to a container that already has children,
use `penpotUtils.addFlexLayout(container, dir)` instead! This preserves the existing visual order of children.
Otherwise, children will be arbitrarily reordered when the children order suddenly determines the display order.
- Check with: `if (board.flex) { ... }`
* **Grid Layout**: A CSS grid-style layout system * **Grid Layout**: A CSS grid-style layout system
- Add to a board with `board.addFlexLayout(): FlexLayout`; instance then accessibly via `board.grid`; - Add to a board with `board.addGridLayout(): GridLayout`; instance then accessibly via `board.grid`;
Check with: `if (board.grid) { ... }` Check with: `if (board.grid) { ... }`
- Properties: `rows`, `columns`, `rowGap`, `columnGap` - Properties: `rows`, `columns`, `rowGap`, `columnGap`
- Children are positioned via 1-based row/column indices - Children are positioned via 1-based row/column indices

View File

@ -1,4 +1,4 @@
import { Fill, FlexLayout, GridLayout, Page, Rectangle, Shape } from "@penpot/plugin-types"; import { Board, Fill, FlexLayout, GridLayout, Page, Rectangle, Shape } from "@penpot/plugin-types";
export class PenpotUtils { export class PenpotUtils {
/** /**
@ -198,6 +198,37 @@ export class PenpotUtils {
shape.y = shape.parent.y + parentY; shape.y = shape.parent.y + parentY;
} }
/**
* Adds a flex layout to a container while preserving the visual order of existing children.
* Without this, adding a flex layout can arbitrarily reorder children.
*
* The method sorts children by their current position (x for "row", y for "column") before
* adding the layout, then reorders them to maintain that visual sequence.
*
* @param container - The container (board) to add the flex layout to
* @param dir - The layout direction: "row" for horizontal, "column" for vertical
* @returns The created FlexLayout instance
*/
public static addFlexLayout(container: Board, dir: "column" | "row"): FlexLayout {
// obtain children sorted by position (ascending)
const children = "children" in container && container.children ? [...container.children] : [];
const sortedChildren = children.sort((a, b) => (dir === "row" ? a.x - b.x : a.y - b.y));
// add the flex layout
const flexLayout = container.addFlexLayout();
flexLayout.dir = dir;
// reorder children to preserve visual order; since the children array is reversed
// relative to visual order for dir="column" or dir="row", we insert each child at
// index 0 in sorted order, which places the first (smallest position) at the highest
// index, making it appear first visually
for (const child of sortedChildren) {
child.setParentIndex(0);
}
return flexLayout;
}
/** /**
* Analyzes all descendants of a shape by applying an evaluator function to each. * Analyzes all descendants of a shape by applying an evaluator function to each.
* Only descendants for which the evaluator returns a non-null/non-undefined value are included in the result. * Only descendants for which the evaluator returns a non-null/non-undefined value are included in the result.