@wire-dsl/engine 0.2.4 → 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.cjs
CHANGED
|
@@ -94,20 +94,21 @@ var SourceMapBuilder = class {
|
|
|
94
94
|
return nodeId;
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
97
|
+
* Generate semantic node ID based on type and subtype
|
|
98
|
+
* Format: {type}-{subtype}-{counter} or {type}-{counter}
|
|
99
|
+
*
|
|
100
|
+
* Examples:
|
|
101
|
+
* - project → "project"
|
|
102
|
+
* - theme → "theme"
|
|
103
|
+
* - mocks → "mocks"
|
|
104
|
+
* - colors → "colors"
|
|
105
|
+
* - screen → "screen-0", "screen-1"
|
|
106
|
+
* - component Button → "component-button-0", "component-button-1"
|
|
107
|
+
* - layout stack → "layout-stack-0", "layout-stack-1"
|
|
108
|
+
* - cell → "cell-0", "cell-1"
|
|
109
|
+
* - component-definition → "define-MyButton"
|
|
110
|
+
* - layout-definition → "define-layout-MyShell"
|
|
111
|
+
*/
|
|
111
112
|
generateNodeId(type, metadata) {
|
|
112
113
|
switch (type) {
|
|
113
114
|
case "project":
|
|
@@ -143,6 +144,8 @@ var SourceMapBuilder = class {
|
|
|
143
144
|
}
|
|
144
145
|
case "component-definition":
|
|
145
146
|
return `define-${metadata?.name || "unknown"}`;
|
|
147
|
+
case "layout-definition":
|
|
148
|
+
return `define-layout-${metadata?.name || "unknown"}`;
|
|
146
149
|
default:
|
|
147
150
|
return `${type}-0`;
|
|
148
151
|
}
|
|
@@ -228,7 +231,7 @@ var SourceMapBuilder = class {
|
|
|
228
231
|
}
|
|
229
232
|
/**
|
|
230
233
|
* Calculate insertionPoints for all container nodes
|
|
231
|
-
* Container nodes: project, screen, layout, cell, component-definition
|
|
234
|
+
* Container nodes: project, screen, layout, cell, component-definition, layout-definition
|
|
232
235
|
*/
|
|
233
236
|
calculateAllInsertionPoints() {
|
|
234
237
|
const containerTypes = [
|
|
@@ -236,7 +239,8 @@ var SourceMapBuilder = class {
|
|
|
236
239
|
"screen",
|
|
237
240
|
"layout",
|
|
238
241
|
"cell",
|
|
239
|
-
"component-definition"
|
|
242
|
+
"component-definition",
|
|
243
|
+
"layout-definition"
|
|
240
244
|
];
|
|
241
245
|
for (const entry of this.entries) {
|
|
242
246
|
if (containerTypes.includes(entry.type)) {
|
|
@@ -462,19 +466,23 @@ var SourceMapBuilder = class {
|
|
|
462
466
|
};
|
|
463
467
|
|
|
464
468
|
// src/parser/index.ts
|
|
465
|
-
var Project = (0, import_chevrotain.createToken)({ name: "Project", pattern: /project/ });
|
|
466
|
-
var Screen = (0, import_chevrotain.createToken)({ name: "Screen", pattern: /screen/ });
|
|
467
|
-
var Layout = (0, import_chevrotain.createToken)({ name: "Layout", pattern: /layout/ });
|
|
468
|
-
var Component = (0, import_chevrotain.createToken)({ name: "Component", pattern: /component/ });
|
|
469
|
+
var Project = (0, import_chevrotain.createToken)({ name: "Project", pattern: /project\b/ });
|
|
470
|
+
var Screen = (0, import_chevrotain.createToken)({ name: "Screen", pattern: /screen\b/ });
|
|
471
|
+
var Layout = (0, import_chevrotain.createToken)({ name: "Layout", pattern: /layout\b/ });
|
|
472
|
+
var Component = (0, import_chevrotain.createToken)({ name: "Component", pattern: /component\b/ });
|
|
469
473
|
var ComponentKeyword = (0, import_chevrotain.createToken)({
|
|
470
474
|
name: "ComponentKeyword",
|
|
471
475
|
pattern: /Component\b/
|
|
472
476
|
});
|
|
473
|
-
var
|
|
474
|
-
|
|
475
|
-
|
|
477
|
+
var LayoutKeyword = (0, import_chevrotain.createToken)({
|
|
478
|
+
name: "LayoutKeyword",
|
|
479
|
+
pattern: /Layout\b/
|
|
480
|
+
});
|
|
481
|
+
var Define = (0, import_chevrotain.createToken)({ name: "Define", pattern: /define\b/ });
|
|
482
|
+
var Style = (0, import_chevrotain.createToken)({ name: "Style", pattern: /style\b/ });
|
|
483
|
+
var Mocks = (0, import_chevrotain.createToken)({ name: "Mocks", pattern: /mocks\b/ });
|
|
476
484
|
var Colors = (0, import_chevrotain.createToken)({ name: "Colors", pattern: /colors(?=\s*\{)/ });
|
|
477
|
-
var Cell = (0, import_chevrotain.createToken)({ name: "Cell", pattern: /cell/ });
|
|
485
|
+
var Cell = (0, import_chevrotain.createToken)({ name: "Cell", pattern: /cell\b/ });
|
|
478
486
|
var LCurly = (0, import_chevrotain.createToken)({ name: "LCurly", pattern: /{/ });
|
|
479
487
|
var RCurly = (0, import_chevrotain.createToken)({ name: "RCurly", pattern: /}/ });
|
|
480
488
|
var LParen = (0, import_chevrotain.createToken)({ name: "LParen", pattern: /\(/ });
|
|
@@ -521,6 +529,7 @@ var allTokens = [
|
|
|
521
529
|
Project,
|
|
522
530
|
Screen,
|
|
523
531
|
Layout,
|
|
532
|
+
LayoutKeyword,
|
|
524
533
|
ComponentKeyword,
|
|
525
534
|
Component,
|
|
526
535
|
Define,
|
|
@@ -553,6 +562,7 @@ var WireDSLParser = class extends import_chevrotain.CstParser {
|
|
|
553
562
|
this.MANY(() => {
|
|
554
563
|
this.OR([
|
|
555
564
|
{ ALT: () => this.SUBRULE(this.definedComponent) },
|
|
565
|
+
{ ALT: () => this.SUBRULE(this.definedLayout) },
|
|
556
566
|
{ ALT: () => this.SUBRULE(this.styleDecl) },
|
|
557
567
|
{ ALT: () => this.SUBRULE(this.mocksDecl) },
|
|
558
568
|
{ ALT: () => this.SUBRULE(this.colorsDecl) },
|
|
@@ -621,6 +631,15 @@ var WireDSLParser = class extends import_chevrotain.CstParser {
|
|
|
621
631
|
]);
|
|
622
632
|
this.CONSUME(RCurly);
|
|
623
633
|
});
|
|
634
|
+
// define Layout "ScreenShell" { layout split { ... } }
|
|
635
|
+
this.definedLayout = this.RULE("definedLayout", () => {
|
|
636
|
+
this.CONSUME(Define);
|
|
637
|
+
this.CONSUME(LayoutKeyword, { LABEL: "layoutKeyword" });
|
|
638
|
+
this.CONSUME(StringLiteral, { LABEL: "layoutName" });
|
|
639
|
+
this.CONSUME(LCurly);
|
|
640
|
+
this.SUBRULE(this.layout);
|
|
641
|
+
this.CONSUME(RCurly);
|
|
642
|
+
});
|
|
624
643
|
// screen Main(background: white) { ... }
|
|
625
644
|
this.screen = this.RULE("screen", () => {
|
|
626
645
|
this.CONSUME(Screen);
|
|
@@ -709,6 +728,7 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
709
728
|
const mocks = {};
|
|
710
729
|
const colors = {};
|
|
711
730
|
const definedComponents = [];
|
|
731
|
+
const definedLayouts = [];
|
|
712
732
|
const screens = [];
|
|
713
733
|
if (ctx.styleDecl && ctx.styleDecl.length > 0) {
|
|
714
734
|
const styleBlock = this.visit(ctx.styleDecl[0]);
|
|
@@ -727,6 +747,11 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
727
747
|
definedComponents.push(this.visit(comp));
|
|
728
748
|
});
|
|
729
749
|
}
|
|
750
|
+
if (ctx.definedLayout) {
|
|
751
|
+
ctx.definedLayout.forEach((layoutDef) => {
|
|
752
|
+
definedLayouts.push(this.visit(layoutDef));
|
|
753
|
+
});
|
|
754
|
+
}
|
|
730
755
|
if (ctx.screen) {
|
|
731
756
|
ctx.screen.forEach((screen) => {
|
|
732
757
|
screens.push(this.visit(screen));
|
|
@@ -739,6 +764,7 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
739
764
|
mocks,
|
|
740
765
|
colors,
|
|
741
766
|
definedComponents,
|
|
767
|
+
definedLayouts,
|
|
742
768
|
screens
|
|
743
769
|
};
|
|
744
770
|
}
|
|
@@ -803,6 +829,15 @@ var WireDSLVisitor = class extends BaseCstVisitor {
|
|
|
803
829
|
body
|
|
804
830
|
};
|
|
805
831
|
}
|
|
832
|
+
definedLayout(ctx) {
|
|
833
|
+
const name = ctx.layoutName[0].image.slice(1, -1);
|
|
834
|
+
const body = this.visit(ctx.layout[0]);
|
|
835
|
+
return {
|
|
836
|
+
type: "definedLayout",
|
|
837
|
+
name,
|
|
838
|
+
body
|
|
839
|
+
};
|
|
840
|
+
}
|
|
806
841
|
screen(ctx) {
|
|
807
842
|
const params = ctx.paramList ? this.visit(ctx.paramList[0]) : {};
|
|
808
843
|
return {
|
|
@@ -933,6 +968,7 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
933
968
|
constructor(sourceMapBuilder) {
|
|
934
969
|
super();
|
|
935
970
|
this.definedComponentNames = /* @__PURE__ */ new Set();
|
|
971
|
+
this.definedLayoutNames = /* @__PURE__ */ new Set();
|
|
936
972
|
this.sourceMapBuilder = sourceMapBuilder;
|
|
937
973
|
}
|
|
938
974
|
project(ctx) {
|
|
@@ -941,6 +977,7 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
941
977
|
const mocks = {};
|
|
942
978
|
const colors = {};
|
|
943
979
|
const definedComponents = [];
|
|
980
|
+
const definedLayouts = [];
|
|
944
981
|
const screens = [];
|
|
945
982
|
const tokens = {
|
|
946
983
|
keyword: ctx.Project[0],
|
|
@@ -955,6 +992,8 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
955
992
|
colors: {},
|
|
956
993
|
definedComponents: [],
|
|
957
994
|
// Will be filled after push
|
|
995
|
+
definedLayouts: [],
|
|
996
|
+
// Will be filled after push
|
|
958
997
|
screens: []
|
|
959
998
|
// Will be filled after push
|
|
960
999
|
};
|
|
@@ -984,6 +1023,11 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
984
1023
|
ast.definedComponents.push(this.visit(comp));
|
|
985
1024
|
});
|
|
986
1025
|
}
|
|
1026
|
+
if (ctx.definedLayout) {
|
|
1027
|
+
ctx.definedLayout.forEach((layoutDef) => {
|
|
1028
|
+
ast.definedLayouts.push(this.visit(layoutDef));
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
987
1031
|
if (ctx.screen) {
|
|
988
1032
|
ctx.screen.forEach((screen) => {
|
|
989
1033
|
ast.screens.push(this.visit(screen));
|
|
@@ -1228,6 +1272,35 @@ var WireDSLVisitorWithSourceMap = class extends WireDSLVisitor {
|
|
|
1228
1272
|
}
|
|
1229
1273
|
return ast;
|
|
1230
1274
|
}
|
|
1275
|
+
definedLayout(ctx) {
|
|
1276
|
+
const name = ctx.layoutName[0].image.slice(1, -1);
|
|
1277
|
+
this.definedLayoutNames.add(name);
|
|
1278
|
+
const tokens = {
|
|
1279
|
+
keyword: ctx.Define[0],
|
|
1280
|
+
name: ctx.layoutName[0],
|
|
1281
|
+
body: ctx.RCurly[0]
|
|
1282
|
+
};
|
|
1283
|
+
const ast = {
|
|
1284
|
+
type: "definedLayout",
|
|
1285
|
+
name,
|
|
1286
|
+
body: {}
|
|
1287
|
+
// Will be filled after push
|
|
1288
|
+
};
|
|
1289
|
+
if (this.sourceMapBuilder) {
|
|
1290
|
+
const nodeId = this.sourceMapBuilder.addNode(
|
|
1291
|
+
"layout-definition",
|
|
1292
|
+
tokens,
|
|
1293
|
+
{ name }
|
|
1294
|
+
);
|
|
1295
|
+
ast._meta = { nodeId };
|
|
1296
|
+
this.sourceMapBuilder.pushParent(nodeId);
|
|
1297
|
+
}
|
|
1298
|
+
ast.body = this.visit(ctx.layout[0]);
|
|
1299
|
+
if (this.sourceMapBuilder) {
|
|
1300
|
+
this.sourceMapBuilder.popParent();
|
|
1301
|
+
}
|
|
1302
|
+
return ast;
|
|
1303
|
+
}
|
|
1231
1304
|
// Override styleDecl to capture style block in SourceMap
|
|
1232
1305
|
styleDecl(ctx) {
|
|
1233
1306
|
const style = {};
|
|
@@ -1407,6 +1480,13 @@ function buildLayoutRulesFromMetadata() {
|
|
|
1407
1480
|
var BUILT_IN_COMPONENTS = new Set(Object.keys(import_components.COMPONENTS));
|
|
1408
1481
|
var COMPONENT_RULES = buildComponentRulesFromMetadata();
|
|
1409
1482
|
var LAYOUT_RULES = buildLayoutRulesFromMetadata();
|
|
1483
|
+
var BUILT_IN_LAYOUTS = new Set(Object.keys(import_components.LAYOUTS));
|
|
1484
|
+
function isPascalCaseIdentifier(name) {
|
|
1485
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
1486
|
+
}
|
|
1487
|
+
function isValidDefinedLayoutName(name) {
|
|
1488
|
+
return /^[a-z][a-z0-9_]*$/.test(name);
|
|
1489
|
+
}
|
|
1410
1490
|
function toFallbackRange() {
|
|
1411
1491
|
return {
|
|
1412
1492
|
start: { line: 1, column: 0 },
|
|
@@ -1498,6 +1578,17 @@ function isBooleanLike(value) {
|
|
|
1498
1578
|
const normalized = String(value).trim().toLowerCase();
|
|
1499
1579
|
return normalized === "true" || normalized === "false";
|
|
1500
1580
|
}
|
|
1581
|
+
function parseBooleanLike(value, fallback = false) {
|
|
1582
|
+
if (typeof value === "number") {
|
|
1583
|
+
if (value === 1) return true;
|
|
1584
|
+
if (value === 0) return false;
|
|
1585
|
+
return fallback;
|
|
1586
|
+
}
|
|
1587
|
+
const normalized = String(value).trim().toLowerCase();
|
|
1588
|
+
if (normalized === "true") return true;
|
|
1589
|
+
if (normalized === "false") return false;
|
|
1590
|
+
return fallback;
|
|
1591
|
+
}
|
|
1501
1592
|
function getPropertyRange(entry, propertyName, mode = "full") {
|
|
1502
1593
|
const prop = entry?.properties?.[propertyName];
|
|
1503
1594
|
if (!prop) return entry?.range || toFallbackRange();
|
|
@@ -1515,21 +1606,55 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1515
1606
|
const diagnostics = [];
|
|
1516
1607
|
const sourceMapByNodeId = new Map(sourceMap.map((entry) => [entry.nodeId, entry]));
|
|
1517
1608
|
const definedComponents = new Set(ast.definedComponents.map((dc) => dc.name));
|
|
1518
|
-
const
|
|
1609
|
+
const definedLayouts = new Set(ast.definedLayouts.map((dl) => dl.name));
|
|
1610
|
+
const emitDiagnostic = (severity, message, code, range, nodeId, suggestion) => {
|
|
1519
1611
|
diagnostics.push({
|
|
1520
1612
|
message,
|
|
1521
1613
|
code,
|
|
1522
|
-
severity
|
|
1614
|
+
severity,
|
|
1523
1615
|
phase: "semantic",
|
|
1524
1616
|
range,
|
|
1525
1617
|
nodeId,
|
|
1526
1618
|
suggestion
|
|
1527
1619
|
});
|
|
1528
1620
|
};
|
|
1529
|
-
const
|
|
1621
|
+
const emitWarning = (message, code, range, nodeId, suggestion) => emitDiagnostic("warning", message, code, range, nodeId, suggestion);
|
|
1622
|
+
const emitError = (message, code, range, nodeId, suggestion) => emitDiagnostic("error", message, code, range, nodeId, suggestion);
|
|
1623
|
+
const countChildrenSlots = (layout) => {
|
|
1624
|
+
let count = 0;
|
|
1625
|
+
for (const child of layout.children) {
|
|
1626
|
+
if (child.type === "component") {
|
|
1627
|
+
if (child.componentType === "Children") count += 1;
|
|
1628
|
+
} else if (child.type === "layout") {
|
|
1629
|
+
count += countChildrenSlots(child);
|
|
1630
|
+
} else if (child.type === "cell") {
|
|
1631
|
+
for (const cellChild of child.children) {
|
|
1632
|
+
if (cellChild.type === "component") {
|
|
1633
|
+
if (cellChild.componentType === "Children") count += 1;
|
|
1634
|
+
} else if (cellChild.type === "layout") {
|
|
1635
|
+
count += countChildrenSlots(cellChild);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return count;
|
|
1641
|
+
};
|
|
1642
|
+
const checkComponent = (component, insideDefinedLayout) => {
|
|
1530
1643
|
const nodeId = component._meta?.nodeId;
|
|
1531
1644
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1532
1645
|
const componentType = component.componentType;
|
|
1646
|
+
if (componentType === "Children") {
|
|
1647
|
+
if (!insideDefinedLayout) {
|
|
1648
|
+
emitError(
|
|
1649
|
+
'Component "Children" can only be used inside a define Layout body.',
|
|
1650
|
+
"CHILDREN_SLOT_OUTSIDE_LAYOUT_DEFINITION",
|
|
1651
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1652
|
+
nodeId,
|
|
1653
|
+
"Move this placeholder into a define Layout block."
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1533
1658
|
if (!BUILT_IN_COMPONENTS.has(componentType) && !definedComponents.has(componentType)) {
|
|
1534
1659
|
emitWarning(
|
|
1535
1660
|
`Component "${componentType}" is not a built-in component and has no local definition.`,
|
|
@@ -1570,7 +1695,8 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1570
1695
|
const enumValues = rules.enumProps?.[propName];
|
|
1571
1696
|
if (enumValues) {
|
|
1572
1697
|
const normalizedValue = String(propValue);
|
|
1573
|
-
|
|
1698
|
+
const isCustomVariantFromColors = propName === "variant" && !enumValues.includes(normalizedValue) && Object.prototype.hasOwnProperty.call(ast.colors || {}, normalizedValue);
|
|
1699
|
+
if (!enumValues.includes(normalizedValue) && !isCustomVariantFromColors) {
|
|
1574
1700
|
emitWarning(
|
|
1575
1701
|
`Invalid value "${normalizedValue}" for property "${propName}" in component "${componentType}".`,
|
|
1576
1702
|
"COMPONENT_INVALID_PROPERTY_VALUE",
|
|
@@ -1590,11 +1716,40 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1590
1716
|
);
|
|
1591
1717
|
}
|
|
1592
1718
|
}
|
|
1719
|
+
if (componentType === "Table") {
|
|
1720
|
+
const hasCaption = String(component.props.caption || "").trim().length > 0;
|
|
1721
|
+
const hasPagination = parseBooleanLike(component.props.pagination ?? "false", false);
|
|
1722
|
+
if (hasCaption && hasPagination) {
|
|
1723
|
+
const rawPaginationAlign = String(component.props.paginationAlign || "right");
|
|
1724
|
+
const paginationAlign = rawPaginationAlign === "left" || rawPaginationAlign === "center" || rawPaginationAlign === "right" ? rawPaginationAlign : "right";
|
|
1725
|
+
const rawCaptionAlign = String(component.props.captionAlign || "");
|
|
1726
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
1727
|
+
if (captionAlign === paginationAlign) {
|
|
1728
|
+
emitWarning(
|
|
1729
|
+
`Table footer collision: "captionAlign" and "paginationAlign" both resolve to "${captionAlign}".`,
|
|
1730
|
+
"TABLE_FOOTER_ALIGNMENT_COLLISION",
|
|
1731
|
+
entry?.range || toFallbackRange(),
|
|
1732
|
+
nodeId,
|
|
1733
|
+
"Use different alignments to avoid visual overlap."
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1593
1738
|
};
|
|
1594
|
-
const checkLayout = (layout) => {
|
|
1739
|
+
const checkLayout = (layout, insideDefinedLayout) => {
|
|
1595
1740
|
const nodeId = layout._meta?.nodeId;
|
|
1596
1741
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1597
1742
|
const rules = LAYOUT_RULES[layout.layoutType];
|
|
1743
|
+
const isDefinedLayoutUsage = definedLayouts.has(layout.layoutType);
|
|
1744
|
+
if (isDefinedLayoutUsage && layout.children.length !== 1) {
|
|
1745
|
+
emitError(
|
|
1746
|
+
`Layout "${layout.layoutType}" expects exactly one child for its Children slot.`,
|
|
1747
|
+
"LAYOUT_DEFINITION_CHILDREN_ARITY",
|
|
1748
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1749
|
+
nodeId,
|
|
1750
|
+
"Provide exactly one nested child block when using this layout."
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1598
1753
|
if (layout.children.length === 0) {
|
|
1599
1754
|
emitWarning(
|
|
1600
1755
|
`Layout "${layout.layoutType}" is empty.`,
|
|
@@ -1604,7 +1759,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1604
1759
|
"Add at least one child: component, layout, or cell."
|
|
1605
1760
|
);
|
|
1606
1761
|
}
|
|
1607
|
-
if (!rules) {
|
|
1762
|
+
if (!rules && !isDefinedLayoutUsage) {
|
|
1608
1763
|
emitWarning(
|
|
1609
1764
|
`Layout type "${layout.layoutType}" is not recognized by semantic validation rules.`,
|
|
1610
1765
|
"LAYOUT_UNKNOWN_TYPE",
|
|
@@ -1612,7 +1767,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1612
1767
|
nodeId,
|
|
1613
1768
|
`Use one of: ${Object.keys(LAYOUT_RULES).join(", ")}.`
|
|
1614
1769
|
);
|
|
1615
|
-
} else {
|
|
1770
|
+
} else if (rules) {
|
|
1616
1771
|
const missingRequiredParams = getMissingRequiredNames(rules.requiredParams, layout.params);
|
|
1617
1772
|
if (missingRequiredParams.length > 0) {
|
|
1618
1773
|
emitWarning(
|
|
@@ -1625,6 +1780,16 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1625
1780
|
}
|
|
1626
1781
|
const allowed = new Set(rules.allowedParams);
|
|
1627
1782
|
for (const [paramName, paramValue] of Object.entries(layout.params)) {
|
|
1783
|
+
if (layout.layoutType === "split" && paramName === "sidebar") {
|
|
1784
|
+
emitError(
|
|
1785
|
+
'Split parameter "sidebar" was removed. Use "left" or "right" instead.',
|
|
1786
|
+
"LAYOUT_SPLIT_SIDEBAR_DEPRECATED",
|
|
1787
|
+
getPropertyRange(entry, paramName, "name"),
|
|
1788
|
+
nodeId,
|
|
1789
|
+
"Example: layout split(left: 260) { ... }"
|
|
1790
|
+
);
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1628
1793
|
if (!allowed.has(paramName)) {
|
|
1629
1794
|
emitWarning(
|
|
1630
1795
|
`Parameter "${paramName}" is not recognized for layout "${layout.layoutType}".`,
|
|
@@ -1663,31 +1828,62 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1663
1828
|
);
|
|
1664
1829
|
}
|
|
1665
1830
|
}
|
|
1666
|
-
if (layout.layoutType === "split" && paramName === "
|
|
1667
|
-
const
|
|
1668
|
-
if (!Number.isFinite(
|
|
1831
|
+
if (layout.layoutType === "split" && (paramName === "left" || paramName === "right")) {
|
|
1832
|
+
const splitSize = Number(paramValue);
|
|
1833
|
+
if (!Number.isFinite(splitSize) || splitSize <= 0) {
|
|
1669
1834
|
emitWarning(
|
|
1670
|
-
|
|
1671
|
-
"
|
|
1835
|
+
`Split "${paramName}" must be a positive number. Falling back to 250.`,
|
|
1836
|
+
"LAYOUT_SPLIT_WIDTH_INVALID",
|
|
1672
1837
|
getPropertyRange(entry, paramName, "value"),
|
|
1673
1838
|
nodeId,
|
|
1674
|
-
|
|
1839
|
+
`Use a value like ${paramName}: 260.`
|
|
1675
1840
|
);
|
|
1676
1841
|
}
|
|
1677
1842
|
}
|
|
1678
1843
|
}
|
|
1844
|
+
if (layout.layoutType === "split") {
|
|
1845
|
+
const hasLeft = layout.params.left !== void 0;
|
|
1846
|
+
const hasRight = layout.params.right !== void 0;
|
|
1847
|
+
if (!hasLeft && !hasRight) {
|
|
1848
|
+
emitError(
|
|
1849
|
+
'Split layout requires exactly one fixed side width: "left" or "right".',
|
|
1850
|
+
"LAYOUT_SPLIT_SIDE_REQUIRED",
|
|
1851
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1852
|
+
nodeId,
|
|
1853
|
+
"Add either left: <number> or right: <number>."
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
if (hasLeft && hasRight) {
|
|
1857
|
+
emitError(
|
|
1858
|
+
'Split layout accepts only one fixed side width: use either "left" or "right", not both.',
|
|
1859
|
+
"LAYOUT_SPLIT_SIDE_CONFLICT",
|
|
1860
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1861
|
+
nodeId,
|
|
1862
|
+
"Remove one of the two parameters."
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
if (layout.children.length !== 2) {
|
|
1866
|
+
emitError(
|
|
1867
|
+
`Split layout requires exactly 2 children, received ${layout.children.length}.`,
|
|
1868
|
+
"LAYOUT_SPLIT_CHILDREN_ARITY",
|
|
1869
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1870
|
+
nodeId,
|
|
1871
|
+
"Provide exactly two child blocks (left/right content)."
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1679
1875
|
}
|
|
1680
1876
|
for (const child of layout.children) {
|
|
1681
1877
|
if (child.type === "component") {
|
|
1682
|
-
checkComponent(child);
|
|
1878
|
+
checkComponent(child, insideDefinedLayout);
|
|
1683
1879
|
} else if (child.type === "layout") {
|
|
1684
|
-
checkLayout(child);
|
|
1880
|
+
checkLayout(child, insideDefinedLayout);
|
|
1685
1881
|
} else if (child.type === "cell") {
|
|
1686
|
-
checkCell(child);
|
|
1882
|
+
checkCell(child, insideDefinedLayout);
|
|
1687
1883
|
}
|
|
1688
1884
|
}
|
|
1689
1885
|
};
|
|
1690
|
-
const checkCell = (cell) => {
|
|
1886
|
+
const checkCell = (cell, insideDefinedLayout) => {
|
|
1691
1887
|
const nodeId = cell._meta?.nodeId;
|
|
1692
1888
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1693
1889
|
if (cell.props.span !== void 0) {
|
|
@@ -1703,12 +1899,54 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1703
1899
|
}
|
|
1704
1900
|
}
|
|
1705
1901
|
for (const child of cell.children) {
|
|
1706
|
-
if (child.type === "component") checkComponent(child);
|
|
1707
|
-
if (child.type === "layout") checkLayout(child);
|
|
1902
|
+
if (child.type === "component") checkComponent(child, insideDefinedLayout);
|
|
1903
|
+
if (child.type === "layout") checkLayout(child, insideDefinedLayout);
|
|
1708
1904
|
}
|
|
1709
1905
|
};
|
|
1906
|
+
for (const componentDef of ast.definedComponents) {
|
|
1907
|
+
const nodeId = componentDef._meta?.nodeId;
|
|
1908
|
+
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1909
|
+
if (!isPascalCaseIdentifier(componentDef.name)) {
|
|
1910
|
+
emitWarning(
|
|
1911
|
+
`Defined component "${componentDef.name}" should use PascalCase naming.`,
|
|
1912
|
+
"COMPONENT_DEFINITION_NAME_STYLE",
|
|
1913
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1914
|
+
nodeId,
|
|
1915
|
+
'Use a name like "MyComponent".'
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
if (componentDef.body.type === "component") {
|
|
1919
|
+
checkComponent(componentDef.body, false);
|
|
1920
|
+
} else {
|
|
1921
|
+
checkLayout(componentDef.body, false);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
for (const layoutDef of ast.definedLayouts) {
|
|
1925
|
+
const nodeId = layoutDef._meta?.nodeId;
|
|
1926
|
+
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1927
|
+
if (!isValidDefinedLayoutName(layoutDef.name)) {
|
|
1928
|
+
emitError(
|
|
1929
|
+
`Defined layout "${layoutDef.name}" must match /^[a-z][a-z0-9_]*$/.`,
|
|
1930
|
+
"LAYOUT_DEFINITION_INVALID_NAME",
|
|
1931
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1932
|
+
nodeId,
|
|
1933
|
+
'Use names like "screen_default" or "appShell".'
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
const childrenSlotCount = countChildrenSlots(layoutDef.body);
|
|
1937
|
+
if (childrenSlotCount !== 1) {
|
|
1938
|
+
emitError(
|
|
1939
|
+
`Defined layout "${layoutDef.name}" must contain exactly one "component Children" placeholder.`,
|
|
1940
|
+
"LAYOUT_DEFINITION_CHILDREN_SLOT_COUNT",
|
|
1941
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1942
|
+
nodeId,
|
|
1943
|
+
'Add exactly one "component Children" in the layout body.'
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
checkLayout(layoutDef.body, true);
|
|
1947
|
+
}
|
|
1710
1948
|
ast.screens.forEach((screen) => {
|
|
1711
|
-
checkLayout(screen.layout);
|
|
1949
|
+
checkLayout(screen.layout, false);
|
|
1712
1950
|
});
|
|
1713
1951
|
return diagnostics;
|
|
1714
1952
|
}
|
|
@@ -1725,7 +1963,7 @@ ${lexResult.errors.map((e) => e.message).join("\n")}`);
|
|
|
1725
1963
|
${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
1726
1964
|
}
|
|
1727
1965
|
const ast = visitor.visit(cst);
|
|
1728
|
-
|
|
1966
|
+
validateDefinitionCycles(ast);
|
|
1729
1967
|
return ast;
|
|
1730
1968
|
}
|
|
1731
1969
|
function parseWireDSLWithSourceMap(input, filePath = "<input>", options) {
|
|
@@ -1756,7 +1994,7 @@ ${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
|
1756
1994
|
const ast = visitorWithSourceMap.visit(cst);
|
|
1757
1995
|
const sourceMap = sourceMapBuilder.build();
|
|
1758
1996
|
try {
|
|
1759
|
-
|
|
1997
|
+
validateDefinitionCycles(ast);
|
|
1760
1998
|
} catch (error) {
|
|
1761
1999
|
const projectEntry = sourceMap.find((entry) => entry.type === "project");
|
|
1762
2000
|
diagnostics.push({
|
|
@@ -1779,83 +2017,101 @@ ${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
|
1779
2017
|
}
|
|
1780
2018
|
return buildParseResult(ast, sourceMap, diagnostics);
|
|
1781
2019
|
}
|
|
1782
|
-
function
|
|
1783
|
-
if (!ast.definedComponents || ast.definedComponents.length === 0) {
|
|
2020
|
+
function validateDefinitionCycles(ast) {
|
|
2021
|
+
if ((!ast.definedComponents || ast.definedComponents.length === 0) && (!ast.definedLayouts || ast.definedLayouts.length === 0)) {
|
|
1784
2022
|
return;
|
|
1785
2023
|
}
|
|
1786
2024
|
const components = /* @__PURE__ */ new Map();
|
|
2025
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
1787
2026
|
ast.definedComponents.forEach((comp) => {
|
|
1788
2027
|
components.set(comp.name, comp);
|
|
1789
2028
|
});
|
|
2029
|
+
ast.definedLayouts.forEach((layoutDef) => {
|
|
2030
|
+
layouts.set(layoutDef.name, layoutDef);
|
|
2031
|
+
});
|
|
2032
|
+
const makeComponentKey = (name) => `component:${name}`;
|
|
2033
|
+
const makeLayoutKey = (name) => `layout:${name}`;
|
|
2034
|
+
const displayKey = (key) => key.split(":")[1];
|
|
2035
|
+
const shouldTrackComponentDependency = (name) => components.has(name) && !BUILT_IN_COMPONENTS.has(name);
|
|
2036
|
+
const shouldTrackLayoutDependency = (name) => layouts.has(name) && !BUILT_IN_LAYOUTS.has(name);
|
|
1790
2037
|
const visited = /* @__PURE__ */ new Set();
|
|
1791
2038
|
const recursionStack = /* @__PURE__ */ new Set();
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
if (
|
|
1807
|
-
|
|
1808
|
-
if (cellChild.type === "component") {
|
|
1809
|
-
deps.add(cellChild.componentType);
|
|
1810
|
-
} else if (cellChild.type === "layout") {
|
|
1811
|
-
const nested = getComponentDependencies(cellChild);
|
|
1812
|
-
nested.forEach((d) => deps.add(d));
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
2039
|
+
const collectLayoutDependencies = (layout, deps) => {
|
|
2040
|
+
if (shouldTrackLayoutDependency(layout.layoutType)) {
|
|
2041
|
+
deps.add(makeLayoutKey(layout.layoutType));
|
|
2042
|
+
}
|
|
2043
|
+
for (const child of layout.children) {
|
|
2044
|
+
if (child.type === "component") {
|
|
2045
|
+
if (shouldTrackComponentDependency(child.componentType)) {
|
|
2046
|
+
deps.add(makeComponentKey(child.componentType));
|
|
2047
|
+
}
|
|
2048
|
+
} else if (child.type === "layout") {
|
|
2049
|
+
collectLayoutDependencies(child, deps);
|
|
2050
|
+
} else if (child.type === "cell") {
|
|
2051
|
+
for (const cellChild of child.children) {
|
|
2052
|
+
if (cellChild.type === "component") {
|
|
2053
|
+
if (shouldTrackComponentDependency(cellChild.componentType)) {
|
|
2054
|
+
deps.add(makeComponentKey(cellChild.componentType));
|
|
1815
2055
|
}
|
|
2056
|
+
} else if (cellChild.type === "layout") {
|
|
2057
|
+
collectLayoutDependencies(cellChild, deps);
|
|
1816
2058
|
}
|
|
1817
|
-
}
|
|
2059
|
+
}
|
|
1818
2060
|
}
|
|
1819
2061
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
if (
|
|
1824
|
-
const
|
|
1825
|
-
const
|
|
1826
|
-
return
|
|
2062
|
+
};
|
|
2063
|
+
const getDependencies = (key) => {
|
|
2064
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2065
|
+
if (key.startsWith("component:")) {
|
|
2066
|
+
const name2 = key.slice("component:".length);
|
|
2067
|
+
const def2 = components.get(name2);
|
|
2068
|
+
if (!def2) return deps;
|
|
2069
|
+
if (def2.body.type === "component") {
|
|
2070
|
+
if (shouldTrackComponentDependency(def2.body.componentType)) {
|
|
2071
|
+
deps.add(makeComponentKey(def2.body.componentType));
|
|
2072
|
+
}
|
|
2073
|
+
} else {
|
|
2074
|
+
collectLayoutDependencies(def2.body, deps);
|
|
2075
|
+
}
|
|
2076
|
+
return deps;
|
|
1827
2077
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
2078
|
+
const name = key.slice("layout:".length);
|
|
2079
|
+
const def = layouts.get(name);
|
|
2080
|
+
if (!def) return deps;
|
|
2081
|
+
collectLayoutDependencies(def.body, deps);
|
|
2082
|
+
return deps;
|
|
2083
|
+
};
|
|
2084
|
+
const findCycle = (key, path = []) => {
|
|
2085
|
+
if (recursionStack.has(key)) {
|
|
2086
|
+
const cycleStart = path.indexOf(key);
|
|
2087
|
+
return path.slice(cycleStart).concat(key);
|
|
1830
2088
|
}
|
|
1831
|
-
|
|
1832
|
-
if (!component) {
|
|
2089
|
+
if (visited.has(key)) {
|
|
1833
2090
|
return null;
|
|
1834
2091
|
}
|
|
1835
|
-
recursionStack.add(
|
|
1836
|
-
const currentPath = [...path,
|
|
1837
|
-
const dependencies =
|
|
2092
|
+
recursionStack.add(key);
|
|
2093
|
+
const currentPath = [...path, key];
|
|
2094
|
+
const dependencies = getDependencies(key);
|
|
1838
2095
|
for (const dep of dependencies) {
|
|
1839
|
-
const
|
|
1840
|
-
if (
|
|
1841
|
-
const cycle = hasCycle(dep, currentPath);
|
|
1842
|
-
if (cycle) {
|
|
1843
|
-
return cycle;
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
2096
|
+
const cycle = findCycle(dep, currentPath);
|
|
2097
|
+
if (cycle) return cycle;
|
|
1846
2098
|
}
|
|
1847
|
-
recursionStack.delete(
|
|
1848
|
-
visited.add(
|
|
2099
|
+
recursionStack.delete(key);
|
|
2100
|
+
visited.add(key);
|
|
1849
2101
|
return null;
|
|
1850
|
-
}
|
|
1851
|
-
|
|
2102
|
+
};
|
|
2103
|
+
const allDefinitions = [
|
|
2104
|
+
...Array.from(components.keys()).map(makeComponentKey),
|
|
2105
|
+
...Array.from(layouts.keys()).map(makeLayoutKey)
|
|
2106
|
+
];
|
|
2107
|
+
for (const key of allDefinitions) {
|
|
1852
2108
|
visited.clear();
|
|
1853
2109
|
recursionStack.clear();
|
|
1854
|
-
const cycle =
|
|
2110
|
+
const cycle = findCycle(key);
|
|
1855
2111
|
if (cycle) {
|
|
1856
2112
|
throw new Error(
|
|
1857
|
-
`Circular component definition detected: ${cycle.join(" \u2192 ")}
|
|
1858
|
-
Components cannot reference each other in a cycle.`
|
|
2113
|
+
`Circular component definition detected: ${cycle.map(displayKey).join(" \u2192 ")}
|
|
2114
|
+
Components and layouts cannot reference each other in a cycle.`
|
|
1859
2115
|
);
|
|
1860
2116
|
}
|
|
1861
2117
|
}
|
|
@@ -1863,6 +2119,7 @@ Components cannot reference each other in a cycle.`
|
|
|
1863
2119
|
|
|
1864
2120
|
// src/ir/index.ts
|
|
1865
2121
|
var import_zod = require("zod");
|
|
2122
|
+
var import_components2 = require("@wire-dsl/language-support/components");
|
|
1866
2123
|
|
|
1867
2124
|
// src/ir/device-presets.ts
|
|
1868
2125
|
var DEVICE_PRESETS = {
|
|
@@ -2027,9 +2284,11 @@ var IRGenerator = class {
|
|
|
2027
2284
|
this.idGen = new IDGenerator();
|
|
2028
2285
|
this.nodes = {};
|
|
2029
2286
|
this.definedComponents = /* @__PURE__ */ new Map();
|
|
2287
|
+
this.definedLayouts = /* @__PURE__ */ new Map();
|
|
2030
2288
|
this.definedComponentIndices = /* @__PURE__ */ new Map();
|
|
2031
2289
|
this.undefinedComponentsUsed = /* @__PURE__ */ new Set();
|
|
2032
2290
|
this.warnings = [];
|
|
2291
|
+
this.errors = [];
|
|
2033
2292
|
this.style = {
|
|
2034
2293
|
density: "normal",
|
|
2035
2294
|
spacing: "md",
|
|
@@ -2042,15 +2301,22 @@ var IRGenerator = class {
|
|
|
2042
2301
|
this.idGen.reset();
|
|
2043
2302
|
this.nodes = {};
|
|
2044
2303
|
this.definedComponents.clear();
|
|
2304
|
+
this.definedLayouts.clear();
|
|
2045
2305
|
this.definedComponentIndices.clear();
|
|
2046
2306
|
this.undefinedComponentsUsed.clear();
|
|
2047
2307
|
this.warnings = [];
|
|
2308
|
+
this.errors = [];
|
|
2048
2309
|
if (ast.definedComponents && ast.definedComponents.length > 0) {
|
|
2049
2310
|
ast.definedComponents.forEach((def, index) => {
|
|
2050
2311
|
this.definedComponents.set(def.name, def);
|
|
2051
2312
|
this.definedComponentIndices.set(def.name, index);
|
|
2052
2313
|
});
|
|
2053
2314
|
}
|
|
2315
|
+
if (ast.definedLayouts && ast.definedLayouts.length > 0) {
|
|
2316
|
+
ast.definedLayouts.forEach((def) => {
|
|
2317
|
+
this.definedLayouts.set(def.name, def);
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2054
2320
|
this.applyStyle(ast.style);
|
|
2055
2321
|
const screens = ast.screens.map(
|
|
2056
2322
|
(screen, screenIndex) => this.convertScreen(screen, screenIndex)
|
|
@@ -2062,6 +2328,11 @@ var IRGenerator = class {
|
|
|
2062
2328
|
Define these components with: define Component "Name" { ... }`
|
|
2063
2329
|
);
|
|
2064
2330
|
}
|
|
2331
|
+
if (this.errors.length > 0) {
|
|
2332
|
+
const messages = this.errors.map((e) => `- [${e.type}] ${e.message}`).join("\n");
|
|
2333
|
+
throw new Error(`IR generation failed with semantic errors:
|
|
2334
|
+
${messages}`);
|
|
2335
|
+
}
|
|
2065
2336
|
const project = {
|
|
2066
2337
|
id: this.sanitizeId(ast.name),
|
|
2067
2338
|
name: ast.name,
|
|
@@ -2188,41 +2459,50 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2188
2459
|
getWarnings() {
|
|
2189
2460
|
return this.warnings;
|
|
2190
2461
|
}
|
|
2191
|
-
convertLayout(layout) {
|
|
2462
|
+
convertLayout(layout, context) {
|
|
2463
|
+
let layoutParams = this.resolveLayoutParams(layout.layoutType, layout.params, context);
|
|
2464
|
+
if (layout.layoutType === "split") {
|
|
2465
|
+
layoutParams = this.normalizeSplitParams(layoutParams);
|
|
2466
|
+
}
|
|
2467
|
+
const layoutChildren = layout.children;
|
|
2468
|
+
const layoutDefinition = this.definedLayouts.get(layout.layoutType);
|
|
2469
|
+
if (layoutDefinition) {
|
|
2470
|
+
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
|
|
2471
|
+
}
|
|
2192
2472
|
const nodeId = this.idGen.generate("node");
|
|
2193
2473
|
const childRefs = [];
|
|
2194
|
-
for (const child of
|
|
2474
|
+
for (const child of layoutChildren) {
|
|
2195
2475
|
if (child.type === "layout") {
|
|
2196
|
-
const childId = this.convertLayout(child);
|
|
2197
|
-
childRefs.push({ ref: childId });
|
|
2476
|
+
const childId = this.convertLayout(child, context);
|
|
2477
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2198
2478
|
} else if (child.type === "component") {
|
|
2199
|
-
const childId = this.convertComponent(child);
|
|
2200
|
-
childRefs.push({ ref: childId });
|
|
2479
|
+
const childId = this.convertComponent(child, context);
|
|
2480
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2201
2481
|
} else if (child.type === "cell") {
|
|
2202
|
-
const childId = this.convertCell(child);
|
|
2203
|
-
childRefs.push({ ref: childId });
|
|
2482
|
+
const childId = this.convertCell(child, context);
|
|
2483
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2204
2484
|
}
|
|
2205
2485
|
}
|
|
2206
2486
|
const style = {};
|
|
2207
|
-
if (
|
|
2208
|
-
style.padding = String(
|
|
2487
|
+
if (layoutParams.padding !== void 0) {
|
|
2488
|
+
style.padding = String(layoutParams.padding);
|
|
2209
2489
|
} else {
|
|
2210
2490
|
style.padding = "none";
|
|
2211
2491
|
}
|
|
2212
|
-
if (
|
|
2213
|
-
style.gap = String(
|
|
2492
|
+
if (layoutParams.gap !== void 0) {
|
|
2493
|
+
style.gap = String(layoutParams.gap);
|
|
2214
2494
|
}
|
|
2215
|
-
if (
|
|
2216
|
-
style.align =
|
|
2495
|
+
if (layoutParams.align !== void 0) {
|
|
2496
|
+
style.align = layoutParams.align;
|
|
2217
2497
|
}
|
|
2218
|
-
if (
|
|
2219
|
-
style.background = String(
|
|
2498
|
+
if (layoutParams.background !== void 0) {
|
|
2499
|
+
style.background = String(layoutParams.background);
|
|
2220
2500
|
}
|
|
2221
2501
|
const containerNode = {
|
|
2222
2502
|
id: nodeId,
|
|
2223
2503
|
kind: "container",
|
|
2224
2504
|
containerType: layout.layoutType,
|
|
2225
|
-
params: this.cleanParams(
|
|
2505
|
+
params: this.cleanParams(layoutParams),
|
|
2226
2506
|
children: childRefs,
|
|
2227
2507
|
style,
|
|
2228
2508
|
meta: {
|
|
@@ -2233,16 +2513,16 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2233
2513
|
this.nodes[nodeId] = containerNode;
|
|
2234
2514
|
return nodeId;
|
|
2235
2515
|
}
|
|
2236
|
-
convertCell(cell) {
|
|
2516
|
+
convertCell(cell, context) {
|
|
2237
2517
|
const nodeId = this.idGen.generate("node");
|
|
2238
2518
|
const childRefs = [];
|
|
2239
2519
|
for (const child of cell.children) {
|
|
2240
2520
|
if (child.type === "layout") {
|
|
2241
|
-
const childId = this.convertLayout(child);
|
|
2242
|
-
childRefs.push({ ref: childId });
|
|
2521
|
+
const childId = this.convertLayout(child, context);
|
|
2522
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2243
2523
|
} else if (child.type === "component") {
|
|
2244
|
-
const childId = this.convertComponent(child);
|
|
2245
|
-
childRefs.push({ ref: childId });
|
|
2524
|
+
const childId = this.convertComponent(child, context);
|
|
2525
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2246
2526
|
}
|
|
2247
2527
|
}
|
|
2248
2528
|
const containerNode = {
|
|
@@ -2263,10 +2543,28 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2263
2543
|
this.nodes[nodeId] = containerNode;
|
|
2264
2544
|
return nodeId;
|
|
2265
2545
|
}
|
|
2266
|
-
convertComponent(component) {
|
|
2546
|
+
convertComponent(component, context) {
|
|
2547
|
+
if (component.componentType === "Children") {
|
|
2548
|
+
if (!context?.allowChildrenSlot) {
|
|
2549
|
+
this.errors.push({
|
|
2550
|
+
type: "children-slot-outside-layout-definition",
|
|
2551
|
+
message: '"Children" placeholder can only be used inside a define Layout body.'
|
|
2552
|
+
});
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
if (!context.childrenSlot) {
|
|
2556
|
+
this.errors.push({
|
|
2557
|
+
type: "children-slot-missing-child",
|
|
2558
|
+
message: `Layout "${context.definitionName}" requires exactly one child for "Children".`
|
|
2559
|
+
});
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
return this.convertASTNode(context.childrenSlot, context);
|
|
2563
|
+
}
|
|
2564
|
+
const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
|
|
2267
2565
|
const definition = this.definedComponents.get(component.componentType);
|
|
2268
2566
|
if (definition) {
|
|
2269
|
-
return this.expandDefinedComponent(definition);
|
|
2567
|
+
return this.expandDefinedComponent(definition, resolvedProps, context);
|
|
2270
2568
|
}
|
|
2271
2569
|
const builtInComponents = /* @__PURE__ */ new Set([
|
|
2272
2570
|
"Button",
|
|
@@ -2309,7 +2607,7 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2309
2607
|
id: nodeId,
|
|
2310
2608
|
kind: "component",
|
|
2311
2609
|
componentType: component.componentType,
|
|
2312
|
-
props:
|
|
2610
|
+
props: resolvedProps,
|
|
2313
2611
|
style: {},
|
|
2314
2612
|
meta: {
|
|
2315
2613
|
nodeId: component._meta?.nodeId
|
|
@@ -2319,15 +2617,196 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2319
2617
|
this.nodes[nodeId] = componentNode;
|
|
2320
2618
|
return nodeId;
|
|
2321
2619
|
}
|
|
2322
|
-
expandDefinedComponent(definition) {
|
|
2620
|
+
expandDefinedComponent(definition, invocationArgs, parentContext) {
|
|
2621
|
+
const context = {
|
|
2622
|
+
args: invocationArgs,
|
|
2623
|
+
providedArgNames: new Set(Object.keys(invocationArgs)),
|
|
2624
|
+
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2625
|
+
definitionName: definition.name,
|
|
2626
|
+
definitionKind: "component",
|
|
2627
|
+
allowChildrenSlot: false
|
|
2628
|
+
};
|
|
2323
2629
|
if (definition.body.type === "layout") {
|
|
2324
|
-
|
|
2630
|
+
const result = this.convertLayout(definition.body, context);
|
|
2631
|
+
this.reportUnusedArguments(context);
|
|
2632
|
+
return result;
|
|
2325
2633
|
} else if (definition.body.type === "component") {
|
|
2326
|
-
|
|
2634
|
+
const result = this.convertComponent(definition.body, context);
|
|
2635
|
+
this.reportUnusedArguments(context);
|
|
2636
|
+
return result;
|
|
2327
2637
|
} else {
|
|
2328
2638
|
throw new Error(`Invalid defined component body type for "${definition.name}"`);
|
|
2329
2639
|
}
|
|
2330
2640
|
}
|
|
2641
|
+
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
|
|
2642
|
+
if (invocationChildren.length !== 1) {
|
|
2643
|
+
this.errors.push({
|
|
2644
|
+
type: "layout-children-arity",
|
|
2645
|
+
message: `Layout "${definition.name}" expects exactly one child, received ${invocationChildren.length}.`
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
const rawSlot = invocationChildren[0];
|
|
2649
|
+
const resolvedSlot = rawSlot ? this.resolveChildrenSlot(rawSlot, parentContext) : void 0;
|
|
2650
|
+
const context = {
|
|
2651
|
+
args: invocationParams,
|
|
2652
|
+
providedArgNames: new Set(Object.keys(invocationParams)),
|
|
2653
|
+
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2654
|
+
definitionName: definition.name,
|
|
2655
|
+
definitionKind: "layout",
|
|
2656
|
+
allowChildrenSlot: true,
|
|
2657
|
+
childrenSlot: resolvedSlot
|
|
2658
|
+
};
|
|
2659
|
+
const nodeId = this.convertLayout(definition.body, context);
|
|
2660
|
+
this.reportUnusedArguments(context);
|
|
2661
|
+
return nodeId;
|
|
2662
|
+
}
|
|
2663
|
+
resolveChildrenSlot(slot, parentContext) {
|
|
2664
|
+
if (slot.type === "component" && slot.componentType === "Children") {
|
|
2665
|
+
if (parentContext?.allowChildrenSlot) {
|
|
2666
|
+
return parentContext.childrenSlot;
|
|
2667
|
+
}
|
|
2668
|
+
this.errors.push({
|
|
2669
|
+
type: "children-slot-outside-layout-definition",
|
|
2670
|
+
message: '"Children" placeholder forwarding is only valid inside define Layout bodies.'
|
|
2671
|
+
});
|
|
2672
|
+
return void 0;
|
|
2673
|
+
}
|
|
2674
|
+
return slot;
|
|
2675
|
+
}
|
|
2676
|
+
convertASTNode(node, context) {
|
|
2677
|
+
if (node.type === "layout") return this.convertLayout(node, context);
|
|
2678
|
+
if (node.type === "component") return this.convertComponent(node, context);
|
|
2679
|
+
return this.convertCell(node, context);
|
|
2680
|
+
}
|
|
2681
|
+
resolveLayoutParams(layoutType, params, context) {
|
|
2682
|
+
const resolved = {};
|
|
2683
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2684
|
+
const resolvedValue = this.resolveBindingValue(
|
|
2685
|
+
value,
|
|
2686
|
+
context,
|
|
2687
|
+
"layout-parameter",
|
|
2688
|
+
layoutType,
|
|
2689
|
+
key
|
|
2690
|
+
);
|
|
2691
|
+
if (resolvedValue !== void 0) {
|
|
2692
|
+
resolved[key] = resolvedValue;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return resolved;
|
|
2696
|
+
}
|
|
2697
|
+
normalizeSplitParams(params) {
|
|
2698
|
+
const normalized = { ...params };
|
|
2699
|
+
if (normalized.sidebar !== void 0 && normalized.left === void 0 && normalized.right === void 0) {
|
|
2700
|
+
normalized.left = normalized.sidebar;
|
|
2701
|
+
this.warnings.push({
|
|
2702
|
+
type: "split-sidebar-deprecated",
|
|
2703
|
+
message: 'Split parameter "sidebar" is deprecated. Use "left" or "right".'
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
delete normalized.sidebar;
|
|
2707
|
+
const hasLeft = normalized.left !== void 0;
|
|
2708
|
+
const hasRight = normalized.right !== void 0;
|
|
2709
|
+
if (hasLeft && hasRight) {
|
|
2710
|
+
delete normalized.right;
|
|
2711
|
+
this.warnings.push({
|
|
2712
|
+
type: "split-side-conflict",
|
|
2713
|
+
message: 'Split layout received both "left" and "right"; keeping "left".'
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
if (!hasLeft && !hasRight) {
|
|
2717
|
+
normalized.left = 250;
|
|
2718
|
+
this.warnings.push({
|
|
2719
|
+
type: "split-side-missing",
|
|
2720
|
+
message: 'Split layout missing both "left" and "right"; defaulting to left: 250.'
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
if (normalized.left !== void 0) {
|
|
2724
|
+
const leftWidth = Number(normalized.left);
|
|
2725
|
+
if (!Number.isFinite(leftWidth) || leftWidth <= 0) {
|
|
2726
|
+
normalized.left = 250;
|
|
2727
|
+
this.warnings.push({
|
|
2728
|
+
type: "split-left-invalid",
|
|
2729
|
+
message: 'Split "left" must be a positive number. Falling back to 250.'
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
if (normalized.right !== void 0) {
|
|
2734
|
+
const rightWidth = Number(normalized.right);
|
|
2735
|
+
if (!Number.isFinite(rightWidth) || rightWidth <= 0) {
|
|
2736
|
+
normalized.right = 250;
|
|
2737
|
+
this.warnings.push({
|
|
2738
|
+
type: "split-right-invalid",
|
|
2739
|
+
message: 'Split "right" must be a positive number. Falling back to 250.'
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
return normalized;
|
|
2744
|
+
}
|
|
2745
|
+
resolveComponentProps(componentType, props, context) {
|
|
2746
|
+
const resolved = {};
|
|
2747
|
+
for (const [key, value] of Object.entries(props)) {
|
|
2748
|
+
const resolvedValue = this.resolveBindingValue(
|
|
2749
|
+
value,
|
|
2750
|
+
context,
|
|
2751
|
+
"component-property",
|
|
2752
|
+
componentType,
|
|
2753
|
+
key
|
|
2754
|
+
);
|
|
2755
|
+
if (resolvedValue !== void 0) {
|
|
2756
|
+
resolved[key] = resolvedValue;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
return resolved;
|
|
2760
|
+
}
|
|
2761
|
+
resolveBindingValue(value, context, kind, targetType, targetName) {
|
|
2762
|
+
if (typeof value !== "string" || !value.startsWith("prop_")) {
|
|
2763
|
+
return value;
|
|
2764
|
+
}
|
|
2765
|
+
const argName = value.slice("prop_".length);
|
|
2766
|
+
if (!context) {
|
|
2767
|
+
return value;
|
|
2768
|
+
}
|
|
2769
|
+
if (Object.prototype.hasOwnProperty.call(context.args, argName)) {
|
|
2770
|
+
context.usedArgNames.add(argName);
|
|
2771
|
+
return context.args[argName];
|
|
2772
|
+
}
|
|
2773
|
+
const required = this.isBindingTargetRequired(kind, targetType, targetName);
|
|
2774
|
+
const descriptor = kind === "component-property" ? "property" : "parameter";
|
|
2775
|
+
const message = `Missing required bound ${descriptor} "${targetName}" for ${kind === "component-property" ? "component" : "layout"} "${targetType}" in ${context.definitionKind} "${context.definitionName}" (expected arg "${argName}").`;
|
|
2776
|
+
if (required) {
|
|
2777
|
+
this.errors.push({ type: "missing-required-bound-value", message });
|
|
2778
|
+
} else {
|
|
2779
|
+
this.warnings.push({
|
|
2780
|
+
type: "missing-bound-value",
|
|
2781
|
+
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}".`
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
return void 0;
|
|
2785
|
+
}
|
|
2786
|
+
isBindingTargetRequired(kind, targetType, targetName) {
|
|
2787
|
+
if (kind === "component-property") {
|
|
2788
|
+
const metadata = import_components2.COMPONENTS[targetType];
|
|
2789
|
+
const property2 = metadata?.properties?.[targetName];
|
|
2790
|
+
if (!property2) return false;
|
|
2791
|
+
return property2.required === true && property2.defaultValue === void 0;
|
|
2792
|
+
}
|
|
2793
|
+
const layoutMetadata = import_components2.LAYOUTS[targetType];
|
|
2794
|
+
if (!layoutMetadata) return false;
|
|
2795
|
+
const property = layoutMetadata.properties?.[targetName];
|
|
2796
|
+
const requiredFromProperty = property?.required === true && property.defaultValue === void 0;
|
|
2797
|
+
const requiredFromLayout = (layoutMetadata.requiredProperties || []).includes(targetName);
|
|
2798
|
+
return requiredFromProperty || requiredFromLayout;
|
|
2799
|
+
}
|
|
2800
|
+
reportUnusedArguments(context) {
|
|
2801
|
+
for (const arg of context.providedArgNames) {
|
|
2802
|
+
if (!context.usedArgNames.has(arg)) {
|
|
2803
|
+
this.warnings.push({
|
|
2804
|
+
type: "unused-definition-argument",
|
|
2805
|
+
message: `Argument "${arg}" is not used by ${context.definitionKind} "${context.definitionName}".`
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2331
2810
|
cleanParams(params) {
|
|
2332
2811
|
const cleaned = {};
|
|
2333
2812
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -2377,9 +2856,24 @@ var ICON_SIZES_BY_DENSITY = {
|
|
|
2377
2856
|
comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
|
|
2378
2857
|
};
|
|
2379
2858
|
var ICON_BUTTON_SIZES_BY_DENSITY = {
|
|
2380
|
-
compact: { sm:
|
|
2381
|
-
normal: { sm:
|
|
2382
|
-
comfortable: { sm:
|
|
2859
|
+
compact: { sm: 20, md: 24, lg: 32 },
|
|
2860
|
+
normal: { sm: 24, md: 32, lg: 40 },
|
|
2861
|
+
comfortable: { sm: 28, md: 40, lg: 48 }
|
|
2862
|
+
};
|
|
2863
|
+
var CONTROL_HEIGHTS_BY_DENSITY = {
|
|
2864
|
+
compact: { sm: 28, md: 32, lg: 36 },
|
|
2865
|
+
normal: { sm: 36, md: 40, lg: 48 },
|
|
2866
|
+
comfortable: { sm: 40, md: 48, lg: 56 }
|
|
2867
|
+
};
|
|
2868
|
+
var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
|
|
2869
|
+
compact: { sm: 20, md: 24, lg: 32 },
|
|
2870
|
+
normal: { sm: 24, md: 32, lg: 40 },
|
|
2871
|
+
comfortable: { sm: 28, md: 40, lg: 48 }
|
|
2872
|
+
};
|
|
2873
|
+
var CONTROL_PADDING_BY_DENSITY = {
|
|
2874
|
+
compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
|
|
2875
|
+
normal: { none: 0, xs: 6, sm: 10, md: 14, lg: 18, xl: 24 },
|
|
2876
|
+
comfortable: { none: 0, xs: 8, sm: 12, md: 16, lg: 22, xl: 28 }
|
|
2383
2877
|
};
|
|
2384
2878
|
function resolveIconSize(size, density = "normal") {
|
|
2385
2879
|
const map = ICON_SIZES_BY_DENSITY[density] || ICON_SIZES_BY_DENSITY.normal;
|
|
@@ -2389,6 +2883,18 @@ function resolveIconButtonSize(size, density = "normal") {
|
|
|
2389
2883
|
const map = ICON_BUTTON_SIZES_BY_DENSITY[density] || ICON_BUTTON_SIZES_BY_DENSITY.normal;
|
|
2390
2884
|
return map[size || "md"] || map.md;
|
|
2391
2885
|
}
|
|
2886
|
+
function resolveControlHeight(size, density = "normal") {
|
|
2887
|
+
const map = CONTROL_HEIGHTS_BY_DENSITY[density] || CONTROL_HEIGHTS_BY_DENSITY.normal;
|
|
2888
|
+
return map[size || "md"] || map.md;
|
|
2889
|
+
}
|
|
2890
|
+
function resolveActionControlHeight(size, density = "normal") {
|
|
2891
|
+
const map = ACTION_CONTROL_HEIGHTS_BY_DENSITY[density] || ACTION_CONTROL_HEIGHTS_BY_DENSITY.normal;
|
|
2892
|
+
return map[size || "md"] || map.md;
|
|
2893
|
+
}
|
|
2894
|
+
function resolveControlHorizontalPadding(padding, density = "normal") {
|
|
2895
|
+
const map = CONTROL_PADDING_BY_DENSITY[density] || CONTROL_PADDING_BY_DENSITY.normal;
|
|
2896
|
+
return map[padding || "md"] ?? map.md;
|
|
2897
|
+
}
|
|
2392
2898
|
|
|
2393
2899
|
// src/shared/heading-levels.ts
|
|
2394
2900
|
var DEFAULT_LEVEL = "h2";
|
|
@@ -2707,6 +3213,41 @@ var LayoutEngine = class {
|
|
|
2707
3213
|
}
|
|
2708
3214
|
return totalHeight;
|
|
2709
3215
|
}
|
|
3216
|
+
if (node.containerType === "split") {
|
|
3217
|
+
const splitGap = this.resolveSpacing(node.style.gap);
|
|
3218
|
+
const leftParam = node.params.left;
|
|
3219
|
+
const rightParam = node.params.right;
|
|
3220
|
+
const leftWidthRaw = Number(leftParam);
|
|
3221
|
+
const rightWidthRaw = Number(rightParam);
|
|
3222
|
+
const hasLeft = leftParam !== void 0;
|
|
3223
|
+
const hasRight = rightParam !== void 0;
|
|
3224
|
+
const leftWidth = Number.isFinite(leftWidthRaw) && leftWidthRaw > 0 ? leftWidthRaw : availableWidth / 2;
|
|
3225
|
+
const rightWidth = Number.isFinite(rightWidthRaw) && rightWidthRaw > 0 ? rightWidthRaw : availableWidth / 2;
|
|
3226
|
+
let maxHeight = 0;
|
|
3227
|
+
node.children.forEach((childRef, index) => {
|
|
3228
|
+
const child = this.nodes[childRef.ref];
|
|
3229
|
+
let childHeight = this.getComponentHeight();
|
|
3230
|
+
const isFirst = index === 0;
|
|
3231
|
+
let childWidth;
|
|
3232
|
+
if (node.children.length >= 2) {
|
|
3233
|
+
if (hasRight && !hasLeft) {
|
|
3234
|
+
childWidth = isFirst ? Math.max(1, availableWidth - rightWidth - splitGap) : rightWidth;
|
|
3235
|
+
} else {
|
|
3236
|
+
childWidth = isFirst ? leftWidth : Math.max(1, availableWidth - leftWidth - splitGap);
|
|
3237
|
+
}
|
|
3238
|
+
} else {
|
|
3239
|
+
childWidth = availableWidth;
|
|
3240
|
+
}
|
|
3241
|
+
if (child?.kind === "component") {
|
|
3242
|
+
childHeight = child.props.height ? Number(child.props.height) : this.getIntrinsicComponentHeight(child, childWidth);
|
|
3243
|
+
} else if (child?.kind === "container") {
|
|
3244
|
+
childHeight = this.calculateContainerHeight(child, childWidth);
|
|
3245
|
+
}
|
|
3246
|
+
maxHeight = Math.max(maxHeight, childHeight);
|
|
3247
|
+
});
|
|
3248
|
+
totalHeight += maxHeight;
|
|
3249
|
+
return totalHeight;
|
|
3250
|
+
}
|
|
2710
3251
|
const direction = node.params.direction || "vertical";
|
|
2711
3252
|
if (node.containerType === "stack" && direction === "horizontal") {
|
|
2712
3253
|
let maxHeight = 0;
|
|
@@ -2829,14 +3370,28 @@ var LayoutEngine = class {
|
|
|
2829
3370
|
calculateSplit(node, x, y, width, height) {
|
|
2830
3371
|
if (node.kind !== "container") return;
|
|
2831
3372
|
const gap = this.resolveSpacing(node.style.gap);
|
|
2832
|
-
const
|
|
3373
|
+
const leftParam = node.params.left;
|
|
3374
|
+
const rightParam = node.params.right;
|
|
3375
|
+
const leftWidthRaw = Number(leftParam);
|
|
3376
|
+
const rightWidthRaw = Number(rightParam);
|
|
3377
|
+
const hasLeft = leftParam !== void 0;
|
|
3378
|
+
const hasRight = rightParam !== void 0;
|
|
3379
|
+
const leftWidth = Number.isFinite(leftWidthRaw) && leftWidthRaw > 0 ? leftWidthRaw : 250;
|
|
3380
|
+
const rightWidth = Number.isFinite(rightWidthRaw) && rightWidthRaw > 0 ? rightWidthRaw : 250;
|
|
2833
3381
|
if (node.children.length === 1) {
|
|
2834
3382
|
this.calculateNode(node.children[0].ref, x, y, width, height, "split");
|
|
2835
3383
|
} else if (node.children.length >= 2) {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3384
|
+
if (hasRight && !hasLeft) {
|
|
3385
|
+
const flexibleLeftWidth = Math.max(1, width - rightWidth - gap);
|
|
3386
|
+
const rightX = x + flexibleLeftWidth + gap;
|
|
3387
|
+
this.calculateNode(node.children[0].ref, x, y, flexibleLeftWidth, height, "split");
|
|
3388
|
+
this.calculateNode(node.children[1].ref, rightX, y, rightWidth, height, "split");
|
|
3389
|
+
} else {
|
|
3390
|
+
const flexibleRightWidth = Math.max(1, width - leftWidth - gap);
|
|
3391
|
+
const rightX = x + leftWidth + gap;
|
|
3392
|
+
this.calculateNode(node.children[0].ref, x, y, leftWidth, height, "split");
|
|
3393
|
+
this.calculateNode(node.children[1].ref, rightX, y, flexibleRightWidth, height, "split");
|
|
3394
|
+
}
|
|
2840
3395
|
}
|
|
2841
3396
|
}
|
|
2842
3397
|
calculatePanel(node, x, y, width, height) {
|
|
@@ -2985,7 +3540,11 @@ var LayoutEngine = class {
|
|
|
2985
3540
|
}
|
|
2986
3541
|
getIntrinsicComponentHeight(node, availableWidth) {
|
|
2987
3542
|
if (node.kind !== "component") return this.getComponentHeight();
|
|
2988
|
-
const
|
|
3543
|
+
const controlSize = String(node.props.size || "md");
|
|
3544
|
+
const density = this.style.density || "normal";
|
|
3545
|
+
const inputControlHeight = resolveControlHeight(controlSize, density);
|
|
3546
|
+
const actionControlHeight = resolveActionControlHeight(controlSize, density);
|
|
3547
|
+
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;
|
|
2989
3548
|
if (node.componentType === "Image") {
|
|
2990
3549
|
const placeholder = String(node.props.placeholder || "landscape");
|
|
2991
3550
|
const aspectRatios = {
|
|
@@ -3012,12 +3571,26 @@ var LayoutEngine = class {
|
|
|
3012
3571
|
}
|
|
3013
3572
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
3014
3573
|
const hasTitle = !!node.props.title;
|
|
3015
|
-
const hasPagination =
|
|
3574
|
+
const hasPagination = this.parseBooleanProp(node.props.pagination, false);
|
|
3575
|
+
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
3576
|
+
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
3577
|
+
const captionAlign = String(node.props.captionAlign || "");
|
|
3578
|
+
const effectiveCaptionAlign = captionAlign === "left" || captionAlign === "center" || captionAlign === "right" ? captionAlign : paginationAlign === "left" ? "right" : "left";
|
|
3579
|
+
const sameFooterAlign = hasCaption && hasPagination && effectiveCaptionAlign === paginationAlign;
|
|
3016
3580
|
const headerHeight = 44;
|
|
3017
3581
|
const rowHeight = 36;
|
|
3018
3582
|
const titleHeight = hasTitle ? 32 : 0;
|
|
3019
|
-
|
|
3020
|
-
|
|
3583
|
+
let footerHeight = 0;
|
|
3584
|
+
if (hasPagination || hasCaption) {
|
|
3585
|
+
const footerBottomPadding = 12;
|
|
3586
|
+
footerHeight += 16;
|
|
3587
|
+
footerHeight += hasPagination ? 32 : 18;
|
|
3588
|
+
if (sameFooterAlign) {
|
|
3589
|
+
footerHeight += 8 + 18;
|
|
3590
|
+
}
|
|
3591
|
+
footerHeight += footerBottomPadding;
|
|
3592
|
+
}
|
|
3593
|
+
return titleHeight + headerHeight + rowCount * rowHeight + footerHeight;
|
|
3021
3594
|
}
|
|
3022
3595
|
if (node.componentType === "Heading") {
|
|
3023
3596
|
const text = String(node.props.text || "Heading");
|
|
@@ -3026,15 +3599,15 @@ var LayoutEngine = class {
|
|
|
3026
3599
|
const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
|
|
3027
3600
|
const lines = this.wrapTextToLines(text, maxWidth, fontSize);
|
|
3028
3601
|
const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
|
|
3029
|
-
const
|
|
3030
|
-
const verticalPadding = resolveHeadingVerticalPadding(node.props.spacing,
|
|
3602
|
+
const density2 = this.style.density || "normal";
|
|
3603
|
+
const verticalPadding = resolveHeadingVerticalPadding(node.props.spacing, density2);
|
|
3031
3604
|
if (verticalPadding === null) {
|
|
3032
3605
|
return Math.max(this.getComponentHeight(), wrappedHeight);
|
|
3033
3606
|
}
|
|
3034
3607
|
return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
|
|
3035
3608
|
}
|
|
3036
3609
|
if (node.componentType === "Text") {
|
|
3037
|
-
const content = String(node.props.
|
|
3610
|
+
const content = String(node.props.text || "");
|
|
3038
3611
|
const { fontSize, lineHeight } = this.getTextMetricsForDensity();
|
|
3039
3612
|
const lineHeightPx = Math.ceil(fontSize * lineHeight);
|
|
3040
3613
|
const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
|
|
@@ -3083,7 +3656,10 @@ var LayoutEngine = class {
|
|
|
3083
3656
|
if (node.componentType === "Divider") return 1;
|
|
3084
3657
|
if (node.componentType === "Separate") return this.getSeparateSize(node);
|
|
3085
3658
|
if (node.componentType === "Input" || node.componentType === "Select") {
|
|
3086
|
-
return
|
|
3659
|
+
return inputControlHeight + controlLabelOffset;
|
|
3660
|
+
}
|
|
3661
|
+
if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
|
|
3662
|
+
return actionControlHeight + controlLabelOffset;
|
|
3087
3663
|
}
|
|
3088
3664
|
return this.getComponentHeight();
|
|
3089
3665
|
}
|
|
@@ -3100,7 +3676,10 @@ var LayoutEngine = class {
|
|
|
3100
3676
|
}
|
|
3101
3677
|
if (node.componentType === "IconButton") {
|
|
3102
3678
|
const size = String(node.props.size || "md");
|
|
3103
|
-
|
|
3679
|
+
const density = this.style.density || "normal";
|
|
3680
|
+
const baseSize = resolveIconButtonSize(size, density);
|
|
3681
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
3682
|
+
return baseSize + extraPadding * 2;
|
|
3104
3683
|
}
|
|
3105
3684
|
if (node.componentType === "Checkbox" || node.componentType === "Radio") {
|
|
3106
3685
|
return 24;
|
|
@@ -3109,11 +3688,13 @@ var LayoutEngine = class {
|
|
|
3109
3688
|
if (node.componentType === "Button" || node.componentType === "Link") {
|
|
3110
3689
|
const text = String(node.props.text || "");
|
|
3111
3690
|
const { fontSize, paddingX } = this.getButtonMetricsForDensity();
|
|
3691
|
+
const density = this.style.density || "normal";
|
|
3692
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
3112
3693
|
const textWidth = this.estimateTextWidth(text, fontSize);
|
|
3113
|
-
return Math.max(60, Math.ceil(textWidth + paddingX * 2));
|
|
3694
|
+
return Math.max(60, Math.ceil(textWidth + (paddingX + extraPadding) * 2));
|
|
3114
3695
|
}
|
|
3115
3696
|
if (node.componentType === "Label" || node.componentType === "Text") {
|
|
3116
|
-
const text = String(node.props.
|
|
3697
|
+
const text = String(node.props.text || "");
|
|
3117
3698
|
return Math.max(60, text.length * 8 + 16);
|
|
3118
3699
|
}
|
|
3119
3700
|
if (node.componentType === "Heading") {
|
|
@@ -3975,7 +4556,7 @@ var THEMES = {
|
|
|
3975
4556
|
bg: "#F8FAFC",
|
|
3976
4557
|
cardBg: "#FFFFFF",
|
|
3977
4558
|
border: "#E2E8F0",
|
|
3978
|
-
text: "#
|
|
4559
|
+
text: "#000000",
|
|
3979
4560
|
textMuted: "#64748B",
|
|
3980
4561
|
primary: "#3B82F6",
|
|
3981
4562
|
primaryHover: "#2563EB",
|
|
@@ -3985,7 +4566,7 @@ var THEMES = {
|
|
|
3985
4566
|
bg: "#0F172A",
|
|
3986
4567
|
cardBg: "#1E293B",
|
|
3987
4568
|
border: "#334155",
|
|
3988
|
-
text: "#
|
|
4569
|
+
text: "#FFFFFF",
|
|
3989
4570
|
textMuted: "#94A3B8",
|
|
3990
4571
|
primary: "#60A5FA",
|
|
3991
4572
|
primaryHover: "#3B82F6",
|
|
@@ -3995,7 +4576,7 @@ var THEMES = {
|
|
|
3995
4576
|
var SVGRenderer = class {
|
|
3996
4577
|
constructor(ir, layout, options) {
|
|
3997
4578
|
this.renderedNodeIds = /* @__PURE__ */ new Set();
|
|
3998
|
-
this.fontFamily = "
|
|
4579
|
+
this.fontFamily = "Arial, Helvetica, sans-serif";
|
|
3999
4580
|
this.parentContainerByChildId = /* @__PURE__ */ new Map();
|
|
4000
4581
|
this.ir = ir;
|
|
4001
4582
|
this.layout = layout;
|
|
@@ -4009,7 +4590,6 @@ var SVGRenderer = class {
|
|
|
4009
4590
|
includeLabels: options?.includeLabels ?? true,
|
|
4010
4591
|
screenName: options?.screenName
|
|
4011
4592
|
};
|
|
4012
|
-
this.renderTheme = THEMES[this.options.theme];
|
|
4013
4593
|
this.colorResolver = new ColorResolver();
|
|
4014
4594
|
this.buildParentContainerIndex();
|
|
4015
4595
|
if (ir.project.mocks && Object.keys(ir.project.mocks).length > 0) {
|
|
@@ -4018,6 +4598,12 @@ var SVGRenderer = class {
|
|
|
4018
4598
|
if (ir.project.colors && Object.keys(ir.project.colors).length > 0) {
|
|
4019
4599
|
this.colorResolver.setCustomColors(ir.project.colors);
|
|
4020
4600
|
}
|
|
4601
|
+
const themeDefaults = THEMES[this.options.theme];
|
|
4602
|
+
this.renderTheme = {
|
|
4603
|
+
...themeDefaults,
|
|
4604
|
+
text: this.resolveTextColor(),
|
|
4605
|
+
textMuted: this.resolveMutedColor()
|
|
4606
|
+
};
|
|
4021
4607
|
}
|
|
4022
4608
|
/**
|
|
4023
4609
|
* Get list of available screens in the project
|
|
@@ -4099,6 +4685,9 @@ var SVGRenderer = class {
|
|
|
4099
4685
|
if (node.containerType === "card") {
|
|
4100
4686
|
this.renderCardBorder(node, pos, containerGroup);
|
|
4101
4687
|
}
|
|
4688
|
+
if (node.containerType === "split") {
|
|
4689
|
+
this.renderSplitDecoration(node, pos, containerGroup);
|
|
4690
|
+
}
|
|
4102
4691
|
node.children.forEach((childRef) => {
|
|
4103
4692
|
this.renderNode(childRef.ref, containerGroup);
|
|
4104
4693
|
});
|
|
@@ -4186,6 +4775,8 @@ var SVGRenderer = class {
|
|
|
4186
4775
|
}
|
|
4187
4776
|
renderHeading(node, pos) {
|
|
4188
4777
|
const text = String(node.props.text || "Heading");
|
|
4778
|
+
const variant = String(node.props.variant || "default");
|
|
4779
|
+
const headingColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
4189
4780
|
const headingTypography = this.getHeadingTypography(node);
|
|
4190
4781
|
const fontSize = headingTypography.fontSize;
|
|
4191
4782
|
const fontWeight = headingTypography.fontWeight;
|
|
@@ -4195,10 +4786,10 @@ var SVGRenderer = class {
|
|
|
4195
4786
|
if (lines.length <= 1) {
|
|
4196
4787
|
return `<g${this.getDataNodeId(node)}>
|
|
4197
4788
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4198
|
-
font-family="
|
|
4789
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4199
4790
|
font-size="${fontSize}"
|
|
4200
4791
|
font-weight="${fontWeight}"
|
|
4201
|
-
fill="${
|
|
4792
|
+
fill="${headingColor}">${this.escapeXml(text)}</text>
|
|
4202
4793
|
</g>`;
|
|
4203
4794
|
}
|
|
4204
4795
|
const tspans = lines.map(
|
|
@@ -4206,41 +4797,46 @@ var SVGRenderer = class {
|
|
|
4206
4797
|
).join("");
|
|
4207
4798
|
return `<g${this.getDataNodeId(node)}>
|
|
4208
4799
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4209
|
-
font-family="
|
|
4800
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4210
4801
|
font-size="${fontSize}"
|
|
4211
4802
|
font-weight="${fontWeight}"
|
|
4212
|
-
fill="${
|
|
4803
|
+
fill="${headingColor}">${tspans}</text>
|
|
4213
4804
|
</g>`;
|
|
4214
4805
|
}
|
|
4215
4806
|
renderButton(node, pos) {
|
|
4216
4807
|
const text = String(node.props.text || "Button");
|
|
4217
4808
|
const variant = String(node.props.variant || "default");
|
|
4809
|
+
const size = String(node.props.size || "md");
|
|
4810
|
+
const density = this.ir.project.style.density || "normal";
|
|
4811
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
4812
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
4218
4813
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
4219
4814
|
const radius = this.tokens.button.radius;
|
|
4220
4815
|
const fontSize = this.tokens.button.fontSize;
|
|
4221
4816
|
const fontWeight = this.tokens.button.fontWeight;
|
|
4222
4817
|
const paddingX = this.tokens.button.paddingX;
|
|
4223
|
-
const
|
|
4818
|
+
const controlHeight = resolveActionControlHeight(size, density);
|
|
4819
|
+
const buttonY = pos.y + labelOffset;
|
|
4820
|
+
const buttonHeight = Math.max(16, Math.min(controlHeight, pos.height - labelOffset));
|
|
4224
4821
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4225
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60), pos.width);
|
|
4226
|
-
const
|
|
4227
|
-
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
4822
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (paddingX + extraPadding) * 2), 60), pos.width);
|
|
4823
|
+
const availableTextWidth = Math.max(0, buttonWidth - (paddingX + extraPadding) * 2);
|
|
4228
4824
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4229
4825
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
4230
4826
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
4231
4827
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4232
4828
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
4233
|
-
const textColor = hasExplicitVariantColor ? "#FFFFFF" :
|
|
4829
|
+
const textColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.85);
|
|
4234
4830
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
4235
4831
|
return `<g${this.getDataNodeId(node)}>
|
|
4236
|
-
<rect x="${pos.x}" y="${
|
|
4832
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
4237
4833
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4238
4834
|
rx="${radius}"
|
|
4239
4835
|
fill="${bgColor}"
|
|
4240
4836
|
stroke="${borderColor}"
|
|
4241
4837
|
stroke-width="1"/>
|
|
4242
|
-
<text x="${pos.x + buttonWidth / 2}" y="${
|
|
4243
|
-
font-family="
|
|
4838
|
+
<text x="${pos.x + buttonWidth / 2}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
4839
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4244
4840
|
font-size="${fontSize}"
|
|
4245
4841
|
font-weight="${fontWeight}"
|
|
4246
4842
|
fill="${textColor}"
|
|
@@ -4250,17 +4846,18 @@ var SVGRenderer = class {
|
|
|
4250
4846
|
renderLink(node, pos) {
|
|
4251
4847
|
const text = String(node.props.text || "Link");
|
|
4252
4848
|
const variant = String(node.props.variant || "primary");
|
|
4849
|
+
const size = String(node.props.size || "md");
|
|
4850
|
+
const density = this.ir.project.style.density || "normal";
|
|
4253
4851
|
const fontSize = this.tokens.button.fontSize;
|
|
4254
4852
|
const fontWeight = this.tokens.button.fontWeight;
|
|
4255
4853
|
const paddingX = this.tokens.button.paddingX;
|
|
4256
|
-
const paddingY = this.tokens.button.paddingY;
|
|
4257
4854
|
const linkColor = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4258
4855
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4259
4856
|
const linkWidth = this.clampControlWidth(
|
|
4260
4857
|
Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60),
|
|
4261
4858
|
pos.width
|
|
4262
4859
|
);
|
|
4263
|
-
const linkHeight =
|
|
4860
|
+
const linkHeight = Math.max(16, Math.min(resolveActionControlHeight(size, density), pos.height));
|
|
4264
4861
|
const availableTextWidth = Math.max(0, linkWidth - paddingX * 2);
|
|
4265
4862
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4266
4863
|
const visibleTextWidth = Math.min(
|
|
@@ -4271,7 +4868,7 @@ var SVGRenderer = class {
|
|
|
4271
4868
|
const underlineY = centerY + 3;
|
|
4272
4869
|
return `<g${this.getDataNodeId(node)}>
|
|
4273
4870
|
<text x="${pos.x + linkWidth / 2}" y="${centerY}"
|
|
4274
|
-
font-family="
|
|
4871
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4275
4872
|
font-size="${fontSize}"
|
|
4276
4873
|
font-weight="${fontWeight}"
|
|
4277
4874
|
fill="${linkColor}"
|
|
@@ -4293,7 +4890,7 @@ var SVGRenderer = class {
|
|
|
4293
4890
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
4294
4891
|
return `<g${this.getDataNodeId(node)}>
|
|
4295
4892
|
${label ? `<text x="${pos.x + paddingX}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4296
|
-
font-family="
|
|
4893
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4297
4894
|
font-size="12"
|
|
4298
4895
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4299
4896
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4303,7 +4900,7 @@ var SVGRenderer = class {
|
|
|
4303
4900
|
stroke="${this.renderTheme.border}"
|
|
4304
4901
|
stroke-width="1"/>
|
|
4305
4902
|
<text x="${pos.x + paddingX}" y="${controlY + controlHeight / 2 + 5}"
|
|
4306
|
-
font-family="
|
|
4903
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4307
4904
|
font-size="${fontSize}"
|
|
4308
4905
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4309
4906
|
</g>`;
|
|
@@ -4313,25 +4910,42 @@ var SVGRenderer = class {
|
|
|
4313
4910
|
const subtitle = String(node.props.subtitle || "");
|
|
4314
4911
|
const actions = String(node.props.actions || "");
|
|
4315
4912
|
const user = String(node.props.user || "");
|
|
4316
|
-
const
|
|
4913
|
+
const variant = String(node.props.variant || "default");
|
|
4914
|
+
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
4915
|
+
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
4916
|
+
const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
|
|
4917
|
+
const radiusMap = {
|
|
4918
|
+
none: 0,
|
|
4919
|
+
sm: 4,
|
|
4920
|
+
md: this.tokens.card.radius,
|
|
4921
|
+
lg: 12,
|
|
4922
|
+
xl: 16
|
|
4923
|
+
};
|
|
4924
|
+
const topbarRadius = radiusMap[String(node.props.radius || "md")] ?? this.tokens.card.radius;
|
|
4317
4925
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
4318
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
4926
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
4927
|
+
if (showBorder || showBackground) {
|
|
4928
|
+
const bg = showBackground ? this.renderTheme.cardBg : "none";
|
|
4929
|
+
const stroke = showBorder ? this.renderTheme.border : "none";
|
|
4930
|
+
svg += `
|
|
4319
4931
|
<rect x="${pos.x}" y="${pos.y}"
|
|
4320
4932
|
width="${pos.width}" height="${pos.height}"
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
stroke
|
|
4324
|
-
|
|
4933
|
+
rx="${topbarRadius}"
|
|
4934
|
+
fill="${bg}"
|
|
4935
|
+
stroke="${stroke}"
|
|
4936
|
+
stroke-width="1"/>`;
|
|
4937
|
+
}
|
|
4938
|
+
svg += `
|
|
4325
4939
|
<!-- Title -->
|
|
4326
4940
|
<text x="${topbar.textX}" y="${topbar.titleY}"
|
|
4327
|
-
font-family="
|
|
4941
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4328
4942
|
font-size="18"
|
|
4329
4943
|
font-weight="600"
|
|
4330
4944
|
fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
|
|
4331
4945
|
if (topbar.hasSubtitle) {
|
|
4332
4946
|
svg += `
|
|
4333
4947
|
<text x="${topbar.textX}" y="${topbar.subtitleY}"
|
|
4334
|
-
font-family="
|
|
4948
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4335
4949
|
font-size="13"
|
|
4336
4950
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
|
|
4337
4951
|
}
|
|
@@ -4359,7 +4973,7 @@ var SVGRenderer = class {
|
|
|
4359
4973
|
fill="${accentColor}"
|
|
4360
4974
|
stroke="none"/>
|
|
4361
4975
|
<text x="${action.x + action.width / 2}" y="${action.y + action.height / 2 + 4}"
|
|
4362
|
-
font-family="
|
|
4976
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4363
4977
|
font-size="12"
|
|
4364
4978
|
font-weight="600"
|
|
4365
4979
|
fill="white"
|
|
@@ -4375,7 +4989,7 @@ var SVGRenderer = class {
|
|
|
4375
4989
|
stroke="${this.renderTheme.border}"
|
|
4376
4990
|
stroke-width="1"/>
|
|
4377
4991
|
<text x="${topbar.userBadge.x + topbar.userBadge.width / 2}" y="${topbar.userBadge.y + topbar.userBadge.height / 2 + 4}"
|
|
4378
|
-
font-family="
|
|
4992
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4379
4993
|
font-size="12"
|
|
4380
4994
|
fill="${this.renderTheme.text}"
|
|
4381
4995
|
text-anchor="middle">${this.escapeXml(topbar.userBadge.label)}</text>`;
|
|
@@ -4438,77 +5052,165 @@ var SVGRenderer = class {
|
|
|
4438
5052
|
</g>`;
|
|
4439
5053
|
output.push(svg);
|
|
4440
5054
|
}
|
|
5055
|
+
renderSplitDecoration(node, pos, output) {
|
|
5056
|
+
if (node.kind !== "container") return;
|
|
5057
|
+
const gap = this.resolveSpacing(node.style.gap);
|
|
5058
|
+
const leftParam = Number(node.params.left);
|
|
5059
|
+
const rightParam = Number(node.params.right);
|
|
5060
|
+
const hasLeft = node.params.left !== void 0;
|
|
5061
|
+
const hasRight = node.params.right !== void 0 && node.params.left === void 0;
|
|
5062
|
+
const fixedLeftWidth = Number.isFinite(leftParam) && leftParam > 0 ? leftParam : 250;
|
|
5063
|
+
const fixedRightWidth = Number.isFinite(rightParam) && rightParam > 0 ? rightParam : 250;
|
|
5064
|
+
const backgroundKey = String(node.style.background || "").trim();
|
|
5065
|
+
const showBorder = this.parseBooleanProp(node.params.border, false);
|
|
5066
|
+
if (backgroundKey) {
|
|
5067
|
+
const fill = this.colorResolver.resolveColor(backgroundKey, this.renderTheme.cardBg);
|
|
5068
|
+
if (hasRight) {
|
|
5069
|
+
const panelX = pos.x + Math.max(0, pos.width - fixedRightWidth);
|
|
5070
|
+
output.push(`<g>
|
|
5071
|
+
<rect x="${panelX}" y="${pos.y}" width="${Math.max(1, fixedRightWidth)}" height="${pos.height}" fill="${fill}" stroke="none"/>
|
|
5072
|
+
</g>`);
|
|
5073
|
+
} else if (hasLeft || !hasRight) {
|
|
5074
|
+
output.push(`<g>
|
|
5075
|
+
<rect x="${pos.x}" y="${pos.y}" width="${Math.max(1, fixedLeftWidth)}" height="${pos.height}" fill="${fill}" stroke="none"/>
|
|
5076
|
+
</g>`);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
if (showBorder) {
|
|
5080
|
+
const dividerX = hasRight ? pos.x + Math.max(0, pos.width - fixedRightWidth - gap / 2) : pos.x + Math.max(0, fixedLeftWidth + gap / 2);
|
|
5081
|
+
output.push(`<g>
|
|
5082
|
+
<line x1="${dividerX}" y1="${pos.y}" x2="${dividerX}" y2="${pos.y + pos.height}" stroke="${this.renderTheme.border}" stroke-width="1"/>
|
|
5083
|
+
</g>`);
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
4441
5086
|
renderTable(node, pos) {
|
|
4442
5087
|
const title = String(node.props.title || "");
|
|
4443
5088
|
const columnsStr = String(node.props.columns || "Col1,Col2,Col3");
|
|
4444
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
5089
|
+
const columns = columnsStr.split(",").map((c) => c.trim()).filter(Boolean);
|
|
4445
5090
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
4446
5091
|
const mockStr = String(node.props.mock || "");
|
|
4447
5092
|
const random = this.parseBooleanProp(node.props.random, false);
|
|
4448
|
-
const
|
|
4449
|
-
const
|
|
4450
|
-
const pageCount = Number(
|
|
5093
|
+
const pagination = this.parseBooleanProp(node.props.pagination, false);
|
|
5094
|
+
const parsedPageCount = Number(node.props.pages || 5);
|
|
5095
|
+
const pageCount = Number.isFinite(parsedPageCount) && parsedPageCount > 0 ? Math.floor(parsedPageCount) : 5;
|
|
4451
5096
|
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
5097
|
+
const actions = String(node.props.actions || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
5098
|
+
const hasActions = actions.length > 0;
|
|
5099
|
+
const caption = String(node.props.caption || "").trim();
|
|
5100
|
+
const hasCaption = caption.length > 0;
|
|
5101
|
+
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
5102
|
+
const showOuterBackground = this.parseBooleanProp(
|
|
5103
|
+
node.props.background ?? node.props.backround,
|
|
5104
|
+
false
|
|
5105
|
+
);
|
|
5106
|
+
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
5107
|
+
const rawCaptionAlign = String(node.props.captionAlign || "");
|
|
5108
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
5109
|
+
const sameFooterAlign = hasCaption && pagination && captionAlign === paginationAlign;
|
|
4452
5110
|
const mockTypes = mockStr ? mockStr.split(",").map((m) => m.trim()).filter(Boolean) : [];
|
|
4453
|
-
|
|
4454
|
-
|
|
5111
|
+
const safeColumns = columns.length > 0 ? columns : ["Column"];
|
|
5112
|
+
while (mockTypes.length < safeColumns.length) {
|
|
5113
|
+
const inferred = MockDataGenerator.inferMockTypeFromColumn(safeColumns[mockTypes.length] || "item");
|
|
4455
5114
|
mockTypes.push(inferred);
|
|
4456
5115
|
}
|
|
4457
5116
|
const headerHeight = 44;
|
|
4458
5117
|
const rowHeight = 36;
|
|
4459
|
-
const
|
|
5118
|
+
const actionColumnWidth = hasActions ? Math.max(96, Math.min(180, actions.length * 26 + 28)) : 0;
|
|
5119
|
+
const dataWidth = Math.max(20, pos.width - actionColumnWidth);
|
|
5120
|
+
const dataColWidth = dataWidth / safeColumns.length;
|
|
4460
5121
|
const mockRows = [];
|
|
4461
5122
|
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
4462
5123
|
const row = {};
|
|
4463
|
-
|
|
5124
|
+
safeColumns.forEach((col, colIdx) => {
|
|
4464
5125
|
const mockType = mockTypes[colIdx] || MockDataGenerator.inferMockTypeFromColumn(col) || "item";
|
|
4465
5126
|
row[col] = MockDataGenerator.getMockValue(mockType, rowIdx, random);
|
|
4466
5127
|
});
|
|
4467
5128
|
mockRows.push(row);
|
|
4468
5129
|
}
|
|
4469
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
5130
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5131
|
+
if (showOuterBorder || showOuterBackground) {
|
|
5132
|
+
const outerFill = showOuterBackground ? this.renderTheme.cardBg : "none";
|
|
5133
|
+
const outerStroke = showOuterBorder ? this.renderTheme.border : "none";
|
|
5134
|
+
svg += `
|
|
4470
5135
|
<rect x="${pos.x}" y="${pos.y}"
|
|
4471
5136
|
width="${pos.width}" height="${pos.height}"
|
|
4472
5137
|
rx="8"
|
|
4473
|
-
fill="${
|
|
4474
|
-
stroke="${
|
|
5138
|
+
fill="${outerFill}"
|
|
5139
|
+
stroke="${outerStroke}"
|
|
4475
5140
|
stroke-width="1"/>`;
|
|
5141
|
+
}
|
|
4476
5142
|
if (title) {
|
|
4477
5143
|
svg += `
|
|
4478
5144
|
<text x="${pos.x + 16}" y="${pos.y + 24}"
|
|
4479
|
-
font-family="
|
|
5145
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4480
5146
|
font-size="13"
|
|
4481
5147
|
font-weight="600"
|
|
4482
5148
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>`;
|
|
4483
5149
|
}
|
|
4484
5150
|
const headerY = pos.y + (title ? 32 : 0);
|
|
4485
|
-
|
|
5151
|
+
if (showInnerBorder) {
|
|
5152
|
+
svg += `
|
|
4486
5153
|
<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
4487
5154
|
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
4488
|
-
|
|
5155
|
+
}
|
|
5156
|
+
safeColumns.forEach((col, i) => {
|
|
4489
5157
|
svg += `
|
|
4490
|
-
<text x="${pos.x + i *
|
|
4491
|
-
font-family="
|
|
5158
|
+
<text x="${pos.x + i * dataColWidth + 12}" y="${headerY + 26}"
|
|
5159
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4492
5160
|
font-size="11"
|
|
4493
5161
|
font-weight="600"
|
|
4494
5162
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(col)}</text>`;
|
|
4495
5163
|
});
|
|
5164
|
+
if (hasActions && showInnerBorder) {
|
|
5165
|
+
const dividerX = pos.x + dataWidth;
|
|
5166
|
+
svg += `
|
|
5167
|
+
<line x1="${dividerX}" y1="${headerY + headerHeight}" x2="${dividerX}" y2="${headerY + headerHeight + mockRows.length * rowHeight}"
|
|
5168
|
+
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
5169
|
+
}
|
|
4496
5170
|
mockRows.forEach((row, rowIdx) => {
|
|
4497
5171
|
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
4498
|
-
|
|
5172
|
+
if (showInnerBorder) {
|
|
5173
|
+
svg += `
|
|
4499
5174
|
<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
4500
5175
|
stroke="${this.renderTheme.border}" stroke-width="0.5"/>`;
|
|
4501
|
-
|
|
5176
|
+
}
|
|
5177
|
+
safeColumns.forEach((col, colIdx) => {
|
|
4502
5178
|
const cellValue = row[col] || "";
|
|
4503
5179
|
svg += `
|
|
4504
|
-
<text x="${pos.x + colIdx *
|
|
4505
|
-
font-family="
|
|
5180
|
+
<text x="${pos.x + colIdx * dataColWidth + 12}" y="${rowY + 22}"
|
|
5181
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4506
5182
|
font-size="12"
|
|
4507
5183
|
fill="${this.renderTheme.text}">${this.escapeXml(cellValue)}</text>`;
|
|
4508
5184
|
});
|
|
5185
|
+
if (hasActions) {
|
|
5186
|
+
const iconSize = 14;
|
|
5187
|
+
const buttonSize = 22;
|
|
5188
|
+
const buttonGap = 6;
|
|
5189
|
+
const actionsWidth = actions.length * buttonSize + Math.max(0, actions.length - 1) * buttonGap;
|
|
5190
|
+
let currentX = pos.x + pos.width - 12 - actionsWidth;
|
|
5191
|
+
const buttonY = rowY + (rowHeight - buttonSize) / 2;
|
|
5192
|
+
actions.forEach((actionIcon) => {
|
|
5193
|
+
const iconSvg = getIcon(actionIcon);
|
|
5194
|
+
const iconX = currentX + (buttonSize - iconSize) / 2;
|
|
5195
|
+
const iconY = buttonY + (buttonSize - iconSize) / 2;
|
|
5196
|
+
svg += `
|
|
5197
|
+
<rect x="${currentX}" y="${buttonY}" width="${buttonSize}" height="${buttonSize}" rx="4"
|
|
5198
|
+
fill="${this.renderTheme.cardBg}" stroke="${showInnerBorder ? this.renderTheme.border : "none"}" stroke-width="1"/>`;
|
|
5199
|
+
if (iconSvg) {
|
|
5200
|
+
svg += `
|
|
5201
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5202
|
+
<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">
|
|
5203
|
+
${this.extractSvgContent(iconSvg)}
|
|
5204
|
+
</svg>
|
|
5205
|
+
</g>`;
|
|
5206
|
+
}
|
|
5207
|
+
currentX += buttonSize + buttonGap;
|
|
5208
|
+
});
|
|
5209
|
+
}
|
|
4509
5210
|
});
|
|
5211
|
+
const footerTop = headerY + headerHeight + mockRows.length * rowHeight + 16;
|
|
4510
5212
|
if (pagination) {
|
|
4511
|
-
const paginationY =
|
|
5213
|
+
const paginationY = sameFooterAlign ? footerTop + 18 + 8 : footerTop;
|
|
4512
5214
|
const buttonWidth = 40;
|
|
4513
5215
|
const buttonHeight = 32;
|
|
4514
5216
|
const gap = 8;
|
|
@@ -4521,18 +5223,25 @@ var SVGRenderer = class {
|
|
|
4521
5223
|
} else {
|
|
4522
5224
|
startX = pos.x + pos.width - totalWidth - 16;
|
|
4523
5225
|
}
|
|
5226
|
+
const previousIcon = getIcon("chevron-left");
|
|
4524
5227
|
svg += `
|
|
4525
5228
|
<rect x="${startX}" y="${paginationY}"
|
|
4526
5229
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4527
5230
|
rx="4"
|
|
4528
5231
|
fill="${this.renderTheme.cardBg}"
|
|
4529
5232
|
stroke="${this.renderTheme.border}"
|
|
4530
|
-
stroke-width="1"
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
5233
|
+
stroke-width="1"/>`;
|
|
5234
|
+
if (previousIcon) {
|
|
5235
|
+
const iconSize = 14;
|
|
5236
|
+
const iconX = startX + (buttonWidth - iconSize) / 2;
|
|
5237
|
+
const iconY = paginationY + (buttonHeight - iconSize) / 2;
|
|
5238
|
+
svg += `
|
|
5239
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5240
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.resolveTextColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5241
|
+
${this.extractSvgContent(previousIcon)}
|
|
5242
|
+
</svg>
|
|
5243
|
+
</g>`;
|
|
5244
|
+
}
|
|
4536
5245
|
for (let i = 1; i <= pageCount; i++) {
|
|
4537
5246
|
const btnX = startX + (buttonWidth + gap) * i;
|
|
4538
5247
|
const isActive = i === 1;
|
|
@@ -4546,24 +5255,49 @@ var SVGRenderer = class {
|
|
|
4546
5255
|
stroke="${this.renderTheme.border}"
|
|
4547
5256
|
stroke-width="1"/>
|
|
4548
5257
|
<text x="${btnX + buttonWidth / 2}" y="${paginationY + buttonHeight / 2 + 4}"
|
|
4549
|
-
font-family="
|
|
5258
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4550
5259
|
font-size="14"
|
|
4551
5260
|
fill="${textColor}"
|
|
4552
5261
|
text-anchor="middle">${i}</text>`;
|
|
4553
5262
|
}
|
|
4554
5263
|
const nextX = startX + (buttonWidth + gap) * (pageCount + 1);
|
|
5264
|
+
const nextIcon = getIcon("chevron-right");
|
|
4555
5265
|
svg += `
|
|
4556
5266
|
<rect x="${nextX}" y="${paginationY}"
|
|
4557
5267
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4558
5268
|
rx="4"
|
|
4559
5269
|
fill="${this.renderTheme.cardBg}"
|
|
4560
5270
|
stroke="${this.renderTheme.border}"
|
|
4561
|
-
stroke-width="1"
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
5271
|
+
stroke-width="1"/>`;
|
|
5272
|
+
if (nextIcon) {
|
|
5273
|
+
const iconSize = 14;
|
|
5274
|
+
const iconX = nextX + (buttonWidth - iconSize) / 2;
|
|
5275
|
+
const iconY = paginationY + (buttonHeight - iconSize) / 2;
|
|
5276
|
+
svg += `
|
|
5277
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5278
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.resolveTextColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5279
|
+
${this.extractSvgContent(nextIcon)}
|
|
5280
|
+
</svg>
|
|
5281
|
+
</g>`;
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
if (hasCaption) {
|
|
5285
|
+
const captionY = sameFooterAlign ? footerTop + 12 : footerTop + (pagination ? 21 : 12);
|
|
5286
|
+
let captionX = pos.x + 16;
|
|
5287
|
+
let textAnchor = "start";
|
|
5288
|
+
if (captionAlign === "center") {
|
|
5289
|
+
captionX = pos.x + pos.width / 2;
|
|
5290
|
+
textAnchor = "middle";
|
|
5291
|
+
} else if (captionAlign === "right") {
|
|
5292
|
+
captionX = pos.x + pos.width - 16;
|
|
5293
|
+
textAnchor = "end";
|
|
5294
|
+
}
|
|
5295
|
+
svg += `
|
|
5296
|
+
<text x="${captionX}" y="${captionY}"
|
|
5297
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5298
|
+
font-size="12"
|
|
5299
|
+
fill="${this.hexToRgba(this.resolveTextColor(), 0.75)}"
|
|
5300
|
+
text-anchor="${textAnchor}">${this.escapeXml(caption)}</text>`;
|
|
4567
5301
|
}
|
|
4568
5302
|
svg += "\n </g>";
|
|
4569
5303
|
return svg;
|
|
@@ -4678,7 +5412,7 @@ var SVGRenderer = class {
|
|
|
4678
5412
|
// TEXT/CONTENT COMPONENTS
|
|
4679
5413
|
// ============================================================================
|
|
4680
5414
|
renderText(node, pos) {
|
|
4681
|
-
const text = String(node.props.
|
|
5415
|
+
const text = String(node.props.text || "Text content");
|
|
4682
5416
|
const fontSize = this.tokens.text.fontSize;
|
|
4683
5417
|
const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
|
|
4684
5418
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
@@ -4688,7 +5422,7 @@ var SVGRenderer = class {
|
|
|
4688
5422
|
).join("");
|
|
4689
5423
|
return `<g${this.getDataNodeId(node)}>
|
|
4690
5424
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4691
|
-
font-family="
|
|
5425
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4692
5426
|
font-size="${fontSize}"
|
|
4693
5427
|
fill="${this.renderTheme.text}">${tspans}</text>
|
|
4694
5428
|
</g>`;
|
|
@@ -4697,7 +5431,7 @@ var SVGRenderer = class {
|
|
|
4697
5431
|
const text = String(node.props.text || "Label");
|
|
4698
5432
|
return `<g${this.getDataNodeId(node)}>
|
|
4699
5433
|
<text x="${pos.x}" y="${pos.y + 12}"
|
|
4700
|
-
font-family="
|
|
5434
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4701
5435
|
font-size="12"
|
|
4702
5436
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
|
|
4703
5437
|
</g>`;
|
|
@@ -4731,7 +5465,7 @@ var SVGRenderer = class {
|
|
|
4731
5465
|
const placeholderY = controlY + fontSize + 6;
|
|
4732
5466
|
return `<g${this.getDataNodeId(node)}>
|
|
4733
5467
|
${label ? `<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4734
|
-
font-family="
|
|
5468
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4735
5469
|
font-size="12"
|
|
4736
5470
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4737
5471
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4741,7 +5475,7 @@ var SVGRenderer = class {
|
|
|
4741
5475
|
stroke="${this.renderTheme.border}"
|
|
4742
5476
|
stroke-width="1"/>
|
|
4743
5477
|
<text x="${pos.x + paddingX}" y="${placeholderY}"
|
|
4744
|
-
font-family="
|
|
5478
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4745
5479
|
font-size="${fontSize}"
|
|
4746
5480
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4747
5481
|
</g>`;
|
|
@@ -4755,7 +5489,7 @@ var SVGRenderer = class {
|
|
|
4755
5489
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
4756
5490
|
return `<g${this.getDataNodeId(node)}>
|
|
4757
5491
|
${label ? `<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4758
|
-
font-family="
|
|
5492
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4759
5493
|
font-size="12"
|
|
4760
5494
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4761
5495
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4765,11 +5499,11 @@ var SVGRenderer = class {
|
|
|
4765
5499
|
stroke="${this.renderTheme.border}"
|
|
4766
5500
|
stroke-width="1"/>
|
|
4767
5501
|
<text x="${pos.x + 12}" y="${centerY}"
|
|
4768
|
-
font-family="
|
|
5502
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4769
5503
|
font-size="14"
|
|
4770
5504
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4771
5505
|
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
4772
|
-
font-family="
|
|
5506
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4773
5507
|
font-size="16"
|
|
4774
5508
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
4775
5509
|
</g>`;
|
|
@@ -4788,12 +5522,12 @@ var SVGRenderer = class {
|
|
|
4788
5522
|
stroke="${this.renderTheme.border}"
|
|
4789
5523
|
stroke-width="1"/>
|
|
4790
5524
|
${checked ? `<text x="${pos.x + checkboxSize / 2}" y="${checkboxY + 14}"
|
|
4791
|
-
font-family="
|
|
5525
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4792
5526
|
font-size="12"
|
|
4793
5527
|
fill="white"
|
|
4794
5528
|
text-anchor="middle">\u2713</text>` : ""}
|
|
4795
5529
|
<text x="${pos.x + checkboxSize + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4796
|
-
font-family="
|
|
5530
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4797
5531
|
font-size="14"
|
|
4798
5532
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4799
5533
|
</g>`;
|
|
@@ -4814,7 +5548,7 @@ var SVGRenderer = class {
|
|
|
4814
5548
|
r="${radioSize / 3.5}"
|
|
4815
5549
|
fill="${controlColor}"/>` : ""}
|
|
4816
5550
|
<text x="${pos.x + radioSize + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4817
|
-
font-family="
|
|
5551
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4818
5552
|
font-size="14"
|
|
4819
5553
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4820
5554
|
</g>`;
|
|
@@ -4836,7 +5570,7 @@ var SVGRenderer = class {
|
|
|
4836
5570
|
r="8"
|
|
4837
5571
|
fill="white"/>
|
|
4838
5572
|
<text x="${pos.x + toggleWidth + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4839
|
-
font-family="
|
|
5573
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4840
5574
|
font-size="14"
|
|
4841
5575
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4842
5576
|
</g>`;
|
|
@@ -4866,7 +5600,7 @@ var SVGRenderer = class {
|
|
|
4866
5600
|
stroke-width="1"/>
|
|
4867
5601
|
<!-- Title -->
|
|
4868
5602
|
<text x="${pos.x + padding}" y="${pos.y + padding + 8}"
|
|
4869
|
-
font-family="
|
|
5603
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4870
5604
|
font-size="14"
|
|
4871
5605
|
font-weight="600"
|
|
4872
5606
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
@@ -4882,7 +5616,7 @@ var SVGRenderer = class {
|
|
|
4882
5616
|
fill="${isActive ? this.renderTheme.primary : "transparent"}"
|
|
4883
5617
|
stroke="none"/>
|
|
4884
5618
|
<text x="${pos.x + 16}" y="${itemY + 22}"
|
|
4885
|
-
font-family="
|
|
5619
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4886
5620
|
font-size="13"
|
|
4887
5621
|
fill="${isActive ? "white" : this.renderTheme.textMuted}">${this.escapeXml(item)}</text>`;
|
|
4888
5622
|
});
|
|
@@ -4908,7 +5642,7 @@ var SVGRenderer = class {
|
|
|
4908
5642
|
stroke="${isActive ? "none" : this.renderTheme.border}"
|
|
4909
5643
|
stroke-width="1"/>
|
|
4910
5644
|
<text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
|
|
4911
|
-
font-family="
|
|
5645
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4912
5646
|
font-size="13"
|
|
4913
5647
|
font-weight="${isActive ? "600" : "500"}"
|
|
4914
5648
|
fill="${isActive ? "white" : this.renderTheme.text}"
|
|
@@ -4971,12 +5705,12 @@ var SVGRenderer = class {
|
|
|
4971
5705
|
rx="6"
|
|
4972
5706
|
fill="${bgColor}"/>
|
|
4973
5707
|
${hasTitle ? `<text x="${contentX}" y="${titleStartY}"
|
|
4974
|
-
font-family="
|
|
5708
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4975
5709
|
font-size="${fontSize}"
|
|
4976
5710
|
font-weight="700"
|
|
4977
5711
|
fill="${bgColor}">${titleTspans}</text>` : ""}
|
|
4978
5712
|
<text x="${contentX}" y="${textStartY}"
|
|
4979
|
-
font-family="
|
|
5713
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4980
5714
|
font-size="${fontSize}"
|
|
4981
5715
|
fill="${bgColor}">${textTspans}</text>
|
|
4982
5716
|
</g>`;
|
|
@@ -4997,7 +5731,7 @@ var SVGRenderer = class {
|
|
|
4997
5731
|
fill="${bgColor}"
|
|
4998
5732
|
stroke="none"/>
|
|
4999
5733
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
5000
|
-
font-family="
|
|
5734
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5001
5735
|
font-size="${fontSize}"
|
|
5002
5736
|
font-weight="600"
|
|
5003
5737
|
fill="${textColor}"
|
|
@@ -5036,20 +5770,20 @@ var SVGRenderer = class {
|
|
|
5036
5770
|
stroke-width="1"/>
|
|
5037
5771
|
|
|
5038
5772
|
<text x="${modalX + padding}" y="${modalY + padding + 16}"
|
|
5039
|
-
font-family="
|
|
5773
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5040
5774
|
font-size="16"
|
|
5041
5775
|
font-weight="600"
|
|
5042
5776
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
5043
5777
|
|
|
5044
5778
|
<!-- Close button -->
|
|
5045
5779
|
<text x="${modalX + pos.width - 16}" y="${modalY + padding + 12}"
|
|
5046
|
-
font-family="
|
|
5780
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5047
5781
|
font-size="18"
|
|
5048
5782
|
fill="${this.renderTheme.textMuted}">\u2715</text>
|
|
5049
5783
|
|
|
5050
5784
|
<!-- Content placeholder -->
|
|
5051
5785
|
<text x="${modalX + pos.width / 2}" y="${modalY + headerHeight + (pos.height - headerHeight) / 2}"
|
|
5052
|
-
font-family="
|
|
5786
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5053
5787
|
font-size="13"
|
|
5054
5788
|
fill="${this.renderTheme.textMuted}"
|
|
5055
5789
|
text-anchor="middle">Modal content</text>
|
|
@@ -5082,7 +5816,7 @@ var SVGRenderer = class {
|
|
|
5082
5816
|
if (title) {
|
|
5083
5817
|
svg += `
|
|
5084
5818
|
<text x="${pos.x + padding}" y="${pos.y + 26}"
|
|
5085
|
-
font-family="
|
|
5819
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5086
5820
|
font-size="13"
|
|
5087
5821
|
font-weight="600"
|
|
5088
5822
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
@@ -5098,7 +5832,7 @@ var SVGRenderer = class {
|
|
|
5098
5832
|
stroke="${this.renderTheme.border}"
|
|
5099
5833
|
stroke-width="0.5"/>
|
|
5100
5834
|
<text x="${pos.x + padding}" y="${itemY + 24}"
|
|
5101
|
-
font-family="
|
|
5835
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5102
5836
|
font-size="13"
|
|
5103
5837
|
fill="${this.renderTheme.text}">${this.escapeXml(item)}</text>`;
|
|
5104
5838
|
}
|
|
@@ -5116,7 +5850,7 @@ var SVGRenderer = class {
|
|
|
5116
5850
|
stroke-width="1"
|
|
5117
5851
|
stroke-dasharray="4 4"/>
|
|
5118
5852
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
|
|
5119
|
-
font-family="
|
|
5853
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5120
5854
|
font-size="12"
|
|
5121
5855
|
fill="${this.renderTheme.textMuted}"
|
|
5122
5856
|
text-anchor="middle">${node.componentType}</text>
|
|
@@ -5164,14 +5898,14 @@ var SVGRenderer = class {
|
|
|
5164
5898
|
|
|
5165
5899
|
<!-- Title -->
|
|
5166
5900
|
<text x="${innerX}" y="${titleY}"
|
|
5167
|
-
font-family="
|
|
5901
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5168
5902
|
font-size="${titleSize}"
|
|
5169
5903
|
font-weight="500"
|
|
5170
5904
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(visibleTitle)}</text>
|
|
5171
5905
|
|
|
5172
5906
|
<!-- Value (Large) -->
|
|
5173
5907
|
<text x="${innerX}" y="${valueY}"
|
|
5174
|
-
font-family="
|
|
5908
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5175
5909
|
font-size="${valueSize}"
|
|
5176
5910
|
font-weight="700"
|
|
5177
5911
|
fill="${accentColor}">${this.escapeXml(value)}</text>`;
|
|
@@ -5194,7 +5928,7 @@ var SVGRenderer = class {
|
|
|
5194
5928
|
svg += `
|
|
5195
5929
|
<!-- Caption -->
|
|
5196
5930
|
<text x="${innerX}" y="${captionY}"
|
|
5197
|
-
font-family="
|
|
5931
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5198
5932
|
font-size="${captionSize}"
|
|
5199
5933
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(visibleCaption)}</text>`;
|
|
5200
5934
|
}
|
|
@@ -5323,7 +6057,7 @@ var SVGRenderer = class {
|
|
|
5323
6057
|
const fontWeight = isLast ? "500" : "400";
|
|
5324
6058
|
svg += `
|
|
5325
6059
|
<text x="${currentX}" y="${pos.y + pos.height / 2 + 4}"
|
|
5326
|
-
font-family="
|
|
6060
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5327
6061
|
font-size="${fontSize}"
|
|
5328
6062
|
font-weight="${fontWeight}"
|
|
5329
6063
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
@@ -5332,7 +6066,7 @@ var SVGRenderer = class {
|
|
|
5332
6066
|
if (!isLast) {
|
|
5333
6067
|
svg += `
|
|
5334
6068
|
<text x="${currentX + 4}" y="${pos.y + pos.height / 2 + 4}"
|
|
5335
|
-
font-family="
|
|
6069
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5336
6070
|
font-size="${fontSize}"
|
|
5337
6071
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(separator)}</text>`;
|
|
5338
6072
|
currentX += separatorWidth;
|
|
@@ -5355,7 +6089,7 @@ var SVGRenderer = class {
|
|
|
5355
6089
|
const itemY = pos.y + index * itemHeight;
|
|
5356
6090
|
const isActive = index === activeIndex;
|
|
5357
6091
|
const bgColor = isActive ? this.hexToRgba(accentColor, 0.15) : "transparent";
|
|
5358
|
-
const textColor = isActive ? this.hexToRgba(accentColor, 0.9) :
|
|
6092
|
+
const textColor = isActive ? this.hexToRgba(accentColor, 0.9) : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
5359
6093
|
const fontWeight = isActive ? "500" : "400";
|
|
5360
6094
|
if (isActive) {
|
|
5361
6095
|
svg += `
|
|
@@ -5372,7 +6106,7 @@ var SVGRenderer = class {
|
|
|
5372
6106
|
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
5373
6107
|
svg += `
|
|
5374
6108
|
<g transform="translate(${currentX}, ${iconY})">
|
|
5375
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
6109
|
+
<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">
|
|
5376
6110
|
${this.extractSvgContent(iconSvg)}
|
|
5377
6111
|
</svg>
|
|
5378
6112
|
</g>`;
|
|
@@ -5381,7 +6115,7 @@ var SVGRenderer = class {
|
|
|
5381
6115
|
}
|
|
5382
6116
|
svg += `
|
|
5383
6117
|
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
5384
|
-
font-family="
|
|
6118
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5385
6119
|
font-size="${fontSize}"
|
|
5386
6120
|
font-weight="${fontWeight}"
|
|
5387
6121
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
@@ -5390,18 +6124,19 @@ var SVGRenderer = class {
|
|
|
5390
6124
|
return svg;
|
|
5391
6125
|
}
|
|
5392
6126
|
renderIcon(node, pos) {
|
|
5393
|
-
const iconType = String(node.props.
|
|
6127
|
+
const iconType = String(node.props.icon || "help-circle");
|
|
5394
6128
|
const size = String(node.props.size || "md");
|
|
6129
|
+
const variant = String(node.props.variant || "default");
|
|
5395
6130
|
const iconSvg = getIcon(iconType);
|
|
6131
|
+
const iconColor = variant === "default" ? this.hexToRgba(this.resolveTextColor(), 0.75) : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
5396
6132
|
if (!iconSvg) {
|
|
5397
6133
|
return `<g${this.getDataNodeId(node)}>
|
|
5398
6134
|
<!-- Icon not found: ${iconType} -->
|
|
5399
|
-
<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="
|
|
5400
|
-
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}" font-family="
|
|
6135
|
+
<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"/>
|
|
6136
|
+
<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>
|
|
5401
6137
|
</g>`;
|
|
5402
6138
|
}
|
|
5403
6139
|
const iconSize = this.getIconSize(size);
|
|
5404
|
-
const iconColor = "rgba(30, 41, 59, 0.75)";
|
|
5405
6140
|
const offsetX = pos.x + (pos.width - iconSize) / 2;
|
|
5406
6141
|
const offsetY = pos.y + (pos.height - iconSize) / 2;
|
|
5407
6142
|
const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
|
|
@@ -5416,23 +6151,31 @@ var SVGRenderer = class {
|
|
|
5416
6151
|
const variant = String(node.props.variant || "default");
|
|
5417
6152
|
const size = String(node.props.size || "md");
|
|
5418
6153
|
const disabled = String(node.props.disabled || "false") === "true";
|
|
6154
|
+
const density = this.ir.project.style.density || "normal";
|
|
6155
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
6156
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
5419
6157
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5420
6158
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
5421
6159
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5422
6160
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
5423
|
-
const iconColor = hasExplicitVariantColor ? "#FFFFFF" :
|
|
6161
|
+
const iconColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
5424
6162
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
5425
6163
|
const opacity = disabled ? "0.5" : "1";
|
|
5426
6164
|
const iconSvg = getIcon(iconName);
|
|
5427
|
-
const buttonSize =
|
|
6165
|
+
const buttonSize = Math.max(
|
|
6166
|
+
16,
|
|
6167
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
6168
|
+
);
|
|
6169
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
5428
6170
|
const radius = 6;
|
|
6171
|
+
const buttonY = pos.y + labelOffset;
|
|
5429
6172
|
let svg = `<g${this.getDataNodeId(node)} opacity="${opacity}">
|
|
5430
6173
|
<!-- IconButton background -->
|
|
5431
|
-
<rect x="${pos.x}" y="${
|
|
6174
|
+
<rect x="${pos.x}" y="${buttonY}" width="${buttonWidth}" height="${buttonSize}" rx="${radius}" fill="${bgColor}" stroke="${borderColor}" stroke-width="1"/>`;
|
|
5432
6175
|
if (iconSvg) {
|
|
5433
6176
|
const iconSize = buttonSize * 0.6;
|
|
5434
|
-
const offsetX = pos.x + (
|
|
5435
|
-
const offsetY =
|
|
6177
|
+
const offsetX = pos.x + (buttonWidth - iconSize) / 2;
|
|
6178
|
+
const offsetY = buttonY + (buttonSize - iconSize) / 2;
|
|
5436
6179
|
svg += `
|
|
5437
6180
|
<!-- Icon -->
|
|
5438
6181
|
<g transform="translate(${offsetX}, ${offsetY})">
|
|
@@ -5465,6 +6208,14 @@ var SVGRenderer = class {
|
|
|
5465
6208
|
resolveChartColor() {
|
|
5466
6209
|
return this.colorResolver.resolveColor("chart", this.renderTheme.primary);
|
|
5467
6210
|
}
|
|
6211
|
+
resolveTextColor() {
|
|
6212
|
+
const fallback = this.options.theme === "dark" ? "#FFFFFF" : "#000000";
|
|
6213
|
+
return this.colorResolver.resolveColor("text", fallback);
|
|
6214
|
+
}
|
|
6215
|
+
resolveMutedColor() {
|
|
6216
|
+
const fallback = this.options.theme === "dark" ? "#94A3B8" : "#64748B";
|
|
6217
|
+
return this.colorResolver.resolveColor("muted", fallback);
|
|
6218
|
+
}
|
|
5468
6219
|
getSemanticVariantColor(variant) {
|
|
5469
6220
|
const semantic = {
|
|
5470
6221
|
primary: this.renderTheme.primary,
|
|
@@ -5791,21 +6542,28 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5791
6542
|
renderButton(node, pos) {
|
|
5792
6543
|
const text = String(node.props.text || "Button");
|
|
5793
6544
|
const variant = String(node.props.variant || "default");
|
|
6545
|
+
const size = String(node.props.size || "md");
|
|
6546
|
+
const density = this.ir.project.style.density || "normal";
|
|
6547
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6548
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
5794
6549
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5795
6550
|
const radius = this.tokens.button.radius;
|
|
5796
6551
|
const fontSize = this.tokens.button.fontSize;
|
|
5797
6552
|
const paddingX = this.tokens.button.paddingX;
|
|
5798
|
-
const
|
|
6553
|
+
const buttonHeight = Math.max(
|
|
6554
|
+
16,
|
|
6555
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
6556
|
+
);
|
|
6557
|
+
const buttonY = pos.y + labelOffset;
|
|
5799
6558
|
const textWidth = text.length * fontSize * 0.6;
|
|
5800
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5801
|
-
const buttonHeight = fontSize + paddingY * 2;
|
|
6559
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + (paddingX + extraPadding) * 2, 60), pos.width);
|
|
5802
6560
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5803
6561
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
5804
6562
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5805
6563
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
5806
6564
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
5807
6565
|
return `<g${this.getDataNodeId(node)}>
|
|
5808
|
-
<rect x="${pos.x}" y="${
|
|
6566
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
5809
6567
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
5810
6568
|
rx="${radius}"
|
|
5811
6569
|
fill="${bgColor}"
|
|
@@ -5819,13 +6577,14 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5819
6577
|
renderLink(node, pos) {
|
|
5820
6578
|
const text = String(node.props.text || "Link");
|
|
5821
6579
|
const variant = String(node.props.variant || "primary");
|
|
6580
|
+
const size = String(node.props.size || "md");
|
|
6581
|
+
const density = this.ir.project.style.density || "normal";
|
|
5822
6582
|
const fontSize = this.tokens.button.fontSize;
|
|
5823
6583
|
const paddingX = this.tokens.button.paddingX;
|
|
5824
|
-
const paddingY = this.tokens.button.paddingY;
|
|
5825
6584
|
const linkColor = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5826
6585
|
const textWidth = this.estimateTextWidth(text, fontSize);
|
|
5827
6586
|
const linkWidth = this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5828
|
-
const linkHeight =
|
|
6587
|
+
const linkHeight = Math.max(16, Math.min(resolveActionControlHeight(size, density), pos.height));
|
|
5829
6588
|
const blockHeight = Math.max(8, Math.round(fontSize * 0.75));
|
|
5830
6589
|
const blockWidth = Math.max(28, Math.min(textWidth, linkWidth - paddingX * 2));
|
|
5831
6590
|
const blockX = pos.x + (linkWidth - blockWidth) / 2;
|
|
@@ -5842,17 +6601,70 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5842
6601
|
stroke-width="1"/>
|
|
5843
6602
|
</g>`;
|
|
5844
6603
|
}
|
|
6604
|
+
/**
|
|
6605
|
+
* Render breadcrumbs as skeleton blocks: <rect> / <rect> / <rect accent>
|
|
6606
|
+
*/
|
|
6607
|
+
renderBreadcrumbs(node, pos) {
|
|
6608
|
+
const itemsStr = String(node.props.items || "Home");
|
|
6609
|
+
const items = itemsStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6610
|
+
const separator = String(node.props.separator || "/");
|
|
6611
|
+
const blockColor = this.renderTheme.border;
|
|
6612
|
+
const charWidth = 6.2;
|
|
6613
|
+
const minBlockWidth = 28;
|
|
6614
|
+
const maxBlockWidth = Math.max(minBlockWidth, Math.floor(pos.width * 0.4));
|
|
6615
|
+
const blockHeight = 12;
|
|
6616
|
+
const blockY = pos.y + (pos.height - blockHeight) / 2;
|
|
6617
|
+
const blockRadius = 4;
|
|
6618
|
+
const blockPaddingX = 10;
|
|
6619
|
+
const itemSpacing = 8;
|
|
6620
|
+
const separatorWidth = 12;
|
|
6621
|
+
const contentRight = pos.x + pos.width;
|
|
6622
|
+
let currentX = pos.x;
|
|
6623
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6624
|
+
items.forEach((item, index) => {
|
|
6625
|
+
if (currentX >= contentRight) return;
|
|
6626
|
+
const isLast = index === items.length - 1;
|
|
6627
|
+
const estimatedTextWidth = item.length * charWidth;
|
|
6628
|
+
let blockWidth = Math.max(
|
|
6629
|
+
minBlockWidth,
|
|
6630
|
+
Math.min(maxBlockWidth, Math.ceil(estimatedTextWidth + blockPaddingX * 2))
|
|
6631
|
+
);
|
|
6632
|
+
blockWidth = Math.min(blockWidth, Math.max(0, contentRight - currentX));
|
|
6633
|
+
if (blockWidth < minBlockWidth) return;
|
|
6634
|
+
const fillColor = blockColor;
|
|
6635
|
+
svg += `
|
|
6636
|
+
<rect x="${currentX}" y="${blockY}"
|
|
6637
|
+
width="${blockWidth}" height="${blockHeight}"
|
|
6638
|
+
rx="${blockRadius}"
|
|
6639
|
+
fill="${fillColor}"
|
|
6640
|
+
stroke="none"/>`;
|
|
6641
|
+
currentX += blockWidth + itemSpacing;
|
|
6642
|
+
if (!isLast && currentX + separatorWidth <= contentRight) {
|
|
6643
|
+
svg += `
|
|
6644
|
+
<text x="${currentX + 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
6645
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
6646
|
+
font-size="12"
|
|
6647
|
+
fill="${blockColor}">${this.escapeXml(separator)}</text>`;
|
|
6648
|
+
currentX += separatorWidth;
|
|
6649
|
+
}
|
|
6650
|
+
});
|
|
6651
|
+
svg += "\n </g>";
|
|
6652
|
+
return svg;
|
|
6653
|
+
}
|
|
5845
6654
|
/**
|
|
5846
6655
|
* Render heading as gray block
|
|
5847
6656
|
*/
|
|
5848
6657
|
renderHeading(node, pos) {
|
|
5849
6658
|
const headingTypography = this.getHeadingTypography(node);
|
|
6659
|
+
const variant = String(node.props.variant || "default");
|
|
6660
|
+
const blockColor = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveTextColor()), 0.35);
|
|
5850
6661
|
return this.renderTextBlock(
|
|
5851
6662
|
node,
|
|
5852
6663
|
pos,
|
|
5853
6664
|
String(node.props.text || "Heading"),
|
|
5854
6665
|
headingTypography.fontSize,
|
|
5855
|
-
headingTypography.lineHeight
|
|
6666
|
+
headingTypography.lineHeight,
|
|
6667
|
+
blockColor
|
|
5856
6668
|
);
|
|
5857
6669
|
}
|
|
5858
6670
|
/**
|
|
@@ -5862,7 +6674,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5862
6674
|
return this.renderTextBlock(
|
|
5863
6675
|
node,
|
|
5864
6676
|
pos,
|
|
5865
|
-
String(node.props.
|
|
6677
|
+
String(node.props.text || "Text content"),
|
|
5866
6678
|
this.tokens.text.fontSize,
|
|
5867
6679
|
this.tokens.text.lineHeight
|
|
5868
6680
|
);
|
|
@@ -6103,18 +6915,42 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6103
6915
|
renderTable(node, pos) {
|
|
6104
6916
|
const title = String(node.props.title || "");
|
|
6105
6917
|
const columnsStr = String(node.props.columns || "Col1,Col2,Col3");
|
|
6106
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
6918
|
+
const columns = columnsStr.split(",").map((c) => c.trim()).filter(Boolean);
|
|
6107
6919
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
6920
|
+
const actions = String(node.props.actions || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
6921
|
+
const hasActions = actions.length > 0;
|
|
6922
|
+
const pagination = this.parseBooleanProp(node.props.pagination, false);
|
|
6923
|
+
const parsedPageCount = Number(node.props.pages || 5);
|
|
6924
|
+
const pageCount = Number.isFinite(parsedPageCount) && parsedPageCount > 0 ? Math.floor(parsedPageCount) : 5;
|
|
6925
|
+
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
6926
|
+
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
6927
|
+
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
6928
|
+
const showOuterBackground = this.parseBooleanProp(
|
|
6929
|
+
node.props.background ?? node.props.backround,
|
|
6930
|
+
false
|
|
6931
|
+
);
|
|
6932
|
+
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
6933
|
+
const rawCaptionAlign = String(node.props.captionAlign || "");
|
|
6934
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
6935
|
+
const sameFooterAlign = hasCaption && pagination && captionAlign === paginationAlign;
|
|
6936
|
+
const safeColumns = columns.length > 0 ? columns : ["Column"];
|
|
6108
6937
|
const headerHeight = 44;
|
|
6109
6938
|
const rowHeight = 36;
|
|
6110
|
-
const
|
|
6111
|
-
|
|
6939
|
+
const actionColumnWidth = hasActions ? Math.max(96, Math.min(180, actions.length * 26 + 28)) : 0;
|
|
6940
|
+
const dataWidth = Math.max(20, pos.width - actionColumnWidth);
|
|
6941
|
+
const colWidth = dataWidth / safeColumns.length;
|
|
6942
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6943
|
+
if (showOuterBorder || showOuterBackground) {
|
|
6944
|
+
const outerFill = showOuterBackground ? this.renderTheme.cardBg : "none";
|
|
6945
|
+
const outerStroke = showOuterBorder ? this.renderTheme.border : "none";
|
|
6946
|
+
svg += `
|
|
6112
6947
|
<rect x="${pos.x}" y="${pos.y}"
|
|
6113
6948
|
width="${pos.width}" height="${pos.height}"
|
|
6114
6949
|
rx="8"
|
|
6115
|
-
fill="${
|
|
6116
|
-
stroke="${
|
|
6950
|
+
fill="${outerFill}"
|
|
6951
|
+
stroke="${outerStroke}"
|
|
6117
6952
|
stroke-width="1"/>`;
|
|
6953
|
+
}
|
|
6118
6954
|
if (title) {
|
|
6119
6955
|
svg += `<rect x="${pos.x + 16}" y="${pos.y + 12}"
|
|
6120
6956
|
width="100" height="12"
|
|
@@ -6122,19 +6958,28 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6122
6958
|
fill="${this.renderTheme.border}"/>`;
|
|
6123
6959
|
}
|
|
6124
6960
|
const headerY = pos.y + (title ? 32 : 0);
|
|
6125
|
-
|
|
6961
|
+
if (showInnerBorder) {
|
|
6962
|
+
svg += `<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
6126
6963
|
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
6127
|
-
|
|
6964
|
+
}
|
|
6965
|
+
safeColumns.forEach((_, i) => {
|
|
6128
6966
|
svg += `<rect x="${pos.x + i * colWidth + 12}" y="${headerY + 16}"
|
|
6129
6967
|
width="50" height="10"
|
|
6130
6968
|
rx="4"
|
|
6131
6969
|
fill="${this.renderTheme.border}"/>`;
|
|
6132
6970
|
});
|
|
6971
|
+
if (hasActions && showInnerBorder) {
|
|
6972
|
+
const dividerX = pos.x + dataWidth;
|
|
6973
|
+
svg += `<line x1="${dividerX}" y1="${headerY + headerHeight}" x2="${dividerX}" y2="${headerY + headerHeight + rowCount * rowHeight}"
|
|
6974
|
+
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
6975
|
+
}
|
|
6133
6976
|
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
6134
6977
|
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
6135
|
-
|
|
6978
|
+
if (showInnerBorder) {
|
|
6979
|
+
svg += `<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
6136
6980
|
stroke="${this.renderTheme.border}" stroke-width="0.5"/>`;
|
|
6137
|
-
|
|
6981
|
+
}
|
|
6982
|
+
safeColumns.forEach((_, colIdx) => {
|
|
6138
6983
|
const variance = (rowIdx * 17 + colIdx * 11) % 5 * 10;
|
|
6139
6984
|
const blockWidth = Math.min(colWidth - 24, 60 + variance);
|
|
6140
6985
|
svg += `<rect x="${pos.x + colIdx * colWidth + 12}" y="${rowY + 12}"
|
|
@@ -6142,6 +6987,45 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6142
6987
|
rx="4"
|
|
6143
6988
|
fill="${this.renderTheme.border}"/>`;
|
|
6144
6989
|
});
|
|
6990
|
+
if (hasActions) {
|
|
6991
|
+
const iconSize = 14;
|
|
6992
|
+
const iconGap = 8;
|
|
6993
|
+
const actionsWidth = actions.length * iconSize + Math.max(0, actions.length - 1) * iconGap;
|
|
6994
|
+
let currentX = pos.x + pos.width - 12 - actionsWidth;
|
|
6995
|
+
const iconY = rowY + (rowHeight - iconSize) / 2;
|
|
6996
|
+
actions.forEach(() => {
|
|
6997
|
+
svg += `<rect x="${currentX}" y="${iconY}" width="${iconSize}" height="${iconSize}" rx="3" fill="${this.renderTheme.border}"/>`;
|
|
6998
|
+
currentX += iconSize + iconGap;
|
|
6999
|
+
});
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
const footerTop = headerY + headerHeight + rowCount * rowHeight + 16;
|
|
7003
|
+
if (hasCaption) {
|
|
7004
|
+
const captionY = sameFooterAlign ? footerTop : footerTop + (pagination ? 10 : 0);
|
|
7005
|
+
const captionWidth = Math.min(220, Math.max(90, pos.width * 0.34));
|
|
7006
|
+
let captionX = pos.x + 16;
|
|
7007
|
+
if (captionAlign === "center") {
|
|
7008
|
+
captionX = pos.x + (pos.width - captionWidth) / 2;
|
|
7009
|
+
} else if (captionAlign === "right") {
|
|
7010
|
+
captionX = pos.x + pos.width - 16 - captionWidth;
|
|
7011
|
+
}
|
|
7012
|
+
svg += `<rect x="${captionX}" y="${captionY}" width="${captionWidth}" height="10" rx="4" fill="${this.renderTheme.border}"/>`;
|
|
7013
|
+
}
|
|
7014
|
+
if (pagination) {
|
|
7015
|
+
const buttonWidth = 28;
|
|
7016
|
+
const buttonHeight = 24;
|
|
7017
|
+
const buttonGap = 8;
|
|
7018
|
+
const totalWidth = (pageCount + 2) * buttonWidth + (pageCount + 1) * buttonGap;
|
|
7019
|
+
const paginationY = sameFooterAlign ? footerTop + 18 : footerTop;
|
|
7020
|
+
let startX = pos.x + pos.width - totalWidth - 16;
|
|
7021
|
+
if (paginationAlign === "left") {
|
|
7022
|
+
startX = pos.x + 16;
|
|
7023
|
+
} else if (paginationAlign === "center") {
|
|
7024
|
+
startX = pos.x + (pos.width - totalWidth) / 2;
|
|
7025
|
+
}
|
|
7026
|
+
for (let i = 0; i < pageCount + 2; i++) {
|
|
7027
|
+
svg += `<rect x="${startX + i * (buttonWidth + buttonGap)}" y="${paginationY}" width="${buttonWidth}" height="${buttonHeight}" rx="4" fill="${this.renderTheme.border}"/>`;
|
|
7028
|
+
}
|
|
6145
7029
|
}
|
|
6146
7030
|
svg += "</g>";
|
|
6147
7031
|
return svg;
|
|
@@ -6154,21 +7038,39 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6154
7038
|
const subtitle = String(node.props.subtitle || "");
|
|
6155
7039
|
const actions = String(node.props.actions || "");
|
|
6156
7040
|
const user = String(node.props.user || "");
|
|
7041
|
+
const variant = String(node.props.variant || "default");
|
|
7042
|
+
const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
|
|
7043
|
+
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
7044
|
+
const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
|
|
7045
|
+
const radiusMap = {
|
|
7046
|
+
none: 0,
|
|
7047
|
+
sm: 4,
|
|
7048
|
+
md: this.tokens.card.radius,
|
|
7049
|
+
lg: 12,
|
|
7050
|
+
xl: 16
|
|
7051
|
+
};
|
|
7052
|
+
const topbarRadius = radiusMap[String(node.props.radius || "md")] ?? this.tokens.card.radius;
|
|
6157
7053
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
6158
7054
|
const titleWidth = Math.max(56, Math.min(topbar.titleMaxWidth * 0.55, topbar.titleMaxWidth));
|
|
6159
7055
|
const subtitleWidth = Math.max(48, Math.min(topbar.titleMaxWidth * 0.4, topbar.titleMaxWidth));
|
|
6160
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
7056
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7057
|
+
if (showBorder || showBackground) {
|
|
7058
|
+
const bg = showBackground ? this.renderTheme.cardBg : "none";
|
|
7059
|
+
const stroke = showBorder ? this.renderTheme.border : "none";
|
|
7060
|
+
svg += `
|
|
6161
7061
|
<rect x="${pos.x}" y="${pos.y}"
|
|
6162
7062
|
width="${pos.width}" height="${pos.height}"
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
stroke
|
|
7063
|
+
rx="${topbarRadius}"
|
|
7064
|
+
fill="${bg}"
|
|
7065
|
+
stroke="${stroke}"
|
|
7066
|
+
stroke-width="1"/>`;
|
|
7067
|
+
}
|
|
6166
7068
|
if (topbar.leftIcon) {
|
|
6167
7069
|
svg += `
|
|
6168
7070
|
<rect x="${topbar.leftIcon.badgeX}" y="${topbar.leftIcon.badgeY}"
|
|
6169
7071
|
width="${topbar.leftIcon.badgeSize}" height="${topbar.leftIcon.badgeSize}"
|
|
6170
7072
|
rx="${topbar.leftIcon.badgeRadius}"
|
|
6171
|
-
fill="${
|
|
7073
|
+
fill="${accentBlock}"/>`;
|
|
6172
7074
|
}
|
|
6173
7075
|
svg += `
|
|
6174
7076
|
<rect x="${topbar.textX}" y="${topbar.titleY - 12}"
|
|
@@ -6187,7 +7089,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6187
7089
|
<rect x="${action.x}" y="${action.y}"
|
|
6188
7090
|
width="${action.width}" height="${action.height}"
|
|
6189
7091
|
rx="6"
|
|
6190
|
-
fill="${
|
|
7092
|
+
fill="${accentBlock}"/>`;
|
|
6191
7093
|
});
|
|
6192
7094
|
if (topbar.userBadge) {
|
|
6193
7095
|
svg += `
|
|
@@ -6255,12 +7157,14 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6255
7157
|
*/
|
|
6256
7158
|
renderIcon(node, pos) {
|
|
6257
7159
|
const size = String(node.props.size || "md");
|
|
7160
|
+
const variant = String(node.props.variant || "default");
|
|
6258
7161
|
const iconSize = this.getIconSize(size);
|
|
7162
|
+
const blockColor = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveTextColor()), 0.35);
|
|
6259
7163
|
return `<g${this.getDataNodeId(node)}>
|
|
6260
7164
|
<rect x="${pos.x}" y="${pos.y + (pos.height - iconSize) / 2}"
|
|
6261
7165
|
width="${iconSize}" height="${iconSize}"
|
|
6262
7166
|
rx="2"
|
|
6263
|
-
fill="${
|
|
7167
|
+
fill="${blockColor}"/>
|
|
6264
7168
|
</g>`;
|
|
6265
7169
|
}
|
|
6266
7170
|
/**
|
|
@@ -6269,15 +7173,23 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6269
7173
|
renderIconButton(node, pos) {
|
|
6270
7174
|
const variant = String(node.props.variant || "default");
|
|
6271
7175
|
const size = String(node.props.size || "md");
|
|
7176
|
+
const density = this.ir.project.style.density || "normal";
|
|
7177
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7178
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6272
7179
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6273
7180
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6274
7181
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
6275
7182
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
6276
7183
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
6277
|
-
const buttonSize =
|
|
7184
|
+
const buttonSize = Math.max(
|
|
7185
|
+
16,
|
|
7186
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
7187
|
+
);
|
|
7188
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
7189
|
+
const buttonY = pos.y + labelOffset;
|
|
6278
7190
|
return `<g${this.getDataNodeId(node)}>
|
|
6279
|
-
<rect x="${pos.x}" y="${
|
|
6280
|
-
width="${
|
|
7191
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
7192
|
+
width="${buttonWidth}" height="${buttonSize}"
|
|
6281
7193
|
rx="6"
|
|
6282
7194
|
fill="${bgColor}"
|
|
6283
7195
|
stroke="${borderColor}"
|
|
@@ -6357,10 +7269,10 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6357
7269
|
/**
|
|
6358
7270
|
* Private helper: Render text as gray block
|
|
6359
7271
|
*/
|
|
6360
|
-
renderTextBlock(node, pos, text, fontSize, lineHeightMultiplier) {
|
|
7272
|
+
renderTextBlock(node, pos, text, fontSize, lineHeightMultiplier, color) {
|
|
6361
7273
|
const lineHeight = Math.ceil(fontSize * lineHeightMultiplier);
|
|
6362
7274
|
const blockHeight = Math.max(8, Math.round(fontSize * 0.75));
|
|
6363
|
-
const blockColor = this.renderTheme.border;
|
|
7275
|
+
const blockColor = color || this.renderTheme.border;
|
|
6364
7276
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
6365
7277
|
const contentHeight = lines.length * lineHeight;
|
|
6366
7278
|
const startY = pos.y + Math.max(0, (pos.height - contentHeight) / 2);
|
|
@@ -6442,32 +7354,39 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6442
7354
|
renderButton(node, pos) {
|
|
6443
7355
|
const text = String(node.props.text || "Button");
|
|
6444
7356
|
const variant = String(node.props.variant || "default");
|
|
7357
|
+
const size = String(node.props.size || "md");
|
|
7358
|
+
const density = this.ir.project.style.density || "normal";
|
|
7359
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
7360
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
6445
7361
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
6446
7362
|
const radius = this.tokens.button.radius;
|
|
6447
7363
|
const fontSize = this.tokens.button.fontSize;
|
|
6448
7364
|
const fontWeight = this.tokens.button.fontWeight;
|
|
6449
7365
|
const paddingX = this.tokens.button.paddingX;
|
|
6450
|
-
const
|
|
7366
|
+
const buttonHeight = Math.max(
|
|
7367
|
+
16,
|
|
7368
|
+
Math.min(resolveControlHeight(size, density), pos.height - labelOffset)
|
|
7369
|
+
);
|
|
7370
|
+
const buttonY = pos.y + labelOffset;
|
|
6451
7371
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
6452
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + paddingX * 2, 60), pos.width);
|
|
6453
|
-
const
|
|
6454
|
-
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
7372
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (paddingX + extraPadding) * 2, 60), pos.width);
|
|
7373
|
+
const availableTextWidth = Math.max(0, buttonWidth - (paddingX + extraPadding) * 2);
|
|
6455
7374
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
6456
7375
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6457
7376
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6458
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7377
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6459
7378
|
const borderColor = variantColor;
|
|
6460
7379
|
const textColor = variantColor;
|
|
6461
7380
|
const strokeWidth = 0.5;
|
|
6462
7381
|
return `<g${this.getDataNodeId(node)}>
|
|
6463
|
-
<rect x="${pos.x}" y="${
|
|
7382
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
6464
7383
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
6465
7384
|
rx="${radius}"
|
|
6466
7385
|
fill="none"
|
|
6467
7386
|
stroke="${borderColor}"
|
|
6468
7387
|
stroke-width="${strokeWidth}"
|
|
6469
7388
|
filter="url(#sketch-rough)"/>
|
|
6470
|
-
<text x="${pos.x + buttonWidth / 2}" y="${
|
|
7389
|
+
<text x="${pos.x + buttonWidth / 2}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
6471
7390
|
font-family="${this.fontFamily}"
|
|
6472
7391
|
font-size="${fontSize}"
|
|
6473
7392
|
font-weight="${fontWeight}"
|
|
@@ -6483,7 +7402,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6483
7402
|
const variant = String(node.props.variant || "default");
|
|
6484
7403
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6485
7404
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6486
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7405
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6487
7406
|
const borderColor = variantColor;
|
|
6488
7407
|
const textColor = variantColor;
|
|
6489
7408
|
const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
|
|
@@ -6510,17 +7429,22 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6510
7429
|
const iconName = String(node.props.icon || "help-circle");
|
|
6511
7430
|
const variant = String(node.props.variant || "default");
|
|
6512
7431
|
const size = String(node.props.size || "md");
|
|
7432
|
+
const density = this.ir.project.style.density || "normal";
|
|
7433
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7434
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6513
7435
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6514
7436
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6515
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7437
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6516
7438
|
const borderColor = variantColor;
|
|
6517
7439
|
const iconColor = variantColor;
|
|
6518
|
-
const buttonSize =
|
|
7440
|
+
const buttonSize = Math.max(16, Math.min(resolveControlHeight(size, density), pos.height - labelOffset));
|
|
7441
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
6519
7442
|
const radius = 6;
|
|
7443
|
+
const buttonY = pos.y + labelOffset;
|
|
6520
7444
|
const iconSvg = this.getIconSvg(iconName);
|
|
6521
7445
|
let svg = `<g${this.getDataNodeId(node)}>
|
|
6522
|
-
<rect x="${pos.x}" y="${
|
|
6523
|
-
width="${
|
|
7446
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
7447
|
+
width="${buttonWidth}" height="${buttonSize}"
|
|
6524
7448
|
rx="${radius}"
|
|
6525
7449
|
fill="none"
|
|
6526
7450
|
stroke="${borderColor}"
|
|
@@ -6528,8 +7452,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6528
7452
|
filter="url(#sketch-rough)"/>`;
|
|
6529
7453
|
if (iconSvg) {
|
|
6530
7454
|
const iconSize = buttonSize * 0.6;
|
|
6531
|
-
const offsetX = pos.x + (
|
|
6532
|
-
const offsetY =
|
|
7455
|
+
const offsetX = pos.x + (buttonWidth - iconSize) / 2;
|
|
7456
|
+
const offsetY = buttonY + (buttonSize - iconSize) / 2;
|
|
6533
7457
|
svg += `
|
|
6534
7458
|
<g transform="translate(${offsetX}, ${offsetY})">
|
|
6535
7459
|
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -6680,6 +7604,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6680
7604
|
*/
|
|
6681
7605
|
renderHeading(node, pos) {
|
|
6682
7606
|
const text = String(node.props.text || "Heading");
|
|
7607
|
+
const variant = String(node.props.variant || "default");
|
|
7608
|
+
const headingColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
6683
7609
|
const headingTypography = this.getHeadingTypography(node);
|
|
6684
7610
|
const fontSize = headingTypography.fontSize;
|
|
6685
7611
|
const fontWeight = headingTypography.fontWeight;
|
|
@@ -6692,7 +7618,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6692
7618
|
font-family="${this.fontFamily}"
|
|
6693
7619
|
font-size="${fontSize}"
|
|
6694
7620
|
font-weight="${fontWeight}"
|
|
6695
|
-
fill="${
|
|
7621
|
+
fill="${headingColor}">${this.escapeXml(text)}</text>
|
|
6696
7622
|
</g>`;
|
|
6697
7623
|
}
|
|
6698
7624
|
const tspans = lines.map(
|
|
@@ -6703,7 +7629,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6703
7629
|
font-family="${this.fontFamily}"
|
|
6704
7630
|
font-size="${fontSize}"
|
|
6705
7631
|
font-weight="${fontWeight}"
|
|
6706
|
-
fill="${
|
|
7632
|
+
fill="${headingColor}">${tspans}</text>
|
|
6707
7633
|
</g>`;
|
|
6708
7634
|
}
|
|
6709
7635
|
/**
|
|
@@ -6714,7 +7640,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6714
7640
|
const subtitle = String(node.props.subtitle || "");
|
|
6715
7641
|
const actions = String(node.props.actions || "");
|
|
6716
7642
|
const user = String(node.props.user || "");
|
|
6717
|
-
const
|
|
7643
|
+
const variant = String(node.props.variant || "default");
|
|
7644
|
+
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
6718
7645
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
6719
7646
|
let svg = `<g${this.getDataNodeId(node)}>
|
|
6720
7647
|
<rect x="${pos.x}" y="${pos.y}"
|
|
@@ -6807,79 +7734,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6807
7734
|
* Render table with sketch filter and Comic Sans
|
|
6808
7735
|
*/
|
|
6809
7736
|
renderTable(node, pos) {
|
|
6810
|
-
const
|
|
6811
|
-
|
|
6812
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
6813
|
-
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
6814
|
-
const mockStr = String(node.props.mock || "");
|
|
6815
|
-
const random = this.parseBooleanProp(node.props.random, false);
|
|
6816
|
-
const mockTypes = mockStr ? mockStr.split(",").map((m) => m.trim()).filter(Boolean) : [];
|
|
6817
|
-
while (mockTypes.length < columns.length) {
|
|
6818
|
-
const inferred = MockDataGenerator.inferMockTypeFromColumn(columns[mockTypes.length] || "item");
|
|
6819
|
-
mockTypes.push(inferred);
|
|
6820
|
-
}
|
|
6821
|
-
const headerHeight = 44;
|
|
6822
|
-
const rowHeight = 36;
|
|
6823
|
-
const colWidth = pos.width / columns.length;
|
|
6824
|
-
const mockRows = [];
|
|
6825
|
-
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
6826
|
-
const row = {};
|
|
6827
|
-
columns.forEach((col, colIdx) => {
|
|
6828
|
-
const mockType = mockTypes[colIdx] || MockDataGenerator.inferMockTypeFromColumn(col) || "item";
|
|
6829
|
-
row[col] = MockDataGenerator.getMockValue(mockType, rowIdx, random);
|
|
6830
|
-
});
|
|
6831
|
-
mockRows.push(row);
|
|
6832
|
-
}
|
|
6833
|
-
let svg = `<g${this.getDataNodeId(node)}>
|
|
6834
|
-
<rect x="${pos.x}" y="${pos.y}"
|
|
6835
|
-
width="${pos.width}" height="${pos.height}"
|
|
6836
|
-
rx="8"
|
|
6837
|
-
fill="${this.renderTheme.cardBg}"
|
|
6838
|
-
stroke="#2D3748"
|
|
6839
|
-
stroke-width="0.5"
|
|
6840
|
-
filter="url(#sketch-rough)"/>`;
|
|
6841
|
-
if (title) {
|
|
6842
|
-
svg += `
|
|
6843
|
-
<text x="${pos.x + 16}" y="${pos.y + 24}"
|
|
6844
|
-
font-family="${this.fontFamily}"
|
|
6845
|
-
font-size="13"
|
|
6846
|
-
font-weight="600"
|
|
6847
|
-
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>`;
|
|
6848
|
-
}
|
|
6849
|
-
const headerY = pos.y + (title ? 32 : 0);
|
|
6850
|
-
svg += `
|
|
6851
|
-
<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
6852
|
-
stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
|
|
6853
|
-
columns.forEach((col, i) => {
|
|
6854
|
-
svg += `
|
|
6855
|
-
<text x="${pos.x + i * colWidth + 12}" y="${headerY + 26}"
|
|
6856
|
-
font-family="${this.fontFamily}"
|
|
6857
|
-
font-size="11"
|
|
6858
|
-
font-weight="600"
|
|
6859
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(col)}</text>`;
|
|
6860
|
-
});
|
|
6861
|
-
mockRows.forEach((row, rowIdx) => {
|
|
6862
|
-
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
6863
|
-
svg += `
|
|
6864
|
-
<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
6865
|
-
stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
|
|
6866
|
-
columns.forEach((col, colIdx) => {
|
|
6867
|
-
const cellValue = row[col] || "";
|
|
6868
|
-
svg += `
|
|
6869
|
-
<text x="${pos.x + colIdx * colWidth + 12}" y="${rowY + 22}"
|
|
6870
|
-
font-family="${this.fontFamily}"
|
|
6871
|
-
font-size="12"
|
|
6872
|
-
fill="${this.renderTheme.text}">${this.escapeXml(cellValue)}</text>`;
|
|
6873
|
-
});
|
|
6874
|
-
});
|
|
6875
|
-
svg += "\n </g>";
|
|
6876
|
-
return svg;
|
|
7737
|
+
const standard = super.renderTable(node, pos);
|
|
7738
|
+
return standard.replace("<g", '<g filter="url(#sketch-rough)"');
|
|
6877
7739
|
}
|
|
6878
7740
|
/**
|
|
6879
7741
|
* Render text with Comic Sans
|
|
6880
7742
|
*/
|
|
6881
7743
|
renderText(node, pos) {
|
|
6882
|
-
const text = String(node.props.
|
|
7744
|
+
const text = String(node.props.text || "Text content");
|
|
6883
7745
|
const fontSize = this.tokens.text.fontSize;
|
|
6884
7746
|
const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
|
|
6885
7747
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
@@ -7456,7 +8318,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7456
8318
|
const itemY = pos.y + index * itemHeight;
|
|
7457
8319
|
const isActive = index === activeIndex;
|
|
7458
8320
|
const bgColor = isActive ? this.hexToRgba(accentColor, 0.15) : "transparent";
|
|
7459
|
-
const textColor = isActive ? accentColor :
|
|
8321
|
+
const textColor = isActive ? accentColor : this.resolveTextColor();
|
|
7460
8322
|
const fontWeight = isActive ? "500" : "400";
|
|
7461
8323
|
if (isActive) {
|
|
7462
8324
|
svg += `
|
|
@@ -7480,22 +8342,23 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7480
8342
|
* Render icon (same as base, icons don't need filter)
|
|
7481
8343
|
*/
|
|
7482
8344
|
renderIcon(node, pos) {
|
|
7483
|
-
const iconType = String(node.props.
|
|
8345
|
+
const iconType = String(node.props.icon || "help-circle");
|
|
7484
8346
|
const size = String(node.props.size || "md");
|
|
8347
|
+
const variant = String(node.props.variant || "default");
|
|
7485
8348
|
const iconSvg = getIcon(iconType);
|
|
7486
8349
|
if (!iconSvg) {
|
|
7487
8350
|
return `<g${this.getDataNodeId(node)}>
|
|
7488
8351
|
<circle cx="${pos.x + pos.width / 2}" cy="${pos.y + pos.height / 2}"
|
|
7489
8352
|
r="${Math.min(pos.width, pos.height) / 2 - 2}"
|
|
7490
|
-
fill="none" stroke="
|
|
8353
|
+
fill="none" stroke="${this.resolveMutedColor()}" stroke-width="0.5"
|
|
7491
8354
|
filter="url(#sketch-rough)"/>
|
|
7492
8355
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
7493
8356
|
font-family="${this.fontFamily}"
|
|
7494
|
-
font-size="12" fill="
|
|
8357
|
+
font-size="12" fill="${this.resolveMutedColor()}" text-anchor="middle">?</text>
|
|
7495
8358
|
</g>`;
|
|
7496
8359
|
}
|
|
7497
8360
|
const iconSize = this.getIconSize(size);
|
|
7498
|
-
const iconColor = "
|
|
8361
|
+
const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
7499
8362
|
const offsetX = pos.x + (pos.width - iconSize) / 2;
|
|
7500
8363
|
const offsetY = pos.y + (pos.height - iconSize) / 2;
|
|
7501
8364
|
return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
|