Skip to content

Commit 4d58e83

Browse files
authored
Refactor layout positioning logic and add pixel rounding (#153)
1 parent c48b802 commit 4d58e83

File tree

4 files changed

+49
-6
lines changed

4 files changed

+49
-6
lines changed

.changeset/cuddly-carpets-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"figma-developer-mcp": patch
3+
---
4+
5+
Refactor layout positioning logic and add pixel rounding.

src/transformers/layout.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { isFrame, isLayout, isRectangle } from "~/utils/identity.js";
1+
import { isInAutoLayoutFlow, isFrame, isLayout, isRectangle } from "~/utils/identity.js";
22
import type {
33
Node as FigmaDocumentNode,
44
HasFramePropertiesTrait,
55
HasLayoutTrait,
66
} from "@figma/rest-api-spec";
7-
import { generateCSSShorthand } from "~/utils/common.js";
7+
import { generateCSSShorthand, pixelRound } from "~/utils/common.js";
88

99
export interface SimplifiedLayout {
1010
mode: "none" | "row" | "column";
@@ -208,17 +208,17 @@ function buildSimplifiedLayoutValues(
208208

209209
// Only include positioning-related properties if parent layout isn't flex or if the node is absolute
210210
if (
211-
isFrame(parent) &&
212211
// If parent is a frame but not an AutoLayout, or if the node is absolute, include positioning-related properties
213-
(!parent.layoutMode || parent.layoutMode === "NONE" || n.layoutPositioning === "ABSOLUTE")
212+
isFrame(parent) &&
213+
!isInAutoLayoutFlow(n, parent)
214214
) {
215215
if (n.layoutPositioning === "ABSOLUTE") {
216216
layoutValues.position = "absolute";
217217
}
218218
if (n.absoluteBoundingBox && parent.absoluteBoundingBox) {
219219
layoutValues.locationRelativeToParent = {
220-
x: n.absoluteBoundingBox.x - parent.absoluteBoundingBox.x,
221-
y: n.absoluteBoundingBox.y - parent.absoluteBoundingBox.y,
220+
x: pixelRound(n.absoluteBoundingBox.x - parent.absoluteBoundingBox.x),
221+
y: pixelRound(n.absoluteBoundingBox.y - parent.absoluteBoundingBox.y),
222222
};
223223
}
224224
}
@@ -255,6 +255,12 @@ function buildSimplifiedLayoutValues(
255255
}
256256

257257
if (Object.keys(dimensions).length > 0) {
258+
if (dimensions.width) {
259+
dimensions.width = pixelRound(dimensions.width);
260+
}
261+
if (dimensions.height) {
262+
dimensions.height = pixelRound(dimensions.height);
263+
}
258264
layoutValues.dimensions = dimensions;
259265
}
260266
}

src/utils/common.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,16 @@ export function parsePaint(raw: Paint): SimplifiedFill {
316316
export function isVisible(element: { visible?: boolean }): boolean {
317317
return element.visible ?? true;
318318
}
319+
320+
/**
321+
* Rounds a number to two decimal places, suitable for pixel value processing.
322+
* @param num The number to be rounded.
323+
* @returns The rounded number with two decimal places.
324+
* @throws TypeError If the input is not a valid number
325+
*/
326+
export function pixelRound(num: number): number {
327+
if (isNaN(num)) {
328+
throw new TypeError(`Input must be a valid number`);
329+
}
330+
return Number(Number(num).toFixed(2));
331+
}

src/utils/identity.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ export function isLayout(val: unknown): val is HasLayoutTrait {
4343
);
4444
}
4545

46+
/**
47+
* Checks if:
48+
* 1. A node is a child to an auto layout frame
49+
* 2. The child adheres to the auto layout rules—i.e. it's not absolutely positioned
50+
*
51+
* @param node - The node to check.
52+
* @param parent - The parent node.
53+
* @returns True if the node is a child of an auto layout frame, false otherwise.
54+
*/
55+
export function isInAutoLayoutFlow(node: unknown, parent: unknown): boolean {
56+
const autoLayoutModes = ["HORIZONTAL", "VERTICAL"];
57+
return (
58+
isFrame(parent) &&
59+
autoLayoutModes.includes(parent.layoutMode ?? "NONE") &&
60+
isLayout(node) &&
61+
node.layoutPositioning !== "ABSOLUTE"
62+
);
63+
}
64+
4665
export function isStrokeWeights(val: unknown): val is StrokeWeights {
4766
return (
4867
typeof val === "object" &&

0 commit comments

Comments
 (0)