From cbb0863746f3d6a2c12eb70de584d4d014add285 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 29 Jan 2026 14:18:42 +0100 Subject: [PATCH] Add helper function PenpotUtils.addFlexLayout for adding a layout without changing relative child positions Resolves #33 Additional changes: Improve prompts pertaining to layouts --- mcp-server/data/prompts.yml | 13 ++++++++----- penpot-plugin/src/PenpotUtils.ts | 33 +++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/mcp-server/data/prompts.yml b/mcp-server/data/prompts.yml index 0e981d6..e57e75d 100644 --- a/mcp-server/data/prompts.yml +++ b/mcp-server/data/prompts.yml @@ -88,11 +88,10 @@ initial_instructions: | Boards can have layout systems that automatically control the positioning and spacing of their children: * **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`; - `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, - 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. @@ -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. To insert at a specific index, use `board.insertChild(index, shape)`, bearing in mind the reversed order for dir="column" 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 - - 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) { ... }` - Properties: `rows`, `columns`, `rowGap`, `columnGap` - Children are positioned via 1-based row/column indices diff --git a/penpot-plugin/src/PenpotUtils.ts b/penpot-plugin/src/PenpotUtils.ts index 88bcc9c..d5044e6 100644 --- a/penpot-plugin/src/PenpotUtils.ts +++ b/penpot-plugin/src/PenpotUtils.ts @@ -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 { /** @@ -198,6 +198,37 @@ export class PenpotUtils { 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. * Only descendants for which the evaluator returns a non-null/non-undefined value are included in the result.