PenpotUtils: Add isContainedIn and setParentXY #26

This commit is contained in:
Dominik Jain 2026-01-14 22:21:20 +01:00
parent 3c1bd875d8
commit c4c37adb25
2 changed files with 40 additions and 9 deletions

View File

@ -42,7 +42,7 @@ initial_instructions: |
* The location properties `x` and `y` refer to the top left corner of a shape's bounding box in the absolute (Page) coordinate system.
These are writable - set them directly to position shapes.
* `parentX` and `parentY` (as well as `boardX` and `boardY`) are READ-ONLY computed properties showing position relative to parent/board.
To position a shape within its parent, set the absolute `x` and `y` properties accordingly.
To position relative to parent, use `penpotUtils.setParentXY(shape, parentX, parentY)` or manually set `shape.x = parent.x + parentX`.
* `width` and `height` are READ-ONLY. Use `resize(width, height)` method to change dimensions.
* `bounds` is a READ-ONLY property. Use `x`, `y` with `resize()` to modify shape bounds.
@ -114,14 +114,7 @@ initial_instructions: |
Example: A card board with an image, and a separate text board positioned below it as a sibling.
This is incorrect if the text should overlay the image. The text should be a child of the card board.
* **Verification**: Always verify visual containment when working with nested structures:
```javascript
const isVisuallyContained =
child.x >= parent.x &&
child.y >= parent.y &&
(child.x + child.width) <= (parent.x + parent.width) &&
(child.y + child.height) <= (parent.y + parent.height);
```
* **Verification**: Use `penpotUtils.isContainedIn(child, parent)` to verify visual containment
# Text Elements
@ -155,6 +148,10 @@ initial_instructions: |
* findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null
If no root is provided, search globally (in all pages).
* findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape[]
* isContainedIn(child: Shape, parent: Shape): boolean
Returns true if child is fully within parent's visual bounds
* setParentXY(shape: Shape, parentX: number, parentY: number): void
Sets shape position relative to its parent (since parentX/parentY are read-only)
General pointers for working with Penpot designs:
* Prefer `penpotUtils` helper functions — avoid reimplementing shape searching.

View File

@ -164,6 +164,40 @@ export class PenpotUtils {
return penpot.generateStyle([shape], { type: "css", includeChildren: true });
}
/**
* Checks if a child shape is fully contained within its parent's bounds.
* Visual containment means all edges of the child are within the parent's bounding box.
*
* @param child - The child shape to check
* @param parent - The parent shape to check against
* @returns true if child is fully contained within parent bounds, false otherwise
*/
public static isContainedIn(child: Shape, parent: Shape): boolean {
return (
child.x >= parent.x &&
child.y >= parent.y &&
child.x + child.width <= parent.x + parent.width &&
child.y + child.height <= parent.y + parent.height
);
}
/**
* Sets the position of a shape relative to its parent's position.
* This is a convenience method since parentX and parentY are read-only properties.
*
* @param shape - The shape to position
* @param parentX - The desired X position relative to the parent
* @param parentY - The desired Y position relative to the parent
* @throws Error if the shape has no parent
*/
public static setParentXY(shape: Shape, parentX: number, parentY: number): void {
if (!shape.parent) {
throw new Error("Shape has no parent - cannot set parent-relative position");
}
shape.x = shape.parent.x + parentX;
shape.y = shape.parent.y + parentY;
}
/**
* Decodes a base64 string to a Uint8Array.
* This is required because the Penpot plugin environment does not provide the atob function.