@wire-dsl/engine 0.2.3 → 0.3.0
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 +1235 -372
- package/dist/index.d.cts +34 -6
- package/dist/index.d.ts +34 -6
- package/dist/index.js +1238 -372
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -45,20 +45,21 @@ var SourceMapBuilder = class {
|
|
|
45
45
|
return nodeId;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
* Generate semantic node ID based on type and subtype
|
|
49
|
+
* Format: {type}-{subtype}-{counter} or {type}-{counter}
|
|
50
|
+
*
|
|
51
|
+
* Examples:
|
|
52
|
+
* - project → "project"
|
|
53
|
+
* - theme → "theme"
|
|
54
|
+
* - mocks → "mocks"
|
|
55
|
+
* - colors → "colors"
|
|
56
|
+
* - screen → "screen-0", "screen-1"
|
|
57
|
+
* - component Button → "component-button-0", "component-button-1"
|
|
58
|
+
* - layout stack → "layout-stack-0", "layout-stack-1"
|
|
59
|
+
* - cell → "cell-0", "cell-1"
|
|
60
|
+
* - component-definition → "define-MyButton"
|
|
61
|
+
* - layout-definition → "define-layout-MyShell"
|
|
62
|
+
*/
|
|
62
63
|
generateNodeId(type, metadata) {
|
|
63
64
|
switch (type) {
|
|
64
65
|
case "project":
|
|
@@ -94,6 +95,8 @@ var SourceMapBuilder = class {
|
|
|
94
95
|
}
|
|
95
96
|
case "component-definition":
|
|
96
97
|
return `define-${metadata?.name || "unknown"}`;
|
|
98
|
+
case "layout-definition":
|
|
99
|
+
return `define-layout-${metadata?.name || "unknown"}`;
|
|
97
100
|
default:
|
|
98
101
|
return `${type}-0`;
|
|
99
102
|
}
|
|
@@ -179,7 +182,7 @@ var SourceMapBuilder = class {
|
|
|
179
182
|
}
|
|
180
183
|
/**
|
|
181
184
|
* Calculate insertionPoints for all container nodes
|
|
182
|
-
* Container nodes: project, screen, layout, cell, component-definition
|
|
185
|
+
* Container nodes: project, screen, layout, cell, component-definition, layout-definition
|
|
183
186
|
*/
|
|
184
187
|
calculateAllInsertionPoints() {
|
|
185
188
|
const containerTypes = [
|
|
@@ -187,7 +190,8 @@ var SourceMapBuilder = class {
|
|
|
187
190
|
"screen",
|
|
188
191
|
"layout",
|
|
189
192
|
"cell",
|
|
190
|
-
"component-definition"
|
|
193
|
+
"component-definition",
|
|
194
|
+
"layout-definition"
|
|
191
195
|
];
|
|
192
196
|
for (const entry of this.entries) {
|
|
193
197
|
if (containerTypes.includes(entry.type)) {
|
|
@@ -413,19 +417,23 @@ var SourceMapBuilder = class {
|
|
|
413
417
|
};
|
|
414
418
|
|
|
415
419
|
// src/parser/index.ts
|
|
416
|
-
var Project = createToken({ name: "Project", pattern: /project/ });
|
|
417
|
-
var Screen = createToken({ name: "Screen", pattern: /screen/ });
|
|
418
|
-
var Layout = createToken({ name: "Layout", pattern: /layout/ });
|
|
419
|
-
var Component = createToken({ name: "Component", pattern: /component/ });
|
|
420
|
+
var Project = createToken({ name: "Project", pattern: /project\b/ });
|
|
421
|
+
var Screen = createToken({ name: "Screen", pattern: /screen\b/ });
|
|
422
|
+
var Layout = createToken({ name: "Layout", pattern: /layout\b/ });
|
|
423
|
+
var Component = createToken({ name: "Component", pattern: /component\b/ });
|
|
420
424
|
var ComponentKeyword = createToken({
|
|
421
425
|
name: "ComponentKeyword",
|
|
422
426
|
pattern: /Component\b/
|
|
423
427
|
});
|
|
424
|
-
var
|
|
425
|
-
|
|
426
|
-
|
|
428
|
+
var LayoutKeyword = createToken({
|
|
429
|
+
name: "LayoutKeyword",
|
|
430
|
+
pattern: /Layout\b/
|
|
431
|
+
});
|
|
432
|
+
var Define = createToken({ name: "Define", pattern: /define\b/ });
|
|
433
|
+
var Style = createToken({ name: "Style", pattern: /style\b/ });
|
|
434
|
+
var Mocks = createToken({ name: "Mocks", pattern: /mocks\b/ });
|
|
427
435
|
var Colors = createToken({ name: "Colors", pattern: /colors(?=\s*\{)/ });
|
|
428
|
-
var Cell = createToken({ name: "Cell", pattern: /cell/ });
|
|
436
|
+
var Cell = createToken({ name: "Cell", pattern: /cell\b/ });
|
|
429
437
|
var LCurly = createToken({ name: "LCurly", pattern: /{/ });
|
|
430
438
|
var RCurly = createToken({ name: "RCurly", pattern: /}/ });
|
|
431
439
|
var LParen = createToken({ name: "LParen", pattern: /\(/ });
|
|
@@ -472,6 +480,7 @@ var allTokens = [
|
|
|
472
480
|
Project,
|
|
473
481
|
Screen,
|
|
474
482
|
Layout,
|
|
483
|
+
LayoutKeyword,
|
|
475
484
|
ComponentKeyword,
|
|
476
485
|
Component,
|
|
477
486
|
Define,
|
|
@@ -504,6 +513,7 @@ var WireDSLParser = class extends CstParser {
|
|
|
504
513
|
this.MANY(() => {
|
|
505
514
|
this.OR([
|
|
506
515
|
{ ALT: () => this.SUBRULE(this.definedComponent) },
|
|
516
|
+
{ ALT: () => this.SUBRULE(this.definedLayout) },
|
|
507
517
|
{ ALT: () => this.SUBRULE(this.styleDecl) },
|
|
508
518
|
{ ALT: () => this.SUBRULE(this.mocksDecl) },
|
|
509
519
|
{ ALT: () => this.SUBRULE(this.colorsDecl) },
|
|
@@ -572,6 +582,15 @@ var WireDSLParser = class extends CstParser {
|
|
|
572
582
|
]);
|
|
573
583
|
this.CONSUME(RCurly);
|
|
574
584
|
});
|
|
585
|
+
// define Layout "ScreenShell" { layout split { ... } }
|
|
586
|
+
this.definedLayout = this.RULE("definedLayout", () => {
|
|
587
|
+
this.CONSUME(Define);
|
|
588
|
+
this.CONSUME(LayoutKeyword, { LABEL: "layoutKeyword" });
|
|
589
|
+
this.CONSUME(StringLiteral, { LABEL: "layoutName" });
|
|
590
|
+
this.CONSUME(LCurly);
|
|
591
|
+
this.SUBRULE(this.layout);
|
|
592
|
+
this.CONSUME(RCurly);
|
|
593
|
+
});
|
|
575
594
|
// screen Main(background: white) { ... }
|
|
576
595
|
this.screen = this.RULE("screen", () => {
|
|
577
596
|
this.CONSUME(Screen);
|
|
@@ -660,6 +679,7 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
660
679
|
const mocks = {};
|
|
661
680
|
const colors = {};
|
|
662
681
|
const definedComponents = [];
|
|
682
|
+
const definedLayouts = [];
|
|
663
683
|
const screens = [];
|
|
664
684
|
if (ctx.styleDecl && ctx.styleDecl.length > 0) {
|
|
665
685
|
const styleBlock = this.visit(ctx.styleDecl[0]);
|
|
@@ -678,6 +698,11 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
678
698
|
definedComponents.push(this.visit(comp));
|
|
679
699
|
});
|
|
680
700
|
}
|
|
701
|
+
if (ctx.definedLayout) {
|
|
702
|
+
ctx.definedLayout.forEach((layoutDef) => {
|
|
703
|
+
definedLayouts.push(this.visit(layoutDef));
|
|
704
|
+
});
|
|
705
|
+
}
|
|
681
706
|
if (ctx.screen) {
|
|
682
707
|
ctx.screen.forEach((screen) => {
|
|
683
708
|
screens.push(this.visit(screen));
|
|
@@ -690,6 +715,7 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
690
715
|
mocks,
|
|
691
716
|
colors,
|
|
692
717
|
definedComponents,
|
|
718
|
+
definedLayouts,
|
|
693
719
|
screens
|
|
694
720
|
};
|
|
695
721
|
}
|
|
@@ -754,6 +780,15 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
754
780
|
body
|
|
755
781
|
};
|
|
756
782
|
}
|
|
783
|
+
definedLayout(ctx) {
|
|
784
|
+
const name = ctx.layoutName[0].image.slice(1, -1);
|
|
785
|
+
const body = this.visit(ctx.layout[0]);
|
|
786
|
+
return {
|
|
787
|
+
type: "definedLayout",
|
|
788
|
+
name,
|
|
789
|
+
body
|
|
790
|
+
};
|
|
791
|
+
}
|
|
757
792
|
screen(ctx) {
|
|
758
793
|
const params = ctx.paramList ? this.visit(ctx.paramList[0]) : {};
|
|
759
794
|
return {
|
|
@@ -884,6 +919,7 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
884
919
|
constructor(sourceMapBuilder) {
|
|
885
920
|
super();
|
|
886
921
|
this.definedComponentNames = /* @__PURE__ */ new Set();
|
|
922
|
+
this.definedLayoutNames = /* @__PURE__ */ new Set();
|
|
887
923
|
this.sourceMapBuilder = sourceMapBuilder;
|
|
888
924
|
}
|
|
889
925
|
project(ctx) {
|
|
@@ -892,6 +928,7 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
892
928
|
const mocks = {};
|
|
893
929
|
const colors = {};
|
|
894
930
|
const definedComponents = [];
|
|
931
|
+
const definedLayouts = [];
|
|
895
932
|
const screens = [];
|
|
896
933
|
const tokens = {
|
|
897
934
|
keyword: ctx.Project[0],
|
|
@@ -906,6 +943,8 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
906
943
|
colors: {},
|
|
907
944
|
definedComponents: [],
|
|
908
945
|
// Will be filled after push
|
|
946
|
+
definedLayouts: [],
|
|
947
|
+
// Will be filled after push
|
|
909
948
|
screens: []
|
|
910
949
|
// Will be filled after push
|
|
911
950
|
};
|
|
@@ -935,6 +974,11 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
935
974
|
ast.definedComponents.push(this.visit(comp));
|
|
936
975
|
});
|
|
937
976
|
}
|
|
977
|
+
if (ctx.definedLayout) {
|
|
978
|
+
ctx.definedLayout.forEach((layoutDef) => {
|
|
979
|
+
ast.definedLayouts.push(this.visit(layoutDef));
|
|
980
|
+
});
|
|
981
|
+
}
|
|
938
982
|
if (ctx.screen) {
|
|
939
983
|
ctx.screen.forEach((screen) => {
|
|
940
984
|
ast.screens.push(this.visit(screen));
|
|
@@ -1179,6 +1223,35 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
1179
1223
|
}
|
|
1180
1224
|
return ast;
|
|
1181
1225
|
}
|
|
1226
|
+
definedLayout(ctx) {
|
|
1227
|
+
const name = ctx.layoutName[0].image.slice(1, -1);
|
|
1228
|
+
this.definedLayoutNames.add(name);
|
|
1229
|
+
const tokens = {
|
|
1230
|
+
keyword: ctx.Define[0],
|
|
1231
|
+
name: ctx.layoutName[0],
|
|
1232
|
+
body: ctx.RCurly[0]
|
|
1233
|
+
};
|
|
1234
|
+
const ast = {
|
|
1235
|
+
type: "definedLayout",
|
|
1236
|
+
name,
|
|
1237
|
+
body: {}
|
|
1238
|
+
// Will be filled after push
|
|
1239
|
+
};
|
|
1240
|
+
if (this.sourceMapBuilder) {
|
|
1241
|
+
const nodeId = this.sourceMapBuilder.addNode(
|
|
1242
|
+
"layout-definition",
|
|
1243
|
+
tokens,
|
|
1244
|
+
{ name }
|
|
1245
|
+
);
|
|
1246
|
+
ast._meta = { nodeId };
|
|
1247
|
+
this.sourceMapBuilder.pushParent(nodeId);
|
|
1248
|
+
}
|
|
1249
|
+
ast.body = this.visit(ctx.layout[0]);
|
|
1250
|
+
if (this.sourceMapBuilder) {
|
|
1251
|
+
this.sourceMapBuilder.popParent();
|
|
1252
|
+
}
|
|
1253
|
+
return ast;
|
|
1254
|
+
}
|
|
1182
1255
|
// Override styleDecl to capture style block in SourceMap
|
|
1183
1256
|
styleDecl(ctx) {
|
|
1184
1257
|
const style = {};
|
|
@@ -1358,6 +1431,13 @@ function buildLayoutRulesFromMetadata() {
|
|
|
1358
1431
|
var BUILT_IN_COMPONENTS = new Set(Object.keys(COMPONENTS));
|
|
1359
1432
|
var COMPONENT_RULES = buildComponentRulesFromMetadata();
|
|
1360
1433
|
var LAYOUT_RULES = buildLayoutRulesFromMetadata();
|
|
1434
|
+
var BUILT_IN_LAYOUTS = new Set(Object.keys(LAYOUTS));
|
|
1435
|
+
function isPascalCaseIdentifier(name) {
|
|
1436
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
1437
|
+
}
|
|
1438
|
+
function isValidDefinedLayoutName(name) {
|
|
1439
|
+
return /^[a-z][a-z0-9_]*$/.test(name);
|
|
1440
|
+
}
|
|
1361
1441
|
function toFallbackRange() {
|
|
1362
1442
|
return {
|
|
1363
1443
|
start: { line: 1, column: 0 },
|
|
@@ -1449,6 +1529,17 @@ function isBooleanLike(value) {
|
|
|
1449
1529
|
const normalized = String(value).trim().toLowerCase();
|
|
1450
1530
|
return normalized === "true" || normalized === "false";
|
|
1451
1531
|
}
|
|
1532
|
+
function parseBooleanLike(value, fallback = false) {
|
|
1533
|
+
if (typeof value === "number") {
|
|
1534
|
+
if (value === 1) return true;
|
|
1535
|
+
if (value === 0) return false;
|
|
1536
|
+
return fallback;
|
|
1537
|
+
}
|
|
1538
|
+
const normalized = String(value).trim().toLowerCase();
|
|
1539
|
+
if (normalized === "true") return true;
|
|
1540
|
+
if (normalized === "false") return false;
|
|
1541
|
+
return fallback;
|
|
1542
|
+
}
|
|
1452
1543
|
function getPropertyRange(entry, propertyName, mode = "full") {
|
|
1453
1544
|
const prop = entry?.properties?.[propertyName];
|
|
1454
1545
|
if (!prop) return entry?.range || toFallbackRange();
|
|
@@ -1466,21 +1557,55 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1466
1557
|
const diagnostics = [];
|
|
1467
1558
|
const sourceMapByNodeId = new Map(sourceMap.map((entry) => [entry.nodeId, entry]));
|
|
1468
1559
|
const definedComponents = new Set(ast.definedComponents.map((dc) => dc.name));
|
|
1469
|
-
const
|
|
1560
|
+
const definedLayouts = new Set(ast.definedLayouts.map((dl) => dl.name));
|
|
1561
|
+
const emitDiagnostic = (severity, message, code, range, nodeId, suggestion) => {
|
|
1470
1562
|
diagnostics.push({
|
|
1471
1563
|
message,
|
|
1472
1564
|
code,
|
|
1473
|
-
severity
|
|
1565
|
+
severity,
|
|
1474
1566
|
phase: "semantic",
|
|
1475
1567
|
range,
|
|
1476
1568
|
nodeId,
|
|
1477
1569
|
suggestion
|
|
1478
1570
|
});
|
|
1479
1571
|
};
|
|
1480
|
-
const
|
|
1572
|
+
const emitWarning = (message, code, range, nodeId, suggestion) => emitDiagnostic("warning", message, code, range, nodeId, suggestion);
|
|
1573
|
+
const emitError = (message, code, range, nodeId, suggestion) => emitDiagnostic("error", message, code, range, nodeId, suggestion);
|
|
1574
|
+
const countChildrenSlots = (layout) => {
|
|
1575
|
+
let count = 0;
|
|
1576
|
+
for (const child of layout.children) {
|
|
1577
|
+
if (child.type === "component") {
|
|
1578
|
+
if (child.componentType === "Children") count += 1;
|
|
1579
|
+
} else if (child.type === "layout") {
|
|
1580
|
+
count += countChildrenSlots(child);
|
|
1581
|
+
} else if (child.type === "cell") {
|
|
1582
|
+
for (const cellChild of child.children) {
|
|
1583
|
+
if (cellChild.type === "component") {
|
|
1584
|
+
if (cellChild.componentType === "Children") count += 1;
|
|
1585
|
+
} else if (cellChild.type === "layout") {
|
|
1586
|
+
count += countChildrenSlots(cellChild);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return count;
|
|
1592
|
+
};
|
|
1593
|
+
const checkComponent = (component, insideDefinedLayout) => {
|
|
1481
1594
|
const nodeId = component._meta?.nodeId;
|
|
1482
1595
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1483
1596
|
const componentType = component.componentType;
|
|
1597
|
+
if (componentType === "Children") {
|
|
1598
|
+
if (!insideDefinedLayout) {
|
|
1599
|
+
emitError(
|
|
1600
|
+
'Component "Children" can only be used inside a define Layout body.',
|
|
1601
|
+
"CHILDREN_SLOT_OUTSIDE_LAYOUT_DEFINITION",
|
|
1602
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1603
|
+
nodeId,
|
|
1604
|
+
"Move this placeholder into a define Layout block."
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1484
1609
|
if (!BUILT_IN_COMPONENTS.has(componentType) && !definedComponents.has(componentType)) {
|
|
1485
1610
|
emitWarning(
|
|
1486
1611
|
`Component "${componentType}" is not a built-in component and has no local definition.`,
|
|
@@ -1521,7 +1646,8 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1521
1646
|
const enumValues = rules.enumProps?.[propName];
|
|
1522
1647
|
if (enumValues) {
|
|
1523
1648
|
const normalizedValue = String(propValue);
|
|
1524
|
-
|
|
1649
|
+
const isCustomVariantFromColors = propName === "variant" && !enumValues.includes(normalizedValue) && Object.prototype.hasOwnProperty.call(ast.colors || {}, normalizedValue);
|
|
1650
|
+
if (!enumValues.includes(normalizedValue) && !isCustomVariantFromColors) {
|
|
1525
1651
|
emitWarning(
|
|
1526
1652
|
`Invalid value "${normalizedValue}" for property "${propName}" in component "${componentType}".`,
|
|
1527
1653
|
"COMPONENT_INVALID_PROPERTY_VALUE",
|
|
@@ -1541,11 +1667,40 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1541
1667
|
);
|
|
1542
1668
|
}
|
|
1543
1669
|
}
|
|
1670
|
+
if (componentType === "Table") {
|
|
1671
|
+
const hasCaption = String(component.props.caption || "").trim().length > 0;
|
|
1672
|
+
const hasPagination = parseBooleanLike(component.props.pagination ?? "false", false);
|
|
1673
|
+
if (hasCaption && hasPagination) {
|
|
1674
|
+
const rawPaginationAlign = String(component.props.paginationAlign || "right");
|
|
1675
|
+
const paginationAlign = rawPaginationAlign === "left" || rawPaginationAlign === "center" || rawPaginationAlign === "right" ? rawPaginationAlign : "right";
|
|
1676
|
+
const rawCaptionAlign = String(component.props.captionAlign || "");
|
|
1677
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
1678
|
+
if (captionAlign === paginationAlign) {
|
|
1679
|
+
emitWarning(
|
|
1680
|
+
`Table footer collision: "captionAlign" and "paginationAlign" both resolve to "${captionAlign}".`,
|
|
1681
|
+
"TABLE_FOOTER_ALIGNMENT_COLLISION",
|
|
1682
|
+
entry?.range || toFallbackRange(),
|
|
1683
|
+
nodeId,
|
|
1684
|
+
"Use different alignments to avoid visual overlap."
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1544
1689
|
};
|
|
1545
|
-
const checkLayout = (layout) => {
|
|
1690
|
+
const checkLayout = (layout, insideDefinedLayout) => {
|
|
1546
1691
|
const nodeId = layout._meta?.nodeId;
|
|
1547
1692
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1548
1693
|
const rules = LAYOUT_RULES[layout.layoutType];
|
|
1694
|
+
const isDefinedLayoutUsage = definedLayouts.has(layout.layoutType);
|
|
1695
|
+
if (isDefinedLayoutUsage && layout.children.length !== 1) {
|
|
1696
|
+
emitError(
|
|
1697
|
+
`Layout "${layout.layoutType}" expects exactly one child for its Children slot.`,
|
|
1698
|
+
"LAYOUT_DEFINITION_CHILDREN_ARITY",
|
|
1699
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1700
|
+
nodeId,
|
|
1701
|
+
"Provide exactly one nested child block when using this layout."
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1549
1704
|
if (layout.children.length === 0) {
|
|
1550
1705
|
emitWarning(
|
|
1551
1706
|
`Layout "${layout.layoutType}" is empty.`,
|
|
@@ -1555,7 +1710,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1555
1710
|
"Add at least one child: component, layout, or cell."
|
|
1556
1711
|
);
|
|
1557
1712
|
}
|
|
1558
|
-
if (!rules) {
|
|
1713
|
+
if (!rules && !isDefinedLayoutUsage) {
|
|
1559
1714
|
emitWarning(
|
|
1560
1715
|
`Layout type "${layout.layoutType}" is not recognized by semantic validation rules.`,
|
|
1561
1716
|
"LAYOUT_UNKNOWN_TYPE",
|
|
@@ -1563,7 +1718,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1563
1718
|
nodeId,
|
|
1564
1719
|
`Use one of: ${Object.keys(LAYOUT_RULES).join(", ")}.`
|
|
1565
1720
|
);
|
|
1566
|
-
} else {
|
|
1721
|
+
} else if (rules) {
|
|
1567
1722
|
const missingRequiredParams = getMissingRequiredNames(rules.requiredParams, layout.params);
|
|
1568
1723
|
if (missingRequiredParams.length > 0) {
|
|
1569
1724
|
emitWarning(
|
|
@@ -1576,6 +1731,16 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1576
1731
|
}
|
|
1577
1732
|
const allowed = new Set(rules.allowedParams);
|
|
1578
1733
|
for (const [paramName, paramValue] of Object.entries(layout.params)) {
|
|
1734
|
+
if (layout.layoutType === "split" && paramName === "sidebar") {
|
|
1735
|
+
emitError(
|
|
1736
|
+
'Split parameter "sidebar" was removed. Use "left" or "right" instead.',
|
|
1737
|
+
"LAYOUT_SPLIT_SIDEBAR_DEPRECATED",
|
|
1738
|
+
getPropertyRange(entry, paramName, "name"),
|
|
1739
|
+
nodeId,
|
|
1740
|
+
"Example: layout split(left: 260) { ... }"
|
|
1741
|
+
);
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1579
1744
|
if (!allowed.has(paramName)) {
|
|
1580
1745
|
emitWarning(
|
|
1581
1746
|
`Parameter "${paramName}" is not recognized for layout "${layout.layoutType}".`,
|
|
@@ -1614,31 +1779,62 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1614
1779
|
);
|
|
1615
1780
|
}
|
|
1616
1781
|
}
|
|
1617
|
-
if (layout.layoutType === "split" && paramName === "
|
|
1618
|
-
const
|
|
1619
|
-
if (!Number.isFinite(
|
|
1782
|
+
if (layout.layoutType === "split" && (paramName === "left" || paramName === "right")) {
|
|
1783
|
+
const splitSize = Number(paramValue);
|
|
1784
|
+
if (!Number.isFinite(splitSize) || splitSize <= 0) {
|
|
1620
1785
|
emitWarning(
|
|
1621
|
-
|
|
1622
|
-
"
|
|
1786
|
+
`Split "${paramName}" must be a positive number. Falling back to 250.`,
|
|
1787
|
+
"LAYOUT_SPLIT_WIDTH_INVALID",
|
|
1623
1788
|
getPropertyRange(entry, paramName, "value"),
|
|
1624
1789
|
nodeId,
|
|
1625
|
-
|
|
1790
|
+
`Use a value like ${paramName}: 260.`
|
|
1626
1791
|
);
|
|
1627
1792
|
}
|
|
1628
1793
|
}
|
|
1629
1794
|
}
|
|
1795
|
+
if (layout.layoutType === "split") {
|
|
1796
|
+
const hasLeft = layout.params.left !== void 0;
|
|
1797
|
+
const hasRight = layout.params.right !== void 0;
|
|
1798
|
+
if (!hasLeft && !hasRight) {
|
|
1799
|
+
emitError(
|
|
1800
|
+
'Split layout requires exactly one fixed side width: "left" or "right".',
|
|
1801
|
+
"LAYOUT_SPLIT_SIDE_REQUIRED",
|
|
1802
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1803
|
+
nodeId,
|
|
1804
|
+
"Add either left: <number> or right: <number>."
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
if (hasLeft && hasRight) {
|
|
1808
|
+
emitError(
|
|
1809
|
+
'Split layout accepts only one fixed side width: use either "left" or "right", not both.',
|
|
1810
|
+
"LAYOUT_SPLIT_SIDE_CONFLICT",
|
|
1811
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1812
|
+
nodeId,
|
|
1813
|
+
"Remove one of the two parameters."
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
if (layout.children.length !== 2) {
|
|
1817
|
+
emitError(
|
|
1818
|
+
`Split layout requires exactly 2 children, received ${layout.children.length}.`,
|
|
1819
|
+
"LAYOUT_SPLIT_CHILDREN_ARITY",
|
|
1820
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1821
|
+
nodeId,
|
|
1822
|
+
"Provide exactly two child blocks (left/right content)."
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1630
1826
|
}
|
|
1631
1827
|
for (const child of layout.children) {
|
|
1632
1828
|
if (child.type === "component") {
|
|
1633
|
-
checkComponent(child);
|
|
1829
|
+
checkComponent(child, insideDefinedLayout);
|
|
1634
1830
|
} else if (child.type === "layout") {
|
|
1635
|
-
checkLayout(child);
|
|
1831
|
+
checkLayout(child, insideDefinedLayout);
|
|
1636
1832
|
} else if (child.type === "cell") {
|
|
1637
|
-
checkCell(child);
|
|
1833
|
+
checkCell(child, insideDefinedLayout);
|
|
1638
1834
|
}
|
|
1639
1835
|
}
|
|
1640
1836
|
};
|
|
1641
|
-
const checkCell = (cell) => {
|
|
1837
|
+
const checkCell = (cell, insideDefinedLayout) => {
|
|
1642
1838
|
const nodeId = cell._meta?.nodeId;
|
|
1643
1839
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1644
1840
|
if (cell.props.span !== void 0) {
|
|
@@ -1654,12 +1850,54 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1654
1850
|
}
|
|
1655
1851
|
}
|
|
1656
1852
|
for (const child of cell.children) {
|
|
1657
|
-
if (child.type === "component") checkComponent(child);
|
|
1658
|
-
if (child.type === "layout") checkLayout(child);
|
|
1853
|
+
if (child.type === "component") checkComponent(child, insideDefinedLayout);
|
|
1854
|
+
if (child.type === "layout") checkLayout(child, insideDefinedLayout);
|
|
1659
1855
|
}
|
|
1660
1856
|
};
|
|
1857
|
+
for (const componentDef of ast.definedComponents) {
|
|
1858
|
+
const nodeId = componentDef._meta?.nodeId;
|
|
1859
|
+
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1860
|
+
if (!isPascalCaseIdentifier(componentDef.name)) {
|
|
1861
|
+
emitWarning(
|
|
1862
|
+
`Defined component "${componentDef.name}" should use PascalCase naming.`,
|
|
1863
|
+
"COMPONENT_DEFINITION_NAME_STYLE",
|
|
1864
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1865
|
+
nodeId,
|
|
1866
|
+
'Use a name like "MyComponent".'
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
if (componentDef.body.type === "component") {
|
|
1870
|
+
checkComponent(componentDef.body, false);
|
|
1871
|
+
} else {
|
|
1872
|
+
checkLayout(componentDef.body, false);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
for (const layoutDef of ast.definedLayouts) {
|
|
1876
|
+
const nodeId = layoutDef._meta?.nodeId;
|
|
1877
|
+
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1878
|
+
if (!isValidDefinedLayoutName(layoutDef.name)) {
|
|
1879
|
+
emitError(
|
|
1880
|
+
`Defined layout "${layoutDef.name}" must match /^[a-z][a-z0-9_]*$/.`,
|
|
1881
|
+
"LAYOUT_DEFINITION_INVALID_NAME",
|
|
1882
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1883
|
+
nodeId,
|
|
1884
|
+
'Use names like "screen_default" or "appShell".'
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
const childrenSlotCount = countChildrenSlots(layoutDef.body);
|
|
1888
|
+
if (childrenSlotCount !== 1) {
|
|
1889
|
+
emitError(
|
|
1890
|
+
`Defined layout "${layoutDef.name}" must contain exactly one "component Children" placeholder.`,
|
|
1891
|
+
"LAYOUT_DEFINITION_CHILDREN_SLOT_COUNT",
|
|
1892
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1893
|
+
nodeId,
|
|
1894
|
+
'Add exactly one "component Children" in the layout body.'
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
checkLayout(layoutDef.body, true);
|
|
1898
|
+
}
|
|
1661
1899
|
ast.screens.forEach((screen) => {
|
|
1662
|
-
checkLayout(screen.layout);
|
|
1900
|
+
checkLayout(screen.layout, false);
|
|
1663
1901
|
});
|
|
1664
1902
|
return diagnostics;
|
|
1665
1903
|
}
|
|
@@ -1676,7 +1914,7 @@ ${lexResult.errors.map((e) => e.message).join("\n")}`);
|
|
|
1676
1914
|
${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
1677
1915
|
}
|
|
1678
1916
|
const ast = visitor.visit(cst);
|
|
1679
|
-
|
|
1917
|
+
validateDefinitionCycles(ast);
|
|
1680
1918
|
return ast;
|
|
1681
1919
|
}
|
|
1682
1920
|
function parseWireDSLWithSourceMap(input, filePath = "<input>", options) {
|
|
@@ -1707,7 +1945,7 @@ ${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
|
1707
1945
|
const ast = visitorWithSourceMap.visit(cst);
|
|
1708
1946
|
const sourceMap = sourceMapBuilder.build();
|
|
1709
1947
|
try {
|
|
1710
|
-
|
|
1948
|
+
validateDefinitionCycles(ast);
|
|
1711
1949
|
} catch (error) {
|
|
1712
1950
|
const projectEntry = sourceMap.find((entry) => entry.type === "project");
|
|
1713
1951
|
diagnostics.push({
|
|
@@ -1730,83 +1968,101 @@ ${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
|
1730
1968
|
}
|
|
1731
1969
|
return buildParseResult(ast, sourceMap, diagnostics);
|
|
1732
1970
|
}
|
|
1733
|
-
function
|
|
1734
|
-
if (!ast.definedComponents || ast.definedComponents.length === 0) {
|
|
1971
|
+
function validateDefinitionCycles(ast) {
|
|
1972
|
+
if ((!ast.definedComponents || ast.definedComponents.length === 0) && (!ast.definedLayouts || ast.definedLayouts.length === 0)) {
|
|
1735
1973
|
return;
|
|
1736
1974
|
}
|
|
1737
1975
|
const components = /* @__PURE__ */ new Map();
|
|
1976
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
1738
1977
|
ast.definedComponents.forEach((comp) => {
|
|
1739
1978
|
components.set(comp.name, comp);
|
|
1740
1979
|
});
|
|
1980
|
+
ast.definedLayouts.forEach((layoutDef) => {
|
|
1981
|
+
layouts.set(layoutDef.name, layoutDef);
|
|
1982
|
+
});
|
|
1983
|
+
const makeComponentKey = (name) => `component:${name}`;
|
|
1984
|
+
const makeLayoutKey = (name) => `layout:${name}`;
|
|
1985
|
+
const displayKey = (key) => key.split(":")[1];
|
|
1986
|
+
const shouldTrackComponentDependency = (name) => components.has(name) && !BUILT_IN_COMPONENTS.has(name);
|
|
1987
|
+
const shouldTrackLayoutDependency = (name) => layouts.has(name) && !BUILT_IN_LAYOUTS.has(name);
|
|
1741
1988
|
const visited = /* @__PURE__ */ new Set();
|
|
1742
1989
|
const recursionStack = /* @__PURE__ */ new Set();
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
if (
|
|
1758
|
-
|
|
1759
|
-
if (cellChild.type === "component") {
|
|
1760
|
-
deps.add(cellChild.componentType);
|
|
1761
|
-
} else if (cellChild.type === "layout") {
|
|
1762
|
-
const nested = getComponentDependencies(cellChild);
|
|
1763
|
-
nested.forEach((d) => deps.add(d));
|
|
1764
|
-
}
|
|
1765
|
-
});
|
|
1990
|
+
const collectLayoutDependencies = (layout, deps) => {
|
|
1991
|
+
if (shouldTrackLayoutDependency(layout.layoutType)) {
|
|
1992
|
+
deps.add(makeLayoutKey(layout.layoutType));
|
|
1993
|
+
}
|
|
1994
|
+
for (const child of layout.children) {
|
|
1995
|
+
if (child.type === "component") {
|
|
1996
|
+
if (shouldTrackComponentDependency(child.componentType)) {
|
|
1997
|
+
deps.add(makeComponentKey(child.componentType));
|
|
1998
|
+
}
|
|
1999
|
+
} else if (child.type === "layout") {
|
|
2000
|
+
collectLayoutDependencies(child, deps);
|
|
2001
|
+
} else if (child.type === "cell") {
|
|
2002
|
+
for (const cellChild of child.children) {
|
|
2003
|
+
if (cellChild.type === "component") {
|
|
2004
|
+
if (shouldTrackComponentDependency(cellChild.componentType)) {
|
|
2005
|
+
deps.add(makeComponentKey(cellChild.componentType));
|
|
1766
2006
|
}
|
|
2007
|
+
} else if (cellChild.type === "layout") {
|
|
2008
|
+
collectLayoutDependencies(cellChild, deps);
|
|
1767
2009
|
}
|
|
1768
|
-
}
|
|
2010
|
+
}
|
|
1769
2011
|
}
|
|
1770
2012
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
if (
|
|
1775
|
-
const
|
|
1776
|
-
const
|
|
1777
|
-
return
|
|
2013
|
+
};
|
|
2014
|
+
const getDependencies = (key) => {
|
|
2015
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2016
|
+
if (key.startsWith("component:")) {
|
|
2017
|
+
const name2 = key.slice("component:".length);
|
|
2018
|
+
const def2 = components.get(name2);
|
|
2019
|
+
if (!def2) return deps;
|
|
2020
|
+
if (def2.body.type === "component") {
|
|
2021
|
+
if (shouldTrackComponentDependency(def2.body.componentType)) {
|
|
2022
|
+
deps.add(makeComponentKey(def2.body.componentType));
|
|
2023
|
+
}
|
|
2024
|
+
} else {
|
|
2025
|
+
collectLayoutDependencies(def2.body, deps);
|
|
2026
|
+
}
|
|
2027
|
+
return deps;
|
|
1778
2028
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
2029
|
+
const name = key.slice("layout:".length);
|
|
2030
|
+
const def = layouts.get(name);
|
|
2031
|
+
if (!def) return deps;
|
|
2032
|
+
collectLayoutDependencies(def.body, deps);
|
|
2033
|
+
return deps;
|
|
2034
|
+
};
|
|
2035
|
+
const findCycle = (key, path = []) => {
|
|
2036
|
+
if (recursionStack.has(key)) {
|
|
2037
|
+
const cycleStart = path.indexOf(key);
|
|
2038
|
+
return path.slice(cycleStart).concat(key);
|
|
1781
2039
|
}
|
|
1782
|
-
|
|
1783
|
-
if (!component) {
|
|
2040
|
+
if (visited.has(key)) {
|
|
1784
2041
|
return null;
|
|
1785
2042
|
}
|
|
1786
|
-
recursionStack.add(
|
|
1787
|
-
const currentPath = [...path,
|
|
1788
|
-
const dependencies =
|
|
2043
|
+
recursionStack.add(key);
|
|
2044
|
+
const currentPath = [...path, key];
|
|
2045
|
+
const dependencies = getDependencies(key);
|
|
1789
2046
|
for (const dep of dependencies) {
|
|
1790
|
-
const
|
|
1791
|
-
if (
|
|
1792
|
-
const cycle = hasCycle(dep, currentPath);
|
|
1793
|
-
if (cycle) {
|
|
1794
|
-
return cycle;
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
2047
|
+
const cycle = findCycle(dep, currentPath);
|
|
2048
|
+
if (cycle) return cycle;
|
|
1797
2049
|
}
|
|
1798
|
-
recursionStack.delete(
|
|
1799
|
-
visited.add(
|
|
2050
|
+
recursionStack.delete(key);
|
|
2051
|
+
visited.add(key);
|
|
1800
2052
|
return null;
|
|
1801
|
-
}
|
|
1802
|
-
|
|
2053
|
+
};
|
|
2054
|
+
const allDefinitions = [
|
|
2055
|
+
...Array.from(components.keys()).map(makeComponentKey),
|
|
2056
|
+
...Array.from(layouts.keys()).map(makeLayoutKey)
|
|
2057
|
+
];
|
|
2058
|
+
for (const key of allDefinitions) {
|
|
1803
2059
|
visited.clear();
|
|
1804
2060
|
recursionStack.clear();
|
|
1805
|
-
const cycle =
|
|
2061
|
+
const cycle = findCycle(key);
|
|
1806
2062
|
if (cycle) {
|
|
1807
2063
|
throw new Error(
|
|
1808
|
-
`Circular component definition detected: ${cycle.join(" \u2192 ")}
|
|
1809
|
-
Components cannot reference each other in a cycle.`
|
|
2064
|
+
`Circular component definition detected: ${cycle.map(displayKey).join(" \u2192 ")}
|
|
2065
|
+
Components and layouts cannot reference each other in a cycle.`
|
|
1810
2066
|
);
|
|
1811
2067
|
}
|
|
1812
2068
|
}
|
|
@@ -1814,6 +2070,10 @@ Components cannot reference each other in a cycle.`
|
|
|
1814
2070
|
|
|
1815
2071
|
// src/ir/index.ts
|
|
1816
2072
|
import { z } from "zod";
|
|
2073
|
+
import {
|
|
2074
|
+
COMPONENTS as COMPONENTS2,
|
|
2075
|
+
LAYOUTS as LAYOUTS2
|
|
2076
|
+
} from "@wire-dsl/language-support/components";
|
|
1817
2077
|
|
|
1818
2078
|
// src/ir/device-presets.ts
|
|
1819
2079
|
var DEVICE_PRESETS = {
|
|
@@ -1978,9 +2238,11 @@ var IRGenerator = class {
|
|
|
1978
2238
|
this.idGen = new IDGenerator();
|
|
1979
2239
|
this.nodes = {};
|
|
1980
2240
|
this.definedComponents = /* @__PURE__ */ new Map();
|
|
2241
|
+
this.definedLayouts = /* @__PURE__ */ new Map();
|
|
1981
2242
|
this.definedComponentIndices = /* @__PURE__ */ new Map();
|
|
1982
2243
|
this.undefinedComponentsUsed = /* @__PURE__ */ new Set();
|
|
1983
2244
|
this.warnings = [];
|
|
2245
|
+
this.errors = [];
|
|
1984
2246
|
this.style = {
|
|
1985
2247
|
density: "normal",
|
|
1986
2248
|
spacing: "md",
|
|
@@ -1993,15 +2255,22 @@ var IRGenerator = class {
|
|
|
1993
2255
|
this.idGen.reset();
|
|
1994
2256
|
this.nodes = {};
|
|
1995
2257
|
this.definedComponents.clear();
|
|
2258
|
+
this.definedLayouts.clear();
|
|
1996
2259
|
this.definedComponentIndices.clear();
|
|
1997
2260
|
this.undefinedComponentsUsed.clear();
|
|
1998
2261
|
this.warnings = [];
|
|
2262
|
+
this.errors = [];
|
|
1999
2263
|
if (ast.definedComponents && ast.definedComponents.length > 0) {
|
|
2000
2264
|
ast.definedComponents.forEach((def, index) => {
|
|
2001
2265
|
this.definedComponents.set(def.name, def);
|
|
2002
2266
|
this.definedComponentIndices.set(def.name, index);
|
|
2003
2267
|
});
|
|
2004
2268
|
}
|
|
2269
|
+
if (ast.definedLayouts && ast.definedLayouts.length > 0) {
|
|
2270
|
+
ast.definedLayouts.forEach((def) => {
|
|
2271
|
+
this.definedLayouts.set(def.name, def);
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2005
2274
|
this.applyStyle(ast.style);
|
|
2006
2275
|
const screens = ast.screens.map(
|
|
2007
2276
|
(screen, screenIndex) => this.convertScreen(screen, screenIndex)
|
|
@@ -2013,6 +2282,11 @@ var IRGenerator = class {
|
|
|
2013
2282
|
Define these components with: define Component "Name" { ... }`
|
|
2014
2283
|
);
|
|
2015
2284
|
}
|
|
2285
|
+
if (this.errors.length > 0) {
|
|
2286
|
+
const messages = this.errors.map((e) => `- [${e.type}] ${e.message}`).join("\n");
|
|
2287
|
+
throw new Error(`IR generation failed with semantic errors:
|
|
2288
|
+
${messages}`);
|
|
2289
|
+
}
|
|
2016
2290
|
const project = {
|
|
2017
2291
|
id: this.sanitizeId(ast.name),
|
|
2018
2292
|
name: ast.name,
|
|
@@ -2139,41 +2413,50 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2139
2413
|
getWarnings() {
|
|
2140
2414
|
return this.warnings;
|
|
2141
2415
|
}
|
|
2142
|
-
convertLayout(layout) {
|
|
2416
|
+
convertLayout(layout, context) {
|
|
2417
|
+
let layoutParams = this.resolveLayoutParams(layout.layoutType, layout.params, context);
|
|
2418
|
+
if (layout.layoutType === "split") {
|
|
2419
|
+
layoutParams = this.normalizeSplitParams(layoutParams);
|
|
2420
|
+
}
|
|
2421
|
+
const layoutChildren = layout.children;
|
|
2422
|
+
const layoutDefinition = this.definedLayouts.get(layout.layoutType);
|
|
2423
|
+
if (layoutDefinition) {
|
|
2424
|
+
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
|
|
2425
|
+
}
|
|
2143
2426
|
const nodeId = this.idGen.generate("node");
|
|
2144
2427
|
const childRefs = [];
|
|
2145
|
-
for (const child of
|
|
2428
|
+
for (const child of layoutChildren) {
|
|
2146
2429
|
if (child.type === "layout") {
|
|
2147
|
-
const childId = this.convertLayout(child);
|
|
2148
|
-
childRefs.push({ ref: childId });
|
|
2430
|
+
const childId = this.convertLayout(child, context);
|
|
2431
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2149
2432
|
} else if (child.type === "component") {
|
|
2150
|
-
const childId = this.convertComponent(child);
|
|
2151
|
-
childRefs.push({ ref: childId });
|
|
2433
|
+
const childId = this.convertComponent(child, context);
|
|
2434
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2152
2435
|
} else if (child.type === "cell") {
|
|
2153
|
-
const childId = this.convertCell(child);
|
|
2154
|
-
childRefs.push({ ref: childId });
|
|
2436
|
+
const childId = this.convertCell(child, context);
|
|
2437
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2155
2438
|
}
|
|
2156
2439
|
}
|
|
2157
2440
|
const style = {};
|
|
2158
|
-
if (
|
|
2159
|
-
style.padding = String(
|
|
2441
|
+
if (layoutParams.padding !== void 0) {
|
|
2442
|
+
style.padding = String(layoutParams.padding);
|
|
2160
2443
|
} else {
|
|
2161
2444
|
style.padding = "none";
|
|
2162
2445
|
}
|
|
2163
|
-
if (
|
|
2164
|
-
style.gap = String(
|
|
2446
|
+
if (layoutParams.gap !== void 0) {
|
|
2447
|
+
style.gap = String(layoutParams.gap);
|
|
2165
2448
|
}
|
|
2166
|
-
if (
|
|
2167
|
-
style.align =
|
|
2449
|
+
if (layoutParams.align !== void 0) {
|
|
2450
|
+
style.align = layoutParams.align;
|
|
2168
2451
|
}
|
|
2169
|
-
if (
|
|
2170
|
-
style.background = String(
|
|
2452
|
+
if (layoutParams.background !== void 0) {
|
|
2453
|
+
style.background = String(layoutParams.background);
|
|
2171
2454
|
}
|
|
2172
2455
|
const containerNode = {
|
|
2173
2456
|
id: nodeId,
|
|
2174
2457
|
kind: "container",
|
|
2175
2458
|
containerType: layout.layoutType,
|
|
2176
|
-
params: this.cleanParams(
|
|
2459
|
+
params: this.cleanParams(layoutParams),
|
|
2177
2460
|
children: childRefs,
|
|
2178
2461
|
style,
|
|
2179
2462
|
meta: {
|
|
@@ -2184,16 +2467,16 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2184
2467
|
this.nodes[nodeId] = containerNode;
|
|
2185
2468
|
return nodeId;
|
|
2186
2469
|
}
|
|
2187
|
-
convertCell(cell) {
|
|
2470
|
+
convertCell(cell, context) {
|
|
2188
2471
|
const nodeId = this.idGen.generate("node");
|
|
2189
2472
|
const childRefs = [];
|
|
2190
2473
|
for (const child of cell.children) {
|
|
2191
2474
|
if (child.type === "layout") {
|
|
2192
|
-
const childId = this.convertLayout(child);
|
|
2193
|
-
childRefs.push({ ref: childId });
|
|
2475
|
+
const childId = this.convertLayout(child, context);
|
|
2476
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2194
2477
|
} else if (child.type === "component") {
|
|
2195
|
-
const childId = this.convertComponent(child);
|
|
2196
|
-
childRefs.push({ ref: childId });
|
|
2478
|
+
const childId = this.convertComponent(child, context);
|
|
2479
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2197
2480
|
}
|
|
2198
2481
|
}
|
|
2199
2482
|
const containerNode = {
|
|
@@ -2214,10 +2497,28 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2214
2497
|
this.nodes[nodeId] = containerNode;
|
|
2215
2498
|
return nodeId;
|
|
2216
2499
|
}
|
|
2217
|
-
convertComponent(component) {
|
|
2500
|
+
convertComponent(component, context) {
|
|
2501
|
+
if (component.componentType === "Children") {
|
|
2502
|
+
if (!context?.allowChildrenSlot) {
|
|
2503
|
+
this.errors.push({
|
|
2504
|
+
type: "children-slot-outside-layout-definition",
|
|
2505
|
+
message: '"Children" placeholder can only be used inside a define Layout body.'
|
|
2506
|
+
});
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
if (!context.childrenSlot) {
|
|
2510
|
+
this.errors.push({
|
|
2511
|
+
type: "children-slot-missing-child",
|
|
2512
|
+
message: `Layout "${context.definitionName}" requires exactly one child for "Children".`
|
|
2513
|
+
});
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
return this.convertASTNode(context.childrenSlot, context);
|
|
2517
|
+
}
|
|
2518
|
+
const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
|
|
2218
2519
|
const definition = this.definedComponents.get(component.componentType);
|
|
2219
2520
|
if (definition) {
|
|
2220
|
-
return this.expandDefinedComponent(definition);
|
|
2521
|
+
return this.expandDefinedComponent(definition, resolvedProps, context);
|
|
2221
2522
|
}
|
|
2222
2523
|
const builtInComponents = /* @__PURE__ */ new Set([
|
|
2223
2524
|
"Button",
|
|
@@ -2260,7 +2561,7 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2260
2561
|
id: nodeId,
|
|
2261
2562
|
kind: "component",
|
|
2262
2563
|
componentType: component.componentType,
|
|
2263
|
-
props:
|
|
2564
|
+
props: resolvedProps,
|
|
2264
2565
|
style: {},
|
|
2265
2566
|
meta: {
|
|
2266
2567
|
nodeId: component._meta?.nodeId
|
|
@@ -2270,15 +2571,196 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2270
2571
|
this.nodes[nodeId] = componentNode;
|
|
2271
2572
|
return nodeId;
|
|
2272
2573
|
}
|
|
2273
|
-
expandDefinedComponent(definition) {
|
|
2574
|
+
expandDefinedComponent(definition, invocationArgs, parentContext) {
|
|
2575
|
+
const context = {
|
|
2576
|
+
args: invocationArgs,
|
|
2577
|
+
providedArgNames: new Set(Object.keys(invocationArgs)),
|
|
2578
|
+
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2579
|
+
definitionName: definition.name,
|
|
2580
|
+
definitionKind: "component",
|
|
2581
|
+
allowChildrenSlot: false
|
|
2582
|
+
};
|
|
2274
2583
|
if (definition.body.type === "layout") {
|
|
2275
|
-
|
|
2584
|
+
const result = this.convertLayout(definition.body, context);
|
|
2585
|
+
this.reportUnusedArguments(context);
|
|
2586
|
+
return result;
|
|
2276
2587
|
} else if (definition.body.type === "component") {
|
|
2277
|
-
|
|
2588
|
+
const result = this.convertComponent(definition.body, context);
|
|
2589
|
+
this.reportUnusedArguments(context);
|
|
2590
|
+
return result;
|
|
2278
2591
|
} else {
|
|
2279
2592
|
throw new Error(`Invalid defined component body type for "${definition.name}"`);
|
|
2280
2593
|
}
|
|
2281
2594
|
}
|
|
2595
|
+
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
|
|
2596
|
+
if (invocationChildren.length !== 1) {
|
|
2597
|
+
this.errors.push({
|
|
2598
|
+
type: "layout-children-arity",
|
|
2599
|
+
message: `Layout "${definition.name}" expects exactly one child, received ${invocationChildren.length}.`
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
const rawSlot = invocationChildren[0];
|
|
2603
|
+
const resolvedSlot = rawSlot ? this.resolveChildrenSlot(rawSlot, parentContext) : void 0;
|
|
2604
|
+
const context = {
|
|
2605
|
+
args: invocationParams,
|
|
2606
|
+
providedArgNames: new Set(Object.keys(invocationParams)),
|
|
2607
|
+
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2608
|
+
definitionName: definition.name,
|
|
2609
|
+
definitionKind: "layout",
|
|
2610
|
+
allowChildrenSlot: true,
|
|
2611
|
+
childrenSlot: resolvedSlot
|
|
2612
|
+
};
|
|
2613
|
+
const nodeId = this.convertLayout(definition.body, context);
|
|
2614
|
+
this.reportUnusedArguments(context);
|
|
2615
|
+
return nodeId;
|
|
2616
|
+
}
|
|
2617
|
+
resolveChildrenSlot(slot, parentContext) {
|
|
2618
|
+
if (slot.type === "component" && slot.componentType === "Children") {
|
|
2619
|
+
if (parentContext?.allowChildrenSlot) {
|
|
2620
|
+
return parentContext.childrenSlot;
|
|
2621
|
+
}
|
|
2622
|
+
this.errors.push({
|
|
2623
|
+
type: "children-slot-outside-layout-definition",
|
|
2624
|
+
message: '"Children" placeholder forwarding is only valid inside define Layout bodies.'
|
|
2625
|
+
});
|
|
2626
|
+
return void 0;
|
|
2627
|
+
}
|
|
2628
|
+
return slot;
|
|
2629
|
+
}
|
|
2630
|
+
convertASTNode(node, context) {
|
|
2631
|
+
if (node.type === "layout") return this.convertLayout(node, context);
|
|
2632
|
+
if (node.type === "component") return this.convertComponent(node, context);
|
|
2633
|
+
return this.convertCell(node, context);
|
|
2634
|
+
}
|
|
2635
|
+
resolveLayoutParams(layoutType, params, context) {
|
|
2636
|
+
const resolved = {};
|
|
2637
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2638
|
+
const resolvedValue = this.resolveBindingValue(
|
|
2639
|
+
value,
|
|
2640
|
+
context,
|
|
2641
|
+
"layout-parameter",
|
|
2642
|
+
layoutType,
|
|
2643
|
+
key
|
|
2644
|
+
);
|
|
2645
|
+
if (resolvedValue !== void 0) {
|
|
2646
|
+
resolved[key] = resolvedValue;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
return resolved;
|
|
2650
|
+
}
|
|
2651
|
+
normalizeSplitParams(params) {
|
|
2652
|
+
const normalized = { ...params };
|
|
2653
|
+
if (normalized.sidebar !== void 0 && normalized.left === void 0 && normalized.right === void 0) {
|
|
2654
|
+
normalized.left = normalized.sidebar;
|
|
2655
|
+
this.warnings.push({
|
|
2656
|
+
type: "split-sidebar-deprecated",
|
|
2657
|
+
message: 'Split parameter "sidebar" is deprecated. Use "left" or "right".'
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
delete normalized.sidebar;
|
|
2661
|
+
const hasLeft = normalized.left !== void 0;
|
|
2662
|
+
const hasRight = normalized.right !== void 0;
|
|
2663
|
+
if (hasLeft && hasRight) {
|
|
2664
|
+
delete normalized.right;
|
|
2665
|
+
this.warnings.push({
|
|
2666
|
+
type: "split-side-conflict",
|
|
2667
|
+
message: 'Split layout received both "left" and "right"; keeping "left".'
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
if (!hasLeft && !hasRight) {
|
|
2671
|
+
normalized.left = 250;
|
|
2672
|
+
this.warnings.push({
|
|
2673
|
+
type: "split-side-missing",
|
|
2674
|
+
message: 'Split layout missing both "left" and "right"; defaulting to left: 250.'
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
if (normalized.left !== void 0) {
|
|
2678
|
+
const leftWidth = Number(normalized.left);
|
|
2679
|
+
if (!Number.isFinite(leftWidth) || leftWidth <= 0) {
|
|
2680
|
+
normalized.left = 250;
|
|
2681
|
+
this.warnings.push({
|
|
2682
|
+
type: "split-left-invalid",
|
|
2683
|
+
message: 'Split "left" must be a positive number. Falling back to 250.'
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
if (normalized.right !== void 0) {
|
|
2688
|
+
const rightWidth = Number(normalized.right);
|
|
2689
|
+
if (!Number.isFinite(rightWidth) || rightWidth <= 0) {
|
|
2690
|
+
normalized.right = 250;
|
|
2691
|
+
this.warnings.push({
|
|
2692
|
+
type: "split-right-invalid",
|
|
2693
|
+
message: 'Split "right" must be a positive number. Falling back to 250.'
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return normalized;
|
|
2698
|
+
}
|
|
2699
|
+
resolveComponentProps(componentType, props, context) {
|
|
2700
|
+
const resolved = {};
|
|
2701
|
+
for (const [key, value] of Object.entries(props)) {
|
|
2702
|
+
const resolvedValue = this.resolveBindingValue(
|
|
2703
|
+
value,
|
|
2704
|
+
context,
|
|
2705
|
+
"component-property",
|
|
2706
|
+
componentType,
|
|
2707
|
+
key
|
|
2708
|
+
);
|
|
2709
|
+
if (resolvedValue !== void 0) {
|
|
2710
|
+
resolved[key] = resolvedValue;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return resolved;
|
|
2714
|
+
}
|
|
2715
|
+
resolveBindingValue(value, context, kind, targetType, targetName) {
|
|
2716
|
+
if (typeof value !== "string" || !value.startsWith("prop_")) {
|
|
2717
|
+
return value;
|
|
2718
|
+
}
|
|
2719
|
+
const argName = value.slice("prop_".length);
|
|
2720
|
+
if (!context) {
|
|
2721
|
+
return value;
|
|
2722
|
+
}
|
|
2723
|
+
if (Object.prototype.hasOwnProperty.call(context.args, argName)) {
|
|
2724
|
+
context.usedArgNames.add(argName);
|
|
2725
|
+
return context.args[argName];
|
|
2726
|
+
}
|
|
2727
|
+
const required = this.isBindingTargetRequired(kind, targetType, targetName);
|
|
2728
|
+
const descriptor = kind === "component-property" ? "property" : "parameter";
|
|
2729
|
+
const message = `Missing required bound ${descriptor} "${targetName}" for ${kind === "component-property" ? "component" : "layout"} "${targetType}" in ${context.definitionKind} "${context.definitionName}" (expected arg "${argName}").`;
|
|
2730
|
+
if (required) {
|
|
2731
|
+
this.errors.push({ type: "missing-required-bound-value", message });
|
|
2732
|
+
} else {
|
|
2733
|
+
this.warnings.push({
|
|
2734
|
+
type: "missing-bound-value",
|
|
2735
|
+
message: `Optional ${descriptor} "${targetName}" in ${kind === "component-property" ? "component" : "layout"} "${targetType}" was omitted because arg "${argName}" was not provided while expanding ${context.definitionKind} "${context.definitionName}".`
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
return void 0;
|
|
2739
|
+
}
|
|
2740
|
+
isBindingTargetRequired(kind, targetType, targetName) {
|
|
2741
|
+
if (kind === "component-property") {
|
|
2742
|
+
const metadata = COMPONENTS2[targetType];
|
|
2743
|
+
const property2 = metadata?.properties?.[targetName];
|
|
2744
|
+
if (!property2) return false;
|
|
2745
|
+
return property2.required === true && property2.defaultValue === void 0;
|
|
2746
|
+
}
|
|
2747
|
+
const layoutMetadata = LAYOUTS2[targetType];
|
|
2748
|
+
if (!layoutMetadata) return false;
|
|
2749
|
+
const property = layoutMetadata.properties?.[targetName];
|
|
2750
|
+
const requiredFromProperty = property?.required === true && property.defaultValue === void 0;
|
|
2751
|
+
const requiredFromLayout = (layoutMetadata.requiredProperties || []).includes(targetName);
|
|
2752
|
+
return requiredFromProperty || requiredFromLayout;
|
|
2753
|
+
}
|
|
2754
|
+
reportUnusedArguments(context) {
|
|
2755
|
+
for (const arg of context.providedArgNames) {
|
|
2756
|
+
if (!context.usedArgNames.has(arg)) {
|
|
2757
|
+
this.warnings.push({
|
|
2758
|
+
type: "unused-definition-argument",
|
|
2759
|
+
message: `Argument "${arg}" is not used by ${context.definitionKind} "${context.definitionName}".`
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2282
2764
|
cleanParams(params) {
|
|
2283
2765
|
const cleaned = {};
|
|
2284
2766
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -2328,9 +2810,24 @@ var ICON_SIZES_BY_DENSITY = {
|
|
|
2328
2810
|
comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
|
|
2329
2811
|
};
|
|
2330
2812
|
var ICON_BUTTON_SIZES_BY_DENSITY = {
|
|
2331
|
-
compact: { sm:
|
|
2332
|
-
normal: { sm:
|
|
2333
|
-
comfortable: { sm:
|
|
2813
|
+
compact: { sm: 20, md: 24, lg: 32 },
|
|
2814
|
+
normal: { sm: 24, md: 32, lg: 40 },
|
|
2815
|
+
comfortable: { sm: 28, md: 40, lg: 48 }
|
|
2816
|
+
};
|
|
2817
|
+
var CONTROL_HEIGHTS_BY_DENSITY = {
|
|
2818
|
+
compact: { sm: 28, md: 32, lg: 36 },
|
|
2819
|
+
normal: { sm: 36, md: 40, lg: 48 },
|
|
2820
|
+
comfortable: { sm: 40, md: 48, lg: 56 }
|
|
2821
|
+
};
|
|
2822
|
+
var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
|
|
2823
|
+
compact: { sm: 20, md: 24, lg: 32 },
|
|
2824
|
+
normal: { sm: 24, md: 32, lg: 40 },
|
|
2825
|
+
comfortable: { sm: 28, md: 40, lg: 48 }
|
|
2826
|
+
};
|
|
2827
|
+
var CONTROL_PADDING_BY_DENSITY = {
|
|
2828
|
+
compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
|
|
2829
|
+
normal: { none: 0, xs: 6, sm: 10, md: 14, lg: 18, xl: 24 },
|
|
2830
|
+
comfortable: { none: 0, xs: 8, sm: 12, md: 16, lg: 22, xl: 28 }
|
|
2334
2831
|
};
|
|
2335
2832
|
function resolveIconSize(size, density = "normal") {
|
|
2336
2833
|
const map = ICON_SIZES_BY_DENSITY[density] || ICON_SIZES_BY_DENSITY.normal;
|
|
@@ -2340,6 +2837,18 @@ function resolveIconButtonSize(size, density = "normal") {
|
|
|
2340
2837
|
const map = ICON_BUTTON_SIZES_BY_DENSITY[density] || ICON_BUTTON_SIZES_BY_DENSITY.normal;
|
|
2341
2838
|
return map[size || "md"] || map.md;
|
|
2342
2839
|
}
|
|
2840
|
+
function resolveControlHeight(size, density = "normal") {
|
|
2841
|
+
const map = CONTROL_HEIGHTS_BY_DENSITY[density] || CONTROL_HEIGHTS_BY_DENSITY.normal;
|
|
2842
|
+
return map[size || "md"] || map.md;
|
|
2843
|
+
}
|
|
2844
|
+
function resolveActionControlHeight(size, density = "normal") {
|
|
2845
|
+
const map = ACTION_CONTROL_HEIGHTS_BY_DENSITY[density] || ACTION_CONTROL_HEIGHTS_BY_DENSITY.normal;
|
|
2846
|
+
return map[size || "md"] || map.md;
|
|
2847
|
+
}
|
|
2848
|
+
function resolveControlHorizontalPadding(padding, density = "normal") {
|
|
2849
|
+
const map = CONTROL_PADDING_BY_DENSITY[density] || CONTROL_PADDING_BY_DENSITY.normal;
|
|
2850
|
+
return map[padding || "md"] ?? map.md;
|
|
2851
|
+
}
|
|
2343
2852
|
|
|
2344
2853
|
// src/shared/heading-levels.ts
|
|
2345
2854
|
var DEFAULT_LEVEL = "h2";
|
|
@@ -2658,6 +3167,41 @@ var LayoutEngine = class {
|
|
|
2658
3167
|
}
|
|
2659
3168
|
return totalHeight;
|
|
2660
3169
|
}
|
|
3170
|
+
if (node.containerType === "split") {
|
|
3171
|
+
const splitGap = this.resolveSpacing(node.style.gap);
|
|
3172
|
+
const leftParam = node.params.left;
|
|
3173
|
+
const rightParam = node.params.right;
|
|
3174
|
+
const leftWidthRaw = Number(leftParam);
|
|
3175
|
+
const rightWidthRaw = Number(rightParam);
|
|
3176
|
+
const hasLeft = leftParam !== void 0;
|
|
3177
|
+
const hasRight = rightParam !== void 0;
|
|
3178
|
+
const leftWidth = Number.isFinite(leftWidthRaw) && leftWidthRaw > 0 ? leftWidthRaw : availableWidth / 2;
|
|
3179
|
+
const rightWidth = Number.isFinite(rightWidthRaw) && rightWidthRaw > 0 ? rightWidthRaw : availableWidth / 2;
|
|
3180
|
+
let maxHeight = 0;
|
|
3181
|
+
node.children.forEach((childRef, index) => {
|
|
3182
|
+
const child = this.nodes[childRef.ref];
|
|
3183
|
+
let childHeight = this.getComponentHeight();
|
|
3184
|
+
const isFirst = index === 0;
|
|
3185
|
+
let childWidth;
|
|
3186
|
+
if (node.children.length >= 2) {
|
|
3187
|
+
if (hasRight && !hasLeft) {
|
|
3188
|
+
childWidth = isFirst ? Math.max(1, availableWidth - rightWidth - splitGap) : rightWidth;
|
|
3189
|
+
} else {
|
|
3190
|
+
childWidth = isFirst ? leftWidth : Math.max(1, availableWidth - leftWidth - splitGap);
|
|
3191
|
+
}
|
|
3192
|
+
} else {
|
|
3193
|
+
childWidth = availableWidth;
|
|
3194
|
+
}
|
|
3195
|
+
if (child?.kind === "component") {
|
|
3196
|
+
childHeight = child.props.height ? Number(child.props.height) : this.getIntrinsicComponentHeight(child, childWidth);
|
|
3197
|
+
} else if (child?.kind === "container") {
|
|
3198
|
+
childHeight = this.calculateContainerHeight(child, childWidth);
|
|
3199
|
+
}
|
|
3200
|
+
maxHeight = Math.max(maxHeight, childHeight);
|
|
3201
|
+
});
|
|
3202
|
+
totalHeight += maxHeight;
|
|
3203
|
+
return totalHeight;
|
|
3204
|
+
}
|
|
2661
3205
|
const direction = node.params.direction || "vertical";
|
|
2662
3206
|
if (node.containerType === "stack" && direction === "horizontal") {
|
|
2663
3207
|
let maxHeight = 0;
|
|
@@ -2780,14 +3324,28 @@ var LayoutEngine = class {
|
|
|
2780
3324
|
calculateSplit(node, x, y, width, height) {
|
|
2781
3325
|
if (node.kind !== "container") return;
|
|
2782
3326
|
const gap = this.resolveSpacing(node.style.gap);
|
|
2783
|
-
const
|
|
3327
|
+
const leftParam = node.params.left;
|
|
3328
|
+
const rightParam = node.params.right;
|
|
3329
|
+
const leftWidthRaw = Number(leftParam);
|
|
3330
|
+
const rightWidthRaw = Number(rightParam);
|
|
3331
|
+
const hasLeft = leftParam !== void 0;
|
|
3332
|
+
const hasRight = rightParam !== void 0;
|
|
3333
|
+
const leftWidth = Number.isFinite(leftWidthRaw) && leftWidthRaw > 0 ? leftWidthRaw : 250;
|
|
3334
|
+
const rightWidth = Number.isFinite(rightWidthRaw) && rightWidthRaw > 0 ? rightWidthRaw : 250;
|
|
2784
3335
|
if (node.children.length === 1) {
|
|
2785
3336
|
this.calculateNode(node.children[0].ref, x, y, width, height, "split");
|
|
2786
3337
|
} else if (node.children.length >= 2) {
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
3338
|
+
if (hasRight && !hasLeft) {
|
|
3339
|
+
const flexibleLeftWidth = Math.max(1, width - rightWidth - gap);
|
|
3340
|
+
const rightX = x + flexibleLeftWidth + gap;
|
|
3341
|
+
this.calculateNode(node.children[0].ref, x, y, flexibleLeftWidth, height, "split");
|
|
3342
|
+
this.calculateNode(node.children[1].ref, rightX, y, rightWidth, height, "split");
|
|
3343
|
+
} else {
|
|
3344
|
+
const flexibleRightWidth = Math.max(1, width - leftWidth - gap);
|
|
3345
|
+
const rightX = x + leftWidth + gap;
|
|
3346
|
+
this.calculateNode(node.children[0].ref, x, y, leftWidth, height, "split");
|
|
3347
|
+
this.calculateNode(node.children[1].ref, rightX, y, flexibleRightWidth, height, "split");
|
|
3348
|
+
}
|
|
2791
3349
|
}
|
|
2792
3350
|
}
|
|
2793
3351
|
calculatePanel(node, x, y, width, height) {
|
|
@@ -2936,7 +3494,11 @@ var LayoutEngine = class {
|
|
|
2936
3494
|
}
|
|
2937
3495
|
getIntrinsicComponentHeight(node, availableWidth) {
|
|
2938
3496
|
if (node.kind !== "component") return this.getComponentHeight();
|
|
2939
|
-
const
|
|
3497
|
+
const controlSize = String(node.props.size || "md");
|
|
3498
|
+
const density = this.style.density || "normal";
|
|
3499
|
+
const inputControlHeight = resolveControlHeight(controlSize, density);
|
|
3500
|
+
const actionControlHeight = resolveActionControlHeight(controlSize, density);
|
|
3501
|
+
const controlLabelOffset = node.componentType === "Input" || node.componentType === "Textarea" || node.componentType === "Select" ? this.getControlLabelOffset(String(node.props.label || "")) : (node.componentType === "Button" || node.componentType === "IconButton") && this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
2940
3502
|
if (node.componentType === "Image") {
|
|
2941
3503
|
const placeholder = String(node.props.placeholder || "landscape");
|
|
2942
3504
|
const aspectRatios = {
|
|
@@ -2963,12 +3525,26 @@ var LayoutEngine = class {
|
|
|
2963
3525
|
}
|
|
2964
3526
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
2965
3527
|
const hasTitle = !!node.props.title;
|
|
2966
|
-
const hasPagination =
|
|
3528
|
+
const hasPagination = this.parseBooleanProp(node.props.pagination, false);
|
|
3529
|
+
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
3530
|
+
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
3531
|
+
const captionAlign = String(node.props.captionAlign || "");
|
|
3532
|
+
const effectiveCaptionAlign = captionAlign === "left" || captionAlign === "center" || captionAlign === "right" ? captionAlign : paginationAlign === "left" ? "right" : "left";
|
|
3533
|
+
const sameFooterAlign = hasCaption && hasPagination && effectiveCaptionAlign === paginationAlign;
|
|
2967
3534
|
const headerHeight = 44;
|
|
2968
3535
|
const rowHeight = 36;
|
|
2969
3536
|
const titleHeight = hasTitle ? 32 : 0;
|
|
2970
|
-
|
|
2971
|
-
|
|
3537
|
+
let footerHeight = 0;
|
|
3538
|
+
if (hasPagination || hasCaption) {
|
|
3539
|
+
const footerBottomPadding = 12;
|
|
3540
|
+
footerHeight += 16;
|
|
3541
|
+
footerHeight += hasPagination ? 32 : 18;
|
|
3542
|
+
if (sameFooterAlign) {
|
|
3543
|
+
footerHeight += 8 + 18;
|
|
3544
|
+
}
|
|
3545
|
+
footerHeight += footerBottomPadding;
|
|
3546
|
+
}
|
|
3547
|
+
return titleHeight + headerHeight + rowCount * rowHeight + footerHeight;
|
|
2972
3548
|
}
|
|
2973
3549
|
if (node.componentType === "Heading") {
|
|
2974
3550
|
const text = String(node.props.text || "Heading");
|
|
@@ -2977,15 +3553,15 @@ var LayoutEngine = class {
|
|
|
2977
3553
|
const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
|
|
2978
3554
|
const lines = this.wrapTextToLines(text, maxWidth, fontSize);
|
|
2979
3555
|
const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
|
|
2980
|
-
const
|
|
2981
|
-
const verticalPadding = resolveHeadingVerticalPadding(node.props.spacing,
|
|
3556
|
+
const density2 = this.style.density || "normal";
|
|
3557
|
+
const verticalPadding = resolveHeadingVerticalPadding(node.props.spacing, density2);
|
|
2982
3558
|
if (verticalPadding === null) {
|
|
2983
3559
|
return Math.max(this.getComponentHeight(), wrappedHeight);
|
|
2984
3560
|
}
|
|
2985
3561
|
return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
|
|
2986
3562
|
}
|
|
2987
3563
|
if (node.componentType === "Text") {
|
|
2988
|
-
const content = String(node.props.
|
|
3564
|
+
const content = String(node.props.text || "");
|
|
2989
3565
|
const { fontSize, lineHeight } = this.getTextMetricsForDensity();
|
|
2990
3566
|
const lineHeightPx = Math.ceil(fontSize * lineHeight);
|
|
2991
3567
|
const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
|
|
@@ -3034,7 +3610,10 @@ var LayoutEngine = class {
|
|
|
3034
3610
|
if (node.componentType === "Divider") return 1;
|
|
3035
3611
|
if (node.componentType === "Separate") return this.getSeparateSize(node);
|
|
3036
3612
|
if (node.componentType === "Input" || node.componentType === "Select") {
|
|
3037
|
-
return
|
|
3613
|
+
return inputControlHeight + controlLabelOffset;
|
|
3614
|
+
}
|
|
3615
|
+
if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
|
|
3616
|
+
return actionControlHeight + controlLabelOffset;
|
|
3038
3617
|
}
|
|
3039
3618
|
return this.getComponentHeight();
|
|
3040
3619
|
}
|
|
@@ -3051,7 +3630,10 @@ var LayoutEngine = class {
|
|
|
3051
3630
|
}
|
|
3052
3631
|
if (node.componentType === "IconButton") {
|
|
3053
3632
|
const size = String(node.props.size || "md");
|
|
3054
|
-
|
|
3633
|
+
const density = this.style.density || "normal";
|
|
3634
|
+
const baseSize = resolveIconButtonSize(size, density);
|
|
3635
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
3636
|
+
return baseSize + extraPadding * 2;
|
|
3055
3637
|
}
|
|
3056
3638
|
if (node.componentType === "Checkbox" || node.componentType === "Radio") {
|
|
3057
3639
|
return 24;
|
|
@@ -3060,11 +3642,13 @@ var LayoutEngine = class {
|
|
|
3060
3642
|
if (node.componentType === "Button" || node.componentType === "Link") {
|
|
3061
3643
|
const text = String(node.props.text || "");
|
|
3062
3644
|
const { fontSize, paddingX } = this.getButtonMetricsForDensity();
|
|
3645
|
+
const density = this.style.density || "normal";
|
|
3646
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
3063
3647
|
const textWidth = this.estimateTextWidth(text, fontSize);
|
|
3064
|
-
return Math.max(60, Math.ceil(textWidth + paddingX * 2));
|
|
3648
|
+
return Math.max(60, Math.ceil(textWidth + (paddingX + extraPadding) * 2));
|
|
3065
3649
|
}
|
|
3066
3650
|
if (node.componentType === "Label" || node.componentType === "Text") {
|
|
3067
|
-
const text = String(node.props.
|
|
3651
|
+
const text = String(node.props.text || "");
|
|
3068
3652
|
return Math.max(60, text.length * 8 + 16);
|
|
3069
3653
|
}
|
|
3070
3654
|
if (node.componentType === "Heading") {
|
|
@@ -3926,7 +4510,7 @@ var THEMES = {
|
|
|
3926
4510
|
bg: "#F8FAFC",
|
|
3927
4511
|
cardBg: "#FFFFFF",
|
|
3928
4512
|
border: "#E2E8F0",
|
|
3929
|
-
text: "#
|
|
4513
|
+
text: "#000000",
|
|
3930
4514
|
textMuted: "#64748B",
|
|
3931
4515
|
primary: "#3B82F6",
|
|
3932
4516
|
primaryHover: "#2563EB",
|
|
@@ -3936,7 +4520,7 @@ var THEMES = {
|
|
|
3936
4520
|
bg: "#0F172A",
|
|
3937
4521
|
cardBg: "#1E293B",
|
|
3938
4522
|
border: "#334155",
|
|
3939
|
-
text: "#
|
|
4523
|
+
text: "#FFFFFF",
|
|
3940
4524
|
textMuted: "#94A3B8",
|
|
3941
4525
|
primary: "#60A5FA",
|
|
3942
4526
|
primaryHover: "#3B82F6",
|
|
@@ -3946,7 +4530,7 @@ var THEMES = {
|
|
|
3946
4530
|
var SVGRenderer = class {
|
|
3947
4531
|
constructor(ir, layout, options) {
|
|
3948
4532
|
this.renderedNodeIds = /* @__PURE__ */ new Set();
|
|
3949
|
-
this.fontFamily = "
|
|
4533
|
+
this.fontFamily = "Arial, Helvetica, sans-serif";
|
|
3950
4534
|
this.parentContainerByChildId = /* @__PURE__ */ new Map();
|
|
3951
4535
|
this.ir = ir;
|
|
3952
4536
|
this.layout = layout;
|
|
@@ -3960,7 +4544,6 @@ var SVGRenderer = class {
|
|
|
3960
4544
|
includeLabels: options?.includeLabels ?? true,
|
|
3961
4545
|
screenName: options?.screenName
|
|
3962
4546
|
};
|
|
3963
|
-
this.renderTheme = THEMES[this.options.theme];
|
|
3964
4547
|
this.colorResolver = new ColorResolver();
|
|
3965
4548
|
this.buildParentContainerIndex();
|
|
3966
4549
|
if (ir.project.mocks && Object.keys(ir.project.mocks).length > 0) {
|
|
@@ -3969,6 +4552,12 @@ var SVGRenderer = class {
|
|
|
3969
4552
|
if (ir.project.colors && Object.keys(ir.project.colors).length > 0) {
|
|
3970
4553
|
this.colorResolver.setCustomColors(ir.project.colors);
|
|
3971
4554
|
}
|
|
4555
|
+
const themeDefaults = THEMES[this.options.theme];
|
|
4556
|
+
this.renderTheme = {
|
|
4557
|
+
...themeDefaults,
|
|
4558
|
+
text: this.resolveTextColor(),
|
|
4559
|
+
textMuted: this.resolveMutedColor()
|
|
4560
|
+
};
|
|
3972
4561
|
}
|
|
3973
4562
|
/**
|
|
3974
4563
|
* Get list of available screens in the project
|
|
@@ -4050,6 +4639,9 @@ var SVGRenderer = class {
|
|
|
4050
4639
|
if (node.containerType === "card") {
|
|
4051
4640
|
this.renderCardBorder(node, pos, containerGroup);
|
|
4052
4641
|
}
|
|
4642
|
+
if (node.containerType === "split") {
|
|
4643
|
+
this.renderSplitDecoration(node, pos, containerGroup);
|
|
4644
|
+
}
|
|
4053
4645
|
node.children.forEach((childRef) => {
|
|
4054
4646
|
this.renderNode(childRef.ref, containerGroup);
|
|
4055
4647
|
});
|
|
@@ -4137,6 +4729,8 @@ var SVGRenderer = class {
|
|
|
4137
4729
|
}
|
|
4138
4730
|
renderHeading(node, pos) {
|
|
4139
4731
|
const text = String(node.props.text || "Heading");
|
|
4732
|
+
const variant = String(node.props.variant || "default");
|
|
4733
|
+
const headingColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
4140
4734
|
const headingTypography = this.getHeadingTypography(node);
|
|
4141
4735
|
const fontSize = headingTypography.fontSize;
|
|
4142
4736
|
const fontWeight = headingTypography.fontWeight;
|
|
@@ -4146,10 +4740,10 @@ var SVGRenderer = class {
|
|
|
4146
4740
|
if (lines.length <= 1) {
|
|
4147
4741
|
return `<g${this.getDataNodeId(node)}>
|
|
4148
4742
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4149
|
-
font-family="
|
|
4743
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4150
4744
|
font-size="${fontSize}"
|
|
4151
4745
|
font-weight="${fontWeight}"
|
|
4152
|
-
fill="${
|
|
4746
|
+
fill="${headingColor}">${this.escapeXml(text)}</text>
|
|
4153
4747
|
</g>`;
|
|
4154
4748
|
}
|
|
4155
4749
|
const tspans = lines.map(
|
|
@@ -4157,41 +4751,46 @@ var SVGRenderer = class {
|
|
|
4157
4751
|
).join("");
|
|
4158
4752
|
return `<g${this.getDataNodeId(node)}>
|
|
4159
4753
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4160
|
-
font-family="
|
|
4754
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4161
4755
|
font-size="${fontSize}"
|
|
4162
4756
|
font-weight="${fontWeight}"
|
|
4163
|
-
fill="${
|
|
4757
|
+
fill="${headingColor}">${tspans}</text>
|
|
4164
4758
|
</g>`;
|
|
4165
4759
|
}
|
|
4166
4760
|
renderButton(node, pos) {
|
|
4167
4761
|
const text = String(node.props.text || "Button");
|
|
4168
4762
|
const variant = String(node.props.variant || "default");
|
|
4763
|
+
const size = String(node.props.size || "md");
|
|
4764
|
+
const density = this.ir.project.style.density || "normal";
|
|
4765
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
4766
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
4169
4767
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
4170
4768
|
const radius = this.tokens.button.radius;
|
|
4171
4769
|
const fontSize = this.tokens.button.fontSize;
|
|
4172
4770
|
const fontWeight = this.tokens.button.fontWeight;
|
|
4173
4771
|
const paddingX = this.tokens.button.paddingX;
|
|
4174
|
-
const
|
|
4772
|
+
const controlHeight = resolveActionControlHeight(size, density);
|
|
4773
|
+
const buttonY = pos.y + labelOffset;
|
|
4774
|
+
const buttonHeight = Math.max(16, Math.min(controlHeight, pos.height - labelOffset));
|
|
4175
4775
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4176
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60), pos.width);
|
|
4177
|
-
const
|
|
4178
|
-
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
4776
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (paddingX + extraPadding) * 2), 60), pos.width);
|
|
4777
|
+
const availableTextWidth = Math.max(0, buttonWidth - (paddingX + extraPadding) * 2);
|
|
4179
4778
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4180
4779
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
4181
4780
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
4182
4781
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4183
4782
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
4184
|
-
const textColor = hasExplicitVariantColor ? "#FFFFFF" :
|
|
4783
|
+
const textColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.85);
|
|
4185
4784
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
4186
4785
|
return `<g${this.getDataNodeId(node)}>
|
|
4187
|
-
<rect x="${pos.x}" y="${
|
|
4786
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
4188
4787
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4189
4788
|
rx="${radius}"
|
|
4190
4789
|
fill="${bgColor}"
|
|
4191
4790
|
stroke="${borderColor}"
|
|
4192
4791
|
stroke-width="1"/>
|
|
4193
|
-
<text x="${pos.x + buttonWidth / 2}" y="${
|
|
4194
|
-
font-family="
|
|
4792
|
+
<text x="${pos.x + buttonWidth / 2}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
4793
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4195
4794
|
font-size="${fontSize}"
|
|
4196
4795
|
font-weight="${fontWeight}"
|
|
4197
4796
|
fill="${textColor}"
|
|
@@ -4201,17 +4800,18 @@ var SVGRenderer = class {
|
|
|
4201
4800
|
renderLink(node, pos) {
|
|
4202
4801
|
const text = String(node.props.text || "Link");
|
|
4203
4802
|
const variant = String(node.props.variant || "primary");
|
|
4803
|
+
const size = String(node.props.size || "md");
|
|
4804
|
+
const density = this.ir.project.style.density || "normal";
|
|
4204
4805
|
const fontSize = this.tokens.button.fontSize;
|
|
4205
4806
|
const fontWeight = this.tokens.button.fontWeight;
|
|
4206
4807
|
const paddingX = this.tokens.button.paddingX;
|
|
4207
|
-
const paddingY = this.tokens.button.paddingY;
|
|
4208
4808
|
const linkColor = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4209
4809
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4210
4810
|
const linkWidth = this.clampControlWidth(
|
|
4211
4811
|
Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60),
|
|
4212
4812
|
pos.width
|
|
4213
4813
|
);
|
|
4214
|
-
const linkHeight =
|
|
4814
|
+
const linkHeight = Math.max(16, Math.min(resolveActionControlHeight(size, density), pos.height));
|
|
4215
4815
|
const availableTextWidth = Math.max(0, linkWidth - paddingX * 2);
|
|
4216
4816
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4217
4817
|
const visibleTextWidth = Math.min(
|
|
@@ -4222,7 +4822,7 @@ var SVGRenderer = class {
|
|
|
4222
4822
|
const underlineY = centerY + 3;
|
|
4223
4823
|
return `<g${this.getDataNodeId(node)}>
|
|
4224
4824
|
<text x="${pos.x + linkWidth / 2}" y="${centerY}"
|
|
4225
|
-
font-family="
|
|
4825
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4226
4826
|
font-size="${fontSize}"
|
|
4227
4827
|
font-weight="${fontWeight}"
|
|
4228
4828
|
fill="${linkColor}"
|
|
@@ -4244,7 +4844,7 @@ var SVGRenderer = class {
|
|
|
4244
4844
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
4245
4845
|
return `<g${this.getDataNodeId(node)}>
|
|
4246
4846
|
${label ? `<text x="${pos.x + paddingX}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4247
|
-
font-family="
|
|
4847
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4248
4848
|
font-size="12"
|
|
4249
4849
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4250
4850
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4254,7 +4854,7 @@ var SVGRenderer = class {
|
|
|
4254
4854
|
stroke="${this.renderTheme.border}"
|
|
4255
4855
|
stroke-width="1"/>
|
|
4256
4856
|
<text x="${pos.x + paddingX}" y="${controlY + controlHeight / 2 + 5}"
|
|
4257
|
-
font-family="
|
|
4857
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4258
4858
|
font-size="${fontSize}"
|
|
4259
4859
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4260
4860
|
</g>`;
|
|
@@ -4264,25 +4864,42 @@ var SVGRenderer = class {
|
|
|
4264
4864
|
const subtitle = String(node.props.subtitle || "");
|
|
4265
4865
|
const actions = String(node.props.actions || "");
|
|
4266
4866
|
const user = String(node.props.user || "");
|
|
4267
|
-
const
|
|
4867
|
+
const variant = String(node.props.variant || "default");
|
|
4868
|
+
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
4869
|
+
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
4870
|
+
const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
|
|
4871
|
+
const radiusMap = {
|
|
4872
|
+
none: 0,
|
|
4873
|
+
sm: 4,
|
|
4874
|
+
md: this.tokens.card.radius,
|
|
4875
|
+
lg: 12,
|
|
4876
|
+
xl: 16
|
|
4877
|
+
};
|
|
4878
|
+
const topbarRadius = radiusMap[String(node.props.radius || "md")] ?? this.tokens.card.radius;
|
|
4268
4879
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
4269
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
4880
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
4881
|
+
if (showBorder || showBackground) {
|
|
4882
|
+
const bg = showBackground ? this.renderTheme.cardBg : "none";
|
|
4883
|
+
const stroke = showBorder ? this.renderTheme.border : "none";
|
|
4884
|
+
svg += `
|
|
4270
4885
|
<rect x="${pos.x}" y="${pos.y}"
|
|
4271
4886
|
width="${pos.width}" height="${pos.height}"
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
stroke
|
|
4275
|
-
|
|
4887
|
+
rx="${topbarRadius}"
|
|
4888
|
+
fill="${bg}"
|
|
4889
|
+
stroke="${stroke}"
|
|
4890
|
+
stroke-width="1"/>`;
|
|
4891
|
+
}
|
|
4892
|
+
svg += `
|
|
4276
4893
|
<!-- Title -->
|
|
4277
4894
|
<text x="${topbar.textX}" y="${topbar.titleY}"
|
|
4278
|
-
font-family="
|
|
4895
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4279
4896
|
font-size="18"
|
|
4280
4897
|
font-weight="600"
|
|
4281
4898
|
fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
|
|
4282
4899
|
if (topbar.hasSubtitle) {
|
|
4283
4900
|
svg += `
|
|
4284
4901
|
<text x="${topbar.textX}" y="${topbar.subtitleY}"
|
|
4285
|
-
font-family="
|
|
4902
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4286
4903
|
font-size="13"
|
|
4287
4904
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
|
|
4288
4905
|
}
|
|
@@ -4310,7 +4927,7 @@ var SVGRenderer = class {
|
|
|
4310
4927
|
fill="${accentColor}"
|
|
4311
4928
|
stroke="none"/>
|
|
4312
4929
|
<text x="${action.x + action.width / 2}" y="${action.y + action.height / 2 + 4}"
|
|
4313
|
-
font-family="
|
|
4930
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4314
4931
|
font-size="12"
|
|
4315
4932
|
font-weight="600"
|
|
4316
4933
|
fill="white"
|
|
@@ -4326,7 +4943,7 @@ var SVGRenderer = class {
|
|
|
4326
4943
|
stroke="${this.renderTheme.border}"
|
|
4327
4944
|
stroke-width="1"/>
|
|
4328
4945
|
<text x="${topbar.userBadge.x + topbar.userBadge.width / 2}" y="${topbar.userBadge.y + topbar.userBadge.height / 2 + 4}"
|
|
4329
|
-
font-family="
|
|
4946
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4330
4947
|
font-size="12"
|
|
4331
4948
|
fill="${this.renderTheme.text}"
|
|
4332
4949
|
text-anchor="middle">${this.escapeXml(topbar.userBadge.label)}</text>`;
|
|
@@ -4389,77 +5006,165 @@ var SVGRenderer = class {
|
|
|
4389
5006
|
</g>`;
|
|
4390
5007
|
output.push(svg);
|
|
4391
5008
|
}
|
|
5009
|
+
renderSplitDecoration(node, pos, output) {
|
|
5010
|
+
if (node.kind !== "container") return;
|
|
5011
|
+
const gap = this.resolveSpacing(node.style.gap);
|
|
5012
|
+
const leftParam = Number(node.params.left);
|
|
5013
|
+
const rightParam = Number(node.params.right);
|
|
5014
|
+
const hasLeft = node.params.left !== void 0;
|
|
5015
|
+
const hasRight = node.params.right !== void 0 && node.params.left === void 0;
|
|
5016
|
+
const fixedLeftWidth = Number.isFinite(leftParam) && leftParam > 0 ? leftParam : 250;
|
|
5017
|
+
const fixedRightWidth = Number.isFinite(rightParam) && rightParam > 0 ? rightParam : 250;
|
|
5018
|
+
const backgroundKey = String(node.style.background || "").trim();
|
|
5019
|
+
const showBorder = this.parseBooleanProp(node.params.border, false);
|
|
5020
|
+
if (backgroundKey) {
|
|
5021
|
+
const fill = this.colorResolver.resolveColor(backgroundKey, this.renderTheme.cardBg);
|
|
5022
|
+
if (hasRight) {
|
|
5023
|
+
const panelX = pos.x + Math.max(0, pos.width - fixedRightWidth);
|
|
5024
|
+
output.push(`<g>
|
|
5025
|
+
<rect x="${panelX}" y="${pos.y}" width="${Math.max(1, fixedRightWidth)}" height="${pos.height}" fill="${fill}" stroke="none"/>
|
|
5026
|
+
</g>`);
|
|
5027
|
+
} else if (hasLeft || !hasRight) {
|
|
5028
|
+
output.push(`<g>
|
|
5029
|
+
<rect x="${pos.x}" y="${pos.y}" width="${Math.max(1, fixedLeftWidth)}" height="${pos.height}" fill="${fill}" stroke="none"/>
|
|
5030
|
+
</g>`);
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
if (showBorder) {
|
|
5034
|
+
const dividerX = hasRight ? pos.x + Math.max(0, pos.width - fixedRightWidth - gap / 2) : pos.x + Math.max(0, fixedLeftWidth + gap / 2);
|
|
5035
|
+
output.push(`<g>
|
|
5036
|
+
<line x1="${dividerX}" y1="${pos.y}" x2="${dividerX}" y2="${pos.y + pos.height}" stroke="${this.renderTheme.border}" stroke-width="1"/>
|
|
5037
|
+
</g>`);
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
4392
5040
|
renderTable(node, pos) {
|
|
4393
5041
|
const title = String(node.props.title || "");
|
|
4394
5042
|
const columnsStr = String(node.props.columns || "Col1,Col2,Col3");
|
|
4395
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
5043
|
+
const columns = columnsStr.split(",").map((c) => c.trim()).filter(Boolean);
|
|
4396
5044
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
4397
5045
|
const mockStr = String(node.props.mock || "");
|
|
4398
5046
|
const random = this.parseBooleanProp(node.props.random, false);
|
|
4399
|
-
const
|
|
4400
|
-
const
|
|
4401
|
-
const pageCount = Number(
|
|
5047
|
+
const pagination = this.parseBooleanProp(node.props.pagination, false);
|
|
5048
|
+
const parsedPageCount = Number(node.props.pages || 5);
|
|
5049
|
+
const pageCount = Number.isFinite(parsedPageCount) && parsedPageCount > 0 ? Math.floor(parsedPageCount) : 5;
|
|
4402
5050
|
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
5051
|
+
const actions = String(node.props.actions || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
5052
|
+
const hasActions = actions.length > 0;
|
|
5053
|
+
const caption = String(node.props.caption || "").trim();
|
|
5054
|
+
const hasCaption = caption.length > 0;
|
|
5055
|
+
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
5056
|
+
const showOuterBackground = this.parseBooleanProp(
|
|
5057
|
+
node.props.background ?? node.props.backround,
|
|
5058
|
+
false
|
|
5059
|
+
);
|
|
5060
|
+
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
5061
|
+
const rawCaptionAlign = String(node.props.captionAlign || "");
|
|
5062
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
5063
|
+
const sameFooterAlign = hasCaption && pagination && captionAlign === paginationAlign;
|
|
4403
5064
|
const mockTypes = mockStr ? mockStr.split(",").map((m) => m.trim()).filter(Boolean) : [];
|
|
4404
|
-
|
|
4405
|
-
|
|
5065
|
+
const safeColumns = columns.length > 0 ? columns : ["Column"];
|
|
5066
|
+
while (mockTypes.length < safeColumns.length) {
|
|
5067
|
+
const inferred = MockDataGenerator.inferMockTypeFromColumn(safeColumns[mockTypes.length] || "item");
|
|
4406
5068
|
mockTypes.push(inferred);
|
|
4407
5069
|
}
|
|
4408
5070
|
const headerHeight = 44;
|
|
4409
5071
|
const rowHeight = 36;
|
|
4410
|
-
const
|
|
5072
|
+
const actionColumnWidth = hasActions ? Math.max(96, Math.min(180, actions.length * 26 + 28)) : 0;
|
|
5073
|
+
const dataWidth = Math.max(20, pos.width - actionColumnWidth);
|
|
5074
|
+
const dataColWidth = dataWidth / safeColumns.length;
|
|
4411
5075
|
const mockRows = [];
|
|
4412
5076
|
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
4413
5077
|
const row = {};
|
|
4414
|
-
|
|
5078
|
+
safeColumns.forEach((col, colIdx) => {
|
|
4415
5079
|
const mockType = mockTypes[colIdx] || MockDataGenerator.inferMockTypeFromColumn(col) || "item";
|
|
4416
5080
|
row[col] = MockDataGenerator.getMockValue(mockType, rowIdx, random);
|
|
4417
5081
|
});
|
|
4418
5082
|
mockRows.push(row);
|
|
4419
5083
|
}
|
|
4420
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
5084
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5085
|
+
if (showOuterBorder || showOuterBackground) {
|
|
5086
|
+
const outerFill = showOuterBackground ? this.renderTheme.cardBg : "none";
|
|
5087
|
+
const outerStroke = showOuterBorder ? this.renderTheme.border : "none";
|
|
5088
|
+
svg += `
|
|
4421
5089
|
<rect x="${pos.x}" y="${pos.y}"
|
|
4422
5090
|
width="${pos.width}" height="${pos.height}"
|
|
4423
5091
|
rx="8"
|
|
4424
|
-
fill="${
|
|
4425
|
-
stroke="${
|
|
5092
|
+
fill="${outerFill}"
|
|
5093
|
+
stroke="${outerStroke}"
|
|
4426
5094
|
stroke-width="1"/>`;
|
|
5095
|
+
}
|
|
4427
5096
|
if (title) {
|
|
4428
5097
|
svg += `
|
|
4429
5098
|
<text x="${pos.x + 16}" y="${pos.y + 24}"
|
|
4430
|
-
font-family="
|
|
5099
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4431
5100
|
font-size="13"
|
|
4432
5101
|
font-weight="600"
|
|
4433
5102
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>`;
|
|
4434
5103
|
}
|
|
4435
5104
|
const headerY = pos.y + (title ? 32 : 0);
|
|
4436
|
-
|
|
5105
|
+
if (showInnerBorder) {
|
|
5106
|
+
svg += `
|
|
4437
5107
|
<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
4438
5108
|
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
4439
|
-
|
|
5109
|
+
}
|
|
5110
|
+
safeColumns.forEach((col, i) => {
|
|
4440
5111
|
svg += `
|
|
4441
|
-
<text x="${pos.x + i *
|
|
4442
|
-
font-family="
|
|
5112
|
+
<text x="${pos.x + i * dataColWidth + 12}" y="${headerY + 26}"
|
|
5113
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4443
5114
|
font-size="11"
|
|
4444
5115
|
font-weight="600"
|
|
4445
5116
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(col)}</text>`;
|
|
4446
5117
|
});
|
|
5118
|
+
if (hasActions && showInnerBorder) {
|
|
5119
|
+
const dividerX = pos.x + dataWidth;
|
|
5120
|
+
svg += `
|
|
5121
|
+
<line x1="${dividerX}" y1="${headerY + headerHeight}" x2="${dividerX}" y2="${headerY + headerHeight + mockRows.length * rowHeight}"
|
|
5122
|
+
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
5123
|
+
}
|
|
4447
5124
|
mockRows.forEach((row, rowIdx) => {
|
|
4448
5125
|
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
4449
|
-
|
|
5126
|
+
if (showInnerBorder) {
|
|
5127
|
+
svg += `
|
|
4450
5128
|
<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
4451
5129
|
stroke="${this.renderTheme.border}" stroke-width="0.5"/>`;
|
|
4452
|
-
|
|
5130
|
+
}
|
|
5131
|
+
safeColumns.forEach((col, colIdx) => {
|
|
4453
5132
|
const cellValue = row[col] || "";
|
|
4454
5133
|
svg += `
|
|
4455
|
-
<text x="${pos.x + colIdx *
|
|
4456
|
-
font-family="
|
|
5134
|
+
<text x="${pos.x + colIdx * dataColWidth + 12}" y="${rowY + 22}"
|
|
5135
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4457
5136
|
font-size="12"
|
|
4458
5137
|
fill="${this.renderTheme.text}">${this.escapeXml(cellValue)}</text>`;
|
|
4459
5138
|
});
|
|
5139
|
+
if (hasActions) {
|
|
5140
|
+
const iconSize = 14;
|
|
5141
|
+
const buttonSize = 22;
|
|
5142
|
+
const buttonGap = 6;
|
|
5143
|
+
const actionsWidth = actions.length * buttonSize + Math.max(0, actions.length - 1) * buttonGap;
|
|
5144
|
+
let currentX = pos.x + pos.width - 12 - actionsWidth;
|
|
5145
|
+
const buttonY = rowY + (rowHeight - buttonSize) / 2;
|
|
5146
|
+
actions.forEach((actionIcon) => {
|
|
5147
|
+
const iconSvg = getIcon(actionIcon);
|
|
5148
|
+
const iconX = currentX + (buttonSize - iconSize) / 2;
|
|
5149
|
+
const iconY = buttonY + (buttonSize - iconSize) / 2;
|
|
5150
|
+
svg += `
|
|
5151
|
+
<rect x="${currentX}" y="${buttonY}" width="${buttonSize}" height="${buttonSize}" rx="4"
|
|
5152
|
+
fill="${this.renderTheme.cardBg}" stroke="${showInnerBorder ? this.renderTheme.border : "none"}" stroke-width="1"/>`;
|
|
5153
|
+
if (iconSvg) {
|
|
5154
|
+
svg += `
|
|
5155
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5156
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.hexToRgba(this.resolveTextColor(), 0.75)}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5157
|
+
${this.extractSvgContent(iconSvg)}
|
|
5158
|
+
</svg>
|
|
5159
|
+
</g>`;
|
|
5160
|
+
}
|
|
5161
|
+
currentX += buttonSize + buttonGap;
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
4460
5164
|
});
|
|
5165
|
+
const footerTop = headerY + headerHeight + mockRows.length * rowHeight + 16;
|
|
4461
5166
|
if (pagination) {
|
|
4462
|
-
const paginationY =
|
|
5167
|
+
const paginationY = sameFooterAlign ? footerTop + 18 + 8 : footerTop;
|
|
4463
5168
|
const buttonWidth = 40;
|
|
4464
5169
|
const buttonHeight = 32;
|
|
4465
5170
|
const gap = 8;
|
|
@@ -4472,18 +5177,25 @@ var SVGRenderer = class {
|
|
|
4472
5177
|
} else {
|
|
4473
5178
|
startX = pos.x + pos.width - totalWidth - 16;
|
|
4474
5179
|
}
|
|
5180
|
+
const previousIcon = getIcon("chevron-left");
|
|
4475
5181
|
svg += `
|
|
4476
5182
|
<rect x="${startX}" y="${paginationY}"
|
|
4477
5183
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4478
5184
|
rx="4"
|
|
4479
5185
|
fill="${this.renderTheme.cardBg}"
|
|
4480
5186
|
stroke="${this.renderTheme.border}"
|
|
4481
|
-
stroke-width="1"
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
5187
|
+
stroke-width="1"/>`;
|
|
5188
|
+
if (previousIcon) {
|
|
5189
|
+
const iconSize = 14;
|
|
5190
|
+
const iconX = startX + (buttonWidth - iconSize) / 2;
|
|
5191
|
+
const iconY = paginationY + (buttonHeight - iconSize) / 2;
|
|
5192
|
+
svg += `
|
|
5193
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5194
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.resolveTextColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5195
|
+
${this.extractSvgContent(previousIcon)}
|
|
5196
|
+
</svg>
|
|
5197
|
+
</g>`;
|
|
5198
|
+
}
|
|
4487
5199
|
for (let i = 1; i <= pageCount; i++) {
|
|
4488
5200
|
const btnX = startX + (buttonWidth + gap) * i;
|
|
4489
5201
|
const isActive = i === 1;
|
|
@@ -4497,24 +5209,49 @@ var SVGRenderer = class {
|
|
|
4497
5209
|
stroke="${this.renderTheme.border}"
|
|
4498
5210
|
stroke-width="1"/>
|
|
4499
5211
|
<text x="${btnX + buttonWidth / 2}" y="${paginationY + buttonHeight / 2 + 4}"
|
|
4500
|
-
font-family="
|
|
5212
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4501
5213
|
font-size="14"
|
|
4502
5214
|
fill="${textColor}"
|
|
4503
5215
|
text-anchor="middle">${i}</text>`;
|
|
4504
5216
|
}
|
|
4505
5217
|
const nextX = startX + (buttonWidth + gap) * (pageCount + 1);
|
|
5218
|
+
const nextIcon = getIcon("chevron-right");
|
|
4506
5219
|
svg += `
|
|
4507
5220
|
<rect x="${nextX}" y="${paginationY}"
|
|
4508
5221
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4509
5222
|
rx="4"
|
|
4510
5223
|
fill="${this.renderTheme.cardBg}"
|
|
4511
5224
|
stroke="${this.renderTheme.border}"
|
|
4512
|
-
stroke-width="1"
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
5225
|
+
stroke-width="1"/>`;
|
|
5226
|
+
if (nextIcon) {
|
|
5227
|
+
const iconSize = 14;
|
|
5228
|
+
const iconX = nextX + (buttonWidth - iconSize) / 2;
|
|
5229
|
+
const iconY = paginationY + (buttonHeight - iconSize) / 2;
|
|
5230
|
+
svg += `
|
|
5231
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5232
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.resolveTextColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5233
|
+
${this.extractSvgContent(nextIcon)}
|
|
5234
|
+
</svg>
|
|
5235
|
+
</g>`;
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
5238
|
+
if (hasCaption) {
|
|
5239
|
+
const captionY = sameFooterAlign ? footerTop + 12 : footerTop + (pagination ? 21 : 12);
|
|
5240
|
+
let captionX = pos.x + 16;
|
|
5241
|
+
let textAnchor = "start";
|
|
5242
|
+
if (captionAlign === "center") {
|
|
5243
|
+
captionX = pos.x + pos.width / 2;
|
|
5244
|
+
textAnchor = "middle";
|
|
5245
|
+
} else if (captionAlign === "right") {
|
|
5246
|
+
captionX = pos.x + pos.width - 16;
|
|
5247
|
+
textAnchor = "end";
|
|
5248
|
+
}
|
|
5249
|
+
svg += `
|
|
5250
|
+
<text x="${captionX}" y="${captionY}"
|
|
5251
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5252
|
+
font-size="12"
|
|
5253
|
+
fill="${this.hexToRgba(this.resolveTextColor(), 0.75)}"
|
|
5254
|
+
text-anchor="${textAnchor}">${this.escapeXml(caption)}</text>`;
|
|
4518
5255
|
}
|
|
4519
5256
|
svg += "\n </g>";
|
|
4520
5257
|
return svg;
|
|
@@ -4629,7 +5366,7 @@ var SVGRenderer = class {
|
|
|
4629
5366
|
// TEXT/CONTENT COMPONENTS
|
|
4630
5367
|
// ============================================================================
|
|
4631
5368
|
renderText(node, pos) {
|
|
4632
|
-
const text = String(node.props.
|
|
5369
|
+
const text = String(node.props.text || "Text content");
|
|
4633
5370
|
const fontSize = this.tokens.text.fontSize;
|
|
4634
5371
|
const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
|
|
4635
5372
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
@@ -4639,7 +5376,7 @@ var SVGRenderer = class {
|
|
|
4639
5376
|
).join("");
|
|
4640
5377
|
return `<g${this.getDataNodeId(node)}>
|
|
4641
5378
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4642
|
-
font-family="
|
|
5379
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4643
5380
|
font-size="${fontSize}"
|
|
4644
5381
|
fill="${this.renderTheme.text}">${tspans}</text>
|
|
4645
5382
|
</g>`;
|
|
@@ -4648,7 +5385,7 @@ var SVGRenderer = class {
|
|
|
4648
5385
|
const text = String(node.props.text || "Label");
|
|
4649
5386
|
return `<g${this.getDataNodeId(node)}>
|
|
4650
5387
|
<text x="${pos.x}" y="${pos.y + 12}"
|
|
4651
|
-
font-family="
|
|
5388
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4652
5389
|
font-size="12"
|
|
4653
5390
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
|
|
4654
5391
|
</g>`;
|
|
@@ -4682,7 +5419,7 @@ var SVGRenderer = class {
|
|
|
4682
5419
|
const placeholderY = controlY + fontSize + 6;
|
|
4683
5420
|
return `<g${this.getDataNodeId(node)}>
|
|
4684
5421
|
${label ? `<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4685
|
-
font-family="
|
|
5422
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4686
5423
|
font-size="12"
|
|
4687
5424
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4688
5425
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4692,7 +5429,7 @@ var SVGRenderer = class {
|
|
|
4692
5429
|
stroke="${this.renderTheme.border}"
|
|
4693
5430
|
stroke-width="1"/>
|
|
4694
5431
|
<text x="${pos.x + paddingX}" y="${placeholderY}"
|
|
4695
|
-
font-family="
|
|
5432
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4696
5433
|
font-size="${fontSize}"
|
|
4697
5434
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4698
5435
|
</g>`;
|
|
@@ -4706,7 +5443,7 @@ var SVGRenderer = class {
|
|
|
4706
5443
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
4707
5444
|
return `<g${this.getDataNodeId(node)}>
|
|
4708
5445
|
${label ? `<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4709
|
-
font-family="
|
|
5446
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4710
5447
|
font-size="12"
|
|
4711
5448
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4712
5449
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4716,11 +5453,11 @@ var SVGRenderer = class {
|
|
|
4716
5453
|
stroke="${this.renderTheme.border}"
|
|
4717
5454
|
stroke-width="1"/>
|
|
4718
5455
|
<text x="${pos.x + 12}" y="${centerY}"
|
|
4719
|
-
font-family="
|
|
5456
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4720
5457
|
font-size="14"
|
|
4721
5458
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4722
5459
|
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
4723
|
-
font-family="
|
|
5460
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4724
5461
|
font-size="16"
|
|
4725
5462
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
4726
5463
|
</g>`;
|
|
@@ -4739,12 +5476,12 @@ var SVGRenderer = class {
|
|
|
4739
5476
|
stroke="${this.renderTheme.border}"
|
|
4740
5477
|
stroke-width="1"/>
|
|
4741
5478
|
${checked ? `<text x="${pos.x + checkboxSize / 2}" y="${checkboxY + 14}"
|
|
4742
|
-
font-family="
|
|
5479
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4743
5480
|
font-size="12"
|
|
4744
5481
|
fill="white"
|
|
4745
5482
|
text-anchor="middle">\u2713</text>` : ""}
|
|
4746
5483
|
<text x="${pos.x + checkboxSize + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4747
|
-
font-family="
|
|
5484
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4748
5485
|
font-size="14"
|
|
4749
5486
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4750
5487
|
</g>`;
|
|
@@ -4765,7 +5502,7 @@ var SVGRenderer = class {
|
|
|
4765
5502
|
r="${radioSize / 3.5}"
|
|
4766
5503
|
fill="${controlColor}"/>` : ""}
|
|
4767
5504
|
<text x="${pos.x + radioSize + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4768
|
-
font-family="
|
|
5505
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4769
5506
|
font-size="14"
|
|
4770
5507
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4771
5508
|
</g>`;
|
|
@@ -4787,7 +5524,7 @@ var SVGRenderer = class {
|
|
|
4787
5524
|
r="8"
|
|
4788
5525
|
fill="white"/>
|
|
4789
5526
|
<text x="${pos.x + toggleWidth + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4790
|
-
font-family="
|
|
5527
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4791
5528
|
font-size="14"
|
|
4792
5529
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4793
5530
|
</g>`;
|
|
@@ -4817,7 +5554,7 @@ var SVGRenderer = class {
|
|
|
4817
5554
|
stroke-width="1"/>
|
|
4818
5555
|
<!-- Title -->
|
|
4819
5556
|
<text x="${pos.x + padding}" y="${pos.y + padding + 8}"
|
|
4820
|
-
font-family="
|
|
5557
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4821
5558
|
font-size="14"
|
|
4822
5559
|
font-weight="600"
|
|
4823
5560
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
@@ -4833,7 +5570,7 @@ var SVGRenderer = class {
|
|
|
4833
5570
|
fill="${isActive ? this.renderTheme.primary : "transparent"}"
|
|
4834
5571
|
stroke="none"/>
|
|
4835
5572
|
<text x="${pos.x + 16}" y="${itemY + 22}"
|
|
4836
|
-
font-family="
|
|
5573
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4837
5574
|
font-size="13"
|
|
4838
5575
|
fill="${isActive ? "white" : this.renderTheme.textMuted}">${this.escapeXml(item)}</text>`;
|
|
4839
5576
|
});
|
|
@@ -4859,7 +5596,7 @@ var SVGRenderer = class {
|
|
|
4859
5596
|
stroke="${isActive ? "none" : this.renderTheme.border}"
|
|
4860
5597
|
stroke-width="1"/>
|
|
4861
5598
|
<text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
|
|
4862
|
-
font-family="
|
|
5599
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4863
5600
|
font-size="13"
|
|
4864
5601
|
font-weight="${isActive ? "600" : "500"}"
|
|
4865
5602
|
fill="${isActive ? "white" : this.renderTheme.text}"
|
|
@@ -4922,12 +5659,12 @@ var SVGRenderer = class {
|
|
|
4922
5659
|
rx="6"
|
|
4923
5660
|
fill="${bgColor}"/>
|
|
4924
5661
|
${hasTitle ? `<text x="${contentX}" y="${titleStartY}"
|
|
4925
|
-
font-family="
|
|
5662
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4926
5663
|
font-size="${fontSize}"
|
|
4927
5664
|
font-weight="700"
|
|
4928
5665
|
fill="${bgColor}">${titleTspans}</text>` : ""}
|
|
4929
5666
|
<text x="${contentX}" y="${textStartY}"
|
|
4930
|
-
font-family="
|
|
5667
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4931
5668
|
font-size="${fontSize}"
|
|
4932
5669
|
fill="${bgColor}">${textTspans}</text>
|
|
4933
5670
|
</g>`;
|
|
@@ -4948,7 +5685,7 @@ var SVGRenderer = class {
|
|
|
4948
5685
|
fill="${bgColor}"
|
|
4949
5686
|
stroke="none"/>
|
|
4950
5687
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
4951
|
-
font-family="
|
|
5688
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4952
5689
|
font-size="${fontSize}"
|
|
4953
5690
|
font-weight="600"
|
|
4954
5691
|
fill="${textColor}"
|
|
@@ -4987,20 +5724,20 @@ var SVGRenderer = class {
|
|
|
4987
5724
|
stroke-width="1"/>
|
|
4988
5725
|
|
|
4989
5726
|
<text x="${modalX + padding}" y="${modalY + padding + 16}"
|
|
4990
|
-
font-family="
|
|
5727
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4991
5728
|
font-size="16"
|
|
4992
5729
|
font-weight="600"
|
|
4993
5730
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
4994
5731
|
|
|
4995
5732
|
<!-- Close button -->
|
|
4996
5733
|
<text x="${modalX + pos.width - 16}" y="${modalY + padding + 12}"
|
|
4997
|
-
font-family="
|
|
5734
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4998
5735
|
font-size="18"
|
|
4999
5736
|
fill="${this.renderTheme.textMuted}">\u2715</text>
|
|
5000
5737
|
|
|
5001
5738
|
<!-- Content placeholder -->
|
|
5002
5739
|
<text x="${modalX + pos.width / 2}" y="${modalY + headerHeight + (pos.height - headerHeight) / 2}"
|
|
5003
|
-
font-family="
|
|
5740
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5004
5741
|
font-size="13"
|
|
5005
5742
|
fill="${this.renderTheme.textMuted}"
|
|
5006
5743
|
text-anchor="middle">Modal content</text>
|
|
@@ -5033,7 +5770,7 @@ var SVGRenderer = class {
|
|
|
5033
5770
|
if (title) {
|
|
5034
5771
|
svg += `
|
|
5035
5772
|
<text x="${pos.x + padding}" y="${pos.y + 26}"
|
|
5036
|
-
font-family="
|
|
5773
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5037
5774
|
font-size="13"
|
|
5038
5775
|
font-weight="600"
|
|
5039
5776
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
@@ -5049,7 +5786,7 @@ var SVGRenderer = class {
|
|
|
5049
5786
|
stroke="${this.renderTheme.border}"
|
|
5050
5787
|
stroke-width="0.5"/>
|
|
5051
5788
|
<text x="${pos.x + padding}" y="${itemY + 24}"
|
|
5052
|
-
font-family="
|
|
5789
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5053
5790
|
font-size="13"
|
|
5054
5791
|
fill="${this.renderTheme.text}">${this.escapeXml(item)}</text>`;
|
|
5055
5792
|
}
|
|
@@ -5067,7 +5804,7 @@ var SVGRenderer = class {
|
|
|
5067
5804
|
stroke-width="1"
|
|
5068
5805
|
stroke-dasharray="4 4"/>
|
|
5069
5806
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
|
|
5070
|
-
font-family="
|
|
5807
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5071
5808
|
font-size="12"
|
|
5072
5809
|
fill="${this.renderTheme.textMuted}"
|
|
5073
5810
|
text-anchor="middle">${node.componentType}</text>
|
|
@@ -5115,14 +5852,14 @@ var SVGRenderer = class {
|
|
|
5115
5852
|
|
|
5116
5853
|
<!-- Title -->
|
|
5117
5854
|
<text x="${innerX}" y="${titleY}"
|
|
5118
|
-
font-family="
|
|
5855
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5119
5856
|
font-size="${titleSize}"
|
|
5120
5857
|
font-weight="500"
|
|
5121
5858
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(visibleTitle)}</text>
|
|
5122
5859
|
|
|
5123
5860
|
<!-- Value (Large) -->
|
|
5124
5861
|
<text x="${innerX}" y="${valueY}"
|
|
5125
|
-
font-family="
|
|
5862
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5126
5863
|
font-size="${valueSize}"
|
|
5127
5864
|
font-weight="700"
|
|
5128
5865
|
fill="${accentColor}">${this.escapeXml(value)}</text>`;
|
|
@@ -5145,7 +5882,7 @@ var SVGRenderer = class {
|
|
|
5145
5882
|
svg += `
|
|
5146
5883
|
<!-- Caption -->
|
|
5147
5884
|
<text x="${innerX}" y="${captionY}"
|
|
5148
|
-
font-family="
|
|
5885
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5149
5886
|
font-size="${captionSize}"
|
|
5150
5887
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(visibleCaption)}</text>`;
|
|
5151
5888
|
}
|
|
@@ -5274,7 +6011,7 @@ var SVGRenderer = class {
|
|
|
5274
6011
|
const fontWeight = isLast ? "500" : "400";
|
|
5275
6012
|
svg += `
|
|
5276
6013
|
<text x="${currentX}" y="${pos.y + pos.height / 2 + 4}"
|
|
5277
|
-
font-family="
|
|
6014
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5278
6015
|
font-size="${fontSize}"
|
|
5279
6016
|
font-weight="${fontWeight}"
|
|
5280
6017
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
@@ -5283,7 +6020,7 @@ var SVGRenderer = class {
|
|
|
5283
6020
|
if (!isLast) {
|
|
5284
6021
|
svg += `
|
|
5285
6022
|
<text x="${currentX + 4}" y="${pos.y + pos.height / 2 + 4}"
|
|
5286
|
-
font-family="
|
|
6023
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5287
6024
|
font-size="${fontSize}"
|
|
5288
6025
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(separator)}</text>`;
|
|
5289
6026
|
currentX += separatorWidth;
|
|
@@ -5306,7 +6043,7 @@ var SVGRenderer = class {
|
|
|
5306
6043
|
const itemY = pos.y + index * itemHeight;
|
|
5307
6044
|
const isActive = index === activeIndex;
|
|
5308
6045
|
const bgColor = isActive ? this.hexToRgba(accentColor, 0.15) : "transparent";
|
|
5309
|
-
const textColor = isActive ? this.hexToRgba(accentColor, 0.9) :
|
|
6046
|
+
const textColor = isActive ? this.hexToRgba(accentColor, 0.9) : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
5310
6047
|
const fontWeight = isActive ? "500" : "400";
|
|
5311
6048
|
if (isActive) {
|
|
5312
6049
|
svg += `
|
|
@@ -5323,7 +6060,7 @@ var SVGRenderer = class {
|
|
|
5323
6060
|
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
5324
6061
|
svg += `
|
|
5325
6062
|
<g transform="translate(${currentX}, ${iconY})">
|
|
5326
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
6063
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.hexToRgba(this.resolveMutedColor(), 0.9)}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5327
6064
|
${this.extractSvgContent(iconSvg)}
|
|
5328
6065
|
</svg>
|
|
5329
6066
|
</g>`;
|
|
@@ -5332,7 +6069,7 @@ var SVGRenderer = class {
|
|
|
5332
6069
|
}
|
|
5333
6070
|
svg += `
|
|
5334
6071
|
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
5335
|
-
font-family="
|
|
6072
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5336
6073
|
font-size="${fontSize}"
|
|
5337
6074
|
font-weight="${fontWeight}"
|
|
5338
6075
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
@@ -5341,18 +6078,19 @@ var SVGRenderer = class {
|
|
|
5341
6078
|
return svg;
|
|
5342
6079
|
}
|
|
5343
6080
|
renderIcon(node, pos) {
|
|
5344
|
-
const iconType = String(node.props.
|
|
6081
|
+
const iconType = String(node.props.icon || "help-circle");
|
|
5345
6082
|
const size = String(node.props.size || "md");
|
|
6083
|
+
const variant = String(node.props.variant || "default");
|
|
5346
6084
|
const iconSvg = getIcon(iconType);
|
|
6085
|
+
const iconColor = variant === "default" ? this.hexToRgba(this.resolveTextColor(), 0.75) : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
5347
6086
|
if (!iconSvg) {
|
|
5348
6087
|
return `<g${this.getDataNodeId(node)}>
|
|
5349
6088
|
<!-- Icon not found: ${iconType} -->
|
|
5350
|
-
<circle cx="${pos.x + pos.width / 2}" cy="${pos.y + pos.height / 2}" r="${Math.min(pos.width, pos.height) / 2 - 2}" fill="none" stroke="
|
|
5351
|
-
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}" font-family="
|
|
6089
|
+
<circle cx="${pos.x + pos.width / 2}" cy="${pos.y + pos.height / 2}" r="${Math.min(pos.width, pos.height) / 2 - 2}" fill="none" stroke="${this.hexToRgba(this.resolveMutedColor(), 0.4)}" stroke-width="1"/>
|
|
6090
|
+
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}" font-family="Arial, Helvetica, sans-serif" font-size="12" fill="${this.hexToRgba(this.resolveMutedColor(), 0.7)}" text-anchor="middle">?</text>
|
|
5352
6091
|
</g>`;
|
|
5353
6092
|
}
|
|
5354
6093
|
const iconSize = this.getIconSize(size);
|
|
5355
|
-
const iconColor = "rgba(30, 41, 59, 0.75)";
|
|
5356
6094
|
const offsetX = pos.x + (pos.width - iconSize) / 2;
|
|
5357
6095
|
const offsetY = pos.y + (pos.height - iconSize) / 2;
|
|
5358
6096
|
const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
|
|
@@ -5367,23 +6105,31 @@ var SVGRenderer = class {
|
|
|
5367
6105
|
const variant = String(node.props.variant || "default");
|
|
5368
6106
|
const size = String(node.props.size || "md");
|
|
5369
6107
|
const disabled = String(node.props.disabled || "false") === "true";
|
|
6108
|
+
const density = this.ir.project.style.density || "normal";
|
|
6109
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
6110
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
5370
6111
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5371
6112
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
5372
6113
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5373
6114
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
5374
|
-
const iconColor = hasExplicitVariantColor ? "#FFFFFF" :
|
|
6115
|
+
const iconColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
5375
6116
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
5376
6117
|
const opacity = disabled ? "0.5" : "1";
|
|
5377
6118
|
const iconSvg = getIcon(iconName);
|
|
5378
|
-
const buttonSize =
|
|
6119
|
+
const buttonSize = Math.max(
|
|
6120
|
+
16,
|
|
6121
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
6122
|
+
);
|
|
6123
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
5379
6124
|
const radius = 6;
|
|
6125
|
+
const buttonY = pos.y + labelOffset;
|
|
5380
6126
|
let svg = `<g${this.getDataNodeId(node)} opacity="${opacity}">
|
|
5381
6127
|
<!-- IconButton background -->
|
|
5382
|
-
<rect x="${pos.x}" y="${
|
|
6128
|
+
<rect x="${pos.x}" y="${buttonY}" width="${buttonWidth}" height="${buttonSize}" rx="${radius}" fill="${bgColor}" stroke="${borderColor}" stroke-width="1"/>`;
|
|
5383
6129
|
if (iconSvg) {
|
|
5384
6130
|
const iconSize = buttonSize * 0.6;
|
|
5385
|
-
const offsetX = pos.x + (
|
|
5386
|
-
const offsetY =
|
|
6131
|
+
const offsetX = pos.x + (buttonWidth - iconSize) / 2;
|
|
6132
|
+
const offsetY = buttonY + (buttonSize - iconSize) / 2;
|
|
5387
6133
|
svg += `
|
|
5388
6134
|
<!-- Icon -->
|
|
5389
6135
|
<g transform="translate(${offsetX}, ${offsetY})">
|
|
@@ -5416,6 +6162,14 @@ var SVGRenderer = class {
|
|
|
5416
6162
|
resolveChartColor() {
|
|
5417
6163
|
return this.colorResolver.resolveColor("chart", this.renderTheme.primary);
|
|
5418
6164
|
}
|
|
6165
|
+
resolveTextColor() {
|
|
6166
|
+
const fallback = this.options.theme === "dark" ? "#FFFFFF" : "#000000";
|
|
6167
|
+
return this.colorResolver.resolveColor("text", fallback);
|
|
6168
|
+
}
|
|
6169
|
+
resolveMutedColor() {
|
|
6170
|
+
const fallback = this.options.theme === "dark" ? "#94A3B8" : "#64748B";
|
|
6171
|
+
return this.colorResolver.resolveColor("muted", fallback);
|
|
6172
|
+
}
|
|
5419
6173
|
getSemanticVariantColor(variant) {
|
|
5420
6174
|
const semantic = {
|
|
5421
6175
|
primary: this.renderTheme.primary,
|
|
@@ -5742,21 +6496,28 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5742
6496
|
renderButton(node, pos) {
|
|
5743
6497
|
const text = String(node.props.text || "Button");
|
|
5744
6498
|
const variant = String(node.props.variant || "default");
|
|
6499
|
+
const size = String(node.props.size || "md");
|
|
6500
|
+
const density = this.ir.project.style.density || "normal";
|
|
6501
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6502
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
5745
6503
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5746
6504
|
const radius = this.tokens.button.radius;
|
|
5747
6505
|
const fontSize = this.tokens.button.fontSize;
|
|
5748
6506
|
const paddingX = this.tokens.button.paddingX;
|
|
5749
|
-
const
|
|
6507
|
+
const buttonHeight = Math.max(
|
|
6508
|
+
16,
|
|
6509
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
6510
|
+
);
|
|
6511
|
+
const buttonY = pos.y + labelOffset;
|
|
5750
6512
|
const textWidth = text.length * fontSize * 0.6;
|
|
5751
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5752
|
-
const buttonHeight = fontSize + paddingY * 2;
|
|
6513
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + (paddingX + extraPadding) * 2, 60), pos.width);
|
|
5753
6514
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5754
6515
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
5755
6516
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5756
6517
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
5757
6518
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
5758
6519
|
return `<g${this.getDataNodeId(node)}>
|
|
5759
|
-
<rect x="${pos.x}" y="${
|
|
6520
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
5760
6521
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
5761
6522
|
rx="${radius}"
|
|
5762
6523
|
fill="${bgColor}"
|
|
@@ -5770,13 +6531,14 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5770
6531
|
renderLink(node, pos) {
|
|
5771
6532
|
const text = String(node.props.text || "Link");
|
|
5772
6533
|
const variant = String(node.props.variant || "primary");
|
|
6534
|
+
const size = String(node.props.size || "md");
|
|
6535
|
+
const density = this.ir.project.style.density || "normal";
|
|
5773
6536
|
const fontSize = this.tokens.button.fontSize;
|
|
5774
6537
|
const paddingX = this.tokens.button.paddingX;
|
|
5775
|
-
const paddingY = this.tokens.button.paddingY;
|
|
5776
6538
|
const linkColor = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5777
6539
|
const textWidth = this.estimateTextWidth(text, fontSize);
|
|
5778
6540
|
const linkWidth = this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5779
|
-
const linkHeight =
|
|
6541
|
+
const linkHeight = Math.max(16, Math.min(resolveActionControlHeight(size, density), pos.height));
|
|
5780
6542
|
const blockHeight = Math.max(8, Math.round(fontSize * 0.75));
|
|
5781
6543
|
const blockWidth = Math.max(28, Math.min(textWidth, linkWidth - paddingX * 2));
|
|
5782
6544
|
const blockX = pos.x + (linkWidth - blockWidth) / 2;
|
|
@@ -5793,17 +6555,70 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5793
6555
|
stroke-width="1"/>
|
|
5794
6556
|
</g>`;
|
|
5795
6557
|
}
|
|
6558
|
+
/**
|
|
6559
|
+
* Render breadcrumbs as skeleton blocks: <rect> / <rect> / <rect accent>
|
|
6560
|
+
*/
|
|
6561
|
+
renderBreadcrumbs(node, pos) {
|
|
6562
|
+
const itemsStr = String(node.props.items || "Home");
|
|
6563
|
+
const items = itemsStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6564
|
+
const separator = String(node.props.separator || "/");
|
|
6565
|
+
const blockColor = this.renderTheme.border;
|
|
6566
|
+
const charWidth = 6.2;
|
|
6567
|
+
const minBlockWidth = 28;
|
|
6568
|
+
const maxBlockWidth = Math.max(minBlockWidth, Math.floor(pos.width * 0.4));
|
|
6569
|
+
const blockHeight = 12;
|
|
6570
|
+
const blockY = pos.y + (pos.height - blockHeight) / 2;
|
|
6571
|
+
const blockRadius = 4;
|
|
6572
|
+
const blockPaddingX = 10;
|
|
6573
|
+
const itemSpacing = 8;
|
|
6574
|
+
const separatorWidth = 12;
|
|
6575
|
+
const contentRight = pos.x + pos.width;
|
|
6576
|
+
let currentX = pos.x;
|
|
6577
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6578
|
+
items.forEach((item, index) => {
|
|
6579
|
+
if (currentX >= contentRight) return;
|
|
6580
|
+
const isLast = index === items.length - 1;
|
|
6581
|
+
const estimatedTextWidth = item.length * charWidth;
|
|
6582
|
+
let blockWidth = Math.max(
|
|
6583
|
+
minBlockWidth,
|
|
6584
|
+
Math.min(maxBlockWidth, Math.ceil(estimatedTextWidth + blockPaddingX * 2))
|
|
6585
|
+
);
|
|
6586
|
+
blockWidth = Math.min(blockWidth, Math.max(0, contentRight - currentX));
|
|
6587
|
+
if (blockWidth < minBlockWidth) return;
|
|
6588
|
+
const fillColor = blockColor;
|
|
6589
|
+
svg += `
|
|
6590
|
+
<rect x="${currentX}" y="${blockY}"
|
|
6591
|
+
width="${blockWidth}" height="${blockHeight}"
|
|
6592
|
+
rx="${blockRadius}"
|
|
6593
|
+
fill="${fillColor}"
|
|
6594
|
+
stroke="none"/>`;
|
|
6595
|
+
currentX += blockWidth + itemSpacing;
|
|
6596
|
+
if (!isLast && currentX + separatorWidth <= contentRight) {
|
|
6597
|
+
svg += `
|
|
6598
|
+
<text x="${currentX + 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
6599
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
6600
|
+
font-size="12"
|
|
6601
|
+
fill="${blockColor}">${this.escapeXml(separator)}</text>`;
|
|
6602
|
+
currentX += separatorWidth;
|
|
6603
|
+
}
|
|
6604
|
+
});
|
|
6605
|
+
svg += "\n </g>";
|
|
6606
|
+
return svg;
|
|
6607
|
+
}
|
|
5796
6608
|
/**
|
|
5797
6609
|
* Render heading as gray block
|
|
5798
6610
|
*/
|
|
5799
6611
|
renderHeading(node, pos) {
|
|
5800
6612
|
const headingTypography = this.getHeadingTypography(node);
|
|
6613
|
+
const variant = String(node.props.variant || "default");
|
|
6614
|
+
const blockColor = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveTextColor()), 0.35);
|
|
5801
6615
|
return this.renderTextBlock(
|
|
5802
6616
|
node,
|
|
5803
6617
|
pos,
|
|
5804
6618
|
String(node.props.text || "Heading"),
|
|
5805
6619
|
headingTypography.fontSize,
|
|
5806
|
-
headingTypography.lineHeight
|
|
6620
|
+
headingTypography.lineHeight,
|
|
6621
|
+
blockColor
|
|
5807
6622
|
);
|
|
5808
6623
|
}
|
|
5809
6624
|
/**
|
|
@@ -5813,7 +6628,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5813
6628
|
return this.renderTextBlock(
|
|
5814
6629
|
node,
|
|
5815
6630
|
pos,
|
|
5816
|
-
String(node.props.
|
|
6631
|
+
String(node.props.text || "Text content"),
|
|
5817
6632
|
this.tokens.text.fontSize,
|
|
5818
6633
|
this.tokens.text.lineHeight
|
|
5819
6634
|
);
|
|
@@ -6054,18 +6869,42 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6054
6869
|
renderTable(node, pos) {
|
|
6055
6870
|
const title = String(node.props.title || "");
|
|
6056
6871
|
const columnsStr = String(node.props.columns || "Col1,Col2,Col3");
|
|
6057
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
6872
|
+
const columns = columnsStr.split(",").map((c) => c.trim()).filter(Boolean);
|
|
6058
6873
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
6874
|
+
const actions = String(node.props.actions || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
6875
|
+
const hasActions = actions.length > 0;
|
|
6876
|
+
const pagination = this.parseBooleanProp(node.props.pagination, false);
|
|
6877
|
+
const parsedPageCount = Number(node.props.pages || 5);
|
|
6878
|
+
const pageCount = Number.isFinite(parsedPageCount) && parsedPageCount > 0 ? Math.floor(parsedPageCount) : 5;
|
|
6879
|
+
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
6880
|
+
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
6881
|
+
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
6882
|
+
const showOuterBackground = this.parseBooleanProp(
|
|
6883
|
+
node.props.background ?? node.props.backround,
|
|
6884
|
+
false
|
|
6885
|
+
);
|
|
6886
|
+
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
6887
|
+
const rawCaptionAlign = String(node.props.captionAlign || "");
|
|
6888
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
6889
|
+
const sameFooterAlign = hasCaption && pagination && captionAlign === paginationAlign;
|
|
6890
|
+
const safeColumns = columns.length > 0 ? columns : ["Column"];
|
|
6059
6891
|
const headerHeight = 44;
|
|
6060
6892
|
const rowHeight = 36;
|
|
6061
|
-
const
|
|
6062
|
-
|
|
6893
|
+
const actionColumnWidth = hasActions ? Math.max(96, Math.min(180, actions.length * 26 + 28)) : 0;
|
|
6894
|
+
const dataWidth = Math.max(20, pos.width - actionColumnWidth);
|
|
6895
|
+
const colWidth = dataWidth / safeColumns.length;
|
|
6896
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6897
|
+
if (showOuterBorder || showOuterBackground) {
|
|
6898
|
+
const outerFill = showOuterBackground ? this.renderTheme.cardBg : "none";
|
|
6899
|
+
const outerStroke = showOuterBorder ? this.renderTheme.border : "none";
|
|
6900
|
+
svg += `
|
|
6063
6901
|
<rect x="${pos.x}" y="${pos.y}"
|
|
6064
6902
|
width="${pos.width}" height="${pos.height}"
|
|
6065
6903
|
rx="8"
|
|
6066
|
-
fill="${
|
|
6067
|
-
stroke="${
|
|
6904
|
+
fill="${outerFill}"
|
|
6905
|
+
stroke="${outerStroke}"
|
|
6068
6906
|
stroke-width="1"/>`;
|
|
6907
|
+
}
|
|
6069
6908
|
if (title) {
|
|
6070
6909
|
svg += `<rect x="${pos.x + 16}" y="${pos.y + 12}"
|
|
6071
6910
|
width="100" height="12"
|
|
@@ -6073,19 +6912,28 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6073
6912
|
fill="${this.renderTheme.border}"/>`;
|
|
6074
6913
|
}
|
|
6075
6914
|
const headerY = pos.y + (title ? 32 : 0);
|
|
6076
|
-
|
|
6915
|
+
if (showInnerBorder) {
|
|
6916
|
+
svg += `<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
6077
6917
|
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
6078
|
-
|
|
6918
|
+
}
|
|
6919
|
+
safeColumns.forEach((_, i) => {
|
|
6079
6920
|
svg += `<rect x="${pos.x + i * colWidth + 12}" y="${headerY + 16}"
|
|
6080
6921
|
width="50" height="10"
|
|
6081
6922
|
rx="4"
|
|
6082
6923
|
fill="${this.renderTheme.border}"/>`;
|
|
6083
6924
|
});
|
|
6925
|
+
if (hasActions && showInnerBorder) {
|
|
6926
|
+
const dividerX = pos.x + dataWidth;
|
|
6927
|
+
svg += `<line x1="${dividerX}" y1="${headerY + headerHeight}" x2="${dividerX}" y2="${headerY + headerHeight + rowCount * rowHeight}"
|
|
6928
|
+
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
6929
|
+
}
|
|
6084
6930
|
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
6085
6931
|
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
6086
|
-
|
|
6932
|
+
if (showInnerBorder) {
|
|
6933
|
+
svg += `<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
6087
6934
|
stroke="${this.renderTheme.border}" stroke-width="0.5"/>`;
|
|
6088
|
-
|
|
6935
|
+
}
|
|
6936
|
+
safeColumns.forEach((_, colIdx) => {
|
|
6089
6937
|
const variance = (rowIdx * 17 + colIdx * 11) % 5 * 10;
|
|
6090
6938
|
const blockWidth = Math.min(colWidth - 24, 60 + variance);
|
|
6091
6939
|
svg += `<rect x="${pos.x + colIdx * colWidth + 12}" y="${rowY + 12}"
|
|
@@ -6093,6 +6941,45 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6093
6941
|
rx="4"
|
|
6094
6942
|
fill="${this.renderTheme.border}"/>`;
|
|
6095
6943
|
});
|
|
6944
|
+
if (hasActions) {
|
|
6945
|
+
const iconSize = 14;
|
|
6946
|
+
const iconGap = 8;
|
|
6947
|
+
const actionsWidth = actions.length * iconSize + Math.max(0, actions.length - 1) * iconGap;
|
|
6948
|
+
let currentX = pos.x + pos.width - 12 - actionsWidth;
|
|
6949
|
+
const iconY = rowY + (rowHeight - iconSize) / 2;
|
|
6950
|
+
actions.forEach(() => {
|
|
6951
|
+
svg += `<rect x="${currentX}" y="${iconY}" width="${iconSize}" height="${iconSize}" rx="3" fill="${this.renderTheme.border}"/>`;
|
|
6952
|
+
currentX += iconSize + iconGap;
|
|
6953
|
+
});
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
const footerTop = headerY + headerHeight + rowCount * rowHeight + 16;
|
|
6957
|
+
if (hasCaption) {
|
|
6958
|
+
const captionY = sameFooterAlign ? footerTop : footerTop + (pagination ? 10 : 0);
|
|
6959
|
+
const captionWidth = Math.min(220, Math.max(90, pos.width * 0.34));
|
|
6960
|
+
let captionX = pos.x + 16;
|
|
6961
|
+
if (captionAlign === "center") {
|
|
6962
|
+
captionX = pos.x + (pos.width - captionWidth) / 2;
|
|
6963
|
+
} else if (captionAlign === "right") {
|
|
6964
|
+
captionX = pos.x + pos.width - 16 - captionWidth;
|
|
6965
|
+
}
|
|
6966
|
+
svg += `<rect x="${captionX}" y="${captionY}" width="${captionWidth}" height="10" rx="4" fill="${this.renderTheme.border}"/>`;
|
|
6967
|
+
}
|
|
6968
|
+
if (pagination) {
|
|
6969
|
+
const buttonWidth = 28;
|
|
6970
|
+
const buttonHeight = 24;
|
|
6971
|
+
const buttonGap = 8;
|
|
6972
|
+
const totalWidth = (pageCount + 2) * buttonWidth + (pageCount + 1) * buttonGap;
|
|
6973
|
+
const paginationY = sameFooterAlign ? footerTop + 18 : footerTop;
|
|
6974
|
+
let startX = pos.x + pos.width - totalWidth - 16;
|
|
6975
|
+
if (paginationAlign === "left") {
|
|
6976
|
+
startX = pos.x + 16;
|
|
6977
|
+
} else if (paginationAlign === "center") {
|
|
6978
|
+
startX = pos.x + (pos.width - totalWidth) / 2;
|
|
6979
|
+
}
|
|
6980
|
+
for (let i = 0; i < pageCount + 2; i++) {
|
|
6981
|
+
svg += `<rect x="${startX + i * (buttonWidth + buttonGap)}" y="${paginationY}" width="${buttonWidth}" height="${buttonHeight}" rx="4" fill="${this.renderTheme.border}"/>`;
|
|
6982
|
+
}
|
|
6096
6983
|
}
|
|
6097
6984
|
svg += "</g>";
|
|
6098
6985
|
return svg;
|
|
@@ -6105,21 +6992,39 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6105
6992
|
const subtitle = String(node.props.subtitle || "");
|
|
6106
6993
|
const actions = String(node.props.actions || "");
|
|
6107
6994
|
const user = String(node.props.user || "");
|
|
6995
|
+
const variant = String(node.props.variant || "default");
|
|
6996
|
+
const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
|
|
6997
|
+
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
6998
|
+
const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
|
|
6999
|
+
const radiusMap = {
|
|
7000
|
+
none: 0,
|
|
7001
|
+
sm: 4,
|
|
7002
|
+
md: this.tokens.card.radius,
|
|
7003
|
+
lg: 12,
|
|
7004
|
+
xl: 16
|
|
7005
|
+
};
|
|
7006
|
+
const topbarRadius = radiusMap[String(node.props.radius || "md")] ?? this.tokens.card.radius;
|
|
6108
7007
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
6109
7008
|
const titleWidth = Math.max(56, Math.min(topbar.titleMaxWidth * 0.55, topbar.titleMaxWidth));
|
|
6110
7009
|
const subtitleWidth = Math.max(48, Math.min(topbar.titleMaxWidth * 0.4, topbar.titleMaxWidth));
|
|
6111
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
7010
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7011
|
+
if (showBorder || showBackground) {
|
|
7012
|
+
const bg = showBackground ? this.renderTheme.cardBg : "none";
|
|
7013
|
+
const stroke = showBorder ? this.renderTheme.border : "none";
|
|
7014
|
+
svg += `
|
|
6112
7015
|
<rect x="${pos.x}" y="${pos.y}"
|
|
6113
7016
|
width="${pos.width}" height="${pos.height}"
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
stroke
|
|
7017
|
+
rx="${topbarRadius}"
|
|
7018
|
+
fill="${bg}"
|
|
7019
|
+
stroke="${stroke}"
|
|
7020
|
+
stroke-width="1"/>`;
|
|
7021
|
+
}
|
|
6117
7022
|
if (topbar.leftIcon) {
|
|
6118
7023
|
svg += `
|
|
6119
7024
|
<rect x="${topbar.leftIcon.badgeX}" y="${topbar.leftIcon.badgeY}"
|
|
6120
7025
|
width="${topbar.leftIcon.badgeSize}" height="${topbar.leftIcon.badgeSize}"
|
|
6121
7026
|
rx="${topbar.leftIcon.badgeRadius}"
|
|
6122
|
-
fill="${
|
|
7027
|
+
fill="${accentBlock}"/>`;
|
|
6123
7028
|
}
|
|
6124
7029
|
svg += `
|
|
6125
7030
|
<rect x="${topbar.textX}" y="${topbar.titleY - 12}"
|
|
@@ -6138,7 +7043,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6138
7043
|
<rect x="${action.x}" y="${action.y}"
|
|
6139
7044
|
width="${action.width}" height="${action.height}"
|
|
6140
7045
|
rx="6"
|
|
6141
|
-
fill="${
|
|
7046
|
+
fill="${accentBlock}"/>`;
|
|
6142
7047
|
});
|
|
6143
7048
|
if (topbar.userBadge) {
|
|
6144
7049
|
svg += `
|
|
@@ -6206,12 +7111,14 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6206
7111
|
*/
|
|
6207
7112
|
renderIcon(node, pos) {
|
|
6208
7113
|
const size = String(node.props.size || "md");
|
|
7114
|
+
const variant = String(node.props.variant || "default");
|
|
6209
7115
|
const iconSize = this.getIconSize(size);
|
|
7116
|
+
const blockColor = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveTextColor()), 0.35);
|
|
6210
7117
|
return `<g${this.getDataNodeId(node)}>
|
|
6211
7118
|
<rect x="${pos.x}" y="${pos.y + (pos.height - iconSize) / 2}"
|
|
6212
7119
|
width="${iconSize}" height="${iconSize}"
|
|
6213
7120
|
rx="2"
|
|
6214
|
-
fill="${
|
|
7121
|
+
fill="${blockColor}"/>
|
|
6215
7122
|
</g>`;
|
|
6216
7123
|
}
|
|
6217
7124
|
/**
|
|
@@ -6220,15 +7127,23 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6220
7127
|
renderIconButton(node, pos) {
|
|
6221
7128
|
const variant = String(node.props.variant || "default");
|
|
6222
7129
|
const size = String(node.props.size || "md");
|
|
7130
|
+
const density = this.ir.project.style.density || "normal";
|
|
7131
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7132
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6223
7133
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6224
7134
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6225
7135
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
6226
7136
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
6227
7137
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
6228
|
-
const buttonSize =
|
|
7138
|
+
const buttonSize = Math.max(
|
|
7139
|
+
16,
|
|
7140
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
7141
|
+
);
|
|
7142
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
7143
|
+
const buttonY = pos.y + labelOffset;
|
|
6229
7144
|
return `<g${this.getDataNodeId(node)}>
|
|
6230
|
-
<rect x="${pos.x}" y="${
|
|
6231
|
-
width="${
|
|
7145
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
7146
|
+
width="${buttonWidth}" height="${buttonSize}"
|
|
6232
7147
|
rx="6"
|
|
6233
7148
|
fill="${bgColor}"
|
|
6234
7149
|
stroke="${borderColor}"
|
|
@@ -6308,10 +7223,10 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6308
7223
|
/**
|
|
6309
7224
|
* Private helper: Render text as gray block
|
|
6310
7225
|
*/
|
|
6311
|
-
renderTextBlock(node, pos, text, fontSize, lineHeightMultiplier) {
|
|
7226
|
+
renderTextBlock(node, pos, text, fontSize, lineHeightMultiplier, color) {
|
|
6312
7227
|
const lineHeight = Math.ceil(fontSize * lineHeightMultiplier);
|
|
6313
7228
|
const blockHeight = Math.max(8, Math.round(fontSize * 0.75));
|
|
6314
|
-
const blockColor = this.renderTheme.border;
|
|
7229
|
+
const blockColor = color || this.renderTheme.border;
|
|
6315
7230
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
6316
7231
|
const contentHeight = lines.length * lineHeight;
|
|
6317
7232
|
const startY = pos.y + Math.max(0, (pos.height - contentHeight) / 2);
|
|
@@ -6393,32 +7308,39 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6393
7308
|
renderButton(node, pos) {
|
|
6394
7309
|
const text = String(node.props.text || "Button");
|
|
6395
7310
|
const variant = String(node.props.variant || "default");
|
|
7311
|
+
const size = String(node.props.size || "md");
|
|
7312
|
+
const density = this.ir.project.style.density || "normal";
|
|
7313
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
7314
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
6396
7315
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
6397
7316
|
const radius = this.tokens.button.radius;
|
|
6398
7317
|
const fontSize = this.tokens.button.fontSize;
|
|
6399
7318
|
const fontWeight = this.tokens.button.fontWeight;
|
|
6400
7319
|
const paddingX = this.tokens.button.paddingX;
|
|
6401
|
-
const
|
|
7320
|
+
const buttonHeight = Math.max(
|
|
7321
|
+
16,
|
|
7322
|
+
Math.min(resolveControlHeight(size, density), pos.height - labelOffset)
|
|
7323
|
+
);
|
|
7324
|
+
const buttonY = pos.y + labelOffset;
|
|
6402
7325
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
6403
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + paddingX * 2, 60), pos.width);
|
|
6404
|
-
const
|
|
6405
|
-
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
7326
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (paddingX + extraPadding) * 2, 60), pos.width);
|
|
7327
|
+
const availableTextWidth = Math.max(0, buttonWidth - (paddingX + extraPadding) * 2);
|
|
6406
7328
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
6407
7329
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6408
7330
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6409
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7331
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6410
7332
|
const borderColor = variantColor;
|
|
6411
7333
|
const textColor = variantColor;
|
|
6412
7334
|
const strokeWidth = 0.5;
|
|
6413
7335
|
return `<g${this.getDataNodeId(node)}>
|
|
6414
|
-
<rect x="${pos.x}" y="${
|
|
7336
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
6415
7337
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
6416
7338
|
rx="${radius}"
|
|
6417
7339
|
fill="none"
|
|
6418
7340
|
stroke="${borderColor}"
|
|
6419
7341
|
stroke-width="${strokeWidth}"
|
|
6420
7342
|
filter="url(#sketch-rough)"/>
|
|
6421
|
-
<text x="${pos.x + buttonWidth / 2}" y="${
|
|
7343
|
+
<text x="${pos.x + buttonWidth / 2}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
6422
7344
|
font-family="${this.fontFamily}"
|
|
6423
7345
|
font-size="${fontSize}"
|
|
6424
7346
|
font-weight="${fontWeight}"
|
|
@@ -6434,7 +7356,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6434
7356
|
const variant = String(node.props.variant || "default");
|
|
6435
7357
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6436
7358
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6437
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7359
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6438
7360
|
const borderColor = variantColor;
|
|
6439
7361
|
const textColor = variantColor;
|
|
6440
7362
|
const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
|
|
@@ -6461,17 +7383,22 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6461
7383
|
const iconName = String(node.props.icon || "help-circle");
|
|
6462
7384
|
const variant = String(node.props.variant || "default");
|
|
6463
7385
|
const size = String(node.props.size || "md");
|
|
7386
|
+
const density = this.ir.project.style.density || "normal";
|
|
7387
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7388
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6464
7389
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6465
7390
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6466
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7391
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6467
7392
|
const borderColor = variantColor;
|
|
6468
7393
|
const iconColor = variantColor;
|
|
6469
|
-
const buttonSize =
|
|
7394
|
+
const buttonSize = Math.max(16, Math.min(resolveControlHeight(size, density), pos.height - labelOffset));
|
|
7395
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
6470
7396
|
const radius = 6;
|
|
7397
|
+
const buttonY = pos.y + labelOffset;
|
|
6471
7398
|
const iconSvg = this.getIconSvg(iconName);
|
|
6472
7399
|
let svg = `<g${this.getDataNodeId(node)}>
|
|
6473
|
-
<rect x="${pos.x}" y="${
|
|
6474
|
-
width="${
|
|
7400
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
7401
|
+
width="${buttonWidth}" height="${buttonSize}"
|
|
6475
7402
|
rx="${radius}"
|
|
6476
7403
|
fill="none"
|
|
6477
7404
|
stroke="${borderColor}"
|
|
@@ -6479,8 +7406,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6479
7406
|
filter="url(#sketch-rough)"/>`;
|
|
6480
7407
|
if (iconSvg) {
|
|
6481
7408
|
const iconSize = buttonSize * 0.6;
|
|
6482
|
-
const offsetX = pos.x + (
|
|
6483
|
-
const offsetY =
|
|
7409
|
+
const offsetX = pos.x + (buttonWidth - iconSize) / 2;
|
|
7410
|
+
const offsetY = buttonY + (buttonSize - iconSize) / 2;
|
|
6484
7411
|
svg += `
|
|
6485
7412
|
<g transform="translate(${offsetX}, ${offsetY})">
|
|
6486
7413
|
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -6631,6 +7558,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6631
7558
|
*/
|
|
6632
7559
|
renderHeading(node, pos) {
|
|
6633
7560
|
const text = String(node.props.text || "Heading");
|
|
7561
|
+
const variant = String(node.props.variant || "default");
|
|
7562
|
+
const headingColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
6634
7563
|
const headingTypography = this.getHeadingTypography(node);
|
|
6635
7564
|
const fontSize = headingTypography.fontSize;
|
|
6636
7565
|
const fontWeight = headingTypography.fontWeight;
|
|
@@ -6643,7 +7572,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6643
7572
|
font-family="${this.fontFamily}"
|
|
6644
7573
|
font-size="${fontSize}"
|
|
6645
7574
|
font-weight="${fontWeight}"
|
|
6646
|
-
fill="${
|
|
7575
|
+
fill="${headingColor}">${this.escapeXml(text)}</text>
|
|
6647
7576
|
</g>`;
|
|
6648
7577
|
}
|
|
6649
7578
|
const tspans = lines.map(
|
|
@@ -6654,7 +7583,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6654
7583
|
font-family="${this.fontFamily}"
|
|
6655
7584
|
font-size="${fontSize}"
|
|
6656
7585
|
font-weight="${fontWeight}"
|
|
6657
|
-
fill="${
|
|
7586
|
+
fill="${headingColor}">${tspans}</text>
|
|
6658
7587
|
</g>`;
|
|
6659
7588
|
}
|
|
6660
7589
|
/**
|
|
@@ -6665,7 +7594,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6665
7594
|
const subtitle = String(node.props.subtitle || "");
|
|
6666
7595
|
const actions = String(node.props.actions || "");
|
|
6667
7596
|
const user = String(node.props.user || "");
|
|
6668
|
-
const
|
|
7597
|
+
const variant = String(node.props.variant || "default");
|
|
7598
|
+
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
6669
7599
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
6670
7600
|
let svg = `<g${this.getDataNodeId(node)}>
|
|
6671
7601
|
<rect x="${pos.x}" y="${pos.y}"
|
|
@@ -6758,79 +7688,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6758
7688
|
* Render table with sketch filter and Comic Sans
|
|
6759
7689
|
*/
|
|
6760
7690
|
renderTable(node, pos) {
|
|
6761
|
-
const
|
|
6762
|
-
|
|
6763
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
6764
|
-
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
6765
|
-
const mockStr = String(node.props.mock || "");
|
|
6766
|
-
const random = this.parseBooleanProp(node.props.random, false);
|
|
6767
|
-
const mockTypes = mockStr ? mockStr.split(",").map((m) => m.trim()).filter(Boolean) : [];
|
|
6768
|
-
while (mockTypes.length < columns.length) {
|
|
6769
|
-
const inferred = MockDataGenerator.inferMockTypeFromColumn(columns[mockTypes.length] || "item");
|
|
6770
|
-
mockTypes.push(inferred);
|
|
6771
|
-
}
|
|
6772
|
-
const headerHeight = 44;
|
|
6773
|
-
const rowHeight = 36;
|
|
6774
|
-
const colWidth = pos.width / columns.length;
|
|
6775
|
-
const mockRows = [];
|
|
6776
|
-
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
6777
|
-
const row = {};
|
|
6778
|
-
columns.forEach((col, colIdx) => {
|
|
6779
|
-
const mockType = mockTypes[colIdx] || MockDataGenerator.inferMockTypeFromColumn(col) || "item";
|
|
6780
|
-
row[col] = MockDataGenerator.getMockValue(mockType, rowIdx, random);
|
|
6781
|
-
});
|
|
6782
|
-
mockRows.push(row);
|
|
6783
|
-
}
|
|
6784
|
-
let svg = `<g${this.getDataNodeId(node)}>
|
|
6785
|
-
<rect x="${pos.x}" y="${pos.y}"
|
|
6786
|
-
width="${pos.width}" height="${pos.height}"
|
|
6787
|
-
rx="8"
|
|
6788
|
-
fill="${this.renderTheme.cardBg}"
|
|
6789
|
-
stroke="#2D3748"
|
|
6790
|
-
stroke-width="0.5"
|
|
6791
|
-
filter="url(#sketch-rough)"/>`;
|
|
6792
|
-
if (title) {
|
|
6793
|
-
svg += `
|
|
6794
|
-
<text x="${pos.x + 16}" y="${pos.y + 24}"
|
|
6795
|
-
font-family="${this.fontFamily}"
|
|
6796
|
-
font-size="13"
|
|
6797
|
-
font-weight="600"
|
|
6798
|
-
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>`;
|
|
6799
|
-
}
|
|
6800
|
-
const headerY = pos.y + (title ? 32 : 0);
|
|
6801
|
-
svg += `
|
|
6802
|
-
<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
6803
|
-
stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
|
|
6804
|
-
columns.forEach((col, i) => {
|
|
6805
|
-
svg += `
|
|
6806
|
-
<text x="${pos.x + i * colWidth + 12}" y="${headerY + 26}"
|
|
6807
|
-
font-family="${this.fontFamily}"
|
|
6808
|
-
font-size="11"
|
|
6809
|
-
font-weight="600"
|
|
6810
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(col)}</text>`;
|
|
6811
|
-
});
|
|
6812
|
-
mockRows.forEach((row, rowIdx) => {
|
|
6813
|
-
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
6814
|
-
svg += `
|
|
6815
|
-
<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
6816
|
-
stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
|
|
6817
|
-
columns.forEach((col, colIdx) => {
|
|
6818
|
-
const cellValue = row[col] || "";
|
|
6819
|
-
svg += `
|
|
6820
|
-
<text x="${pos.x + colIdx * colWidth + 12}" y="${rowY + 22}"
|
|
6821
|
-
font-family="${this.fontFamily}"
|
|
6822
|
-
font-size="12"
|
|
6823
|
-
fill="${this.renderTheme.text}">${this.escapeXml(cellValue)}</text>`;
|
|
6824
|
-
});
|
|
6825
|
-
});
|
|
6826
|
-
svg += "\n </g>";
|
|
6827
|
-
return svg;
|
|
7691
|
+
const standard = super.renderTable(node, pos);
|
|
7692
|
+
return standard.replace("<g", '<g filter="url(#sketch-rough)"');
|
|
6828
7693
|
}
|
|
6829
7694
|
/**
|
|
6830
7695
|
* Render text with Comic Sans
|
|
6831
7696
|
*/
|
|
6832
7697
|
renderText(node, pos) {
|
|
6833
|
-
const text = String(node.props.
|
|
7698
|
+
const text = String(node.props.text || "Text content");
|
|
6834
7699
|
const fontSize = this.tokens.text.fontSize;
|
|
6835
7700
|
const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
|
|
6836
7701
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
@@ -7407,7 +8272,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7407
8272
|
const itemY = pos.y + index * itemHeight;
|
|
7408
8273
|
const isActive = index === activeIndex;
|
|
7409
8274
|
const bgColor = isActive ? this.hexToRgba(accentColor, 0.15) : "transparent";
|
|
7410
|
-
const textColor = isActive ? accentColor :
|
|
8275
|
+
const textColor = isActive ? accentColor : this.resolveTextColor();
|
|
7411
8276
|
const fontWeight = isActive ? "500" : "400";
|
|
7412
8277
|
if (isActive) {
|
|
7413
8278
|
svg += `
|
|
@@ -7431,22 +8296,23 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7431
8296
|
* Render icon (same as base, icons don't need filter)
|
|
7432
8297
|
*/
|
|
7433
8298
|
renderIcon(node, pos) {
|
|
7434
|
-
const iconType = String(node.props.
|
|
8299
|
+
const iconType = String(node.props.icon || "help-circle");
|
|
7435
8300
|
const size = String(node.props.size || "md");
|
|
8301
|
+
const variant = String(node.props.variant || "default");
|
|
7436
8302
|
const iconSvg = getIcon(iconType);
|
|
7437
8303
|
if (!iconSvg) {
|
|
7438
8304
|
return `<g${this.getDataNodeId(node)}>
|
|
7439
8305
|
<circle cx="${pos.x + pos.width / 2}" cy="${pos.y + pos.height / 2}"
|
|
7440
8306
|
r="${Math.min(pos.width, pos.height) / 2 - 2}"
|
|
7441
|
-
fill="none" stroke="
|
|
8307
|
+
fill="none" stroke="${this.resolveMutedColor()}" stroke-width="0.5"
|
|
7442
8308
|
filter="url(#sketch-rough)"/>
|
|
7443
8309
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
7444
8310
|
font-family="${this.fontFamily}"
|
|
7445
|
-
font-size="12" fill="
|
|
8311
|
+
font-size="12" fill="${this.resolveMutedColor()}" text-anchor="middle">?</text>
|
|
7446
8312
|
</g>`;
|
|
7447
8313
|
}
|
|
7448
8314
|
const iconSize = this.getIconSize(size);
|
|
7449
|
-
const iconColor = "
|
|
8315
|
+
const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
7450
8316
|
const offsetX = pos.x + (pos.width - iconSize) / 2;
|
|
7451
8317
|
const offsetY = pos.y + (pos.height - iconSize) / 2;
|
|
7452
8318
|
return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
|