Skip to content

Commit 782dcaa

Browse files
committed
Add min/max width/height.
1 parent 3cc5b2d commit 782dcaa

15 files changed

Lines changed: 249 additions & 88 deletions

File tree

packages/backend/src/code.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,12 @@ const processNodePair = async (
333333
if (!jsonNode.layoutSizingHorizontal)
334334
jsonNode.layoutSizingHorizontal = "FIXED";
335335
if (!jsonNode.layoutSizingVertical) jsonNode.layoutSizingVertical = "FIXED";
336+
if (!jsonNode.primaryAxisAlignItems) {
337+
jsonNode.primaryAxisAlignItems = "MIN";
338+
}
339+
if (!jsonNode.counterAxisAlignItems) {
340+
jsonNode.counterAxisAlignItems = "MIN";
341+
}
336342

337343
// If layout sizing is HUG but there are no children, set it to FIXED
338344
const hasChildren =

packages/backend/src/common/nodeWidthHeight.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
import { Size } from "types";
22

33
export const nodeSize = (node: SceneNode, optimizeLayout: boolean): Size => {
4-
const hasLayout =
5-
"layoutAlign" in node && node.parent && "layoutMode" in node.parent;
6-
7-
if (!hasLayout) {
8-
return { width: node.width, height: node.height };
9-
}
10-
114
const nodeAuto =
125
(optimizeLayout && "inferredAutoLayout" in node
136
? node.inferredAutoLayout
147
: null) ?? node;
158

16-
if ("layoutMode" in nodeAuto && nodeAuto.layoutMode === "NONE") {
17-
return { width: node.width, height: node.height };
18-
}
19-
20-
// const parentLayoutMode = node.parent.layoutMode;
21-
const parentLayoutMode = optimizeLayout
22-
? node.parent.inferredAutoLayout?.layoutMode
23-
: node.parent.layoutMode;
24-
259
// Check for explicit layout sizing properties
2610
if (
2711
"layoutSizingHorizontal" in nodeAuto &&
@@ -44,6 +28,17 @@ export const nodeSize = (node: SceneNode, optimizeLayout: boolean): Size => {
4428
return { width, height };
4529
}
4630

31+
if ("layoutMode" in nodeAuto && nodeAuto.layoutMode === "NONE") {
32+
return { width: node.width, height: node.height };
33+
}
34+
35+
const hasLayout =
36+
"layoutAlign" in node && node.parent && "layoutMode" in node.parent;
37+
38+
if (!hasLayout) {
39+
return { width: node.width, height: node.height };
40+
}
41+
4742
return { width: node.width, height: node.height };
4843

4944
// const isWidthFill =

packages/backend/src/flutter/builderImpl/flutterSize.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const flutterSizeWH = (node: SceneNode): string => {
1111
export const flutterSize = (
1212
node: SceneNode,
1313
optimizeLayout: boolean,
14-
): { width: string; height: string; isExpanded: boolean } => {
14+
): { width: string; height: string; isExpanded: boolean; constraints: Record<string, string> } => {
1515
const size = nodeSize(node, optimizeLayout);
1616
let isExpanded: boolean = false;
1717

@@ -53,5 +53,24 @@ export const flutterSize = (
5353
}
5454
}
5555

56-
return { width: propWidth, height: propHeight, isExpanded };
56+
// Handle min/max constraints
57+
const constraints: Record<string, string> = {};
58+
59+
if (node.minWidth !== undefined && node.minWidth !== null) {
60+
constraints.minWidth = numberToFixedString(node.minWidth);
61+
}
62+
63+
if (node.maxWidth !== undefined && node.maxWidth !== null) {
64+
constraints.maxWidth = numberToFixedString(node.maxWidth);
65+
}
66+
67+
if (node.minHeight !== undefined && node.minHeight !== null) {
68+
constraints.minHeight = numberToFixedString(node.minHeight);
69+
}
70+
71+
if (node.maxHeight !== undefined && node.maxHeight !== null) {
72+
constraints.maxHeight = numberToFixedString(node.maxHeight);
73+
}
74+
75+
return { width: propWidth, height: propHeight, isExpanded, constraints };
5776
};

packages/backend/src/flutter/flutterContainer.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export const flutterContainer = (
2828

2929
// ignore for Groups
3030
const propBoxDecoration = getDecoration(node);
31-
const { width, height, isExpanded } = flutterSize(node, optimizeLayout);
31+
const { width, height, isExpanded, constraints } = flutterSize(
32+
node,
33+
optimizeLayout,
34+
);
3235

3336
const clipBehavior =
3437
"clipsContent" in node && node.clipsContent === true
@@ -46,6 +49,8 @@ export const flutterContainer = (
4649
}
4750

4851
let result: string;
52+
const hasConstraints = constraints && Object.keys(constraints).length > 0;
53+
4954
if (width || height || propBoxDecoration || clipBehavior) {
5055
const parsedDecoration = skipDefaultProperty(
5156
propBoxDecoration,
@@ -69,6 +74,14 @@ export const flutterContainer = (
6974
result = child;
7075
}
7176

77+
// Apply constraints if any exist
78+
if (hasConstraints) {
79+
result = generateWidgetCode("ConstrainedBox", {
80+
constraints: generateWidgetCode("BoxConstraints", constraints),
81+
child: result,
82+
});
83+
}
84+
7285
// Add Expanded() when parent is a Row/Column and width is full.
7386
if (isExpanded) {
7487
result = generateWidgetCode("Expanded", {

packages/backend/src/flutter/flutterMain.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ const makeRowColumn = (
239239
// mainAxisSize: getFlex(node, autoLayout),
240240
mainAxisAlignment: getMainAxisAlignment(autoLayout),
241241
crossAxisAlignment: getCrossAxisAlignment(autoLayout),
242-
children: [children],
243242
};
244243

245244
// Add spacing parameter if itemSpacing is set
@@ -249,6 +248,8 @@ const makeRowColumn = (
249248
addWarning("Flutter doesn't support negative itemSpacing");
250249
}
251250

251+
widgetProps.children = [children];
252+
252253
return generateWidgetCode(rowOrColumn, widgetProps);
253254
};
254255

packages/backend/src/flutter/flutterTextBuilder.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,34 @@ export class FlutterTextBuilder extends FlutterDefaultBuilder {
168168

169169
textAutoSize(node: TextNode): this {
170170
let result = this.child;
171-
171+
172+
// Get constraints for the node
173+
const constraints: Record<string, string> = {};
174+
175+
if (node.minWidth !== undefined && node.minWidth !== null) {
176+
constraints.minWidth = numberToFixedString(node.minWidth);
177+
}
178+
179+
if (node.maxWidth !== undefined && node.maxWidth !== null) {
180+
constraints.maxWidth = numberToFixedString(node.maxWidth);
181+
}
182+
183+
if (node.minHeight !== undefined && node.minHeight !== null) {
184+
constraints.minHeight = numberToFixedString(node.minHeight);
185+
}
186+
187+
if (node.maxHeight !== undefined && node.maxHeight !== null) {
188+
constraints.maxHeight = numberToFixedString(node.maxHeight);
189+
}
190+
191+
const hasConstraints = Object.keys(constraints).length > 0;
192+
if (hasConstraints) {
193+
result = generateWidgetCode("ConstrainedBox", {
194+
constraints: generateWidgetCode("BoxConstraints", constraints),
195+
child: result,
196+
});
197+
}
198+
172199
switch (node.textAutoResize) {
173200
case "WIDTH_AND_HEIGHT":
174201
break;
@@ -187,9 +214,8 @@ export class FlutterTextBuilder extends FlutterDefaultBuilder {
187214
});
188215
break;
189216
}
190-
217+
191218
result = wrapTextWithLayerBlur(node, result);
192-
193219
this.child = result;
194220
return this;
195221
}

packages/backend/src/html/builderImpl/htmlSize.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ export const htmlSizePartial = (
66
node: SceneNode,
77
isJsx: boolean,
88
optimizeLayout: boolean,
9-
): { width: string; height: string } => {
9+
): { width: string; height: string; constraints: string[] } => {
1010
if (isPreviewGlobal && node.parent === undefined) {
1111
return {
1212
width: formatWithJSX("width", isJsx, "100%"),
1313
height: formatWithJSX("height", isJsx, "100%"),
14+
constraints: [],
1415
};
1516
}
1617

@@ -50,5 +51,29 @@ export const htmlSizePartial = (
5051
}
5152
}
5253

53-
return { width: w, height: h };
54+
// Handle min/max width/height constraints
55+
const constraints = [];
56+
57+
if (node.maxWidth !== undefined && node.maxWidth !== null) {
58+
constraints.push(formatWithJSX("max-width", isJsx, node.maxWidth));
59+
}
60+
61+
if (node.minWidth !== undefined && node.minWidth !== null) {
62+
constraints.push(formatWithJSX("min-width", isJsx, node.minWidth));
63+
}
64+
65+
if (node.maxHeight !== undefined && node.maxHeight !== null) {
66+
constraints.push(formatWithJSX("max-height", isJsx, node.maxHeight));
67+
}
68+
69+
if (node.minHeight !== undefined && node.minHeight !== null) {
70+
constraints.push(formatWithJSX("min-height", isJsx, node.minHeight));
71+
}
72+
73+
// Return constraints separately instead of appending to width/height
74+
return {
75+
width: w,
76+
height: h,
77+
constraints: constraints,
78+
};
5479
};

packages/backend/src/html/htmlDefaultBuilder.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ export class HtmlDefaultBuilder {
320320

321321
size(): this {
322322
const { node, settings } = this;
323-
const { width, height } = htmlSizePartial(
323+
const { width, height, constraints } = htmlSizePartial(
324324
node,
325325
settings.htmlGenerationMode === "jsx",
326326
settings.optimizeLayout,
@@ -342,6 +342,11 @@ export class HtmlDefaultBuilder {
342342
this.addStyles(width, height);
343343
}
344344

345+
// Add constraints as separate styles
346+
if (constraints.length > 0) {
347+
this.addStyles(...constraints);
348+
}
349+
345350
return this;
346351
}
347352

packages/backend/src/swiftui/builderImpl/swiftuiSize.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,39 @@ import { numberToFixedString } from "../../common/numToAutoFixed";
33

44
export const swiftuiSize = (
55
node: SceneNode,
6-
optimizeLayout: boolean = false,
7-
): { width: string; height: string } => {
8-
const size = nodeSize(node, optimizeLayout);
6+
optimize: boolean = false,
7+
): { width: string; height: string; constraints: string[] } => {
8+
const size = nodeSize(node, optimize);
99

10-
// if width is set as maxWidth, height must also be set as maxHeight (not height)
11-
const shouldExtend = size.height === "fill" || size.width === "fill";
10+
const constraintProps: string[] = [];
11+
let width = "";
12+
let height = "";
1213

13-
// this cast will always be true, since nodeWidthHeight was called with false to relative.
14-
let propWidth = "";
14+
// Handle width and height
1515
if (typeof size.width === "number") {
16-
const w = numberToFixedString(size.width);
17-
18-
if (shouldExtend) {
19-
propWidth = `minWidth: ${w}, maxWidth: ${w}`;
20-
} else {
21-
propWidth = `width: ${w}`;
22-
}
23-
} else if (size.width === "fill") {
24-
propWidth = `maxWidth: .infinity`;
16+
width = `width: ${numberToFixedString(size.width)}`;
2517
}
26-
27-
let propHeight = "";
2818
if (typeof size.height === "number") {
29-
const h = numberToFixedString(size.height);
19+
height = `height: ${numberToFixedString(size.height)}`;
20+
}
3021

31-
if (shouldExtend) {
32-
propHeight = `minHeight: ${h}, maxHeight: ${h}`;
33-
} else {
34-
propHeight = `height: ${h}`;
35-
}
36-
} else if (size.height === "fill") {
37-
propHeight = `maxHeight: .infinity`;
22+
// Handle min/max constraints
23+
if (node.minWidth !== undefined && node.minWidth !== null) {
24+
constraintProps.push(`minWidth: ${numberToFixedString(node.minWidth)}`);
25+
}
26+
if (node.maxWidth !== undefined && node.maxWidth !== null) {
27+
constraintProps.push(`maxWidth: ${numberToFixedString(node.maxWidth)}`);
28+
}
29+
if (node.minHeight !== undefined && node.minHeight !== null) {
30+
constraintProps.push(`minHeight: ${numberToFixedString(node.minHeight)}`);
31+
}
32+
if (node.maxHeight !== undefined && node.maxHeight !== null) {
33+
constraintProps.push(`maxHeight: ${numberToFixedString(node.maxHeight)}`);
3834
}
3935

40-
return { width: propWidth, height: propHeight };
36+
return {
37+
width,
38+
height,
39+
constraints: constraintProps,
40+
};
4141
};

packages/backend/src/swiftui/swiftuiDefaultBuilder.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,14 @@ export class SwiftuiDefaultBuilder {
139139
}
140140

141141
size(node: SceneNode, optimize: boolean): this {
142-
const { width, height } = swiftuiSize(node, optimize);
143-
const sizes = [width, height].filter((d) => d);
144-
if (sizes.length > 0) {
145-
this.pushModifier([`frame`, sizes.join(", ")]);
142+
const { width, height, constraints } = swiftuiSize(node, optimize);
143+
if (width || height) {
144+
this.pushModifier([`frame`, [width, height].filter(Boolean).join(", ")]);
145+
}
146+
147+
// Add constraints if any exist
148+
if (constraints.length > 0) {
149+
this.pushModifier([`frame`, constraints.join(", ")]);
146150
}
147151

148152
return this;

0 commit comments

Comments
 (0)