@wire-dsl/engine 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +123 -27
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +123 -27
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1498,12 +1498,47 @@ var DEVICE_PRESETS = {
|
|
|
1498
1498
|
category: "print",
|
|
1499
1499
|
description: "A4 portrait at 96 DPI"
|
|
1500
1500
|
},
|
|
1501
|
-
|
|
1502
|
-
name: "
|
|
1503
|
-
width:
|
|
1504
|
-
minHeight:
|
|
1501
|
+
"mobile-landscape": {
|
|
1502
|
+
name: "Mobile Landscape",
|
|
1503
|
+
width: 812,
|
|
1504
|
+
minHeight: 375,
|
|
1505
|
+
category: "mobile",
|
|
1506
|
+
description: "Mobile landscape viewport"
|
|
1507
|
+
},
|
|
1508
|
+
"mobile-min": {
|
|
1509
|
+
name: "Mobile Minimum",
|
|
1510
|
+
width: 375,
|
|
1511
|
+
minHeight: 375,
|
|
1512
|
+
category: "mobile",
|
|
1513
|
+
description: "Mobile minimum viewport"
|
|
1514
|
+
},
|
|
1515
|
+
"desktop-min": {
|
|
1516
|
+
name: "Desktop Minimum",
|
|
1517
|
+
width: 1280,
|
|
1518
|
+
minHeight: 640,
|
|
1519
|
+
category: "desktop",
|
|
1520
|
+
description: "Desktop minimum viewport"
|
|
1521
|
+
},
|
|
1522
|
+
"tablet-landscape": {
|
|
1523
|
+
name: "Tablet Landscape",
|
|
1524
|
+
width: 1024,
|
|
1525
|
+
minHeight: 768,
|
|
1526
|
+
category: "tablet",
|
|
1527
|
+
description: "Tablet landscape viewport"
|
|
1528
|
+
},
|
|
1529
|
+
"tablet-min": {
|
|
1530
|
+
name: "Tablet Minimum",
|
|
1531
|
+
width: 768,
|
|
1532
|
+
minHeight: 384,
|
|
1533
|
+
category: "tablet",
|
|
1534
|
+
description: "Tablet minimum viewport"
|
|
1535
|
+
},
|
|
1536
|
+
"print-landscape": {
|
|
1537
|
+
name: "Print A4 Landscape",
|
|
1538
|
+
width: 1123,
|
|
1539
|
+
minHeight: 794,
|
|
1505
1540
|
category: "print",
|
|
1506
|
-
description: "A4
|
|
1541
|
+
description: "A4 landscape at 96 DPI"
|
|
1507
1542
|
}
|
|
1508
1543
|
};
|
|
1509
1544
|
function resolveDevicePreset(device) {
|
|
@@ -2166,25 +2201,53 @@ var LayoutEngine = class {
|
|
|
2166
2201
|
});
|
|
2167
2202
|
} else {
|
|
2168
2203
|
const childWidths = [];
|
|
2204
|
+
const explicitHeightFlags = [];
|
|
2205
|
+
const blockButtonIndices = /* @__PURE__ */ new Set();
|
|
2169
2206
|
let stackHeight = 0;
|
|
2170
|
-
children.forEach((childRef) => {
|
|
2207
|
+
children.forEach((childRef, index) => {
|
|
2171
2208
|
const childNode = this.nodes[childRef.ref];
|
|
2172
2209
|
let childWidth = this.getIntrinsicComponentWidth(childNode);
|
|
2173
2210
|
let childHeight = this.getComponentHeight();
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2211
|
+
const hasExplicitHeight = childNode?.kind === "component" && !!childNode.props.height;
|
|
2212
|
+
const hasExplicitWidth = childNode?.kind === "component" && !!childNode.props.width;
|
|
2213
|
+
const isBlockButton = childNode?.kind === "component" && childNode.componentType === "Button" && !hasExplicitWidth && this.parseBooleanProp(childNode.props.block, false);
|
|
2214
|
+
if (isBlockButton) {
|
|
2215
|
+
childWidth = 0;
|
|
2216
|
+
blockButtonIndices.add(index);
|
|
2217
|
+
} else if (hasExplicitWidth) {
|
|
2177
2218
|
childWidth = Number(childNode.props.width);
|
|
2219
|
+
}
|
|
2220
|
+
if (hasExplicitHeight) {
|
|
2221
|
+
childHeight = Number(childNode.props.height);
|
|
2222
|
+
}
|
|
2223
|
+
childWidths.push(childWidth);
|
|
2224
|
+
explicitHeightFlags.push(hasExplicitHeight);
|
|
2225
|
+
});
|
|
2226
|
+
const totalGapWidth = gap * Math.max(0, children.length - 1);
|
|
2227
|
+
if (blockButtonIndices.size > 0) {
|
|
2228
|
+
const fixedWidth = childWidths.reduce((sum, w, idx) => {
|
|
2229
|
+
return blockButtonIndices.has(idx) ? sum : sum + w;
|
|
2230
|
+
}, 0);
|
|
2231
|
+
const remainingWidth = width - totalGapWidth - fixedWidth;
|
|
2232
|
+
const widthPerBlock = Math.max(1, remainingWidth / blockButtonIndices.size);
|
|
2233
|
+
blockButtonIndices.forEach((index) => {
|
|
2234
|
+
childWidths[index] = widthPerBlock;
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
children.forEach((childRef, index) => {
|
|
2238
|
+
const childNode = this.nodes[childRef.ref];
|
|
2239
|
+
let childHeight = this.getComponentHeight();
|
|
2240
|
+
const childWidth = childWidths[index];
|
|
2241
|
+
if (explicitHeightFlags[index] && childNode?.kind === "component") {
|
|
2242
|
+
childHeight = Number(childNode.props.height);
|
|
2178
2243
|
} else if (childNode?.kind === "container") {
|
|
2179
2244
|
childHeight = this.calculateContainerHeight(childNode, childWidth);
|
|
2180
2245
|
} else if (childNode?.kind === "component") {
|
|
2181
2246
|
childHeight = this.getIntrinsicComponentHeight(childNode, childWidth);
|
|
2182
2247
|
}
|
|
2183
|
-
childWidths.push(childWidth);
|
|
2184
2248
|
stackHeight = Math.max(stackHeight, childHeight);
|
|
2185
2249
|
});
|
|
2186
2250
|
const totalChildWidth = childWidths.reduce((sum, w) => sum + w, 0);
|
|
2187
|
-
const totalGapWidth = gap * Math.max(0, children.length - 1);
|
|
2188
2251
|
const totalContentWidth = totalChildWidth + totalGapWidth;
|
|
2189
2252
|
let startX = x;
|
|
2190
2253
|
if (align === "center") {
|
|
@@ -2460,12 +2523,13 @@ var LayoutEngine = class {
|
|
|
2460
2523
|
getButtonMetricsForDensity() {
|
|
2461
2524
|
switch (this.style.density) {
|
|
2462
2525
|
case "compact":
|
|
2463
|
-
return { fontSize: 12, paddingX:
|
|
2464
|
-
case "comfortable":
|
|
2465
|
-
return { fontSize: 16, paddingX: 16 };
|
|
2526
|
+
return { fontSize: 12, paddingX: 12 };
|
|
2466
2527
|
case "normal":
|
|
2528
|
+
return { fontSize: 14, paddingX: 18 };
|
|
2529
|
+
case "comfortable":
|
|
2530
|
+
return { fontSize: 16, paddingX: 24 };
|
|
2467
2531
|
default:
|
|
2468
|
-
return { fontSize: 14, paddingX:
|
|
2532
|
+
return { fontSize: 14, paddingX: 18 };
|
|
2469
2533
|
}
|
|
2470
2534
|
}
|
|
2471
2535
|
getHeadingMetricsForDensity(level) {
|
|
@@ -2718,6 +2782,15 @@ var LayoutEngine = class {
|
|
|
2718
2782
|
}
|
|
2719
2783
|
return width;
|
|
2720
2784
|
}
|
|
2785
|
+
parseBooleanProp(value, fallback = false) {
|
|
2786
|
+
if (typeof value === "boolean") return value;
|
|
2787
|
+
if (typeof value === "string") {
|
|
2788
|
+
const normalized = value.toLowerCase().trim();
|
|
2789
|
+
if (normalized === "true") return true;
|
|
2790
|
+
if (normalized === "false") return false;
|
|
2791
|
+
}
|
|
2792
|
+
return fallback;
|
|
2793
|
+
}
|
|
2721
2794
|
adjustNodeYPositions(nodeId, deltaY) {
|
|
2722
2795
|
const node = this.nodes[nodeId];
|
|
2723
2796
|
if (!node) return;
|
|
@@ -3359,8 +3432,8 @@ var DENSITY_TOKENS = {
|
|
|
3359
3432
|
xl: 12
|
|
3360
3433
|
},
|
|
3361
3434
|
button: {
|
|
3362
|
-
paddingX:
|
|
3363
|
-
paddingY:
|
|
3435
|
+
paddingX: 12,
|
|
3436
|
+
paddingY: 6,
|
|
3364
3437
|
radius: 4,
|
|
3365
3438
|
fontSize: 12,
|
|
3366
3439
|
fontWeight: 500
|
|
@@ -3407,8 +3480,8 @@ var DENSITY_TOKENS = {
|
|
|
3407
3480
|
xl: 24
|
|
3408
3481
|
},
|
|
3409
3482
|
button: {
|
|
3410
|
-
paddingX:
|
|
3411
|
-
paddingY:
|
|
3483
|
+
paddingX: 18,
|
|
3484
|
+
paddingY: 9,
|
|
3412
3485
|
radius: 6,
|
|
3413
3486
|
fontSize: 14,
|
|
3414
3487
|
fontWeight: 500
|
|
@@ -3455,8 +3528,8 @@ var DENSITY_TOKENS = {
|
|
|
3455
3528
|
xl: 32
|
|
3456
3529
|
},
|
|
3457
3530
|
button: {
|
|
3458
|
-
paddingX:
|
|
3459
|
-
paddingY:
|
|
3531
|
+
paddingX: 24,
|
|
3532
|
+
paddingY: 12,
|
|
3460
3533
|
radius: 8,
|
|
3461
3534
|
fontSize: 16,
|
|
3462
3535
|
fontWeight: 500
|
|
@@ -3527,6 +3600,7 @@ var SVGRenderer = class {
|
|
|
3527
3600
|
constructor(ir, layout, options) {
|
|
3528
3601
|
this.renderedNodeIds = /* @__PURE__ */ new Set();
|
|
3529
3602
|
this.fontFamily = "system-ui, -apple-system, sans-serif";
|
|
3603
|
+
this.parentContainerByChildId = /* @__PURE__ */ new Map();
|
|
3530
3604
|
this.ir = ir;
|
|
3531
3605
|
this.layout = layout;
|
|
3532
3606
|
this.selectedScreenName = options?.screenName;
|
|
@@ -3541,6 +3615,7 @@ var SVGRenderer = class {
|
|
|
3541
3615
|
};
|
|
3542
3616
|
this.renderTheme = THEMES[this.options.theme];
|
|
3543
3617
|
this.colorResolver = new ColorResolver();
|
|
3618
|
+
this.buildParentContainerIndex();
|
|
3544
3619
|
if (ir.project.mocks && Object.keys(ir.project.mocks).length > 0) {
|
|
3545
3620
|
MockDataGenerator.setCustomMocks(ir.project.mocks);
|
|
3546
3621
|
}
|
|
@@ -3744,16 +3819,14 @@ var SVGRenderer = class {
|
|
|
3744
3819
|
renderButton(node, pos) {
|
|
3745
3820
|
const text = String(node.props.text || "Button");
|
|
3746
3821
|
const variant = String(node.props.variant || "default");
|
|
3822
|
+
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
3747
3823
|
const radius = this.tokens.button.radius;
|
|
3748
3824
|
const fontSize = this.tokens.button.fontSize;
|
|
3749
3825
|
const fontWeight = this.tokens.button.fontWeight;
|
|
3750
3826
|
const paddingX = this.tokens.button.paddingX;
|
|
3751
3827
|
const paddingY = this.tokens.button.paddingY;
|
|
3752
3828
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
3753
|
-
const buttonWidth = this.clampControlWidth(
|
|
3754
|
-
Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60),
|
|
3755
|
-
pos.width
|
|
3756
|
-
);
|
|
3829
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60), pos.width);
|
|
3757
3830
|
const buttonHeight = fontSize + paddingY * 2;
|
|
3758
3831
|
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
3759
3832
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
@@ -5266,6 +5339,27 @@ var SVGRenderer = class {
|
|
|
5266
5339
|
}
|
|
5267
5340
|
return fallback;
|
|
5268
5341
|
}
|
|
5342
|
+
shouldButtonFillAvailableWidth(node) {
|
|
5343
|
+
if (this.parseBooleanProp(node.props.block, false)) {
|
|
5344
|
+
return true;
|
|
5345
|
+
}
|
|
5346
|
+
const parent = this.parentContainerByChildId.get(node.id);
|
|
5347
|
+
if (!parent || parent.containerType !== "stack") {
|
|
5348
|
+
return false;
|
|
5349
|
+
}
|
|
5350
|
+
const direction = String(parent.params.direction || "vertical");
|
|
5351
|
+
const align = parent.style.align || "justify";
|
|
5352
|
+
return direction === "horizontal" && align === "justify";
|
|
5353
|
+
}
|
|
5354
|
+
buildParentContainerIndex() {
|
|
5355
|
+
this.parentContainerByChildId.clear();
|
|
5356
|
+
Object.values(this.ir.project.nodes).forEach((node) => {
|
|
5357
|
+
if (node.kind !== "container") return;
|
|
5358
|
+
node.children.forEach((childRef) => {
|
|
5359
|
+
this.parentContainerByChildId.set(childRef.ref, node);
|
|
5360
|
+
});
|
|
5361
|
+
});
|
|
5362
|
+
}
|
|
5269
5363
|
escapeXml(text) {
|
|
5270
5364
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5271
5365
|
}
|
|
@@ -5301,12 +5395,13 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5301
5395
|
renderButton(node, pos) {
|
|
5302
5396
|
const text = String(node.props.text || "Button");
|
|
5303
5397
|
const variant = String(node.props.variant || "default");
|
|
5398
|
+
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5304
5399
|
const radius = this.tokens.button.radius;
|
|
5305
5400
|
const fontSize = this.tokens.button.fontSize;
|
|
5306
5401
|
const paddingX = this.tokens.button.paddingX;
|
|
5307
5402
|
const paddingY = this.tokens.button.paddingY;
|
|
5308
5403
|
const textWidth = text.length * fontSize * 0.6;
|
|
5309
|
-
const buttonWidth = this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5404
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5310
5405
|
const buttonHeight = fontSize + paddingY * 2;
|
|
5311
5406
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5312
5407
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
@@ -5951,13 +6046,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
5951
6046
|
renderButton(node, pos) {
|
|
5952
6047
|
const text = String(node.props.text || "Button");
|
|
5953
6048
|
const variant = String(node.props.variant || "default");
|
|
6049
|
+
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5954
6050
|
const radius = this.tokens.button.radius;
|
|
5955
6051
|
const fontSize = this.tokens.button.fontSize;
|
|
5956
6052
|
const fontWeight = this.tokens.button.fontWeight;
|
|
5957
6053
|
const paddingX = this.tokens.button.paddingX;
|
|
5958
6054
|
const paddingY = this.tokens.button.paddingY;
|
|
5959
6055
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
5960
|
-
const buttonWidth = this.clampControlWidth(Math.max(idealTextWidth + paddingX * 2, 60), pos.width);
|
|
6056
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + paddingX * 2, 60), pos.width);
|
|
5961
6057
|
const buttonHeight = fontSize + paddingY * 2;
|
|
5962
6058
|
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
5963
6059
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
package/dist/index.d.cts
CHANGED
|
@@ -408,6 +408,7 @@ declare class LayoutEngine {
|
|
|
408
408
|
private calculateChildHeight;
|
|
409
409
|
private calculateChildWidth;
|
|
410
410
|
private estimateTextWidth;
|
|
411
|
+
private parseBooleanProp;
|
|
411
412
|
private adjustNodeYPositions;
|
|
412
413
|
}
|
|
413
414
|
declare function calculateLayout(ir: IRContract): LayoutResult;
|
|
@@ -561,6 +562,7 @@ declare class SVGRenderer {
|
|
|
561
562
|
protected renderedNodeIds: Set<string>;
|
|
562
563
|
protected colorResolver: ColorResolver;
|
|
563
564
|
protected fontFamily: string;
|
|
565
|
+
private parentContainerByChildId;
|
|
564
566
|
constructor(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions);
|
|
565
567
|
/**
|
|
566
568
|
* Get list of available screens in the project
|
|
@@ -691,6 +693,8 @@ declare class SVGRenderer {
|
|
|
691
693
|
};
|
|
692
694
|
};
|
|
693
695
|
protected parseBooleanProp(value: unknown, fallback?: boolean): boolean;
|
|
696
|
+
protected shouldButtonFillAvailableWidth(node: IRComponentNode): boolean;
|
|
697
|
+
private buildParentContainerIndex;
|
|
694
698
|
protected escapeXml(text: string): string;
|
|
695
699
|
/**
|
|
696
700
|
* Get data-node-id attribute string for SVG elements
|
package/dist/index.d.ts
CHANGED
|
@@ -408,6 +408,7 @@ declare class LayoutEngine {
|
|
|
408
408
|
private calculateChildHeight;
|
|
409
409
|
private calculateChildWidth;
|
|
410
410
|
private estimateTextWidth;
|
|
411
|
+
private parseBooleanProp;
|
|
411
412
|
private adjustNodeYPositions;
|
|
412
413
|
}
|
|
413
414
|
declare function calculateLayout(ir: IRContract): LayoutResult;
|
|
@@ -561,6 +562,7 @@ declare class SVGRenderer {
|
|
|
561
562
|
protected renderedNodeIds: Set<string>;
|
|
562
563
|
protected colorResolver: ColorResolver;
|
|
563
564
|
protected fontFamily: string;
|
|
565
|
+
private parentContainerByChildId;
|
|
564
566
|
constructor(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions);
|
|
565
567
|
/**
|
|
566
568
|
* Get list of available screens in the project
|
|
@@ -691,6 +693,8 @@ declare class SVGRenderer {
|
|
|
691
693
|
};
|
|
692
694
|
};
|
|
693
695
|
protected parseBooleanProp(value: unknown, fallback?: boolean): boolean;
|
|
696
|
+
protected shouldButtonFillAvailableWidth(node: IRComponentNode): boolean;
|
|
697
|
+
private buildParentContainerIndex;
|
|
694
698
|
protected escapeXml(text: string): string;
|
|
695
699
|
/**
|
|
696
700
|
* Get data-node-id attribute string for SVG elements
|
package/dist/index.js
CHANGED
|
@@ -1449,12 +1449,47 @@ var DEVICE_PRESETS = {
|
|
|
1449
1449
|
category: "print",
|
|
1450
1450
|
description: "A4 portrait at 96 DPI"
|
|
1451
1451
|
},
|
|
1452
|
-
|
|
1453
|
-
name: "
|
|
1454
|
-
width:
|
|
1455
|
-
minHeight:
|
|
1452
|
+
"mobile-landscape": {
|
|
1453
|
+
name: "Mobile Landscape",
|
|
1454
|
+
width: 812,
|
|
1455
|
+
minHeight: 375,
|
|
1456
|
+
category: "mobile",
|
|
1457
|
+
description: "Mobile landscape viewport"
|
|
1458
|
+
},
|
|
1459
|
+
"mobile-min": {
|
|
1460
|
+
name: "Mobile Minimum",
|
|
1461
|
+
width: 375,
|
|
1462
|
+
minHeight: 375,
|
|
1463
|
+
category: "mobile",
|
|
1464
|
+
description: "Mobile minimum viewport"
|
|
1465
|
+
},
|
|
1466
|
+
"desktop-min": {
|
|
1467
|
+
name: "Desktop Minimum",
|
|
1468
|
+
width: 1280,
|
|
1469
|
+
minHeight: 640,
|
|
1470
|
+
category: "desktop",
|
|
1471
|
+
description: "Desktop minimum viewport"
|
|
1472
|
+
},
|
|
1473
|
+
"tablet-landscape": {
|
|
1474
|
+
name: "Tablet Landscape",
|
|
1475
|
+
width: 1024,
|
|
1476
|
+
minHeight: 768,
|
|
1477
|
+
category: "tablet",
|
|
1478
|
+
description: "Tablet landscape viewport"
|
|
1479
|
+
},
|
|
1480
|
+
"tablet-min": {
|
|
1481
|
+
name: "Tablet Minimum",
|
|
1482
|
+
width: 768,
|
|
1483
|
+
minHeight: 384,
|
|
1484
|
+
category: "tablet",
|
|
1485
|
+
description: "Tablet minimum viewport"
|
|
1486
|
+
},
|
|
1487
|
+
"print-landscape": {
|
|
1488
|
+
name: "Print A4 Landscape",
|
|
1489
|
+
width: 1123,
|
|
1490
|
+
minHeight: 794,
|
|
1456
1491
|
category: "print",
|
|
1457
|
-
description: "A4
|
|
1492
|
+
description: "A4 landscape at 96 DPI"
|
|
1458
1493
|
}
|
|
1459
1494
|
};
|
|
1460
1495
|
function resolveDevicePreset(device) {
|
|
@@ -2117,25 +2152,53 @@ var LayoutEngine = class {
|
|
|
2117
2152
|
});
|
|
2118
2153
|
} else {
|
|
2119
2154
|
const childWidths = [];
|
|
2155
|
+
const explicitHeightFlags = [];
|
|
2156
|
+
const blockButtonIndices = /* @__PURE__ */ new Set();
|
|
2120
2157
|
let stackHeight = 0;
|
|
2121
|
-
children.forEach((childRef) => {
|
|
2158
|
+
children.forEach((childRef, index) => {
|
|
2122
2159
|
const childNode = this.nodes[childRef.ref];
|
|
2123
2160
|
let childWidth = this.getIntrinsicComponentWidth(childNode);
|
|
2124
2161
|
let childHeight = this.getComponentHeight();
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2162
|
+
const hasExplicitHeight = childNode?.kind === "component" && !!childNode.props.height;
|
|
2163
|
+
const hasExplicitWidth = childNode?.kind === "component" && !!childNode.props.width;
|
|
2164
|
+
const isBlockButton = childNode?.kind === "component" && childNode.componentType === "Button" && !hasExplicitWidth && this.parseBooleanProp(childNode.props.block, false);
|
|
2165
|
+
if (isBlockButton) {
|
|
2166
|
+
childWidth = 0;
|
|
2167
|
+
blockButtonIndices.add(index);
|
|
2168
|
+
} else if (hasExplicitWidth) {
|
|
2128
2169
|
childWidth = Number(childNode.props.width);
|
|
2170
|
+
}
|
|
2171
|
+
if (hasExplicitHeight) {
|
|
2172
|
+
childHeight = Number(childNode.props.height);
|
|
2173
|
+
}
|
|
2174
|
+
childWidths.push(childWidth);
|
|
2175
|
+
explicitHeightFlags.push(hasExplicitHeight);
|
|
2176
|
+
});
|
|
2177
|
+
const totalGapWidth = gap * Math.max(0, children.length - 1);
|
|
2178
|
+
if (blockButtonIndices.size > 0) {
|
|
2179
|
+
const fixedWidth = childWidths.reduce((sum, w, idx) => {
|
|
2180
|
+
return blockButtonIndices.has(idx) ? sum : sum + w;
|
|
2181
|
+
}, 0);
|
|
2182
|
+
const remainingWidth = width - totalGapWidth - fixedWidth;
|
|
2183
|
+
const widthPerBlock = Math.max(1, remainingWidth / blockButtonIndices.size);
|
|
2184
|
+
blockButtonIndices.forEach((index) => {
|
|
2185
|
+
childWidths[index] = widthPerBlock;
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
children.forEach((childRef, index) => {
|
|
2189
|
+
const childNode = this.nodes[childRef.ref];
|
|
2190
|
+
let childHeight = this.getComponentHeight();
|
|
2191
|
+
const childWidth = childWidths[index];
|
|
2192
|
+
if (explicitHeightFlags[index] && childNode?.kind === "component") {
|
|
2193
|
+
childHeight = Number(childNode.props.height);
|
|
2129
2194
|
} else if (childNode?.kind === "container") {
|
|
2130
2195
|
childHeight = this.calculateContainerHeight(childNode, childWidth);
|
|
2131
2196
|
} else if (childNode?.kind === "component") {
|
|
2132
2197
|
childHeight = this.getIntrinsicComponentHeight(childNode, childWidth);
|
|
2133
2198
|
}
|
|
2134
|
-
childWidths.push(childWidth);
|
|
2135
2199
|
stackHeight = Math.max(stackHeight, childHeight);
|
|
2136
2200
|
});
|
|
2137
2201
|
const totalChildWidth = childWidths.reduce((sum, w) => sum + w, 0);
|
|
2138
|
-
const totalGapWidth = gap * Math.max(0, children.length - 1);
|
|
2139
2202
|
const totalContentWidth = totalChildWidth + totalGapWidth;
|
|
2140
2203
|
let startX = x;
|
|
2141
2204
|
if (align === "center") {
|
|
@@ -2411,12 +2474,13 @@ var LayoutEngine = class {
|
|
|
2411
2474
|
getButtonMetricsForDensity() {
|
|
2412
2475
|
switch (this.style.density) {
|
|
2413
2476
|
case "compact":
|
|
2414
|
-
return { fontSize: 12, paddingX:
|
|
2415
|
-
case "comfortable":
|
|
2416
|
-
return { fontSize: 16, paddingX: 16 };
|
|
2477
|
+
return { fontSize: 12, paddingX: 12 };
|
|
2417
2478
|
case "normal":
|
|
2479
|
+
return { fontSize: 14, paddingX: 18 };
|
|
2480
|
+
case "comfortable":
|
|
2481
|
+
return { fontSize: 16, paddingX: 24 };
|
|
2418
2482
|
default:
|
|
2419
|
-
return { fontSize: 14, paddingX:
|
|
2483
|
+
return { fontSize: 14, paddingX: 18 };
|
|
2420
2484
|
}
|
|
2421
2485
|
}
|
|
2422
2486
|
getHeadingMetricsForDensity(level) {
|
|
@@ -2669,6 +2733,15 @@ var LayoutEngine = class {
|
|
|
2669
2733
|
}
|
|
2670
2734
|
return width;
|
|
2671
2735
|
}
|
|
2736
|
+
parseBooleanProp(value, fallback = false) {
|
|
2737
|
+
if (typeof value === "boolean") return value;
|
|
2738
|
+
if (typeof value === "string") {
|
|
2739
|
+
const normalized = value.toLowerCase().trim();
|
|
2740
|
+
if (normalized === "true") return true;
|
|
2741
|
+
if (normalized === "false") return false;
|
|
2742
|
+
}
|
|
2743
|
+
return fallback;
|
|
2744
|
+
}
|
|
2672
2745
|
adjustNodeYPositions(nodeId, deltaY) {
|
|
2673
2746
|
const node = this.nodes[nodeId];
|
|
2674
2747
|
if (!node) return;
|
|
@@ -3310,8 +3383,8 @@ var DENSITY_TOKENS = {
|
|
|
3310
3383
|
xl: 12
|
|
3311
3384
|
},
|
|
3312
3385
|
button: {
|
|
3313
|
-
paddingX:
|
|
3314
|
-
paddingY:
|
|
3386
|
+
paddingX: 12,
|
|
3387
|
+
paddingY: 6,
|
|
3315
3388
|
radius: 4,
|
|
3316
3389
|
fontSize: 12,
|
|
3317
3390
|
fontWeight: 500
|
|
@@ -3358,8 +3431,8 @@ var DENSITY_TOKENS = {
|
|
|
3358
3431
|
xl: 24
|
|
3359
3432
|
},
|
|
3360
3433
|
button: {
|
|
3361
|
-
paddingX:
|
|
3362
|
-
paddingY:
|
|
3434
|
+
paddingX: 18,
|
|
3435
|
+
paddingY: 9,
|
|
3363
3436
|
radius: 6,
|
|
3364
3437
|
fontSize: 14,
|
|
3365
3438
|
fontWeight: 500
|
|
@@ -3406,8 +3479,8 @@ var DENSITY_TOKENS = {
|
|
|
3406
3479
|
xl: 32
|
|
3407
3480
|
},
|
|
3408
3481
|
button: {
|
|
3409
|
-
paddingX:
|
|
3410
|
-
paddingY:
|
|
3482
|
+
paddingX: 24,
|
|
3483
|
+
paddingY: 12,
|
|
3411
3484
|
radius: 8,
|
|
3412
3485
|
fontSize: 16,
|
|
3413
3486
|
fontWeight: 500
|
|
@@ -3478,6 +3551,7 @@ var SVGRenderer = class {
|
|
|
3478
3551
|
constructor(ir, layout, options) {
|
|
3479
3552
|
this.renderedNodeIds = /* @__PURE__ */ new Set();
|
|
3480
3553
|
this.fontFamily = "system-ui, -apple-system, sans-serif";
|
|
3554
|
+
this.parentContainerByChildId = /* @__PURE__ */ new Map();
|
|
3481
3555
|
this.ir = ir;
|
|
3482
3556
|
this.layout = layout;
|
|
3483
3557
|
this.selectedScreenName = options?.screenName;
|
|
@@ -3492,6 +3566,7 @@ var SVGRenderer = class {
|
|
|
3492
3566
|
};
|
|
3493
3567
|
this.renderTheme = THEMES[this.options.theme];
|
|
3494
3568
|
this.colorResolver = new ColorResolver();
|
|
3569
|
+
this.buildParentContainerIndex();
|
|
3495
3570
|
if (ir.project.mocks && Object.keys(ir.project.mocks).length > 0) {
|
|
3496
3571
|
MockDataGenerator.setCustomMocks(ir.project.mocks);
|
|
3497
3572
|
}
|
|
@@ -3695,16 +3770,14 @@ var SVGRenderer = class {
|
|
|
3695
3770
|
renderButton(node, pos) {
|
|
3696
3771
|
const text = String(node.props.text || "Button");
|
|
3697
3772
|
const variant = String(node.props.variant || "default");
|
|
3773
|
+
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
3698
3774
|
const radius = this.tokens.button.radius;
|
|
3699
3775
|
const fontSize = this.tokens.button.fontSize;
|
|
3700
3776
|
const fontWeight = this.tokens.button.fontWeight;
|
|
3701
3777
|
const paddingX = this.tokens.button.paddingX;
|
|
3702
3778
|
const paddingY = this.tokens.button.paddingY;
|
|
3703
3779
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
3704
|
-
const buttonWidth = this.clampControlWidth(
|
|
3705
|
-
Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60),
|
|
3706
|
-
pos.width
|
|
3707
|
-
);
|
|
3780
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60), pos.width);
|
|
3708
3781
|
const buttonHeight = fontSize + paddingY * 2;
|
|
3709
3782
|
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
3710
3783
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
@@ -5217,6 +5290,27 @@ var SVGRenderer = class {
|
|
|
5217
5290
|
}
|
|
5218
5291
|
return fallback;
|
|
5219
5292
|
}
|
|
5293
|
+
shouldButtonFillAvailableWidth(node) {
|
|
5294
|
+
if (this.parseBooleanProp(node.props.block, false)) {
|
|
5295
|
+
return true;
|
|
5296
|
+
}
|
|
5297
|
+
const parent = this.parentContainerByChildId.get(node.id);
|
|
5298
|
+
if (!parent || parent.containerType !== "stack") {
|
|
5299
|
+
return false;
|
|
5300
|
+
}
|
|
5301
|
+
const direction = String(parent.params.direction || "vertical");
|
|
5302
|
+
const align = parent.style.align || "justify";
|
|
5303
|
+
return direction === "horizontal" && align === "justify";
|
|
5304
|
+
}
|
|
5305
|
+
buildParentContainerIndex() {
|
|
5306
|
+
this.parentContainerByChildId.clear();
|
|
5307
|
+
Object.values(this.ir.project.nodes).forEach((node) => {
|
|
5308
|
+
if (node.kind !== "container") return;
|
|
5309
|
+
node.children.forEach((childRef) => {
|
|
5310
|
+
this.parentContainerByChildId.set(childRef.ref, node);
|
|
5311
|
+
});
|
|
5312
|
+
});
|
|
5313
|
+
}
|
|
5220
5314
|
escapeXml(text) {
|
|
5221
5315
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5222
5316
|
}
|
|
@@ -5252,12 +5346,13 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5252
5346
|
renderButton(node, pos) {
|
|
5253
5347
|
const text = String(node.props.text || "Button");
|
|
5254
5348
|
const variant = String(node.props.variant || "default");
|
|
5349
|
+
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5255
5350
|
const radius = this.tokens.button.radius;
|
|
5256
5351
|
const fontSize = this.tokens.button.fontSize;
|
|
5257
5352
|
const paddingX = this.tokens.button.paddingX;
|
|
5258
5353
|
const paddingY = this.tokens.button.paddingY;
|
|
5259
5354
|
const textWidth = text.length * fontSize * 0.6;
|
|
5260
|
-
const buttonWidth = this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5355
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5261
5356
|
const buttonHeight = fontSize + paddingY * 2;
|
|
5262
5357
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5263
5358
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
@@ -5902,13 +5997,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
5902
5997
|
renderButton(node, pos) {
|
|
5903
5998
|
const text = String(node.props.text || "Button");
|
|
5904
5999
|
const variant = String(node.props.variant || "default");
|
|
6000
|
+
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5905
6001
|
const radius = this.tokens.button.radius;
|
|
5906
6002
|
const fontSize = this.tokens.button.fontSize;
|
|
5907
6003
|
const fontWeight = this.tokens.button.fontWeight;
|
|
5908
6004
|
const paddingX = this.tokens.button.paddingX;
|
|
5909
6005
|
const paddingY = this.tokens.button.paddingY;
|
|
5910
6006
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
5911
|
-
const buttonWidth = this.clampControlWidth(Math.max(idealTextWidth + paddingX * 2, 60), pos.width);
|
|
6007
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + paddingX * 2, 60), pos.width);
|
|
5912
6008
|
const buttonHeight = fontSize + paddingY * 2;
|
|
5913
6009
|
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
5914
6010
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|