Add PenpotUtils.analyzeDescendants as a powerful utility function for validation #26

This commit is contained in:
Dominik Jain 2026-01-15 16:28:07 +01:00
parent c4c37adb25
commit 90459f0ba4
2 changed files with 58 additions and 0 deletions

View File

@ -152,6 +152,10 @@ initial_instructions: |
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)
* analyzeDescendants<T>(root: Shape, evaluator: (descendant: Shape) => T | null | undefined, maxDepth?: number): Array<{ shape: Shape, result: T }>
General-purpose utility for analyzing/validating descendants
Calls evaluator on each descendant; collects non-null/undefined results
Powerful pattern: evaluator can return corrector functions or diagnostic data
General pointers for working with Penpot designs:
* Prefer `penpotUtils` helper functions — avoid reimplementing shape searching.
@ -173,6 +177,18 @@ initial_instructions: |
const structure = penpotUtils.shapeStructure(penpot.selection[0]);
* Find shapes in current selection/board:
const shapes = penpotUtils.findShapes(predicate, penpot.selection[0] || penpot.root);
* Validate/analyze descendants (returns corrector functions):
const fixes = penpotUtils.analyzeDescendants(board, (shape) => {
const xMod = shape.parentX % 4;
if (xMod !== 0) {
return () => penpotUtils.setParentXY(shape, Math.round(shape.parentX / 4) * 4, shape.parentY);
}
});
fixes.forEach(f => f.result()); // Apply all fixes
* Find containment violations:
const violations = penpotUtils.analyzeDescendants(board, (shape) => {
return !penpotUtils.isContainedIn(shape, board) ? 'outside-bounds' : null;
});
# Asset Libraries

View File

@ -198,6 +198,48 @@ export class PenpotUtils {
shape.y = shape.parent.y + parentY;
}
/**
* 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.
* This is a general-purpose utility for validation, analysis, or collecting corrector functions.
*
* @param root - The root shape whose descendants to analyze
* @param evaluator - Function called for each descendant; return null/undefined to skip
* @param maxDepth - Optional maximum depth to traverse (undefined for unlimited)
* @returns Array of objects containing the shape and the evaluator's result
*/
public static analyzeDescendants<T>(
root: Shape,
evaluator: (descendant: Shape) => T | null | undefined,
maxDepth: number | undefined = undefined
): Array<{ shape: Shape; result: NonNullable<T> }> {
const results: Array<{ shape: Shape; result: NonNullable<T> }> = [];
const traverse = (shape: Shape, currentDepth: number): void => {
const result = evaluator(shape);
if (result !== null && result !== undefined) {
results.push({ shape, result: result as NonNullable<T> });
}
if (maxDepth === undefined || currentDepth < maxDepth) {
if ("children" in shape && shape.children) {
for (const child of shape.children) {
traverse(child, currentDepth + 1);
}
}
}
};
// Start traversal with root's children (not root itself)
if ("children" in root && root.children) {
for (const child of root.children) {
traverse(child, 1);
}
}
return results;
}
/**
* Decodes a base64 string to a Uint8Array.
* This is required because the Penpot plugin environment does not provide the atob function.