@wire-dsl/engine 0.2.4 → 0.4.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 +1646 -475
- package/dist/index.d.cts +40 -6
- package/dist/index.d.ts +40 -6
- package/dist/index.js +1649 -475
- 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,9 @@ 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
|
+
const isPropReference = normalizedValue.startsWith("prop_");
|
|
1700
|
+
if (!enumValues.includes(normalizedValue) && !isCustomVariantFromColors && !isPropReference) {
|
|
1574
1701
|
emitWarning(
|
|
1575
1702
|
`Invalid value "${normalizedValue}" for property "${propName}" in component "${componentType}".`,
|
|
1576
1703
|
"COMPONENT_INVALID_PROPERTY_VALUE",
|
|
@@ -1590,11 +1717,40 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1590
1717
|
);
|
|
1591
1718
|
}
|
|
1592
1719
|
}
|
|
1720
|
+
if (componentType === "Table") {
|
|
1721
|
+
const hasCaption = String(component.props.caption || "").trim().length > 0;
|
|
1722
|
+
const hasPagination = parseBooleanLike(component.props.pagination ?? "false", false);
|
|
1723
|
+
if (hasCaption && hasPagination) {
|
|
1724
|
+
const rawPaginationAlign = String(component.props.paginationAlign || "right");
|
|
1725
|
+
const paginationAlign = rawPaginationAlign === "left" || rawPaginationAlign === "center" || rawPaginationAlign === "right" ? rawPaginationAlign : "right";
|
|
1726
|
+
const rawCaptionAlign = String(component.props.captionAlign || "");
|
|
1727
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
1728
|
+
if (captionAlign === paginationAlign) {
|
|
1729
|
+
emitWarning(
|
|
1730
|
+
`Table footer collision: "captionAlign" and "paginationAlign" both resolve to "${captionAlign}".`,
|
|
1731
|
+
"TABLE_FOOTER_ALIGNMENT_COLLISION",
|
|
1732
|
+
entry?.range || toFallbackRange(),
|
|
1733
|
+
nodeId,
|
|
1734
|
+
"Use different alignments to avoid visual overlap."
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1593
1739
|
};
|
|
1594
|
-
const checkLayout = (layout) => {
|
|
1740
|
+
const checkLayout = (layout, insideDefinedLayout) => {
|
|
1595
1741
|
const nodeId = layout._meta?.nodeId;
|
|
1596
1742
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1597
1743
|
const rules = LAYOUT_RULES[layout.layoutType];
|
|
1744
|
+
const isDefinedLayoutUsage = definedLayouts.has(layout.layoutType);
|
|
1745
|
+
if (isDefinedLayoutUsage && layout.children.length !== 1) {
|
|
1746
|
+
emitError(
|
|
1747
|
+
`Layout "${layout.layoutType}" expects exactly one child for its Children slot.`,
|
|
1748
|
+
"LAYOUT_DEFINITION_CHILDREN_ARITY",
|
|
1749
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1750
|
+
nodeId,
|
|
1751
|
+
"Provide exactly one nested child block when using this layout."
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1598
1754
|
if (layout.children.length === 0) {
|
|
1599
1755
|
emitWarning(
|
|
1600
1756
|
`Layout "${layout.layoutType}" is empty.`,
|
|
@@ -1604,7 +1760,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1604
1760
|
"Add at least one child: component, layout, or cell."
|
|
1605
1761
|
);
|
|
1606
1762
|
}
|
|
1607
|
-
if (!rules) {
|
|
1763
|
+
if (!rules && !isDefinedLayoutUsage) {
|
|
1608
1764
|
emitWarning(
|
|
1609
1765
|
`Layout type "${layout.layoutType}" is not recognized by semantic validation rules.`,
|
|
1610
1766
|
"LAYOUT_UNKNOWN_TYPE",
|
|
@@ -1612,7 +1768,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1612
1768
|
nodeId,
|
|
1613
1769
|
`Use one of: ${Object.keys(LAYOUT_RULES).join(", ")}.`
|
|
1614
1770
|
);
|
|
1615
|
-
} else {
|
|
1771
|
+
} else if (rules) {
|
|
1616
1772
|
const missingRequiredParams = getMissingRequiredNames(rules.requiredParams, layout.params);
|
|
1617
1773
|
if (missingRequiredParams.length > 0) {
|
|
1618
1774
|
emitWarning(
|
|
@@ -1625,6 +1781,16 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1625
1781
|
}
|
|
1626
1782
|
const allowed = new Set(rules.allowedParams);
|
|
1627
1783
|
for (const [paramName, paramValue] of Object.entries(layout.params)) {
|
|
1784
|
+
if (layout.layoutType === "split" && paramName === "sidebar") {
|
|
1785
|
+
emitError(
|
|
1786
|
+
'Split parameter "sidebar" was removed. Use "left" or "right" instead.',
|
|
1787
|
+
"LAYOUT_SPLIT_SIDEBAR_DEPRECATED",
|
|
1788
|
+
getPropertyRange(entry, paramName, "name"),
|
|
1789
|
+
nodeId,
|
|
1790
|
+
"Example: layout split(left: 260) { ... }"
|
|
1791
|
+
);
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1628
1794
|
if (!allowed.has(paramName)) {
|
|
1629
1795
|
emitWarning(
|
|
1630
1796
|
`Parameter "${paramName}" is not recognized for layout "${layout.layoutType}".`,
|
|
@@ -1641,7 +1807,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1641
1807
|
const enumValues = rules.enumParams?.[paramName];
|
|
1642
1808
|
if (enumValues) {
|
|
1643
1809
|
const normalizedValue = String(paramValue);
|
|
1644
|
-
if (!enumValues.includes(normalizedValue)) {
|
|
1810
|
+
if (!enumValues.includes(normalizedValue) && !normalizedValue.startsWith("prop_")) {
|
|
1645
1811
|
emitWarning(
|
|
1646
1812
|
`Invalid value "${normalizedValue}" for parameter "${paramName}" in layout "${layout.layoutType}".`,
|
|
1647
1813
|
"LAYOUT_INVALID_PARAMETER_VALUE",
|
|
@@ -1663,31 +1829,62 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1663
1829
|
);
|
|
1664
1830
|
}
|
|
1665
1831
|
}
|
|
1666
|
-
if (layout.layoutType === "split" && paramName === "
|
|
1667
|
-
const
|
|
1668
|
-
if (!Number.isFinite(
|
|
1832
|
+
if (layout.layoutType === "split" && (paramName === "left" || paramName === "right")) {
|
|
1833
|
+
const splitSize = Number(paramValue);
|
|
1834
|
+
if (!Number.isFinite(splitSize) || splitSize <= 0) {
|
|
1669
1835
|
emitWarning(
|
|
1670
|
-
|
|
1671
|
-
"
|
|
1836
|
+
`Split "${paramName}" must be a positive number. Falling back to 250.`,
|
|
1837
|
+
"LAYOUT_SPLIT_WIDTH_INVALID",
|
|
1672
1838
|
getPropertyRange(entry, paramName, "value"),
|
|
1673
1839
|
nodeId,
|
|
1674
|
-
|
|
1840
|
+
`Use a value like ${paramName}: 260.`
|
|
1675
1841
|
);
|
|
1676
1842
|
}
|
|
1677
1843
|
}
|
|
1678
1844
|
}
|
|
1845
|
+
if (layout.layoutType === "split") {
|
|
1846
|
+
const hasLeft = layout.params.left !== void 0;
|
|
1847
|
+
const hasRight = layout.params.right !== void 0;
|
|
1848
|
+
if (!hasLeft && !hasRight) {
|
|
1849
|
+
emitError(
|
|
1850
|
+
'Split layout requires exactly one fixed side width: "left" or "right".',
|
|
1851
|
+
"LAYOUT_SPLIT_SIDE_REQUIRED",
|
|
1852
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1853
|
+
nodeId,
|
|
1854
|
+
"Add either left: <number> or right: <number>."
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
if (hasLeft && hasRight) {
|
|
1858
|
+
emitError(
|
|
1859
|
+
'Split layout accepts only one fixed side width: use either "left" or "right", not both.',
|
|
1860
|
+
"LAYOUT_SPLIT_SIDE_CONFLICT",
|
|
1861
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1862
|
+
nodeId,
|
|
1863
|
+
"Remove one of the two parameters."
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
if (layout.children.length !== 2) {
|
|
1867
|
+
emitError(
|
|
1868
|
+
`Split layout requires exactly 2 children, received ${layout.children.length}.`,
|
|
1869
|
+
"LAYOUT_SPLIT_CHILDREN_ARITY",
|
|
1870
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1871
|
+
nodeId,
|
|
1872
|
+
"Provide exactly two child blocks (left/right content)."
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1679
1876
|
}
|
|
1680
1877
|
for (const child of layout.children) {
|
|
1681
1878
|
if (child.type === "component") {
|
|
1682
|
-
checkComponent(child);
|
|
1879
|
+
checkComponent(child, insideDefinedLayout);
|
|
1683
1880
|
} else if (child.type === "layout") {
|
|
1684
|
-
checkLayout(child);
|
|
1881
|
+
checkLayout(child, insideDefinedLayout);
|
|
1685
1882
|
} else if (child.type === "cell") {
|
|
1686
|
-
checkCell(child);
|
|
1883
|
+
checkCell(child, insideDefinedLayout);
|
|
1687
1884
|
}
|
|
1688
1885
|
}
|
|
1689
1886
|
};
|
|
1690
|
-
const checkCell = (cell) => {
|
|
1887
|
+
const checkCell = (cell, insideDefinedLayout) => {
|
|
1691
1888
|
const nodeId = cell._meta?.nodeId;
|
|
1692
1889
|
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1693
1890
|
if (cell.props.span !== void 0) {
|
|
@@ -1703,12 +1900,54 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1703
1900
|
}
|
|
1704
1901
|
}
|
|
1705
1902
|
for (const child of cell.children) {
|
|
1706
|
-
if (child.type === "component") checkComponent(child);
|
|
1707
|
-
if (child.type === "layout") checkLayout(child);
|
|
1903
|
+
if (child.type === "component") checkComponent(child, insideDefinedLayout);
|
|
1904
|
+
if (child.type === "layout") checkLayout(child, insideDefinedLayout);
|
|
1708
1905
|
}
|
|
1709
1906
|
};
|
|
1907
|
+
for (const componentDef of ast.definedComponents) {
|
|
1908
|
+
const nodeId = componentDef._meta?.nodeId;
|
|
1909
|
+
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1910
|
+
if (!isPascalCaseIdentifier(componentDef.name)) {
|
|
1911
|
+
emitWarning(
|
|
1912
|
+
`Defined component "${componentDef.name}" should use PascalCase naming.`,
|
|
1913
|
+
"COMPONENT_DEFINITION_NAME_STYLE",
|
|
1914
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1915
|
+
nodeId,
|
|
1916
|
+
'Use a name like "MyComponent".'
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
if (componentDef.body.type === "component") {
|
|
1920
|
+
checkComponent(componentDef.body, false);
|
|
1921
|
+
} else {
|
|
1922
|
+
checkLayout(componentDef.body, false);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
for (const layoutDef of ast.definedLayouts) {
|
|
1926
|
+
const nodeId = layoutDef._meta?.nodeId;
|
|
1927
|
+
const entry = nodeId ? sourceMapByNodeId.get(nodeId) : void 0;
|
|
1928
|
+
if (!isValidDefinedLayoutName(layoutDef.name)) {
|
|
1929
|
+
emitError(
|
|
1930
|
+
`Defined layout "${layoutDef.name}" must match /^[a-z][a-z0-9_]*$/.`,
|
|
1931
|
+
"LAYOUT_DEFINITION_INVALID_NAME",
|
|
1932
|
+
entry?.nameRange || entry?.range || toFallbackRange(),
|
|
1933
|
+
nodeId,
|
|
1934
|
+
'Use names like "screen_default" or "appShell".'
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
const childrenSlotCount = countChildrenSlots(layoutDef.body);
|
|
1938
|
+
if (childrenSlotCount !== 1) {
|
|
1939
|
+
emitError(
|
|
1940
|
+
`Defined layout "${layoutDef.name}" must contain exactly one "component Children" placeholder.`,
|
|
1941
|
+
"LAYOUT_DEFINITION_CHILDREN_SLOT_COUNT",
|
|
1942
|
+
entry?.bodyRange || entry?.range || toFallbackRange(),
|
|
1943
|
+
nodeId,
|
|
1944
|
+
'Add exactly one "component Children" in the layout body.'
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
checkLayout(layoutDef.body, true);
|
|
1948
|
+
}
|
|
1710
1949
|
ast.screens.forEach((screen) => {
|
|
1711
|
-
checkLayout(screen.layout);
|
|
1950
|
+
checkLayout(screen.layout, false);
|
|
1712
1951
|
});
|
|
1713
1952
|
return diagnostics;
|
|
1714
1953
|
}
|
|
@@ -1725,7 +1964,7 @@ ${lexResult.errors.map((e) => e.message).join("\n")}`);
|
|
|
1725
1964
|
${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
1726
1965
|
}
|
|
1727
1966
|
const ast = visitor.visit(cst);
|
|
1728
|
-
|
|
1967
|
+
validateDefinitionCycles(ast);
|
|
1729
1968
|
return ast;
|
|
1730
1969
|
}
|
|
1731
1970
|
function parseWireDSLWithSourceMap(input, filePath = "<input>", options) {
|
|
@@ -1756,7 +1995,7 @@ ${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
|
1756
1995
|
const ast = visitorWithSourceMap.visit(cst);
|
|
1757
1996
|
const sourceMap = sourceMapBuilder.build();
|
|
1758
1997
|
try {
|
|
1759
|
-
|
|
1998
|
+
validateDefinitionCycles(ast);
|
|
1760
1999
|
} catch (error) {
|
|
1761
2000
|
const projectEntry = sourceMap.find((entry) => entry.type === "project");
|
|
1762
2001
|
diagnostics.push({
|
|
@@ -1779,83 +2018,101 @@ ${parserInstance.errors.map((e) => e.message).join("\n")}`);
|
|
|
1779
2018
|
}
|
|
1780
2019
|
return buildParseResult(ast, sourceMap, diagnostics);
|
|
1781
2020
|
}
|
|
1782
|
-
function
|
|
1783
|
-
if (!ast.definedComponents || ast.definedComponents.length === 0) {
|
|
2021
|
+
function validateDefinitionCycles(ast) {
|
|
2022
|
+
if ((!ast.definedComponents || ast.definedComponents.length === 0) && (!ast.definedLayouts || ast.definedLayouts.length === 0)) {
|
|
1784
2023
|
return;
|
|
1785
2024
|
}
|
|
1786
2025
|
const components = /* @__PURE__ */ new Map();
|
|
2026
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
1787
2027
|
ast.definedComponents.forEach((comp) => {
|
|
1788
2028
|
components.set(comp.name, comp);
|
|
1789
2029
|
});
|
|
2030
|
+
ast.definedLayouts.forEach((layoutDef) => {
|
|
2031
|
+
layouts.set(layoutDef.name, layoutDef);
|
|
2032
|
+
});
|
|
2033
|
+
const makeComponentKey = (name) => `component:${name}`;
|
|
2034
|
+
const makeLayoutKey = (name) => `layout:${name}`;
|
|
2035
|
+
const displayKey = (key) => key.split(":")[1];
|
|
2036
|
+
const shouldTrackComponentDependency = (name) => components.has(name) && !BUILT_IN_COMPONENTS.has(name);
|
|
2037
|
+
const shouldTrackLayoutDependency = (name) => layouts.has(name) && !BUILT_IN_LAYOUTS.has(name);
|
|
1790
2038
|
const visited = /* @__PURE__ */ new Set();
|
|
1791
2039
|
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
|
-
});
|
|
2040
|
+
const collectLayoutDependencies = (layout, deps) => {
|
|
2041
|
+
if (shouldTrackLayoutDependency(layout.layoutType)) {
|
|
2042
|
+
deps.add(makeLayoutKey(layout.layoutType));
|
|
2043
|
+
}
|
|
2044
|
+
for (const child of layout.children) {
|
|
2045
|
+
if (child.type === "component") {
|
|
2046
|
+
if (shouldTrackComponentDependency(child.componentType)) {
|
|
2047
|
+
deps.add(makeComponentKey(child.componentType));
|
|
2048
|
+
}
|
|
2049
|
+
} else if (child.type === "layout") {
|
|
2050
|
+
collectLayoutDependencies(child, deps);
|
|
2051
|
+
} else if (child.type === "cell") {
|
|
2052
|
+
for (const cellChild of child.children) {
|
|
2053
|
+
if (cellChild.type === "component") {
|
|
2054
|
+
if (shouldTrackComponentDependency(cellChild.componentType)) {
|
|
2055
|
+
deps.add(makeComponentKey(cellChild.componentType));
|
|
1815
2056
|
}
|
|
2057
|
+
} else if (cellChild.type === "layout") {
|
|
2058
|
+
collectLayoutDependencies(cellChild, deps);
|
|
1816
2059
|
}
|
|
1817
|
-
}
|
|
2060
|
+
}
|
|
1818
2061
|
}
|
|
1819
2062
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
if (
|
|
1824
|
-
const
|
|
1825
|
-
const
|
|
1826
|
-
return
|
|
2063
|
+
};
|
|
2064
|
+
const getDependencies = (key) => {
|
|
2065
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2066
|
+
if (key.startsWith("component:")) {
|
|
2067
|
+
const name2 = key.slice("component:".length);
|
|
2068
|
+
const def2 = components.get(name2);
|
|
2069
|
+
if (!def2) return deps;
|
|
2070
|
+
if (def2.body.type === "component") {
|
|
2071
|
+
if (shouldTrackComponentDependency(def2.body.componentType)) {
|
|
2072
|
+
deps.add(makeComponentKey(def2.body.componentType));
|
|
2073
|
+
}
|
|
2074
|
+
} else {
|
|
2075
|
+
collectLayoutDependencies(def2.body, deps);
|
|
2076
|
+
}
|
|
2077
|
+
return deps;
|
|
1827
2078
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
2079
|
+
const name = key.slice("layout:".length);
|
|
2080
|
+
const def = layouts.get(name);
|
|
2081
|
+
if (!def) return deps;
|
|
2082
|
+
collectLayoutDependencies(def.body, deps);
|
|
2083
|
+
return deps;
|
|
2084
|
+
};
|
|
2085
|
+
const findCycle = (key, path = []) => {
|
|
2086
|
+
if (recursionStack.has(key)) {
|
|
2087
|
+
const cycleStart = path.indexOf(key);
|
|
2088
|
+
return path.slice(cycleStart).concat(key);
|
|
1830
2089
|
}
|
|
1831
|
-
|
|
1832
|
-
if (!component) {
|
|
2090
|
+
if (visited.has(key)) {
|
|
1833
2091
|
return null;
|
|
1834
2092
|
}
|
|
1835
|
-
recursionStack.add(
|
|
1836
|
-
const currentPath = [...path,
|
|
1837
|
-
const dependencies =
|
|
2093
|
+
recursionStack.add(key);
|
|
2094
|
+
const currentPath = [...path, key];
|
|
2095
|
+
const dependencies = getDependencies(key);
|
|
1838
2096
|
for (const dep of dependencies) {
|
|
1839
|
-
const
|
|
1840
|
-
if (
|
|
1841
|
-
const cycle = hasCycle(dep, currentPath);
|
|
1842
|
-
if (cycle) {
|
|
1843
|
-
return cycle;
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
2097
|
+
const cycle = findCycle(dep, currentPath);
|
|
2098
|
+
if (cycle) return cycle;
|
|
1846
2099
|
}
|
|
1847
|
-
recursionStack.delete(
|
|
1848
|
-
visited.add(
|
|
2100
|
+
recursionStack.delete(key);
|
|
2101
|
+
visited.add(key);
|
|
1849
2102
|
return null;
|
|
1850
|
-
}
|
|
1851
|
-
|
|
2103
|
+
};
|
|
2104
|
+
const allDefinitions = [
|
|
2105
|
+
...Array.from(components.keys()).map(makeComponentKey),
|
|
2106
|
+
...Array.from(layouts.keys()).map(makeLayoutKey)
|
|
2107
|
+
];
|
|
2108
|
+
for (const key of allDefinitions) {
|
|
1852
2109
|
visited.clear();
|
|
1853
2110
|
recursionStack.clear();
|
|
1854
|
-
const cycle =
|
|
2111
|
+
const cycle = findCycle(key);
|
|
1855
2112
|
if (cycle) {
|
|
1856
2113
|
throw new Error(
|
|
1857
|
-
`Circular component definition detected: ${cycle.join(" \u2192 ")}
|
|
1858
|
-
Components cannot reference each other in a cycle.`
|
|
2114
|
+
`Circular component definition detected: ${cycle.map(displayKey).join(" \u2192 ")}
|
|
2115
|
+
Components and layouts cannot reference each other in a cycle.`
|
|
1859
2116
|
);
|
|
1860
2117
|
}
|
|
1861
2118
|
}
|
|
@@ -1863,6 +2120,7 @@ Components cannot reference each other in a cycle.`
|
|
|
1863
2120
|
|
|
1864
2121
|
// src/ir/index.ts
|
|
1865
2122
|
var import_zod = require("zod");
|
|
2123
|
+
var import_components2 = require("@wire-dsl/language-support/components");
|
|
1866
2124
|
|
|
1867
2125
|
// src/ir/device-presets.ts
|
|
1868
2126
|
var DEVICE_PRESETS = {
|
|
@@ -2027,9 +2285,11 @@ var IRGenerator = class {
|
|
|
2027
2285
|
this.idGen = new IDGenerator();
|
|
2028
2286
|
this.nodes = {};
|
|
2029
2287
|
this.definedComponents = /* @__PURE__ */ new Map();
|
|
2288
|
+
this.definedLayouts = /* @__PURE__ */ new Map();
|
|
2030
2289
|
this.definedComponentIndices = /* @__PURE__ */ new Map();
|
|
2031
2290
|
this.undefinedComponentsUsed = /* @__PURE__ */ new Set();
|
|
2032
2291
|
this.warnings = [];
|
|
2292
|
+
this.errors = [];
|
|
2033
2293
|
this.style = {
|
|
2034
2294
|
density: "normal",
|
|
2035
2295
|
spacing: "md",
|
|
@@ -2042,15 +2302,22 @@ var IRGenerator = class {
|
|
|
2042
2302
|
this.idGen.reset();
|
|
2043
2303
|
this.nodes = {};
|
|
2044
2304
|
this.definedComponents.clear();
|
|
2305
|
+
this.definedLayouts.clear();
|
|
2045
2306
|
this.definedComponentIndices.clear();
|
|
2046
2307
|
this.undefinedComponentsUsed.clear();
|
|
2047
2308
|
this.warnings = [];
|
|
2309
|
+
this.errors = [];
|
|
2048
2310
|
if (ast.definedComponents && ast.definedComponents.length > 0) {
|
|
2049
2311
|
ast.definedComponents.forEach((def, index) => {
|
|
2050
2312
|
this.definedComponents.set(def.name, def);
|
|
2051
2313
|
this.definedComponentIndices.set(def.name, index);
|
|
2052
2314
|
});
|
|
2053
2315
|
}
|
|
2316
|
+
if (ast.definedLayouts && ast.definedLayouts.length > 0) {
|
|
2317
|
+
ast.definedLayouts.forEach((def) => {
|
|
2318
|
+
this.definedLayouts.set(def.name, def);
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2054
2321
|
this.applyStyle(ast.style);
|
|
2055
2322
|
const screens = ast.screens.map(
|
|
2056
2323
|
(screen, screenIndex) => this.convertScreen(screen, screenIndex)
|
|
@@ -2062,6 +2329,11 @@ var IRGenerator = class {
|
|
|
2062
2329
|
Define these components with: define Component "Name" { ... }`
|
|
2063
2330
|
);
|
|
2064
2331
|
}
|
|
2332
|
+
if (this.errors.length > 0) {
|
|
2333
|
+
const messages = this.errors.map((e) => `- [${e.type}] ${e.message}`).join("\n");
|
|
2334
|
+
throw new Error(`IR generation failed with semantic errors:
|
|
2335
|
+
${messages}`);
|
|
2336
|
+
}
|
|
2065
2337
|
const project = {
|
|
2066
2338
|
id: this.sanitizeId(ast.name),
|
|
2067
2339
|
name: ast.name,
|
|
@@ -2188,41 +2460,50 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2188
2460
|
getWarnings() {
|
|
2189
2461
|
return this.warnings;
|
|
2190
2462
|
}
|
|
2191
|
-
convertLayout(layout) {
|
|
2463
|
+
convertLayout(layout, context) {
|
|
2464
|
+
let layoutParams = this.resolveLayoutParams(layout.layoutType, layout.params, context);
|
|
2465
|
+
if (layout.layoutType === "split") {
|
|
2466
|
+
layoutParams = this.normalizeSplitParams(layoutParams);
|
|
2467
|
+
}
|
|
2468
|
+
const layoutChildren = layout.children;
|
|
2469
|
+
const layoutDefinition = this.definedLayouts.get(layout.layoutType);
|
|
2470
|
+
if (layoutDefinition) {
|
|
2471
|
+
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
|
|
2472
|
+
}
|
|
2192
2473
|
const nodeId = this.idGen.generate("node");
|
|
2193
2474
|
const childRefs = [];
|
|
2194
|
-
for (const child of
|
|
2475
|
+
for (const child of layoutChildren) {
|
|
2195
2476
|
if (child.type === "layout") {
|
|
2196
|
-
const childId = this.convertLayout(child);
|
|
2197
|
-
childRefs.push({ ref: childId });
|
|
2477
|
+
const childId = this.convertLayout(child, context);
|
|
2478
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2198
2479
|
} else if (child.type === "component") {
|
|
2199
|
-
const childId = this.convertComponent(child);
|
|
2200
|
-
childRefs.push({ ref: childId });
|
|
2480
|
+
const childId = this.convertComponent(child, context);
|
|
2481
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2201
2482
|
} else if (child.type === "cell") {
|
|
2202
|
-
const childId = this.convertCell(child);
|
|
2203
|
-
childRefs.push({ ref: childId });
|
|
2483
|
+
const childId = this.convertCell(child, context);
|
|
2484
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2204
2485
|
}
|
|
2205
2486
|
}
|
|
2206
2487
|
const style = {};
|
|
2207
|
-
if (
|
|
2208
|
-
style.padding = String(
|
|
2488
|
+
if (layoutParams.padding !== void 0) {
|
|
2489
|
+
style.padding = String(layoutParams.padding);
|
|
2209
2490
|
} else {
|
|
2210
2491
|
style.padding = "none";
|
|
2211
2492
|
}
|
|
2212
|
-
if (
|
|
2213
|
-
style.gap = String(
|
|
2493
|
+
if (layoutParams.gap !== void 0) {
|
|
2494
|
+
style.gap = String(layoutParams.gap);
|
|
2214
2495
|
}
|
|
2215
|
-
if (
|
|
2216
|
-
style.align =
|
|
2496
|
+
if (layoutParams.align !== void 0) {
|
|
2497
|
+
style.align = layoutParams.align;
|
|
2217
2498
|
}
|
|
2218
|
-
if (
|
|
2219
|
-
style.background = String(
|
|
2499
|
+
if (layoutParams.background !== void 0) {
|
|
2500
|
+
style.background = String(layoutParams.background);
|
|
2220
2501
|
}
|
|
2221
2502
|
const containerNode = {
|
|
2222
2503
|
id: nodeId,
|
|
2223
2504
|
kind: "container",
|
|
2224
2505
|
containerType: layout.layoutType,
|
|
2225
|
-
params: this.cleanParams(
|
|
2506
|
+
params: this.cleanParams(layoutParams),
|
|
2226
2507
|
children: childRefs,
|
|
2227
2508
|
style,
|
|
2228
2509
|
meta: {
|
|
@@ -2233,16 +2514,16 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2233
2514
|
this.nodes[nodeId] = containerNode;
|
|
2234
2515
|
return nodeId;
|
|
2235
2516
|
}
|
|
2236
|
-
convertCell(cell) {
|
|
2517
|
+
convertCell(cell, context) {
|
|
2237
2518
|
const nodeId = this.idGen.generate("node");
|
|
2238
2519
|
const childRefs = [];
|
|
2239
2520
|
for (const child of cell.children) {
|
|
2240
2521
|
if (child.type === "layout") {
|
|
2241
|
-
const childId = this.convertLayout(child);
|
|
2242
|
-
childRefs.push({ ref: childId });
|
|
2522
|
+
const childId = this.convertLayout(child, context);
|
|
2523
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2243
2524
|
} else if (child.type === "component") {
|
|
2244
|
-
const childId = this.convertComponent(child);
|
|
2245
|
-
childRefs.push({ ref: childId });
|
|
2525
|
+
const childId = this.convertComponent(child, context);
|
|
2526
|
+
if (childId) childRefs.push({ ref: childId });
|
|
2246
2527
|
}
|
|
2247
2528
|
}
|
|
2248
2529
|
const containerNode = {
|
|
@@ -2263,10 +2544,28 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2263
2544
|
this.nodes[nodeId] = containerNode;
|
|
2264
2545
|
return nodeId;
|
|
2265
2546
|
}
|
|
2266
|
-
convertComponent(component) {
|
|
2547
|
+
convertComponent(component, context) {
|
|
2548
|
+
if (component.componentType === "Children") {
|
|
2549
|
+
if (!context?.allowChildrenSlot) {
|
|
2550
|
+
this.errors.push({
|
|
2551
|
+
type: "children-slot-outside-layout-definition",
|
|
2552
|
+
message: '"Children" placeholder can only be used inside a define Layout body.'
|
|
2553
|
+
});
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
if (!context.childrenSlot) {
|
|
2557
|
+
this.errors.push({
|
|
2558
|
+
type: "children-slot-missing-child",
|
|
2559
|
+
message: `Layout "${context.definitionName}" requires exactly one child for "Children".`
|
|
2560
|
+
});
|
|
2561
|
+
return null;
|
|
2562
|
+
}
|
|
2563
|
+
return this.convertASTNode(context.childrenSlot, context);
|
|
2564
|
+
}
|
|
2565
|
+
const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
|
|
2267
2566
|
const definition = this.definedComponents.get(component.componentType);
|
|
2268
2567
|
if (definition) {
|
|
2269
|
-
return this.expandDefinedComponent(definition);
|
|
2568
|
+
return this.expandDefinedComponent(definition, resolvedProps, context);
|
|
2270
2569
|
}
|
|
2271
2570
|
const builtInComponents = /* @__PURE__ */ new Set([
|
|
2272
2571
|
"Button",
|
|
@@ -2309,7 +2608,7 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2309
2608
|
id: nodeId,
|
|
2310
2609
|
kind: "component",
|
|
2311
2610
|
componentType: component.componentType,
|
|
2312
|
-
props:
|
|
2611
|
+
props: resolvedProps,
|
|
2313
2612
|
style: {},
|
|
2314
2613
|
meta: {
|
|
2315
2614
|
nodeId: component._meta?.nodeId
|
|
@@ -2319,15 +2618,224 @@ Define these components with: define Component "Name" { ... }`
|
|
|
2319
2618
|
this.nodes[nodeId] = componentNode;
|
|
2320
2619
|
return nodeId;
|
|
2321
2620
|
}
|
|
2322
|
-
expandDefinedComponent(definition) {
|
|
2621
|
+
expandDefinedComponent(definition, invocationArgs, parentContext) {
|
|
2622
|
+
const context = {
|
|
2623
|
+
args: invocationArgs,
|
|
2624
|
+
providedArgNames: new Set(Object.keys(invocationArgs)),
|
|
2625
|
+
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2626
|
+
definitionName: definition.name,
|
|
2627
|
+
definitionKind: "component",
|
|
2628
|
+
allowChildrenSlot: false
|
|
2629
|
+
};
|
|
2323
2630
|
if (definition.body.type === "layout") {
|
|
2324
|
-
|
|
2631
|
+
const result = this.convertLayout(definition.body, context);
|
|
2632
|
+
this.reportUnusedArguments(context);
|
|
2633
|
+
return result;
|
|
2325
2634
|
} else if (definition.body.type === "component") {
|
|
2326
|
-
|
|
2635
|
+
const result = this.convertComponent(definition.body, context);
|
|
2636
|
+
this.reportUnusedArguments(context);
|
|
2637
|
+
return result;
|
|
2327
2638
|
} else {
|
|
2328
2639
|
throw new Error(`Invalid defined component body type for "${definition.name}"`);
|
|
2329
2640
|
}
|
|
2330
2641
|
}
|
|
2642
|
+
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
|
|
2643
|
+
if (invocationChildren.length !== 1) {
|
|
2644
|
+
this.errors.push({
|
|
2645
|
+
type: "layout-children-arity",
|
|
2646
|
+
message: `Layout "${definition.name}" expects exactly one child, received ${invocationChildren.length}.`
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
const rawSlot = invocationChildren[0];
|
|
2650
|
+
const resolvedSlot = rawSlot ? this.resolveChildrenSlot(rawSlot, parentContext) : void 0;
|
|
2651
|
+
const context = {
|
|
2652
|
+
args: invocationParams,
|
|
2653
|
+
providedArgNames: new Set(Object.keys(invocationParams)),
|
|
2654
|
+
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2655
|
+
definitionName: definition.name,
|
|
2656
|
+
definitionKind: "layout",
|
|
2657
|
+
allowChildrenSlot: true,
|
|
2658
|
+
childrenSlot: resolvedSlot
|
|
2659
|
+
};
|
|
2660
|
+
const nodeId = this.convertLayout(definition.body, context);
|
|
2661
|
+
this.reportUnusedArguments(context);
|
|
2662
|
+
return nodeId;
|
|
2663
|
+
}
|
|
2664
|
+
resolveChildrenSlot(slot, parentContext) {
|
|
2665
|
+
if (slot.type === "component" && slot.componentType === "Children") {
|
|
2666
|
+
if (parentContext?.allowChildrenSlot) {
|
|
2667
|
+
return parentContext.childrenSlot;
|
|
2668
|
+
}
|
|
2669
|
+
this.errors.push({
|
|
2670
|
+
type: "children-slot-outside-layout-definition",
|
|
2671
|
+
message: '"Children" placeholder forwarding is only valid inside define Layout bodies.'
|
|
2672
|
+
});
|
|
2673
|
+
return void 0;
|
|
2674
|
+
}
|
|
2675
|
+
return slot;
|
|
2676
|
+
}
|
|
2677
|
+
convertASTNode(node, context) {
|
|
2678
|
+
if (node.type === "layout") return this.convertLayout(node, context);
|
|
2679
|
+
if (node.type === "component") return this.convertComponent(node, context);
|
|
2680
|
+
return this.convertCell(node, context);
|
|
2681
|
+
}
|
|
2682
|
+
resolveLayoutParams(layoutType, params, context) {
|
|
2683
|
+
const resolved = {};
|
|
2684
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2685
|
+
const resolvedValue = this.resolveBindingValue(
|
|
2686
|
+
value,
|
|
2687
|
+
context,
|
|
2688
|
+
"layout-parameter",
|
|
2689
|
+
layoutType,
|
|
2690
|
+
key
|
|
2691
|
+
);
|
|
2692
|
+
if (resolvedValue !== void 0) {
|
|
2693
|
+
const wasPropReference = typeof value === "string" && value.startsWith("prop_");
|
|
2694
|
+
if (wasPropReference) {
|
|
2695
|
+
const layoutMetadata = import_components2.LAYOUTS[layoutType];
|
|
2696
|
+
const property = layoutMetadata?.properties?.[key];
|
|
2697
|
+
if (property?.type === "enum" && Array.isArray(property.options)) {
|
|
2698
|
+
const normalizedValue = String(resolvedValue);
|
|
2699
|
+
if (!property.options.includes(normalizedValue)) {
|
|
2700
|
+
this.warnings.push({
|
|
2701
|
+
type: "invalid-bound-enum-value",
|
|
2702
|
+
message: `Invalid value "${normalizedValue}" for parameter "${key}" in layout "${layoutType}". Expected one of: ${property.options.join(", ")}.`
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
resolved[key] = resolvedValue;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
return resolved;
|
|
2711
|
+
}
|
|
2712
|
+
normalizeSplitParams(params) {
|
|
2713
|
+
const normalized = { ...params };
|
|
2714
|
+
if (normalized.sidebar !== void 0 && normalized.left === void 0 && normalized.right === void 0) {
|
|
2715
|
+
normalized.left = normalized.sidebar;
|
|
2716
|
+
this.warnings.push({
|
|
2717
|
+
type: "split-sidebar-deprecated",
|
|
2718
|
+
message: 'Split parameter "sidebar" is deprecated. Use "left" or "right".'
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
delete normalized.sidebar;
|
|
2722
|
+
const hasLeft = normalized.left !== void 0;
|
|
2723
|
+
const hasRight = normalized.right !== void 0;
|
|
2724
|
+
if (hasLeft && hasRight) {
|
|
2725
|
+
delete normalized.right;
|
|
2726
|
+
this.warnings.push({
|
|
2727
|
+
type: "split-side-conflict",
|
|
2728
|
+
message: 'Split layout received both "left" and "right"; keeping "left".'
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
if (!hasLeft && !hasRight) {
|
|
2732
|
+
normalized.left = 250;
|
|
2733
|
+
this.warnings.push({
|
|
2734
|
+
type: "split-side-missing",
|
|
2735
|
+
message: 'Split layout missing both "left" and "right"; defaulting to left: 250.'
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
if (normalized.left !== void 0) {
|
|
2739
|
+
const leftWidth = Number(normalized.left);
|
|
2740
|
+
if (!Number.isFinite(leftWidth) || leftWidth <= 0) {
|
|
2741
|
+
normalized.left = 250;
|
|
2742
|
+
this.warnings.push({
|
|
2743
|
+
type: "split-left-invalid",
|
|
2744
|
+
message: 'Split "left" must be a positive number. Falling back to 250.'
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
if (normalized.right !== void 0) {
|
|
2749
|
+
const rightWidth = Number(normalized.right);
|
|
2750
|
+
if (!Number.isFinite(rightWidth) || rightWidth <= 0) {
|
|
2751
|
+
normalized.right = 250;
|
|
2752
|
+
this.warnings.push({
|
|
2753
|
+
type: "split-right-invalid",
|
|
2754
|
+
message: 'Split "right" must be a positive number. Falling back to 250.'
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
return normalized;
|
|
2759
|
+
}
|
|
2760
|
+
resolveComponentProps(componentType, props, context) {
|
|
2761
|
+
const resolved = {};
|
|
2762
|
+
for (const [key, value] of Object.entries(props)) {
|
|
2763
|
+
const resolvedValue = this.resolveBindingValue(
|
|
2764
|
+
value,
|
|
2765
|
+
context,
|
|
2766
|
+
"component-property",
|
|
2767
|
+
componentType,
|
|
2768
|
+
key
|
|
2769
|
+
);
|
|
2770
|
+
if (resolvedValue !== void 0) {
|
|
2771
|
+
const wasPropReference = typeof value === "string" && value.startsWith("prop_");
|
|
2772
|
+
if (wasPropReference) {
|
|
2773
|
+
const metadata = import_components2.COMPONENTS[componentType];
|
|
2774
|
+
const property = metadata?.properties?.[key];
|
|
2775
|
+
if (property?.type === "enum" && Array.isArray(property.options)) {
|
|
2776
|
+
const normalizedValue = String(resolvedValue);
|
|
2777
|
+
if (!property.options.includes(normalizedValue)) {
|
|
2778
|
+
this.warnings.push({
|
|
2779
|
+
type: "invalid-bound-enum-value",
|
|
2780
|
+
message: `Invalid value "${normalizedValue}" for property "${key}" in component "${componentType}". Expected one of: ${property.options.join(", ")}.`
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
resolved[key] = resolvedValue;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return resolved;
|
|
2789
|
+
}
|
|
2790
|
+
resolveBindingValue(value, context, kind, targetType, targetName) {
|
|
2791
|
+
if (typeof value !== "string" || !value.startsWith("prop_")) {
|
|
2792
|
+
return value;
|
|
2793
|
+
}
|
|
2794
|
+
const argName = value.slice("prop_".length);
|
|
2795
|
+
if (!context) {
|
|
2796
|
+
return value;
|
|
2797
|
+
}
|
|
2798
|
+
if (Object.prototype.hasOwnProperty.call(context.args, argName)) {
|
|
2799
|
+
context.usedArgNames.add(argName);
|
|
2800
|
+
return context.args[argName];
|
|
2801
|
+
}
|
|
2802
|
+
const required = this.isBindingTargetRequired(kind, targetType, targetName);
|
|
2803
|
+
const descriptor = kind === "component-property" ? "property" : "parameter";
|
|
2804
|
+
const message = `Missing required bound ${descriptor} "${targetName}" for ${kind === "component-property" ? "component" : "layout"} "${targetType}" in ${context.definitionKind} "${context.definitionName}" (expected arg "${argName}").`;
|
|
2805
|
+
if (required) {
|
|
2806
|
+
this.errors.push({ type: "missing-required-bound-value", message });
|
|
2807
|
+
} else {
|
|
2808
|
+
this.warnings.push({
|
|
2809
|
+
type: "missing-bound-value",
|
|
2810
|
+
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}".`
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
return void 0;
|
|
2814
|
+
}
|
|
2815
|
+
isBindingTargetRequired(kind, targetType, targetName) {
|
|
2816
|
+
if (kind === "component-property") {
|
|
2817
|
+
const metadata = import_components2.COMPONENTS[targetType];
|
|
2818
|
+
const property2 = metadata?.properties?.[targetName];
|
|
2819
|
+
if (!property2) return false;
|
|
2820
|
+
return property2.required === true && property2.defaultValue === void 0;
|
|
2821
|
+
}
|
|
2822
|
+
const layoutMetadata = import_components2.LAYOUTS[targetType];
|
|
2823
|
+
if (!layoutMetadata) return false;
|
|
2824
|
+
const property = layoutMetadata.properties?.[targetName];
|
|
2825
|
+
const requiredFromProperty = property?.required === true && property.defaultValue === void 0;
|
|
2826
|
+
const requiredFromLayout = (layoutMetadata.requiredProperties || []).includes(targetName);
|
|
2827
|
+
return requiredFromProperty || requiredFromLayout;
|
|
2828
|
+
}
|
|
2829
|
+
reportUnusedArguments(context) {
|
|
2830
|
+
for (const arg of context.providedArgNames) {
|
|
2831
|
+
if (!context.usedArgNames.has(arg)) {
|
|
2832
|
+
this.warnings.push({
|
|
2833
|
+
type: "unused-definition-argument",
|
|
2834
|
+
message: `Argument "${arg}" is not used by ${context.definitionKind} "${context.definitionName}".`
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2331
2839
|
cleanParams(params) {
|
|
2332
2840
|
const cleaned = {};
|
|
2333
2841
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -2377,9 +2885,24 @@ var ICON_SIZES_BY_DENSITY = {
|
|
|
2377
2885
|
comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
|
|
2378
2886
|
};
|
|
2379
2887
|
var ICON_BUTTON_SIZES_BY_DENSITY = {
|
|
2380
|
-
compact: { sm:
|
|
2381
|
-
normal: { sm:
|
|
2382
|
-
comfortable: { sm:
|
|
2888
|
+
compact: { sm: 20, md: 24, lg: 32 },
|
|
2889
|
+
normal: { sm: 24, md: 32, lg: 40 },
|
|
2890
|
+
comfortable: { sm: 28, md: 40, lg: 48 }
|
|
2891
|
+
};
|
|
2892
|
+
var CONTROL_HEIGHTS_BY_DENSITY = {
|
|
2893
|
+
compact: { sm: 28, md: 32, lg: 36 },
|
|
2894
|
+
normal: { sm: 36, md: 40, lg: 48 },
|
|
2895
|
+
comfortable: { sm: 40, md: 48, lg: 56 }
|
|
2896
|
+
};
|
|
2897
|
+
var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
|
|
2898
|
+
compact: { sm: 20, md: 24, lg: 32 },
|
|
2899
|
+
normal: { sm: 24, md: 32, lg: 40 },
|
|
2900
|
+
comfortable: { sm: 28, md: 40, lg: 48 }
|
|
2901
|
+
};
|
|
2902
|
+
var CONTROL_PADDING_BY_DENSITY = {
|
|
2903
|
+
compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
|
|
2904
|
+
normal: { none: 0, xs: 6, sm: 10, md: 14, lg: 18, xl: 24 },
|
|
2905
|
+
comfortable: { none: 0, xs: 8, sm: 12, md: 16, lg: 22, xl: 28 }
|
|
2383
2906
|
};
|
|
2384
2907
|
function resolveIconSize(size, density = "normal") {
|
|
2385
2908
|
const map = ICON_SIZES_BY_DENSITY[density] || ICON_SIZES_BY_DENSITY.normal;
|
|
@@ -2389,6 +2912,18 @@ function resolveIconButtonSize(size, density = "normal") {
|
|
|
2389
2912
|
const map = ICON_BUTTON_SIZES_BY_DENSITY[density] || ICON_BUTTON_SIZES_BY_DENSITY.normal;
|
|
2390
2913
|
return map[size || "md"] || map.md;
|
|
2391
2914
|
}
|
|
2915
|
+
function resolveControlHeight(size, density = "normal") {
|
|
2916
|
+
const map = CONTROL_HEIGHTS_BY_DENSITY[density] || CONTROL_HEIGHTS_BY_DENSITY.normal;
|
|
2917
|
+
return map[size || "md"] || map.md;
|
|
2918
|
+
}
|
|
2919
|
+
function resolveActionControlHeight(size, density = "normal") {
|
|
2920
|
+
const map = ACTION_CONTROL_HEIGHTS_BY_DENSITY[density] || ACTION_CONTROL_HEIGHTS_BY_DENSITY.normal;
|
|
2921
|
+
return map[size || "md"] || map.md;
|
|
2922
|
+
}
|
|
2923
|
+
function resolveControlHorizontalPadding(padding, density = "normal") {
|
|
2924
|
+
const map = CONTROL_PADDING_BY_DENSITY[density] || CONTROL_PADDING_BY_DENSITY.normal;
|
|
2925
|
+
return map[padding || "md"] ?? map.md;
|
|
2926
|
+
}
|
|
2392
2927
|
|
|
2393
2928
|
// src/shared/heading-levels.ts
|
|
2394
2929
|
var DEFAULT_LEVEL = "h2";
|
|
@@ -2707,6 +3242,41 @@ var LayoutEngine = class {
|
|
|
2707
3242
|
}
|
|
2708
3243
|
return totalHeight;
|
|
2709
3244
|
}
|
|
3245
|
+
if (node.containerType === "split") {
|
|
3246
|
+
const splitGap = this.resolveSpacing(node.style.gap);
|
|
3247
|
+
const leftParam = node.params.left;
|
|
3248
|
+
const rightParam = node.params.right;
|
|
3249
|
+
const leftWidthRaw = Number(leftParam);
|
|
3250
|
+
const rightWidthRaw = Number(rightParam);
|
|
3251
|
+
const hasLeft = leftParam !== void 0;
|
|
3252
|
+
const hasRight = rightParam !== void 0;
|
|
3253
|
+
const leftWidth = Number.isFinite(leftWidthRaw) && leftWidthRaw > 0 ? leftWidthRaw : availableWidth / 2;
|
|
3254
|
+
const rightWidth = Number.isFinite(rightWidthRaw) && rightWidthRaw > 0 ? rightWidthRaw : availableWidth / 2;
|
|
3255
|
+
let maxHeight = 0;
|
|
3256
|
+
node.children.forEach((childRef, index) => {
|
|
3257
|
+
const child = this.nodes[childRef.ref];
|
|
3258
|
+
let childHeight = this.getComponentHeight();
|
|
3259
|
+
const isFirst = index === 0;
|
|
3260
|
+
let childWidth;
|
|
3261
|
+
if (node.children.length >= 2) {
|
|
3262
|
+
if (hasRight && !hasLeft) {
|
|
3263
|
+
childWidth = isFirst ? Math.max(1, availableWidth - rightWidth - splitGap) : rightWidth;
|
|
3264
|
+
} else {
|
|
3265
|
+
childWidth = isFirst ? leftWidth : Math.max(1, availableWidth - leftWidth - splitGap);
|
|
3266
|
+
}
|
|
3267
|
+
} else {
|
|
3268
|
+
childWidth = availableWidth;
|
|
3269
|
+
}
|
|
3270
|
+
if (child?.kind === "component") {
|
|
3271
|
+
childHeight = child.props.height ? Number(child.props.height) : this.getIntrinsicComponentHeight(child, childWidth);
|
|
3272
|
+
} else if (child?.kind === "container") {
|
|
3273
|
+
childHeight = this.calculateContainerHeight(child, childWidth);
|
|
3274
|
+
}
|
|
3275
|
+
maxHeight = Math.max(maxHeight, childHeight);
|
|
3276
|
+
});
|
|
3277
|
+
totalHeight += maxHeight;
|
|
3278
|
+
return totalHeight;
|
|
3279
|
+
}
|
|
2710
3280
|
const direction = node.params.direction || "vertical";
|
|
2711
3281
|
if (node.containerType === "stack" && direction === "horizontal") {
|
|
2712
3282
|
let maxHeight = 0;
|
|
@@ -2829,14 +3399,28 @@ var LayoutEngine = class {
|
|
|
2829
3399
|
calculateSplit(node, x, y, width, height) {
|
|
2830
3400
|
if (node.kind !== "container") return;
|
|
2831
3401
|
const gap = this.resolveSpacing(node.style.gap);
|
|
2832
|
-
const
|
|
3402
|
+
const leftParam = node.params.left;
|
|
3403
|
+
const rightParam = node.params.right;
|
|
3404
|
+
const leftWidthRaw = Number(leftParam);
|
|
3405
|
+
const rightWidthRaw = Number(rightParam);
|
|
3406
|
+
const hasLeft = leftParam !== void 0;
|
|
3407
|
+
const hasRight = rightParam !== void 0;
|
|
3408
|
+
const leftWidth = Number.isFinite(leftWidthRaw) && leftWidthRaw > 0 ? leftWidthRaw : 250;
|
|
3409
|
+
const rightWidth = Number.isFinite(rightWidthRaw) && rightWidthRaw > 0 ? rightWidthRaw : 250;
|
|
2833
3410
|
if (node.children.length === 1) {
|
|
2834
3411
|
this.calculateNode(node.children[0].ref, x, y, width, height, "split");
|
|
2835
3412
|
} else if (node.children.length >= 2) {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3413
|
+
if (hasRight && !hasLeft) {
|
|
3414
|
+
const flexibleLeftWidth = Math.max(1, width - rightWidth - gap);
|
|
3415
|
+
const rightX = x + flexibleLeftWidth + gap;
|
|
3416
|
+
this.calculateNode(node.children[0].ref, x, y, flexibleLeftWidth, height, "split");
|
|
3417
|
+
this.calculateNode(node.children[1].ref, rightX, y, rightWidth, height, "split");
|
|
3418
|
+
} else {
|
|
3419
|
+
const flexibleRightWidth = Math.max(1, width - leftWidth - gap);
|
|
3420
|
+
const rightX = x + leftWidth + gap;
|
|
3421
|
+
this.calculateNode(node.children[0].ref, x, y, leftWidth, height, "split");
|
|
3422
|
+
this.calculateNode(node.children[1].ref, rightX, y, flexibleRightWidth, height, "split");
|
|
3423
|
+
}
|
|
2840
3424
|
}
|
|
2841
3425
|
}
|
|
2842
3426
|
calculatePanel(node, x, y, width, height) {
|
|
@@ -2985,7 +3569,11 @@ var LayoutEngine = class {
|
|
|
2985
3569
|
}
|
|
2986
3570
|
getIntrinsicComponentHeight(node, availableWidth) {
|
|
2987
3571
|
if (node.kind !== "component") return this.getComponentHeight();
|
|
2988
|
-
const
|
|
3572
|
+
const controlSize = String(node.props.size || "md");
|
|
3573
|
+
const density = this.style.density || "normal";
|
|
3574
|
+
const inputControlHeight = resolveControlHeight(controlSize, density);
|
|
3575
|
+
const actionControlHeight = resolveActionControlHeight(controlSize, density);
|
|
3576
|
+
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
3577
|
if (node.componentType === "Image") {
|
|
2990
3578
|
const placeholder = String(node.props.placeholder || "landscape");
|
|
2991
3579
|
const aspectRatios = {
|
|
@@ -3012,12 +3600,26 @@ var LayoutEngine = class {
|
|
|
3012
3600
|
}
|
|
3013
3601
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
3014
3602
|
const hasTitle = !!node.props.title;
|
|
3015
|
-
const hasPagination =
|
|
3603
|
+
const hasPagination = this.parseBooleanProp(node.props.pagination, false);
|
|
3604
|
+
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
3605
|
+
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
3606
|
+
const captionAlign = String(node.props.captionAlign || "");
|
|
3607
|
+
const effectiveCaptionAlign = captionAlign === "left" || captionAlign === "center" || captionAlign === "right" ? captionAlign : paginationAlign === "left" ? "right" : "left";
|
|
3608
|
+
const sameFooterAlign = hasCaption && hasPagination && effectiveCaptionAlign === paginationAlign;
|
|
3016
3609
|
const headerHeight = 44;
|
|
3017
3610
|
const rowHeight = 36;
|
|
3018
3611
|
const titleHeight = hasTitle ? 32 : 0;
|
|
3019
|
-
|
|
3020
|
-
|
|
3612
|
+
let footerHeight = 0;
|
|
3613
|
+
if (hasPagination || hasCaption) {
|
|
3614
|
+
const footerBottomPadding = 12;
|
|
3615
|
+
footerHeight += 16;
|
|
3616
|
+
footerHeight += hasPagination ? 32 : 18;
|
|
3617
|
+
if (sameFooterAlign) {
|
|
3618
|
+
footerHeight += 8 + 18;
|
|
3619
|
+
}
|
|
3620
|
+
footerHeight += footerBottomPadding;
|
|
3621
|
+
}
|
|
3622
|
+
return titleHeight + headerHeight + rowCount * rowHeight + footerHeight;
|
|
3021
3623
|
}
|
|
3022
3624
|
if (node.componentType === "Heading") {
|
|
3023
3625
|
const text = String(node.props.text || "Heading");
|
|
@@ -3026,15 +3628,15 @@ var LayoutEngine = class {
|
|
|
3026
3628
|
const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
|
|
3027
3629
|
const lines = this.wrapTextToLines(text, maxWidth, fontSize);
|
|
3028
3630
|
const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
|
|
3029
|
-
const
|
|
3030
|
-
const verticalPadding = resolveHeadingVerticalPadding(node.props.spacing,
|
|
3631
|
+
const density2 = this.style.density || "normal";
|
|
3632
|
+
const verticalPadding = resolveHeadingVerticalPadding(node.props.spacing, density2);
|
|
3031
3633
|
if (verticalPadding === null) {
|
|
3032
3634
|
return Math.max(this.getComponentHeight(), wrappedHeight);
|
|
3033
3635
|
}
|
|
3034
3636
|
return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
|
|
3035
3637
|
}
|
|
3036
3638
|
if (node.componentType === "Text") {
|
|
3037
|
-
const content = String(node.props.
|
|
3639
|
+
const content = String(node.props.text || "");
|
|
3038
3640
|
const { fontSize, lineHeight } = this.getTextMetricsForDensity();
|
|
3039
3641
|
const lineHeightPx = Math.ceil(fontSize * lineHeight);
|
|
3040
3642
|
const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
|
|
@@ -3083,7 +3685,10 @@ var LayoutEngine = class {
|
|
|
3083
3685
|
if (node.componentType === "Divider") return 1;
|
|
3084
3686
|
if (node.componentType === "Separate") return this.getSeparateSize(node);
|
|
3085
3687
|
if (node.componentType === "Input" || node.componentType === "Select") {
|
|
3086
|
-
return
|
|
3688
|
+
return inputControlHeight + controlLabelOffset;
|
|
3689
|
+
}
|
|
3690
|
+
if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
|
|
3691
|
+
return actionControlHeight + controlLabelOffset;
|
|
3087
3692
|
}
|
|
3088
3693
|
return this.getComponentHeight();
|
|
3089
3694
|
}
|
|
@@ -3100,7 +3705,10 @@ var LayoutEngine = class {
|
|
|
3100
3705
|
}
|
|
3101
3706
|
if (node.componentType === "IconButton") {
|
|
3102
3707
|
const size = String(node.props.size || "md");
|
|
3103
|
-
|
|
3708
|
+
const density = this.style.density || "normal";
|
|
3709
|
+
const baseSize = resolveIconButtonSize(size, density);
|
|
3710
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
3711
|
+
return baseSize + extraPadding * 2;
|
|
3104
3712
|
}
|
|
3105
3713
|
if (node.componentType === "Checkbox" || node.componentType === "Radio") {
|
|
3106
3714
|
return 24;
|
|
@@ -3109,11 +3717,13 @@ var LayoutEngine = class {
|
|
|
3109
3717
|
if (node.componentType === "Button" || node.componentType === "Link") {
|
|
3110
3718
|
const text = String(node.props.text || "");
|
|
3111
3719
|
const { fontSize, paddingX } = this.getButtonMetricsForDensity();
|
|
3720
|
+
const density = this.style.density || "normal";
|
|
3721
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
3112
3722
|
const textWidth = this.estimateTextWidth(text, fontSize);
|
|
3113
|
-
return Math.max(60, Math.ceil(textWidth + paddingX * 2));
|
|
3723
|
+
return Math.max(60, Math.ceil(textWidth + (paddingX + extraPadding) * 2));
|
|
3114
3724
|
}
|
|
3115
3725
|
if (node.componentType === "Label" || node.componentType === "Text") {
|
|
3116
|
-
const text = String(node.props.
|
|
3726
|
+
const text = String(node.props.text || "");
|
|
3117
3727
|
return Math.max(60, text.length * 8 + 16);
|
|
3118
3728
|
}
|
|
3119
3729
|
if (node.componentType === "Heading") {
|
|
@@ -3975,27 +4585,27 @@ var THEMES = {
|
|
|
3975
4585
|
bg: "#F8FAFC",
|
|
3976
4586
|
cardBg: "#FFFFFF",
|
|
3977
4587
|
border: "#E2E8F0",
|
|
3978
|
-
text: "#
|
|
4588
|
+
text: "#000000",
|
|
3979
4589
|
textMuted: "#64748B",
|
|
3980
4590
|
primary: "#3B82F6",
|
|
3981
4591
|
primaryHover: "#2563EB",
|
|
3982
4592
|
primaryLight: "#EFF6FF"
|
|
3983
4593
|
},
|
|
3984
4594
|
dark: {
|
|
3985
|
-
bg: "#
|
|
3986
|
-
cardBg: "#
|
|
3987
|
-
border: "#
|
|
3988
|
-
text: "#
|
|
3989
|
-
textMuted: "#
|
|
4595
|
+
bg: "#111111",
|
|
4596
|
+
cardBg: "#1C1C1C",
|
|
4597
|
+
border: "#303030",
|
|
4598
|
+
text: "#F0F0F0",
|
|
4599
|
+
textMuted: "#808080",
|
|
3990
4600
|
primary: "#60A5FA",
|
|
3991
4601
|
primaryHover: "#3B82F6",
|
|
3992
|
-
primaryLight: "#
|
|
4602
|
+
primaryLight: "#1C2A3A"
|
|
3993
4603
|
}
|
|
3994
4604
|
};
|
|
3995
4605
|
var SVGRenderer = class {
|
|
3996
4606
|
constructor(ir, layout, options) {
|
|
3997
4607
|
this.renderedNodeIds = /* @__PURE__ */ new Set();
|
|
3998
|
-
this.fontFamily = "
|
|
4608
|
+
this.fontFamily = "Arial, Helvetica, sans-serif";
|
|
3999
4609
|
this.parentContainerByChildId = /* @__PURE__ */ new Map();
|
|
4000
4610
|
this.ir = ir;
|
|
4001
4611
|
this.layout = layout;
|
|
@@ -4009,7 +4619,6 @@ var SVGRenderer = class {
|
|
|
4009
4619
|
includeLabels: options?.includeLabels ?? true,
|
|
4010
4620
|
screenName: options?.screenName
|
|
4011
4621
|
};
|
|
4012
|
-
this.renderTheme = THEMES[this.options.theme];
|
|
4013
4622
|
this.colorResolver = new ColorResolver();
|
|
4014
4623
|
this.buildParentContainerIndex();
|
|
4015
4624
|
if (ir.project.mocks && Object.keys(ir.project.mocks).length > 0) {
|
|
@@ -4018,6 +4627,12 @@ var SVGRenderer = class {
|
|
|
4018
4627
|
if (ir.project.colors && Object.keys(ir.project.colors).length > 0) {
|
|
4019
4628
|
this.colorResolver.setCustomColors(ir.project.colors);
|
|
4020
4629
|
}
|
|
4630
|
+
const themeDefaults = THEMES[this.options.theme];
|
|
4631
|
+
this.renderTheme = {
|
|
4632
|
+
...themeDefaults,
|
|
4633
|
+
text: this.resolveTextColor(),
|
|
4634
|
+
textMuted: this.resolveMutedColor()
|
|
4635
|
+
};
|
|
4021
4636
|
}
|
|
4022
4637
|
/**
|
|
4023
4638
|
* Get list of available screens in the project
|
|
@@ -4099,6 +4714,9 @@ var SVGRenderer = class {
|
|
|
4099
4714
|
if (node.containerType === "card") {
|
|
4100
4715
|
this.renderCardBorder(node, pos, containerGroup);
|
|
4101
4716
|
}
|
|
4717
|
+
if (node.containerType === "split") {
|
|
4718
|
+
this.renderSplitDecoration(node, pos, containerGroup);
|
|
4719
|
+
}
|
|
4102
4720
|
node.children.forEach((childRef) => {
|
|
4103
4721
|
this.renderNode(childRef.ref, containerGroup);
|
|
4104
4722
|
});
|
|
@@ -4186,6 +4804,8 @@ var SVGRenderer = class {
|
|
|
4186
4804
|
}
|
|
4187
4805
|
renderHeading(node, pos) {
|
|
4188
4806
|
const text = String(node.props.text || "Heading");
|
|
4807
|
+
const variant = String(node.props.variant || "default");
|
|
4808
|
+
const headingColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
4189
4809
|
const headingTypography = this.getHeadingTypography(node);
|
|
4190
4810
|
const fontSize = headingTypography.fontSize;
|
|
4191
4811
|
const fontWeight = headingTypography.fontWeight;
|
|
@@ -4195,10 +4815,10 @@ var SVGRenderer = class {
|
|
|
4195
4815
|
if (lines.length <= 1) {
|
|
4196
4816
|
return `<g${this.getDataNodeId(node)}>
|
|
4197
4817
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4198
|
-
font-family="
|
|
4818
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4199
4819
|
font-size="${fontSize}"
|
|
4200
4820
|
font-weight="${fontWeight}"
|
|
4201
|
-
fill="${
|
|
4821
|
+
fill="${headingColor}">${this.escapeXml(text)}</text>
|
|
4202
4822
|
</g>`;
|
|
4203
4823
|
}
|
|
4204
4824
|
const tspans = lines.map(
|
|
@@ -4206,61 +4826,101 @@ var SVGRenderer = class {
|
|
|
4206
4826
|
).join("");
|
|
4207
4827
|
return `<g${this.getDataNodeId(node)}>
|
|
4208
4828
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4209
|
-
font-family="
|
|
4829
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4210
4830
|
font-size="${fontSize}"
|
|
4211
4831
|
font-weight="${fontWeight}"
|
|
4212
|
-
fill="${
|
|
4832
|
+
fill="${headingColor}">${tspans}</text>
|
|
4213
4833
|
</g>`;
|
|
4214
4834
|
}
|
|
4215
4835
|
renderButton(node, pos) {
|
|
4216
4836
|
const text = String(node.props.text || "Button");
|
|
4217
4837
|
const variant = String(node.props.variant || "default");
|
|
4838
|
+
const size = String(node.props.size || "md");
|
|
4839
|
+
const density = this.ir.project.style.density || "normal";
|
|
4840
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
4841
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
4218
4842
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
4843
|
+
const iconName = String(node.props.icon || "").trim();
|
|
4844
|
+
const iconAlign = String(node.props.iconAlign || "left").toLowerCase();
|
|
4219
4845
|
const radius = this.tokens.button.radius;
|
|
4220
4846
|
const fontSize = this.tokens.button.fontSize;
|
|
4221
4847
|
const fontWeight = this.tokens.button.fontWeight;
|
|
4222
4848
|
const paddingX = this.tokens.button.paddingX;
|
|
4223
|
-
const
|
|
4849
|
+
const controlHeight = resolveActionControlHeight(size, density);
|
|
4850
|
+
const buttonY = pos.y + labelOffset;
|
|
4851
|
+
const buttonHeight = Math.max(16, Math.min(controlHeight, pos.height - labelOffset));
|
|
4852
|
+
const iconSvg = iconName ? getIcon(iconName) : null;
|
|
4853
|
+
const iconSize = iconSvg ? Math.round(fontSize * 1.1) : 0;
|
|
4854
|
+
const iconGap = iconSvg ? 8 : 0;
|
|
4855
|
+
const edgePad = 12;
|
|
4856
|
+
const textPad = paddingX + extraPadding;
|
|
4224
4857
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4225
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth +
|
|
4226
|
-
const
|
|
4227
|
-
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
4858
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (iconSvg ? iconSize + iconGap : 0) + textPad * 2), 60), pos.width);
|
|
4859
|
+
const availableTextWidth = Math.max(0, buttonWidth - textPad * 2 - (iconSvg ? iconSize + iconGap : 0));
|
|
4228
4860
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4229
4861
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
4230
4862
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
4231
4863
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4232
|
-
const
|
|
4233
|
-
const
|
|
4234
|
-
const
|
|
4235
|
-
|
|
4236
|
-
|
|
4864
|
+
const isDarkMode = this.options.theme === "dark";
|
|
4865
|
+
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : isDarkMode ? "rgba(48, 48, 55, 0.9)" : "rgba(226, 232, 240, 0.9)";
|
|
4866
|
+
const textColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.85);
|
|
4867
|
+
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : isDarkMode ? "rgba(75, 75, 88, 0.8)" : "rgba(100, 116, 139, 0.4)";
|
|
4868
|
+
const iconOffsetY = buttonY + (buttonHeight - iconSize) / 2;
|
|
4869
|
+
const iconX = iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize : pos.x + edgePad;
|
|
4870
|
+
const textAlign = String(node.props.align || "center").toLowerCase();
|
|
4871
|
+
const sidePad = textPad + 4;
|
|
4872
|
+
let textX;
|
|
4873
|
+
let textAnchor;
|
|
4874
|
+
if (textAlign === "left") {
|
|
4875
|
+
textX = iconSvg && iconAlign === "left" ? pos.x + edgePad + iconSize + iconGap : pos.x + sidePad;
|
|
4876
|
+
textAnchor = "start";
|
|
4877
|
+
} else if (textAlign === "right") {
|
|
4878
|
+
textX = iconSvg && iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize - iconGap : pos.x + buttonWidth - sidePad;
|
|
4879
|
+
textAnchor = "end";
|
|
4880
|
+
} else {
|
|
4881
|
+
textX = pos.x + buttonWidth / 2;
|
|
4882
|
+
textAnchor = "middle";
|
|
4883
|
+
}
|
|
4884
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
4885
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
4237
4886
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4238
4887
|
rx="${radius}"
|
|
4239
4888
|
fill="${bgColor}"
|
|
4240
4889
|
stroke="${borderColor}"
|
|
4241
|
-
stroke-width="1"
|
|
4242
|
-
|
|
4243
|
-
|
|
4890
|
+
stroke-width="1"/>`;
|
|
4891
|
+
if (iconSvg) {
|
|
4892
|
+
svg += `
|
|
4893
|
+
<g transform="translate(${iconX}, ${iconOffsetY})">
|
|
4894
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${textColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
4895
|
+
${this.extractSvgContent(iconSvg)}
|
|
4896
|
+
</svg>
|
|
4897
|
+
</g>`;
|
|
4898
|
+
}
|
|
4899
|
+
svg += `
|
|
4900
|
+
<text x="${textX}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
4901
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4244
4902
|
font-size="${fontSize}"
|
|
4245
4903
|
font-weight="${fontWeight}"
|
|
4246
4904
|
fill="${textColor}"
|
|
4247
|
-
text-anchor="
|
|
4905
|
+
text-anchor="${textAnchor}">${this.escapeXml(visibleText)}</text>
|
|
4248
4906
|
</g>`;
|
|
4907
|
+
return svg;
|
|
4249
4908
|
}
|
|
4250
4909
|
renderLink(node, pos) {
|
|
4251
4910
|
const text = String(node.props.text || "Link");
|
|
4252
4911
|
const variant = String(node.props.variant || "primary");
|
|
4912
|
+
const size = String(node.props.size || "md");
|
|
4913
|
+
const density = this.ir.project.style.density || "normal";
|
|
4253
4914
|
const fontSize = this.tokens.button.fontSize;
|
|
4254
4915
|
const fontWeight = this.tokens.button.fontWeight;
|
|
4255
4916
|
const paddingX = this.tokens.button.paddingX;
|
|
4256
|
-
const paddingY = this.tokens.button.paddingY;
|
|
4257
4917
|
const linkColor = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4258
4918
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4259
4919
|
const linkWidth = this.clampControlWidth(
|
|
4260
4920
|
Math.max(Math.ceil(idealTextWidth + paddingX * 2), 60),
|
|
4261
4921
|
pos.width
|
|
4262
4922
|
);
|
|
4263
|
-
const linkHeight =
|
|
4923
|
+
const linkHeight = Math.max(16, Math.min(resolveActionControlHeight(size, density), pos.height));
|
|
4264
4924
|
const availableTextWidth = Math.max(0, linkWidth - paddingX * 2);
|
|
4265
4925
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4266
4926
|
const visibleTextWidth = Math.min(
|
|
@@ -4271,7 +4931,7 @@ var SVGRenderer = class {
|
|
|
4271
4931
|
const underlineY = centerY + 3;
|
|
4272
4932
|
return `<g${this.getDataNodeId(node)}>
|
|
4273
4933
|
<text x="${pos.x + linkWidth / 2}" y="${centerY}"
|
|
4274
|
-
font-family="
|
|
4934
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4275
4935
|
font-size="${fontSize}"
|
|
4276
4936
|
font-weight="${fontWeight}"
|
|
4277
4937
|
fill="${linkColor}"
|
|
@@ -4285,53 +4945,108 @@ var SVGRenderer = class {
|
|
|
4285
4945
|
renderInput(node, pos) {
|
|
4286
4946
|
const label = String(node.props.label || "");
|
|
4287
4947
|
const placeholder = String(node.props.placeholder || "");
|
|
4948
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
4949
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
4288
4950
|
const radius = this.tokens.input.radius;
|
|
4289
4951
|
const fontSize = this.tokens.input.fontSize;
|
|
4290
4952
|
const paddingX = this.tokens.input.paddingX;
|
|
4291
4953
|
const labelOffset = this.getControlLabelOffset(label);
|
|
4292
4954
|
const controlY = pos.y + labelOffset;
|
|
4293
4955
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4956
|
+
const iconSize = 16;
|
|
4957
|
+
const iconPad = 12;
|
|
4958
|
+
const iconInnerGap = 8;
|
|
4959
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
4960
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
4961
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
4962
|
+
const rightOffset = iconRightSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
4963
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : paddingX);
|
|
4964
|
+
const iconColor = this.hexToRgba(this.resolveMutedColor(), 0.8);
|
|
4965
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
4966
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
4967
|
+
if (label) {
|
|
4968
|
+
svg += `
|
|
4969
|
+
<text x="${pos.x + paddingX}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4970
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4971
|
+
font-size="12"
|
|
4972
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
4973
|
+
}
|
|
4974
|
+
svg += `
|
|
4299
4975
|
<rect x="${pos.x}" y="${controlY}"
|
|
4300
4976
|
width="${pos.width}" height="${controlHeight}"
|
|
4301
4977
|
rx="${radius}"
|
|
4302
4978
|
fill="${this.renderTheme.cardBg}"
|
|
4303
4979
|
stroke="${this.renderTheme.border}"
|
|
4304
|
-
stroke-width="1"
|
|
4305
|
-
|
|
4306
|
-
|
|
4980
|
+
stroke-width="1"/>`;
|
|
4981
|
+
if (iconLeftSvg) {
|
|
4982
|
+
svg += `
|
|
4983
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
4984
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
4985
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
4986
|
+
</svg>
|
|
4987
|
+
</g>`;
|
|
4988
|
+
}
|
|
4989
|
+
if (iconRightSvg) {
|
|
4990
|
+
svg += `
|
|
4991
|
+
<g transform="translate(${pos.x + pos.width - iconPad - iconSize}, ${iconCenterY})">
|
|
4992
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
4993
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
4994
|
+
</svg>
|
|
4995
|
+
</g>`;
|
|
4996
|
+
}
|
|
4997
|
+
if (placeholder) {
|
|
4998
|
+
const availPlaceholderWidth = pos.width - (iconLeftSvg ? leftOffset : paddingX) - (iconRightSvg ? rightOffset : paddingX);
|
|
4999
|
+
const visiblePlaceholder = this.truncateTextToWidth(placeholder, Math.max(0, availPlaceholderWidth), fontSize);
|
|
5000
|
+
svg += `
|
|
5001
|
+
<text x="${textX}" y="${controlY + controlHeight / 2 + 5}"
|
|
5002
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4307
5003
|
font-size="${fontSize}"
|
|
4308
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
4309
|
-
|
|
5004
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePlaceholder)}</text>`;
|
|
5005
|
+
}
|
|
5006
|
+
svg += "\n </g>";
|
|
5007
|
+
return svg;
|
|
4310
5008
|
}
|
|
4311
5009
|
renderTopbar(node, pos) {
|
|
4312
5010
|
const title = String(node.props.title || "App");
|
|
4313
5011
|
const subtitle = String(node.props.subtitle || "");
|
|
4314
5012
|
const actions = String(node.props.actions || "");
|
|
4315
5013
|
const user = String(node.props.user || "");
|
|
4316
|
-
const
|
|
5014
|
+
const variant = String(node.props.variant || "default");
|
|
5015
|
+
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
5016
|
+
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
5017
|
+
const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
|
|
5018
|
+
const radiusMap = {
|
|
5019
|
+
none: 0,
|
|
5020
|
+
sm: 4,
|
|
5021
|
+
md: this.tokens.card.radius,
|
|
5022
|
+
lg: 12,
|
|
5023
|
+
xl: 16
|
|
5024
|
+
};
|
|
5025
|
+
const topbarRadius = radiusMap[String(node.props.radius || "md")] ?? this.tokens.card.radius;
|
|
4317
5026
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
4318
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
5027
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5028
|
+
if (showBorder || showBackground) {
|
|
5029
|
+
const bg = showBackground ? this.renderTheme.cardBg : "none";
|
|
5030
|
+
const stroke = showBorder ? this.renderTheme.border : "none";
|
|
5031
|
+
svg += `
|
|
4319
5032
|
<rect x="${pos.x}" y="${pos.y}"
|
|
4320
5033
|
width="${pos.width}" height="${pos.height}"
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
stroke
|
|
4324
|
-
|
|
5034
|
+
rx="${topbarRadius}"
|
|
5035
|
+
fill="${bg}"
|
|
5036
|
+
stroke="${stroke}"
|
|
5037
|
+
stroke-width="1"/>`;
|
|
5038
|
+
}
|
|
5039
|
+
svg += `
|
|
4325
5040
|
<!-- Title -->
|
|
4326
5041
|
<text x="${topbar.textX}" y="${topbar.titleY}"
|
|
4327
|
-
font-family="
|
|
5042
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4328
5043
|
font-size="18"
|
|
4329
5044
|
font-weight="600"
|
|
4330
5045
|
fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
|
|
4331
5046
|
if (topbar.hasSubtitle) {
|
|
4332
5047
|
svg += `
|
|
4333
5048
|
<text x="${topbar.textX}" y="${topbar.subtitleY}"
|
|
4334
|
-
font-family="
|
|
5049
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4335
5050
|
font-size="13"
|
|
4336
5051
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
|
|
4337
5052
|
}
|
|
@@ -4359,7 +5074,7 @@ var SVGRenderer = class {
|
|
|
4359
5074
|
fill="${accentColor}"
|
|
4360
5075
|
stroke="none"/>
|
|
4361
5076
|
<text x="${action.x + action.width / 2}" y="${action.y + action.height / 2 + 4}"
|
|
4362
|
-
font-family="
|
|
5077
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4363
5078
|
font-size="12"
|
|
4364
5079
|
font-weight="600"
|
|
4365
5080
|
fill="white"
|
|
@@ -4375,7 +5090,7 @@ var SVGRenderer = class {
|
|
|
4375
5090
|
stroke="${this.renderTheme.border}"
|
|
4376
5091
|
stroke-width="1"/>
|
|
4377
5092
|
<text x="${topbar.userBadge.x + topbar.userBadge.width / 2}" y="${topbar.userBadge.y + topbar.userBadge.height / 2 + 4}"
|
|
4378
|
-
font-family="
|
|
5093
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4379
5094
|
font-size="12"
|
|
4380
5095
|
fill="${this.renderTheme.text}"
|
|
4381
5096
|
text-anchor="middle">${this.escapeXml(topbar.userBadge.label)}</text>`;
|
|
@@ -4438,77 +5153,165 @@ var SVGRenderer = class {
|
|
|
4438
5153
|
</g>`;
|
|
4439
5154
|
output.push(svg);
|
|
4440
5155
|
}
|
|
5156
|
+
renderSplitDecoration(node, pos, output) {
|
|
5157
|
+
if (node.kind !== "container") return;
|
|
5158
|
+
const gap = this.resolveSpacing(node.style.gap);
|
|
5159
|
+
const leftParam = Number(node.params.left);
|
|
5160
|
+
const rightParam = Number(node.params.right);
|
|
5161
|
+
const hasLeft = node.params.left !== void 0;
|
|
5162
|
+
const hasRight = node.params.right !== void 0 && node.params.left === void 0;
|
|
5163
|
+
const fixedLeftWidth = Number.isFinite(leftParam) && leftParam > 0 ? leftParam : 250;
|
|
5164
|
+
const fixedRightWidth = Number.isFinite(rightParam) && rightParam > 0 ? rightParam : 250;
|
|
5165
|
+
const backgroundKey = String(node.style.background || "").trim();
|
|
5166
|
+
const showBorder = this.parseBooleanProp(node.params.border, false);
|
|
5167
|
+
if (backgroundKey) {
|
|
5168
|
+
const fill = this.colorResolver.resolveColor(backgroundKey, this.renderTheme.cardBg);
|
|
5169
|
+
if (hasRight) {
|
|
5170
|
+
const panelX = pos.x + Math.max(0, pos.width - fixedRightWidth);
|
|
5171
|
+
output.push(`<g>
|
|
5172
|
+
<rect x="${panelX}" y="${pos.y}" width="${Math.max(1, fixedRightWidth)}" height="${pos.height}" fill="${fill}" stroke="none"/>
|
|
5173
|
+
</g>`);
|
|
5174
|
+
} else if (hasLeft || !hasRight) {
|
|
5175
|
+
output.push(`<g>
|
|
5176
|
+
<rect x="${pos.x}" y="${pos.y}" width="${Math.max(1, fixedLeftWidth)}" height="${pos.height}" fill="${fill}" stroke="none"/>
|
|
5177
|
+
</g>`);
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
if (showBorder) {
|
|
5181
|
+
const dividerX = hasRight ? pos.x + Math.max(0, pos.width - fixedRightWidth - gap / 2) : pos.x + Math.max(0, fixedLeftWidth + gap / 2);
|
|
5182
|
+
output.push(`<g>
|
|
5183
|
+
<line x1="${dividerX}" y1="${pos.y}" x2="${dividerX}" y2="${pos.y + pos.height}" stroke="${this.renderTheme.border}" stroke-width="1"/>
|
|
5184
|
+
</g>`);
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
4441
5187
|
renderTable(node, pos) {
|
|
4442
5188
|
const title = String(node.props.title || "");
|
|
4443
5189
|
const columnsStr = String(node.props.columns || "Col1,Col2,Col3");
|
|
4444
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
5190
|
+
const columns = columnsStr.split(",").map((c) => c.trim()).filter(Boolean);
|
|
4445
5191
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
4446
5192
|
const mockStr = String(node.props.mock || "");
|
|
4447
5193
|
const random = this.parseBooleanProp(node.props.random, false);
|
|
4448
|
-
const
|
|
4449
|
-
const
|
|
4450
|
-
const pageCount = Number(
|
|
5194
|
+
const pagination = this.parseBooleanProp(node.props.pagination, false);
|
|
5195
|
+
const parsedPageCount = Number(node.props.pages || 5);
|
|
5196
|
+
const pageCount = Number.isFinite(parsedPageCount) && parsedPageCount > 0 ? Math.floor(parsedPageCount) : 5;
|
|
4451
5197
|
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
5198
|
+
const actions = String(node.props.actions || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
5199
|
+
const hasActions = actions.length > 0;
|
|
5200
|
+
const caption = String(node.props.caption || "").trim();
|
|
5201
|
+
const hasCaption = caption.length > 0;
|
|
5202
|
+
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
5203
|
+
const showOuterBackground = this.parseBooleanProp(
|
|
5204
|
+
node.props.background ?? node.props.backround,
|
|
5205
|
+
false
|
|
5206
|
+
);
|
|
5207
|
+
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
5208
|
+
const rawCaptionAlign = String(node.props.captionAlign || "");
|
|
5209
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
5210
|
+
const sameFooterAlign = hasCaption && pagination && captionAlign === paginationAlign;
|
|
4452
5211
|
const mockTypes = mockStr ? mockStr.split(",").map((m) => m.trim()).filter(Boolean) : [];
|
|
4453
|
-
|
|
4454
|
-
|
|
5212
|
+
const safeColumns = columns.length > 0 ? columns : ["Column"];
|
|
5213
|
+
while (mockTypes.length < safeColumns.length) {
|
|
5214
|
+
const inferred = MockDataGenerator.inferMockTypeFromColumn(safeColumns[mockTypes.length] || "item");
|
|
4455
5215
|
mockTypes.push(inferred);
|
|
4456
5216
|
}
|
|
4457
5217
|
const headerHeight = 44;
|
|
4458
5218
|
const rowHeight = 36;
|
|
4459
|
-
const
|
|
5219
|
+
const actionColumnWidth = hasActions ? Math.max(96, Math.min(180, actions.length * 26 + 28)) : 0;
|
|
5220
|
+
const dataWidth = Math.max(20, pos.width - actionColumnWidth);
|
|
5221
|
+
const dataColWidth = dataWidth / safeColumns.length;
|
|
4460
5222
|
const mockRows = [];
|
|
4461
5223
|
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
4462
5224
|
const row = {};
|
|
4463
|
-
|
|
5225
|
+
safeColumns.forEach((col, colIdx) => {
|
|
4464
5226
|
const mockType = mockTypes[colIdx] || MockDataGenerator.inferMockTypeFromColumn(col) || "item";
|
|
4465
5227
|
row[col] = MockDataGenerator.getMockValue(mockType, rowIdx, random);
|
|
4466
5228
|
});
|
|
4467
5229
|
mockRows.push(row);
|
|
4468
5230
|
}
|
|
4469
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
5231
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5232
|
+
if (showOuterBorder || showOuterBackground) {
|
|
5233
|
+
const outerFill = showOuterBackground ? this.renderTheme.cardBg : "none";
|
|
5234
|
+
const outerStroke = showOuterBorder ? this.renderTheme.border : "none";
|
|
5235
|
+
svg += `
|
|
4470
5236
|
<rect x="${pos.x}" y="${pos.y}"
|
|
4471
5237
|
width="${pos.width}" height="${pos.height}"
|
|
4472
5238
|
rx="8"
|
|
4473
|
-
fill="${
|
|
4474
|
-
stroke="${
|
|
5239
|
+
fill="${outerFill}"
|
|
5240
|
+
stroke="${outerStroke}"
|
|
4475
5241
|
stroke-width="1"/>`;
|
|
5242
|
+
}
|
|
4476
5243
|
if (title) {
|
|
4477
5244
|
svg += `
|
|
4478
5245
|
<text x="${pos.x + 16}" y="${pos.y + 24}"
|
|
4479
|
-
font-family="
|
|
5246
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4480
5247
|
font-size="13"
|
|
4481
5248
|
font-weight="600"
|
|
4482
5249
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>`;
|
|
4483
5250
|
}
|
|
4484
5251
|
const headerY = pos.y + (title ? 32 : 0);
|
|
4485
|
-
|
|
5252
|
+
if (showInnerBorder) {
|
|
5253
|
+
svg += `
|
|
4486
5254
|
<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
4487
5255
|
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
4488
|
-
|
|
5256
|
+
}
|
|
5257
|
+
safeColumns.forEach((col, i) => {
|
|
4489
5258
|
svg += `
|
|
4490
|
-
<text x="${pos.x + i *
|
|
4491
|
-
font-family="
|
|
5259
|
+
<text x="${pos.x + i * dataColWidth + 12}" y="${headerY + 26}"
|
|
5260
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4492
5261
|
font-size="11"
|
|
4493
5262
|
font-weight="600"
|
|
4494
5263
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(col)}</text>`;
|
|
4495
5264
|
});
|
|
5265
|
+
if (hasActions && showInnerBorder) {
|
|
5266
|
+
const dividerX = pos.x + dataWidth;
|
|
5267
|
+
svg += `
|
|
5268
|
+
<line x1="${dividerX}" y1="${headerY + headerHeight}" x2="${dividerX}" y2="${headerY + headerHeight + mockRows.length * rowHeight}"
|
|
5269
|
+
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
5270
|
+
}
|
|
4496
5271
|
mockRows.forEach((row, rowIdx) => {
|
|
4497
5272
|
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
4498
|
-
|
|
5273
|
+
if (showInnerBorder) {
|
|
5274
|
+
svg += `
|
|
4499
5275
|
<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
4500
5276
|
stroke="${this.renderTheme.border}" stroke-width="0.5"/>`;
|
|
4501
|
-
|
|
5277
|
+
}
|
|
5278
|
+
safeColumns.forEach((col, colIdx) => {
|
|
4502
5279
|
const cellValue = row[col] || "";
|
|
4503
5280
|
svg += `
|
|
4504
|
-
<text x="${pos.x + colIdx *
|
|
4505
|
-
font-family="
|
|
5281
|
+
<text x="${pos.x + colIdx * dataColWidth + 12}" y="${rowY + 22}"
|
|
5282
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4506
5283
|
font-size="12"
|
|
4507
5284
|
fill="${this.renderTheme.text}">${this.escapeXml(cellValue)}</text>`;
|
|
4508
5285
|
});
|
|
5286
|
+
if (hasActions) {
|
|
5287
|
+
const iconSize = 14;
|
|
5288
|
+
const buttonSize = 22;
|
|
5289
|
+
const buttonGap = 6;
|
|
5290
|
+
const actionsWidth = actions.length * buttonSize + Math.max(0, actions.length - 1) * buttonGap;
|
|
5291
|
+
let currentX = pos.x + pos.width - 12 - actionsWidth;
|
|
5292
|
+
const buttonY = rowY + (rowHeight - buttonSize) / 2;
|
|
5293
|
+
actions.forEach((actionIcon) => {
|
|
5294
|
+
const iconSvg = getIcon(actionIcon);
|
|
5295
|
+
const iconX = currentX + (buttonSize - iconSize) / 2;
|
|
5296
|
+
const iconY = buttonY + (buttonSize - iconSize) / 2;
|
|
5297
|
+
svg += `
|
|
5298
|
+
<rect x="${currentX}" y="${buttonY}" width="${buttonSize}" height="${buttonSize}" rx="4"
|
|
5299
|
+
fill="${this.renderTheme.cardBg}" stroke="${showInnerBorder ? this.renderTheme.border : "none"}" stroke-width="1"/>`;
|
|
5300
|
+
if (iconSvg) {
|
|
5301
|
+
svg += `
|
|
5302
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5303
|
+
<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">
|
|
5304
|
+
${this.extractSvgContent(iconSvg)}
|
|
5305
|
+
</svg>
|
|
5306
|
+
</g>`;
|
|
5307
|
+
}
|
|
5308
|
+
currentX += buttonSize + buttonGap;
|
|
5309
|
+
});
|
|
5310
|
+
}
|
|
4509
5311
|
});
|
|
5312
|
+
const footerTop = headerY + headerHeight + mockRows.length * rowHeight + 16;
|
|
4510
5313
|
if (pagination) {
|
|
4511
|
-
const paginationY =
|
|
5314
|
+
const paginationY = sameFooterAlign ? footerTop + 18 + 8 : footerTop;
|
|
4512
5315
|
const buttonWidth = 40;
|
|
4513
5316
|
const buttonHeight = 32;
|
|
4514
5317
|
const gap = 8;
|
|
@@ -4521,18 +5324,25 @@ var SVGRenderer = class {
|
|
|
4521
5324
|
} else {
|
|
4522
5325
|
startX = pos.x + pos.width - totalWidth - 16;
|
|
4523
5326
|
}
|
|
5327
|
+
const previousIcon = getIcon("chevron-left");
|
|
4524
5328
|
svg += `
|
|
4525
5329
|
<rect x="${startX}" y="${paginationY}"
|
|
4526
5330
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4527
5331
|
rx="4"
|
|
4528
5332
|
fill="${this.renderTheme.cardBg}"
|
|
4529
5333
|
stroke="${this.renderTheme.border}"
|
|
4530
|
-
stroke-width="1"
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
5334
|
+
stroke-width="1"/>`;
|
|
5335
|
+
if (previousIcon) {
|
|
5336
|
+
const iconSize = 14;
|
|
5337
|
+
const iconX = startX + (buttonWidth - iconSize) / 2;
|
|
5338
|
+
const iconY = paginationY + (buttonHeight - iconSize) / 2;
|
|
5339
|
+
svg += `
|
|
5340
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5341
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.resolveTextColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5342
|
+
${this.extractSvgContent(previousIcon)}
|
|
5343
|
+
</svg>
|
|
5344
|
+
</g>`;
|
|
5345
|
+
}
|
|
4536
5346
|
for (let i = 1; i <= pageCount; i++) {
|
|
4537
5347
|
const btnX = startX + (buttonWidth + gap) * i;
|
|
4538
5348
|
const isActive = i === 1;
|
|
@@ -4546,24 +5356,49 @@ var SVGRenderer = class {
|
|
|
4546
5356
|
stroke="${this.renderTheme.border}"
|
|
4547
5357
|
stroke-width="1"/>
|
|
4548
5358
|
<text x="${btnX + buttonWidth / 2}" y="${paginationY + buttonHeight / 2 + 4}"
|
|
4549
|
-
font-family="
|
|
5359
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4550
5360
|
font-size="14"
|
|
4551
5361
|
fill="${textColor}"
|
|
4552
5362
|
text-anchor="middle">${i}</text>`;
|
|
4553
5363
|
}
|
|
4554
5364
|
const nextX = startX + (buttonWidth + gap) * (pageCount + 1);
|
|
5365
|
+
const nextIcon = getIcon("chevron-right");
|
|
4555
5366
|
svg += `
|
|
4556
5367
|
<rect x="${nextX}" y="${paginationY}"
|
|
4557
5368
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4558
5369
|
rx="4"
|
|
4559
5370
|
fill="${this.renderTheme.cardBg}"
|
|
4560
5371
|
stroke="${this.renderTheme.border}"
|
|
4561
|
-
stroke-width="1"
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
5372
|
+
stroke-width="1"/>`;
|
|
5373
|
+
if (nextIcon) {
|
|
5374
|
+
const iconSize = 14;
|
|
5375
|
+
const iconX = nextX + (buttonWidth - iconSize) / 2;
|
|
5376
|
+
const iconY = paginationY + (buttonHeight - iconSize) / 2;
|
|
5377
|
+
svg += `
|
|
5378
|
+
<g transform="translate(${iconX}, ${iconY})">
|
|
5379
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.resolveTextColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5380
|
+
${this.extractSvgContent(nextIcon)}
|
|
5381
|
+
</svg>
|
|
5382
|
+
</g>`;
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
if (hasCaption) {
|
|
5386
|
+
const captionY = sameFooterAlign ? footerTop + 12 : footerTop + (pagination ? 21 : 12);
|
|
5387
|
+
let captionX = pos.x + 16;
|
|
5388
|
+
let textAnchor = "start";
|
|
5389
|
+
if (captionAlign === "center") {
|
|
5390
|
+
captionX = pos.x + pos.width / 2;
|
|
5391
|
+
textAnchor = "middle";
|
|
5392
|
+
} else if (captionAlign === "right") {
|
|
5393
|
+
captionX = pos.x + pos.width - 16;
|
|
5394
|
+
textAnchor = "end";
|
|
5395
|
+
}
|
|
5396
|
+
svg += `
|
|
5397
|
+
<text x="${captionX}" y="${captionY}"
|
|
5398
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5399
|
+
font-size="12"
|
|
5400
|
+
fill="${this.hexToRgba(this.resolveTextColor(), 0.75)}"
|
|
5401
|
+
text-anchor="${textAnchor}">${this.escapeXml(caption)}</text>`;
|
|
4567
5402
|
}
|
|
4568
5403
|
svg += "\n </g>";
|
|
4569
5404
|
return svg;
|
|
@@ -4678,7 +5513,7 @@ var SVGRenderer = class {
|
|
|
4678
5513
|
// TEXT/CONTENT COMPONENTS
|
|
4679
5514
|
// ============================================================================
|
|
4680
5515
|
renderText(node, pos) {
|
|
4681
|
-
const text = String(node.props.
|
|
5516
|
+
const text = String(node.props.text || "Text content");
|
|
4682
5517
|
const fontSize = this.tokens.text.fontSize;
|
|
4683
5518
|
const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
|
|
4684
5519
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
@@ -4688,7 +5523,7 @@ var SVGRenderer = class {
|
|
|
4688
5523
|
).join("");
|
|
4689
5524
|
return `<g${this.getDataNodeId(node)}>
|
|
4690
5525
|
<text x="${pos.x}" y="${firstLineY}"
|
|
4691
|
-
font-family="
|
|
5526
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4692
5527
|
font-size="${fontSize}"
|
|
4693
5528
|
fill="${this.renderTheme.text}">${tspans}</text>
|
|
4694
5529
|
</g>`;
|
|
@@ -4697,7 +5532,7 @@ var SVGRenderer = class {
|
|
|
4697
5532
|
const text = String(node.props.text || "Label");
|
|
4698
5533
|
return `<g${this.getDataNodeId(node)}>
|
|
4699
5534
|
<text x="${pos.x}" y="${pos.y + 12}"
|
|
4700
|
-
font-family="
|
|
5535
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4701
5536
|
font-size="12"
|
|
4702
5537
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
|
|
4703
5538
|
</g>`;
|
|
@@ -4731,7 +5566,7 @@ var SVGRenderer = class {
|
|
|
4731
5566
|
const placeholderY = controlY + fontSize + 6;
|
|
4732
5567
|
return `<g${this.getDataNodeId(node)}>
|
|
4733
5568
|
${label ? `<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4734
|
-
font-family="
|
|
5569
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4735
5570
|
font-size="12"
|
|
4736
5571
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>` : ""}
|
|
4737
5572
|
<rect x="${pos.x}" y="${controlY}"
|
|
@@ -4741,7 +5576,7 @@ var SVGRenderer = class {
|
|
|
4741
5576
|
stroke="${this.renderTheme.border}"
|
|
4742
5577
|
stroke-width="1"/>
|
|
4743
5578
|
<text x="${pos.x + paddingX}" y="${placeholderY}"
|
|
4744
|
-
font-family="
|
|
5579
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4745
5580
|
font-size="${fontSize}"
|
|
4746
5581
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(placeholder)}</text>
|
|
4747
5582
|
</g>`;
|
|
@@ -4749,30 +5584,66 @@ var SVGRenderer = class {
|
|
|
4749
5584
|
renderSelect(node, pos) {
|
|
4750
5585
|
const label = String(node.props.label || "");
|
|
4751
5586
|
const placeholder = String(node.props.placeholder || "Select...");
|
|
5587
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
5588
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
4752
5589
|
const labelOffset = this.getControlLabelOffset(label);
|
|
4753
5590
|
const controlY = pos.y + labelOffset;
|
|
4754
5591
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
4755
5592
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
font-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
5593
|
+
const iconSize = 16;
|
|
5594
|
+
const iconPad = 12;
|
|
5595
|
+
const iconInnerGap = 8;
|
|
5596
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
5597
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
5598
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
5599
|
+
const chevronWidth = 20;
|
|
5600
|
+
const iconColor = this.hexToRgba(this.resolveMutedColor(), 0.8);
|
|
5601
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
5602
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5603
|
+
if (label) {
|
|
5604
|
+
svg += `
|
|
5605
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
5606
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5607
|
+
font-size="12"
|
|
5608
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
5609
|
+
}
|
|
5610
|
+
svg += `
|
|
5611
|
+
<rect x="${pos.x}" y="${controlY}"
|
|
5612
|
+
width="${pos.width}" height="${controlHeight}"
|
|
5613
|
+
rx="6"
|
|
5614
|
+
fill="${this.renderTheme.cardBg}"
|
|
5615
|
+
stroke="${this.renderTheme.border}"
|
|
5616
|
+
stroke-width="1"/>`;
|
|
5617
|
+
if (iconLeftSvg) {
|
|
5618
|
+
svg += `
|
|
5619
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
5620
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5621
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
5622
|
+
</svg>
|
|
5623
|
+
</g>`;
|
|
5624
|
+
}
|
|
5625
|
+
if (iconRightSvg) {
|
|
5626
|
+
svg += `
|
|
5627
|
+
<g transform="translate(${pos.x + pos.width - chevronWidth - iconPad - iconSize}, ${iconCenterY})">
|
|
5628
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5629
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
5630
|
+
</svg>
|
|
5631
|
+
</g>`;
|
|
5632
|
+
}
|
|
5633
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : 12);
|
|
5634
|
+
const availPlaceholderWidth = pos.width - (iconLeftSvg ? leftOffset : 12) - chevronWidth - (iconRightSvg ? iconPad + iconSize + iconInnerGap : 0);
|
|
5635
|
+
const visiblePlaceholder = this.truncateTextToWidth(placeholder, Math.max(0, availPlaceholderWidth), 14);
|
|
5636
|
+
svg += `
|
|
5637
|
+
<text x="${textX}" y="${centerY}"
|
|
5638
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5639
|
+
font-size="14"
|
|
5640
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePlaceholder)}</text>
|
|
5641
|
+
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
5642
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5643
|
+
font-size="16"
|
|
4774
5644
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
4775
5645
|
</g>`;
|
|
5646
|
+
return svg;
|
|
4776
5647
|
}
|
|
4777
5648
|
renderCheckbox(node, pos) {
|
|
4778
5649
|
const label = String(node.props.label || "Checkbox");
|
|
@@ -4788,12 +5659,12 @@ var SVGRenderer = class {
|
|
|
4788
5659
|
stroke="${this.renderTheme.border}"
|
|
4789
5660
|
stroke-width="1"/>
|
|
4790
5661
|
${checked ? `<text x="${pos.x + checkboxSize / 2}" y="${checkboxY + 14}"
|
|
4791
|
-
font-family="
|
|
5662
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4792
5663
|
font-size="12"
|
|
4793
5664
|
fill="white"
|
|
4794
5665
|
text-anchor="middle">\u2713</text>` : ""}
|
|
4795
5666
|
<text x="${pos.x + checkboxSize + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4796
|
-
font-family="
|
|
5667
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4797
5668
|
font-size="14"
|
|
4798
5669
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4799
5670
|
</g>`;
|
|
@@ -4814,7 +5685,7 @@ var SVGRenderer = class {
|
|
|
4814
5685
|
r="${radioSize / 3.5}"
|
|
4815
5686
|
fill="${controlColor}"/>` : ""}
|
|
4816
5687
|
<text x="${pos.x + radioSize + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4817
|
-
font-family="
|
|
5688
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4818
5689
|
font-size="14"
|
|
4819
5690
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4820
5691
|
</g>`;
|
|
@@ -4836,7 +5707,7 @@ var SVGRenderer = class {
|
|
|
4836
5707
|
r="8"
|
|
4837
5708
|
fill="white"/>
|
|
4838
5709
|
<text x="${pos.x + toggleWidth + 12}" y="${pos.y + pos.height / 2 + 5}"
|
|
4839
|
-
font-family="
|
|
5710
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4840
5711
|
font-size="14"
|
|
4841
5712
|
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>
|
|
4842
5713
|
</g>`;
|
|
@@ -4866,7 +5737,7 @@ var SVGRenderer = class {
|
|
|
4866
5737
|
stroke-width="1"/>
|
|
4867
5738
|
<!-- Title -->
|
|
4868
5739
|
<text x="${pos.x + padding}" y="${pos.y + padding + 8}"
|
|
4869
|
-
font-family="
|
|
5740
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4870
5741
|
font-size="14"
|
|
4871
5742
|
font-weight="600"
|
|
4872
5743
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
@@ -4882,7 +5753,7 @@ var SVGRenderer = class {
|
|
|
4882
5753
|
fill="${isActive ? this.renderTheme.primary : "transparent"}"
|
|
4883
5754
|
stroke="none"/>
|
|
4884
5755
|
<text x="${pos.x + 16}" y="${itemY + 22}"
|
|
4885
|
-
font-family="
|
|
5756
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4886
5757
|
font-size="13"
|
|
4887
5758
|
fill="${isActive ? "white" : this.renderTheme.textMuted}">${this.escapeXml(item)}</text>`;
|
|
4888
5759
|
});
|
|
@@ -4908,7 +5779,7 @@ var SVGRenderer = class {
|
|
|
4908
5779
|
stroke="${isActive ? "none" : this.renderTheme.border}"
|
|
4909
5780
|
stroke-width="1"/>
|
|
4910
5781
|
<text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
|
|
4911
|
-
font-family="
|
|
5782
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4912
5783
|
font-size="13"
|
|
4913
5784
|
font-weight="${isActive ? "600" : "500"}"
|
|
4914
5785
|
fill="${isActive ? "white" : this.renderTheme.text}"
|
|
@@ -4971,12 +5842,12 @@ var SVGRenderer = class {
|
|
|
4971
5842
|
rx="6"
|
|
4972
5843
|
fill="${bgColor}"/>
|
|
4973
5844
|
${hasTitle ? `<text x="${contentX}" y="${titleStartY}"
|
|
4974
|
-
font-family="
|
|
5845
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4975
5846
|
font-size="${fontSize}"
|
|
4976
5847
|
font-weight="700"
|
|
4977
5848
|
fill="${bgColor}">${titleTspans}</text>` : ""}
|
|
4978
5849
|
<text x="${contentX}" y="${textStartY}"
|
|
4979
|
-
font-family="
|
|
5850
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
4980
5851
|
font-size="${fontSize}"
|
|
4981
5852
|
fill="${bgColor}">${textTspans}</text>
|
|
4982
5853
|
</g>`;
|
|
@@ -4997,7 +5868,7 @@ var SVGRenderer = class {
|
|
|
4997
5868
|
fill="${bgColor}"
|
|
4998
5869
|
stroke="none"/>
|
|
4999
5870
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
5000
|
-
font-family="
|
|
5871
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5001
5872
|
font-size="${fontSize}"
|
|
5002
5873
|
font-weight="600"
|
|
5003
5874
|
fill="${textColor}"
|
|
@@ -5036,20 +5907,20 @@ var SVGRenderer = class {
|
|
|
5036
5907
|
stroke-width="1"/>
|
|
5037
5908
|
|
|
5038
5909
|
<text x="${modalX + padding}" y="${modalY + padding + 16}"
|
|
5039
|
-
font-family="
|
|
5910
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5040
5911
|
font-size="16"
|
|
5041
5912
|
font-weight="600"
|
|
5042
5913
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
5043
5914
|
|
|
5044
5915
|
<!-- Close button -->
|
|
5045
5916
|
<text x="${modalX + pos.width - 16}" y="${modalY + padding + 12}"
|
|
5046
|
-
font-family="
|
|
5917
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5047
5918
|
font-size="18"
|
|
5048
5919
|
fill="${this.renderTheme.textMuted}">\u2715</text>
|
|
5049
5920
|
|
|
5050
5921
|
<!-- Content placeholder -->
|
|
5051
5922
|
<text x="${modalX + pos.width / 2}" y="${modalY + headerHeight + (pos.height - headerHeight) / 2}"
|
|
5052
|
-
font-family="
|
|
5923
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5053
5924
|
font-size="13"
|
|
5054
5925
|
fill="${this.renderTheme.textMuted}"
|
|
5055
5926
|
text-anchor="middle">Modal content</text>
|
|
@@ -5082,7 +5953,7 @@ var SVGRenderer = class {
|
|
|
5082
5953
|
if (title) {
|
|
5083
5954
|
svg += `
|
|
5084
5955
|
<text x="${pos.x + padding}" y="${pos.y + 26}"
|
|
5085
|
-
font-family="
|
|
5956
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5086
5957
|
font-size="13"
|
|
5087
5958
|
font-weight="600"
|
|
5088
5959
|
fill="${this.renderTheme.text}">${this.escapeXml(title)}</text>
|
|
@@ -5098,7 +5969,7 @@ var SVGRenderer = class {
|
|
|
5098
5969
|
stroke="${this.renderTheme.border}"
|
|
5099
5970
|
stroke-width="0.5"/>
|
|
5100
5971
|
<text x="${pos.x + padding}" y="${itemY + 24}"
|
|
5101
|
-
font-family="
|
|
5972
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5102
5973
|
font-size="13"
|
|
5103
5974
|
fill="${this.renderTheme.text}">${this.escapeXml(item)}</text>`;
|
|
5104
5975
|
}
|
|
@@ -5116,7 +5987,7 @@ var SVGRenderer = class {
|
|
|
5116
5987
|
stroke-width="1"
|
|
5117
5988
|
stroke-dasharray="4 4"/>
|
|
5118
5989
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
|
|
5119
|
-
font-family="
|
|
5990
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5120
5991
|
font-size="12"
|
|
5121
5992
|
fill="${this.renderTheme.textMuted}"
|
|
5122
5993
|
text-anchor="middle">${node.componentType}</text>
|
|
@@ -5164,14 +6035,14 @@ var SVGRenderer = class {
|
|
|
5164
6035
|
|
|
5165
6036
|
<!-- Title -->
|
|
5166
6037
|
<text x="${innerX}" y="${titleY}"
|
|
5167
|
-
font-family="
|
|
6038
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5168
6039
|
font-size="${titleSize}"
|
|
5169
6040
|
font-weight="500"
|
|
5170
6041
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(visibleTitle)}</text>
|
|
5171
6042
|
|
|
5172
6043
|
<!-- Value (Large) -->
|
|
5173
6044
|
<text x="${innerX}" y="${valueY}"
|
|
5174
|
-
font-family="
|
|
6045
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5175
6046
|
font-size="${valueSize}"
|
|
5176
6047
|
font-weight="700"
|
|
5177
6048
|
fill="${accentColor}">${this.escapeXml(value)}</text>`;
|
|
@@ -5194,7 +6065,7 @@ var SVGRenderer = class {
|
|
|
5194
6065
|
svg += `
|
|
5195
6066
|
<!-- Caption -->
|
|
5196
6067
|
<text x="${innerX}" y="${captionY}"
|
|
5197
|
-
font-family="
|
|
6068
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5198
6069
|
font-size="${captionSize}"
|
|
5199
6070
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(visibleCaption)}</text>`;
|
|
5200
6071
|
}
|
|
@@ -5205,7 +6076,9 @@ var SVGRenderer = class {
|
|
|
5205
6076
|
renderImage(node, pos) {
|
|
5206
6077
|
const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
|
|
5207
6078
|
const placeholderIcon = String(node.props.icon || "").trim();
|
|
6079
|
+
const variant = String(node.props.variant || "").trim();
|
|
5208
6080
|
const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
|
|
6081
|
+
const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
|
|
5209
6082
|
const aspectRatios = {
|
|
5210
6083
|
landscape: 16 / 9,
|
|
5211
6084
|
portrait: 2 / 3,
|
|
@@ -5223,30 +6096,29 @@ var SVGRenderer = class {
|
|
|
5223
6096
|
}
|
|
5224
6097
|
const offsetX = pos.x + (pos.width - iconWidth) / 2;
|
|
5225
6098
|
const offsetY = pos.y + (pos.height - iconHeight) / 2;
|
|
5226
|
-
let svg = `<g${this.getDataNodeId(node)}>
|
|
5227
|
-
<!-- Image Background -->
|
|
5228
|
-
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="#E8E8E8"/>`;
|
|
5229
6099
|
if (placeholder === "icon" && placeholderIconSvg) {
|
|
5230
|
-
const
|
|
5231
|
-
const
|
|
5232
|
-
const
|
|
5233
|
-
const
|
|
5234
|
-
const
|
|
5235
|
-
const
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
rx="${Math.max(4, badgeSize * 0.2)}"
|
|
5241
|
-
fill="rgba(255, 255, 255, 0.6)"
|
|
5242
|
-
stroke="#888"
|
|
5243
|
-
stroke-width="1"/>
|
|
6100
|
+
const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
|
|
6101
|
+
const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
|
|
6102
|
+
const variantColor = hasVariant ? this.resolveVariantColor(variant, this.renderTheme.primary) : null;
|
|
6103
|
+
const bgColor = hasVariant ? this.hexToRgba(variantColor, 0.12) : imageBg;
|
|
6104
|
+
const iconColor = hasVariant ? variantColor : this.options.theme === "dark" ? "#888888" : "#666666";
|
|
6105
|
+
const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
|
|
6106
|
+
const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
|
|
6107
|
+
const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
|
|
6108
|
+
return `<g${this.getDataNodeId(node)}>
|
|
6109
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
|
|
5244
6110
|
<g transform="translate(${iconOffsetX}, ${iconOffsetY})">
|
|
5245
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
6111
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5246
6112
|
${this.extractSvgContent(placeholderIconSvg)}
|
|
5247
6113
|
</svg>
|
|
5248
|
-
</g
|
|
5249
|
-
}
|
|
6114
|
+
</g>
|
|
6115
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
|
|
6116
|
+
</g>`;
|
|
6117
|
+
}
|
|
6118
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
6119
|
+
<!-- Image Background -->
|
|
6120
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
|
|
6121
|
+
if (["landscape", "portrait", "square"].includes(placeholder)) {
|
|
5250
6122
|
const cameraCx = offsetX + iconWidth / 2;
|
|
5251
6123
|
const cameraCy = offsetY + iconHeight / 2;
|
|
5252
6124
|
const scale = Math.min(iconWidth, iconHeight) / 24;
|
|
@@ -5323,7 +6195,7 @@ var SVGRenderer = class {
|
|
|
5323
6195
|
const fontWeight = isLast ? "500" : "400";
|
|
5324
6196
|
svg += `
|
|
5325
6197
|
<text x="${currentX}" y="${pos.y + pos.height / 2 + 4}"
|
|
5326
|
-
font-family="
|
|
6198
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5327
6199
|
font-size="${fontSize}"
|
|
5328
6200
|
font-weight="${fontWeight}"
|
|
5329
6201
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
@@ -5332,7 +6204,7 @@ var SVGRenderer = class {
|
|
|
5332
6204
|
if (!isLast) {
|
|
5333
6205
|
svg += `
|
|
5334
6206
|
<text x="${currentX + 4}" y="${pos.y + pos.height / 2 + 4}"
|
|
5335
|
-
font-family="
|
|
6207
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5336
6208
|
font-size="${fontSize}"
|
|
5337
6209
|
fill="${this.renderTheme.textMuted}">${this.escapeXml(separator)}</text>`;
|
|
5338
6210
|
currentX += separatorWidth;
|
|
@@ -5350,18 +6222,22 @@ var SVGRenderer = class {
|
|
|
5350
6222
|
const fontSize = 14;
|
|
5351
6223
|
const activeIndex = Number(node.props.active || 0);
|
|
5352
6224
|
const accentColor = this.resolveAccentColor();
|
|
6225
|
+
const variantProp = String(node.props.variant || "").trim();
|
|
6226
|
+
const semanticVariant = variantProp ? this.getSemanticVariantColor(variantProp) : void 0;
|
|
6227
|
+
const hasVariant = variantProp.length > 0 && (semanticVariant !== void 0 || this.colorResolver.hasColor(variantProp));
|
|
6228
|
+
const activeColor = hasVariant ? this.resolveVariantColor(variantProp, accentColor) : accentColor;
|
|
5353
6229
|
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5354
6230
|
items.forEach((item, index) => {
|
|
5355
6231
|
const itemY = pos.y + index * itemHeight;
|
|
5356
6232
|
const isActive = index === activeIndex;
|
|
5357
|
-
const bgColor = isActive ? this.hexToRgba(
|
|
5358
|
-
const textColor = isActive ? this.hexToRgba(
|
|
6233
|
+
const bgColor = isActive ? this.hexToRgba(activeColor, 0.15) : "transparent";
|
|
6234
|
+
const textColor = isActive ? this.hexToRgba(activeColor, 0.9) : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
5359
6235
|
const fontWeight = isActive ? "500" : "400";
|
|
5360
6236
|
if (isActive) {
|
|
5361
6237
|
svg += `
|
|
5362
|
-
<rect x="${pos.x}" y="${itemY}"
|
|
5363
|
-
width="${pos.width}" height="${itemHeight}"
|
|
5364
|
-
rx="6"
|
|
6238
|
+
<rect x="${pos.x}" y="${itemY}"
|
|
6239
|
+
width="${pos.width}" height="${itemHeight}"
|
|
6240
|
+
rx="6"
|
|
5365
6241
|
fill="${bgColor}"/>`;
|
|
5366
6242
|
}
|
|
5367
6243
|
let currentX = pos.x + 12;
|
|
@@ -5370,9 +6246,10 @@ var SVGRenderer = class {
|
|
|
5370
6246
|
if (iconSvg) {
|
|
5371
6247
|
const iconSize = 16;
|
|
5372
6248
|
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
6249
|
+
const iconColor = isActive ? this.hexToRgba(activeColor, 0.9) : this.hexToRgba(this.resolveMutedColor(), 0.9);
|
|
5373
6250
|
svg += `
|
|
5374
6251
|
<g transform="translate(${currentX}, ${iconY})">
|
|
5375
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
6252
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5376
6253
|
${this.extractSvgContent(iconSvg)}
|
|
5377
6254
|
</svg>
|
|
5378
6255
|
</g>`;
|
|
@@ -5380,28 +6257,29 @@ var SVGRenderer = class {
|
|
|
5380
6257
|
}
|
|
5381
6258
|
}
|
|
5382
6259
|
svg += `
|
|
5383
|
-
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
5384
|
-
font-family="
|
|
5385
|
-
font-size="${fontSize}"
|
|
5386
|
-
font-weight="${fontWeight}"
|
|
6260
|
+
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
6261
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
6262
|
+
font-size="${fontSize}"
|
|
6263
|
+
font-weight="${fontWeight}"
|
|
5387
6264
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
5388
6265
|
});
|
|
5389
6266
|
svg += "\n </g>";
|
|
5390
6267
|
return svg;
|
|
5391
6268
|
}
|
|
5392
6269
|
renderIcon(node, pos) {
|
|
5393
|
-
const iconType = String(node.props.
|
|
6270
|
+
const iconType = String(node.props.icon || "help-circle");
|
|
5394
6271
|
const size = String(node.props.size || "md");
|
|
6272
|
+
const variant = String(node.props.variant || "default");
|
|
5395
6273
|
const iconSvg = getIcon(iconType);
|
|
6274
|
+
const iconColor = variant === "default" ? this.hexToRgba(this.resolveTextColor(), 0.75) : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
5396
6275
|
if (!iconSvg) {
|
|
5397
6276
|
return `<g${this.getDataNodeId(node)}>
|
|
5398
6277
|
<!-- 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="
|
|
6278
|
+
<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"/>
|
|
6279
|
+
<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
6280
|
</g>`;
|
|
5402
6281
|
}
|
|
5403
6282
|
const iconSize = this.getIconSize(size);
|
|
5404
|
-
const iconColor = "rgba(30, 41, 59, 0.75)";
|
|
5405
6283
|
const offsetX = pos.x + (pos.width - iconSize) / 2;
|
|
5406
6284
|
const offsetY = pos.y + (pos.height - iconSize) / 2;
|
|
5407
6285
|
const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
|
|
@@ -5416,23 +6294,32 @@ var SVGRenderer = class {
|
|
|
5416
6294
|
const variant = String(node.props.variant || "default");
|
|
5417
6295
|
const size = String(node.props.size || "md");
|
|
5418
6296
|
const disabled = String(node.props.disabled || "false") === "true";
|
|
6297
|
+
const density = this.ir.project.style.density || "normal";
|
|
6298
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
6299
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
5419
6300
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5420
6301
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
5421
6302
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5422
|
-
const
|
|
5423
|
-
const
|
|
5424
|
-
const
|
|
6303
|
+
const isDarkMode = this.options.theme === "dark";
|
|
6304
|
+
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : isDarkMode ? "rgba(48, 48, 55, 0.9)" : "rgba(226, 232, 240, 0.9)";
|
|
6305
|
+
const iconColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
6306
|
+
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : isDarkMode ? "rgba(75, 75, 88, 0.8)" : "rgba(100, 116, 139, 0.4)";
|
|
5425
6307
|
const opacity = disabled ? "0.5" : "1";
|
|
5426
6308
|
const iconSvg = getIcon(iconName);
|
|
5427
|
-
const buttonSize =
|
|
6309
|
+
const buttonSize = Math.max(
|
|
6310
|
+
16,
|
|
6311
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
6312
|
+
);
|
|
6313
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
5428
6314
|
const radius = 6;
|
|
6315
|
+
const buttonY = pos.y + labelOffset;
|
|
5429
6316
|
let svg = `<g${this.getDataNodeId(node)} opacity="${opacity}">
|
|
5430
6317
|
<!-- IconButton background -->
|
|
5431
|
-
<rect x="${pos.x}" y="${
|
|
6318
|
+
<rect x="${pos.x}" y="${buttonY}" width="${buttonWidth}" height="${buttonSize}" rx="${radius}" fill="${bgColor}" stroke="${borderColor}" stroke-width="1"/>`;
|
|
5432
6319
|
if (iconSvg) {
|
|
5433
6320
|
const iconSize = buttonSize * 0.6;
|
|
5434
|
-
const offsetX = pos.x + (
|
|
5435
|
-
const offsetY =
|
|
6321
|
+
const offsetX = pos.x + (buttonWidth - iconSize) / 2;
|
|
6322
|
+
const offsetY = buttonY + (buttonSize - iconSize) / 2;
|
|
5436
6323
|
svg += `
|
|
5437
6324
|
<!-- Icon -->
|
|
5438
6325
|
<g transform="translate(${offsetX}, ${offsetY})">
|
|
@@ -5465,15 +6352,46 @@ var SVGRenderer = class {
|
|
|
5465
6352
|
resolveChartColor() {
|
|
5466
6353
|
return this.colorResolver.resolveColor("chart", this.renderTheme.primary);
|
|
5467
6354
|
}
|
|
6355
|
+
resolveTextColor() {
|
|
6356
|
+
const fallback = this.options.theme === "dark" ? "#FFFFFF" : "#000000";
|
|
6357
|
+
return this.colorResolver.resolveColor("text", fallback);
|
|
6358
|
+
}
|
|
6359
|
+
resolveMutedColor() {
|
|
6360
|
+
const fallback = this.options.theme === "dark" ? "#94A3B8" : "#64748B";
|
|
6361
|
+
return this.colorResolver.resolveColor("muted", fallback);
|
|
6362
|
+
}
|
|
5468
6363
|
getSemanticVariantColor(variant) {
|
|
5469
|
-
const
|
|
6364
|
+
const isDark = this.options.theme === "dark";
|
|
6365
|
+
const semantic = isDark ? {
|
|
6366
|
+
// Muted mid-range — readable on #111111 without being neon
|
|
6367
|
+
primary: this.renderTheme.primary,
|
|
6368
|
+
// already theme-aware (#60A5FA)
|
|
6369
|
+
secondary: "#7E8EA2",
|
|
6370
|
+
// desaturated slate
|
|
6371
|
+
success: "#22A06B",
|
|
6372
|
+
// muted emerald
|
|
6373
|
+
warning: "#B38010",
|
|
6374
|
+
// deep amber
|
|
6375
|
+
danger: "#CC4444",
|
|
6376
|
+
// muted red
|
|
6377
|
+
error: "#CC4444",
|
|
6378
|
+
info: "#2485AF"
|
|
6379
|
+
// muted sky
|
|
6380
|
+
} : {
|
|
6381
|
+
// Tailwind 500-level — works on white/light backgrounds
|
|
5470
6382
|
primary: this.renderTheme.primary,
|
|
6383
|
+
// #3B82F6
|
|
5471
6384
|
secondary: "#64748B",
|
|
6385
|
+
// Slate 500
|
|
5472
6386
|
success: "#10B981",
|
|
6387
|
+
// Emerald 500
|
|
5473
6388
|
warning: "#F59E0B",
|
|
6389
|
+
// Amber 500
|
|
5474
6390
|
danger: "#EF4444",
|
|
6391
|
+
// Red 500
|
|
5475
6392
|
error: "#EF4444",
|
|
5476
6393
|
info: "#0EA5E9"
|
|
6394
|
+
// Sky 500
|
|
5477
6395
|
};
|
|
5478
6396
|
return semantic[variant];
|
|
5479
6397
|
}
|
|
@@ -5791,21 +6709,28 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5791
6709
|
renderButton(node, pos) {
|
|
5792
6710
|
const text = String(node.props.text || "Button");
|
|
5793
6711
|
const variant = String(node.props.variant || "default");
|
|
6712
|
+
const size = String(node.props.size || "md");
|
|
6713
|
+
const density = this.ir.project.style.density || "normal";
|
|
6714
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6715
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
5794
6716
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
5795
6717
|
const radius = this.tokens.button.radius;
|
|
5796
6718
|
const fontSize = this.tokens.button.fontSize;
|
|
5797
6719
|
const paddingX = this.tokens.button.paddingX;
|
|
5798
|
-
const
|
|
6720
|
+
const buttonHeight = Math.max(
|
|
6721
|
+
16,
|
|
6722
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
6723
|
+
);
|
|
6724
|
+
const buttonY = pos.y + labelOffset;
|
|
5799
6725
|
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;
|
|
6726
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(textWidth + (paddingX + extraPadding) * 2, 60), pos.width);
|
|
5802
6727
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
5803
6728
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
5804
6729
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5805
6730
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
5806
6731
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
5807
6732
|
return `<g${this.getDataNodeId(node)}>
|
|
5808
|
-
<rect x="${pos.x}" y="${
|
|
6733
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
5809
6734
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
5810
6735
|
rx="${radius}"
|
|
5811
6736
|
fill="${bgColor}"
|
|
@@ -5819,13 +6744,14 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5819
6744
|
renderLink(node, pos) {
|
|
5820
6745
|
const text = String(node.props.text || "Link");
|
|
5821
6746
|
const variant = String(node.props.variant || "primary");
|
|
6747
|
+
const size = String(node.props.size || "md");
|
|
6748
|
+
const density = this.ir.project.style.density || "normal";
|
|
5822
6749
|
const fontSize = this.tokens.button.fontSize;
|
|
5823
6750
|
const paddingX = this.tokens.button.paddingX;
|
|
5824
|
-
const paddingY = this.tokens.button.paddingY;
|
|
5825
6751
|
const linkColor = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
5826
6752
|
const textWidth = this.estimateTextWidth(text, fontSize);
|
|
5827
6753
|
const linkWidth = this.clampControlWidth(Math.max(textWidth + paddingX * 2, 60), pos.width);
|
|
5828
|
-
const linkHeight =
|
|
6754
|
+
const linkHeight = Math.max(16, Math.min(resolveActionControlHeight(size, density), pos.height));
|
|
5829
6755
|
const blockHeight = Math.max(8, Math.round(fontSize * 0.75));
|
|
5830
6756
|
const blockWidth = Math.max(28, Math.min(textWidth, linkWidth - paddingX * 2));
|
|
5831
6757
|
const blockX = pos.x + (linkWidth - blockWidth) / 2;
|
|
@@ -5842,17 +6768,70 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5842
6768
|
stroke-width="1"/>
|
|
5843
6769
|
</g>`;
|
|
5844
6770
|
}
|
|
6771
|
+
/**
|
|
6772
|
+
* Render breadcrumbs as skeleton blocks: <rect> / <rect> / <rect accent>
|
|
6773
|
+
*/
|
|
6774
|
+
renderBreadcrumbs(node, pos) {
|
|
6775
|
+
const itemsStr = String(node.props.items || "Home");
|
|
6776
|
+
const items = itemsStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6777
|
+
const separator = String(node.props.separator || "/");
|
|
6778
|
+
const blockColor = this.renderTheme.border;
|
|
6779
|
+
const charWidth = 6.2;
|
|
6780
|
+
const minBlockWidth = 28;
|
|
6781
|
+
const maxBlockWidth = Math.max(minBlockWidth, Math.floor(pos.width * 0.4));
|
|
6782
|
+
const blockHeight = 12;
|
|
6783
|
+
const blockY = pos.y + (pos.height - blockHeight) / 2;
|
|
6784
|
+
const blockRadius = 4;
|
|
6785
|
+
const blockPaddingX = 10;
|
|
6786
|
+
const itemSpacing = 8;
|
|
6787
|
+
const separatorWidth = 12;
|
|
6788
|
+
const contentRight = pos.x + pos.width;
|
|
6789
|
+
let currentX = pos.x;
|
|
6790
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6791
|
+
items.forEach((item, index) => {
|
|
6792
|
+
if (currentX >= contentRight) return;
|
|
6793
|
+
const isLast = index === items.length - 1;
|
|
6794
|
+
const estimatedTextWidth = item.length * charWidth;
|
|
6795
|
+
let blockWidth = Math.max(
|
|
6796
|
+
minBlockWidth,
|
|
6797
|
+
Math.min(maxBlockWidth, Math.ceil(estimatedTextWidth + blockPaddingX * 2))
|
|
6798
|
+
);
|
|
6799
|
+
blockWidth = Math.min(blockWidth, Math.max(0, contentRight - currentX));
|
|
6800
|
+
if (blockWidth < minBlockWidth) return;
|
|
6801
|
+
const fillColor = blockColor;
|
|
6802
|
+
svg += `
|
|
6803
|
+
<rect x="${currentX}" y="${blockY}"
|
|
6804
|
+
width="${blockWidth}" height="${blockHeight}"
|
|
6805
|
+
rx="${blockRadius}"
|
|
6806
|
+
fill="${fillColor}"
|
|
6807
|
+
stroke="none"/>`;
|
|
6808
|
+
currentX += blockWidth + itemSpacing;
|
|
6809
|
+
if (!isLast && currentX + separatorWidth <= contentRight) {
|
|
6810
|
+
svg += `
|
|
6811
|
+
<text x="${currentX + 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
6812
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
6813
|
+
font-size="12"
|
|
6814
|
+
fill="${blockColor}">${this.escapeXml(separator)}</text>`;
|
|
6815
|
+
currentX += separatorWidth;
|
|
6816
|
+
}
|
|
6817
|
+
});
|
|
6818
|
+
svg += "\n </g>";
|
|
6819
|
+
return svg;
|
|
6820
|
+
}
|
|
5845
6821
|
/**
|
|
5846
6822
|
* Render heading as gray block
|
|
5847
6823
|
*/
|
|
5848
6824
|
renderHeading(node, pos) {
|
|
5849
6825
|
const headingTypography = this.getHeadingTypography(node);
|
|
6826
|
+
const variant = String(node.props.variant || "default");
|
|
6827
|
+
const blockColor = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveTextColor()), 0.35);
|
|
5850
6828
|
return this.renderTextBlock(
|
|
5851
6829
|
node,
|
|
5852
6830
|
pos,
|
|
5853
6831
|
String(node.props.text || "Heading"),
|
|
5854
6832
|
headingTypography.fontSize,
|
|
5855
|
-
headingTypography.lineHeight
|
|
6833
|
+
headingTypography.lineHeight,
|
|
6834
|
+
blockColor
|
|
5856
6835
|
);
|
|
5857
6836
|
}
|
|
5858
6837
|
/**
|
|
@@ -5862,7 +6841,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5862
6841
|
return this.renderTextBlock(
|
|
5863
6842
|
node,
|
|
5864
6843
|
pos,
|
|
5865
|
-
String(node.props.
|
|
6844
|
+
String(node.props.text || "Text content"),
|
|
5866
6845
|
this.tokens.text.fontSize,
|
|
5867
6846
|
this.tokens.text.lineHeight
|
|
5868
6847
|
);
|
|
@@ -5873,6 +6852,19 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
5873
6852
|
renderLabel(node, pos) {
|
|
5874
6853
|
return this.renderTextBlock(node, pos, String(node.props.text || "Label"), 12, 1.2);
|
|
5875
6854
|
}
|
|
6855
|
+
/**
|
|
6856
|
+
* Render image as a plain skeleton rectangle — no icon, no placeholder label,
|
|
6857
|
+
* just a filled block with the correct dimensions (aspect-ratio is preserved
|
|
6858
|
+
* by the layout engine, so pos already has the right size).
|
|
6859
|
+
*/
|
|
6860
|
+
renderImage(node, pos) {
|
|
6861
|
+
return `<g${this.getDataNodeId(node)}>
|
|
6862
|
+
<rect x="${pos.x}" y="${pos.y}"
|
|
6863
|
+
width="${pos.width}" height="${pos.height}"
|
|
6864
|
+
rx="4"
|
|
6865
|
+
fill="${this.renderTheme.border}"/>
|
|
6866
|
+
</g>`;
|
|
6867
|
+
}
|
|
5876
6868
|
/**
|
|
5877
6869
|
* Render badge as shape only (no text)
|
|
5878
6870
|
*/
|
|
@@ -6103,18 +7095,42 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6103
7095
|
renderTable(node, pos) {
|
|
6104
7096
|
const title = String(node.props.title || "");
|
|
6105
7097
|
const columnsStr = String(node.props.columns || "Col1,Col2,Col3");
|
|
6106
|
-
const columns = columnsStr.split(",").map((c) => c.trim());
|
|
7098
|
+
const columns = columnsStr.split(",").map((c) => c.trim()).filter(Boolean);
|
|
6107
7099
|
const rowCount = Number(node.props.rows || node.props.rowsMock || 5);
|
|
7100
|
+
const actions = String(node.props.actions || "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
7101
|
+
const hasActions = actions.length > 0;
|
|
7102
|
+
const pagination = this.parseBooleanProp(node.props.pagination, false);
|
|
7103
|
+
const parsedPageCount = Number(node.props.pages || 5);
|
|
7104
|
+
const pageCount = Number.isFinite(parsedPageCount) && parsedPageCount > 0 ? Math.floor(parsedPageCount) : 5;
|
|
7105
|
+
const paginationAlign = String(node.props.paginationAlign || "right");
|
|
7106
|
+
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
7107
|
+
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
7108
|
+
const showOuterBackground = this.parseBooleanProp(
|
|
7109
|
+
node.props.background ?? node.props.backround,
|
|
7110
|
+
false
|
|
7111
|
+
);
|
|
7112
|
+
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
7113
|
+
const rawCaptionAlign = String(node.props.captionAlign || "");
|
|
7114
|
+
const captionAlign = rawCaptionAlign === "left" || rawCaptionAlign === "center" || rawCaptionAlign === "right" ? rawCaptionAlign : paginationAlign === "left" ? "right" : "left";
|
|
7115
|
+
const sameFooterAlign = hasCaption && pagination && captionAlign === paginationAlign;
|
|
7116
|
+
const safeColumns = columns.length > 0 ? columns : ["Column"];
|
|
6108
7117
|
const headerHeight = 44;
|
|
6109
7118
|
const rowHeight = 36;
|
|
6110
|
-
const
|
|
6111
|
-
|
|
7119
|
+
const actionColumnWidth = hasActions ? Math.max(96, Math.min(180, actions.length * 26 + 28)) : 0;
|
|
7120
|
+
const dataWidth = Math.max(20, pos.width - actionColumnWidth);
|
|
7121
|
+
const colWidth = dataWidth / safeColumns.length;
|
|
7122
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7123
|
+
if (showOuterBorder || showOuterBackground) {
|
|
7124
|
+
const outerFill = showOuterBackground ? this.renderTheme.cardBg : "none";
|
|
7125
|
+
const outerStroke = showOuterBorder ? this.renderTheme.border : "none";
|
|
7126
|
+
svg += `
|
|
6112
7127
|
<rect x="${pos.x}" y="${pos.y}"
|
|
6113
7128
|
width="${pos.width}" height="${pos.height}"
|
|
6114
7129
|
rx="8"
|
|
6115
|
-
fill="${
|
|
6116
|
-
stroke="${
|
|
7130
|
+
fill="${outerFill}"
|
|
7131
|
+
stroke="${outerStroke}"
|
|
6117
7132
|
stroke-width="1"/>`;
|
|
7133
|
+
}
|
|
6118
7134
|
if (title) {
|
|
6119
7135
|
svg += `<rect x="${pos.x + 16}" y="${pos.y + 12}"
|
|
6120
7136
|
width="100" height="12"
|
|
@@ -6122,19 +7138,28 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6122
7138
|
fill="${this.renderTheme.border}"/>`;
|
|
6123
7139
|
}
|
|
6124
7140
|
const headerY = pos.y + (title ? 32 : 0);
|
|
6125
|
-
|
|
7141
|
+
if (showInnerBorder) {
|
|
7142
|
+
svg += `<line x1="${pos.x}" y1="${headerY + headerHeight}" x2="${pos.x + pos.width}" y2="${headerY + headerHeight}"
|
|
6126
7143
|
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
6127
|
-
|
|
7144
|
+
}
|
|
7145
|
+
safeColumns.forEach((_, i) => {
|
|
6128
7146
|
svg += `<rect x="${pos.x + i * colWidth + 12}" y="${headerY + 16}"
|
|
6129
7147
|
width="50" height="10"
|
|
6130
7148
|
rx="4"
|
|
6131
7149
|
fill="${this.renderTheme.border}"/>`;
|
|
6132
7150
|
});
|
|
7151
|
+
if (hasActions && showInnerBorder) {
|
|
7152
|
+
const dividerX = pos.x + dataWidth;
|
|
7153
|
+
svg += `<line x1="${dividerX}" y1="${headerY + headerHeight}" x2="${dividerX}" y2="${headerY + headerHeight + rowCount * rowHeight}"
|
|
7154
|
+
stroke="${this.renderTheme.border}" stroke-width="1"/>`;
|
|
7155
|
+
}
|
|
6133
7156
|
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
|
|
6134
7157
|
const rowY = headerY + headerHeight + rowIdx * rowHeight;
|
|
6135
|
-
|
|
7158
|
+
if (showInnerBorder) {
|
|
7159
|
+
svg += `<line x1="${pos.x}" y1="${rowY + rowHeight}" x2="${pos.x + pos.width}" y2="${rowY + rowHeight}"
|
|
6136
7160
|
stroke="${this.renderTheme.border}" stroke-width="0.5"/>`;
|
|
6137
|
-
|
|
7161
|
+
}
|
|
7162
|
+
safeColumns.forEach((_, colIdx) => {
|
|
6138
7163
|
const variance = (rowIdx * 17 + colIdx * 11) % 5 * 10;
|
|
6139
7164
|
const blockWidth = Math.min(colWidth - 24, 60 + variance);
|
|
6140
7165
|
svg += `<rect x="${pos.x + colIdx * colWidth + 12}" y="${rowY + 12}"
|
|
@@ -6142,6 +7167,45 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6142
7167
|
rx="4"
|
|
6143
7168
|
fill="${this.renderTheme.border}"/>`;
|
|
6144
7169
|
});
|
|
7170
|
+
if (hasActions) {
|
|
7171
|
+
const iconSize = 14;
|
|
7172
|
+
const iconGap = 8;
|
|
7173
|
+
const actionsWidth = actions.length * iconSize + Math.max(0, actions.length - 1) * iconGap;
|
|
7174
|
+
let currentX = pos.x + pos.width - 12 - actionsWidth;
|
|
7175
|
+
const iconY = rowY + (rowHeight - iconSize) / 2;
|
|
7176
|
+
actions.forEach(() => {
|
|
7177
|
+
svg += `<rect x="${currentX}" y="${iconY}" width="${iconSize}" height="${iconSize}" rx="3" fill="${this.renderTheme.border}"/>`;
|
|
7178
|
+
currentX += iconSize + iconGap;
|
|
7179
|
+
});
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
const footerTop = headerY + headerHeight + rowCount * rowHeight + 16;
|
|
7183
|
+
if (hasCaption) {
|
|
7184
|
+
const captionY = sameFooterAlign ? footerTop : footerTop + (pagination ? 10 : 0);
|
|
7185
|
+
const captionWidth = Math.min(220, Math.max(90, pos.width * 0.34));
|
|
7186
|
+
let captionX = pos.x + 16;
|
|
7187
|
+
if (captionAlign === "center") {
|
|
7188
|
+
captionX = pos.x + (pos.width - captionWidth) / 2;
|
|
7189
|
+
} else if (captionAlign === "right") {
|
|
7190
|
+
captionX = pos.x + pos.width - 16 - captionWidth;
|
|
7191
|
+
}
|
|
7192
|
+
svg += `<rect x="${captionX}" y="${captionY}" width="${captionWidth}" height="10" rx="4" fill="${this.renderTheme.border}"/>`;
|
|
7193
|
+
}
|
|
7194
|
+
if (pagination) {
|
|
7195
|
+
const buttonWidth = 28;
|
|
7196
|
+
const buttonHeight = 24;
|
|
7197
|
+
const buttonGap = 8;
|
|
7198
|
+
const totalWidth = (pageCount + 2) * buttonWidth + (pageCount + 1) * buttonGap;
|
|
7199
|
+
const paginationY = sameFooterAlign ? footerTop + 18 : footerTop;
|
|
7200
|
+
let startX = pos.x + pos.width - totalWidth - 16;
|
|
7201
|
+
if (paginationAlign === "left") {
|
|
7202
|
+
startX = pos.x + 16;
|
|
7203
|
+
} else if (paginationAlign === "center") {
|
|
7204
|
+
startX = pos.x + (pos.width - totalWidth) / 2;
|
|
7205
|
+
}
|
|
7206
|
+
for (let i = 0; i < pageCount + 2; i++) {
|
|
7207
|
+
svg += `<rect x="${startX + i * (buttonWidth + buttonGap)}" y="${paginationY}" width="${buttonWidth}" height="${buttonHeight}" rx="4" fill="${this.renderTheme.border}"/>`;
|
|
7208
|
+
}
|
|
6145
7209
|
}
|
|
6146
7210
|
svg += "</g>";
|
|
6147
7211
|
return svg;
|
|
@@ -6154,21 +7218,39 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6154
7218
|
const subtitle = String(node.props.subtitle || "");
|
|
6155
7219
|
const actions = String(node.props.actions || "");
|
|
6156
7220
|
const user = String(node.props.user || "");
|
|
7221
|
+
const variant = String(node.props.variant || "default");
|
|
7222
|
+
const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
|
|
7223
|
+
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
7224
|
+
const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
|
|
7225
|
+
const radiusMap = {
|
|
7226
|
+
none: 0,
|
|
7227
|
+
sm: 4,
|
|
7228
|
+
md: this.tokens.card.radius,
|
|
7229
|
+
lg: 12,
|
|
7230
|
+
xl: 16
|
|
7231
|
+
};
|
|
7232
|
+
const topbarRadius = radiusMap[String(node.props.radius || "md")] ?? this.tokens.card.radius;
|
|
6157
7233
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
6158
7234
|
const titleWidth = Math.max(56, Math.min(topbar.titleMaxWidth * 0.55, topbar.titleMaxWidth));
|
|
6159
7235
|
const subtitleWidth = Math.max(48, Math.min(topbar.titleMaxWidth * 0.4, topbar.titleMaxWidth));
|
|
6160
|
-
let svg = `<g${this.getDataNodeId(node)}
|
|
7236
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7237
|
+
if (showBorder || showBackground) {
|
|
7238
|
+
const bg = showBackground ? this.renderTheme.cardBg : "none";
|
|
7239
|
+
const stroke = showBorder ? this.renderTheme.border : "none";
|
|
7240
|
+
svg += `
|
|
6161
7241
|
<rect x="${pos.x}" y="${pos.y}"
|
|
6162
7242
|
width="${pos.width}" height="${pos.height}"
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
stroke
|
|
7243
|
+
rx="${topbarRadius}"
|
|
7244
|
+
fill="${bg}"
|
|
7245
|
+
stroke="${stroke}"
|
|
7246
|
+
stroke-width="1"/>`;
|
|
7247
|
+
}
|
|
6166
7248
|
if (topbar.leftIcon) {
|
|
6167
7249
|
svg += `
|
|
6168
7250
|
<rect x="${topbar.leftIcon.badgeX}" y="${topbar.leftIcon.badgeY}"
|
|
6169
7251
|
width="${topbar.leftIcon.badgeSize}" height="${topbar.leftIcon.badgeSize}"
|
|
6170
7252
|
rx="${topbar.leftIcon.badgeRadius}"
|
|
6171
|
-
fill="${
|
|
7253
|
+
fill="${accentBlock}"/>`;
|
|
6172
7254
|
}
|
|
6173
7255
|
svg += `
|
|
6174
7256
|
<rect x="${topbar.textX}" y="${topbar.titleY - 12}"
|
|
@@ -6187,7 +7269,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6187
7269
|
<rect x="${action.x}" y="${action.y}"
|
|
6188
7270
|
width="${action.width}" height="${action.height}"
|
|
6189
7271
|
rx="6"
|
|
6190
|
-
fill="${
|
|
7272
|
+
fill="${accentBlock}"/>`;
|
|
6191
7273
|
});
|
|
6192
7274
|
if (topbar.userBadge) {
|
|
6193
7275
|
svg += `
|
|
@@ -6255,12 +7337,14 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6255
7337
|
*/
|
|
6256
7338
|
renderIcon(node, pos) {
|
|
6257
7339
|
const size = String(node.props.size || "md");
|
|
7340
|
+
const variant = String(node.props.variant || "default");
|
|
6258
7341
|
const iconSize = this.getIconSize(size);
|
|
7342
|
+
const blockColor = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveTextColor()), 0.35);
|
|
6259
7343
|
return `<g${this.getDataNodeId(node)}>
|
|
6260
7344
|
<rect x="${pos.x}" y="${pos.y + (pos.height - iconSize) / 2}"
|
|
6261
7345
|
width="${iconSize}" height="${iconSize}"
|
|
6262
7346
|
rx="2"
|
|
6263
|
-
fill="${
|
|
7347
|
+
fill="${blockColor}"/>
|
|
6264
7348
|
</g>`;
|
|
6265
7349
|
}
|
|
6266
7350
|
/**
|
|
@@ -6269,15 +7353,23 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6269
7353
|
renderIconButton(node, pos) {
|
|
6270
7354
|
const variant = String(node.props.variant || "default");
|
|
6271
7355
|
const size = String(node.props.size || "md");
|
|
7356
|
+
const density = this.ir.project.style.density || "normal";
|
|
7357
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7358
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6272
7359
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6273
7360
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6274
7361
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
6275
7362
|
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : "rgba(226, 232, 240, 0.9)";
|
|
6276
7363
|
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
6277
|
-
const buttonSize =
|
|
7364
|
+
const buttonSize = Math.max(
|
|
7365
|
+
16,
|
|
7366
|
+
Math.min(resolveActionControlHeight(size, density), pos.height - labelOffset)
|
|
7367
|
+
);
|
|
7368
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
7369
|
+
const buttonY = pos.y + labelOffset;
|
|
6278
7370
|
return `<g${this.getDataNodeId(node)}>
|
|
6279
|
-
<rect x="${pos.x}" y="${
|
|
6280
|
-
width="${
|
|
7371
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
7372
|
+
width="${buttonWidth}" height="${buttonSize}"
|
|
6281
7373
|
rx="6"
|
|
6282
7374
|
fill="${bgColor}"
|
|
6283
7375
|
stroke="${borderColor}"
|
|
@@ -6357,10 +7449,10 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6357
7449
|
/**
|
|
6358
7450
|
* Private helper: Render text as gray block
|
|
6359
7451
|
*/
|
|
6360
|
-
renderTextBlock(node, pos, text, fontSize, lineHeightMultiplier) {
|
|
7452
|
+
renderTextBlock(node, pos, text, fontSize, lineHeightMultiplier, color) {
|
|
6361
7453
|
const lineHeight = Math.ceil(fontSize * lineHeightMultiplier);
|
|
6362
7454
|
const blockHeight = Math.max(8, Math.round(fontSize * 0.75));
|
|
6363
|
-
const blockColor = this.renderTheme.border;
|
|
7455
|
+
const blockColor = color || this.renderTheme.border;
|
|
6364
7456
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
6365
7457
|
const contentHeight = lines.length * lineHeight;
|
|
6366
7458
|
const startY = pos.y + Math.max(0, (pos.height - contentHeight) / 2);
|
|
@@ -6442,38 +7534,78 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6442
7534
|
renderButton(node, pos) {
|
|
6443
7535
|
const text = String(node.props.text || "Button");
|
|
6444
7536
|
const variant = String(node.props.variant || "default");
|
|
7537
|
+
const size = String(node.props.size || "md");
|
|
7538
|
+
const density = this.ir.project.style.density || "normal";
|
|
7539
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
7540
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
6445
7541
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
7542
|
+
const iconName = String(node.props.icon || "").trim();
|
|
7543
|
+
const iconAlign = String(node.props.iconAlign || "left").toLowerCase();
|
|
6446
7544
|
const radius = this.tokens.button.radius;
|
|
6447
7545
|
const fontSize = this.tokens.button.fontSize;
|
|
6448
7546
|
const fontWeight = this.tokens.button.fontWeight;
|
|
6449
7547
|
const paddingX = this.tokens.button.paddingX;
|
|
6450
|
-
const
|
|
7548
|
+
const buttonHeight = Math.max(
|
|
7549
|
+
16,
|
|
7550
|
+
Math.min(resolveControlHeight(size, density), pos.height - labelOffset)
|
|
7551
|
+
);
|
|
7552
|
+
const buttonY = pos.y + labelOffset;
|
|
7553
|
+
const iconSvg = iconName ? getIcon(iconName) : null;
|
|
7554
|
+
const iconSize = iconSvg ? Math.round(fontSize * 1.1) : 0;
|
|
7555
|
+
const iconGap = iconSvg ? 8 : 0;
|
|
7556
|
+
const edgePad = 12;
|
|
7557
|
+
const textPad = paddingX + extraPadding;
|
|
6451
7558
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
6452
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth +
|
|
6453
|
-
const
|
|
6454
|
-
const availableTextWidth = Math.max(0, buttonWidth - paddingX * 2);
|
|
7559
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (iconSvg ? iconSize + iconGap : 0) + textPad * 2, 60), pos.width);
|
|
7560
|
+
const availableTextWidth = Math.max(0, buttonWidth - textPad * 2 - (iconSvg ? iconSize + iconGap : 0));
|
|
6455
7561
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
6456
7562
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6457
7563
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6458
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7564
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6459
7565
|
const borderColor = variantColor;
|
|
6460
7566
|
const textColor = variantColor;
|
|
6461
7567
|
const strokeWidth = 0.5;
|
|
6462
|
-
|
|
6463
|
-
|
|
7568
|
+
const iconOffsetY = buttonY + (buttonHeight - iconSize) / 2;
|
|
7569
|
+
const iconX = iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize : pos.x + edgePad;
|
|
7570
|
+
const textAlign = String(node.props.align || "center").toLowerCase();
|
|
7571
|
+
const sidePad = textPad + 4;
|
|
7572
|
+
let textX;
|
|
7573
|
+
let textAnchor;
|
|
7574
|
+
if (textAlign === "left") {
|
|
7575
|
+
textX = iconSvg && iconAlign === "left" ? pos.x + edgePad + iconSize + iconGap : pos.x + sidePad;
|
|
7576
|
+
textAnchor = "start";
|
|
7577
|
+
} else if (textAlign === "right") {
|
|
7578
|
+
textX = iconSvg && iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize - iconGap : pos.x + buttonWidth - sidePad;
|
|
7579
|
+
textAnchor = "end";
|
|
7580
|
+
} else {
|
|
7581
|
+
textX = pos.x + buttonWidth / 2;
|
|
7582
|
+
textAnchor = "middle";
|
|
7583
|
+
}
|
|
7584
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
7585
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
6464
7586
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
6465
7587
|
rx="${radius}"
|
|
6466
7588
|
fill="none"
|
|
6467
7589
|
stroke="${borderColor}"
|
|
6468
7590
|
stroke-width="${strokeWidth}"
|
|
6469
|
-
filter="url(#sketch-rough)"
|
|
6470
|
-
|
|
7591
|
+
filter="url(#sketch-rough)"/>`;
|
|
7592
|
+
if (iconSvg) {
|
|
7593
|
+
svg += `
|
|
7594
|
+
<g transform="translate(${iconX}, ${iconOffsetY})">
|
|
7595
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${textColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7596
|
+
${this.extractSvgContent(iconSvg)}
|
|
7597
|
+
</svg>
|
|
7598
|
+
</g>`;
|
|
7599
|
+
}
|
|
7600
|
+
svg += `
|
|
7601
|
+
<text x="${textX}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
6471
7602
|
font-family="${this.fontFamily}"
|
|
6472
7603
|
font-size="${fontSize}"
|
|
6473
7604
|
font-weight="${fontWeight}"
|
|
6474
7605
|
fill="${textColor}"
|
|
6475
|
-
text-anchor="
|
|
7606
|
+
text-anchor="${textAnchor}">${this.escapeXml(visibleText)}</text>
|
|
6476
7607
|
</g>`;
|
|
7608
|
+
return svg;
|
|
6477
7609
|
}
|
|
6478
7610
|
/**
|
|
6479
7611
|
* Render badge with colored border instead of fill
|
|
@@ -6483,7 +7615,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6483
7615
|
const variant = String(node.props.variant || "default");
|
|
6484
7616
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6485
7617
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6486
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7618
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6487
7619
|
const borderColor = variantColor;
|
|
6488
7620
|
const textColor = variantColor;
|
|
6489
7621
|
const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
|
|
@@ -6510,17 +7642,22 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6510
7642
|
const iconName = String(node.props.icon || "help-circle");
|
|
6511
7643
|
const variant = String(node.props.variant || "default");
|
|
6512
7644
|
const size = String(node.props.size || "md");
|
|
7645
|
+
const density = this.ir.project.style.density || "normal";
|
|
7646
|
+
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7647
|
+
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
6513
7648
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6514
7649
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6515
|
-
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) :
|
|
7650
|
+
const variantColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.resolveTextColor();
|
|
6516
7651
|
const borderColor = variantColor;
|
|
6517
7652
|
const iconColor = variantColor;
|
|
6518
|
-
const buttonSize =
|
|
7653
|
+
const buttonSize = Math.max(16, Math.min(resolveControlHeight(size, density), pos.height - labelOffset));
|
|
7654
|
+
const buttonWidth = buttonSize + extraPadding * 2;
|
|
6519
7655
|
const radius = 6;
|
|
7656
|
+
const buttonY = pos.y + labelOffset;
|
|
6520
7657
|
const iconSvg = this.getIconSvg(iconName);
|
|
6521
7658
|
let svg = `<g${this.getDataNodeId(node)}>
|
|
6522
|
-
<rect x="${pos.x}" y="${
|
|
6523
|
-
width="${
|
|
7659
|
+
<rect x="${pos.x}" y="${buttonY}"
|
|
7660
|
+
width="${buttonWidth}" height="${buttonSize}"
|
|
6524
7661
|
rx="${radius}"
|
|
6525
7662
|
fill="none"
|
|
6526
7663
|
stroke="${borderColor}"
|
|
@@ -6528,8 +7665,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6528
7665
|
filter="url(#sketch-rough)"/>`;
|
|
6529
7666
|
if (iconSvg) {
|
|
6530
7667
|
const iconSize = buttonSize * 0.6;
|
|
6531
|
-
const offsetX = pos.x + (
|
|
6532
|
-
const offsetY =
|
|
7668
|
+
const offsetX = pos.x + (buttonWidth - iconSize) / 2;
|
|
7669
|
+
const offsetY = buttonY + (buttonSize - iconSize) / 2;
|
|
6533
7670
|
svg += `
|
|
6534
7671
|
<g transform="translate(${offsetX}, ${offsetY})">
|
|
6535
7672
|
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -6593,29 +7730,67 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6593
7730
|
renderInput(node, pos) {
|
|
6594
7731
|
const label = String(node.props.label || "");
|
|
6595
7732
|
const placeholder = String(node.props.placeholder || "");
|
|
7733
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
7734
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
6596
7735
|
const radius = this.tokens.input.radius;
|
|
6597
7736
|
const fontSize = this.tokens.input.fontSize;
|
|
6598
7737
|
const paddingX = this.tokens.input.paddingX;
|
|
6599
7738
|
const labelOffset = this.getControlLabelOffset(label);
|
|
6600
7739
|
const controlY = pos.y + labelOffset;
|
|
6601
7740
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
6602
|
-
|
|
6603
|
-
|
|
7741
|
+
const iconSize = 16;
|
|
7742
|
+
const iconPad = 12;
|
|
7743
|
+
const iconInnerGap = 8;
|
|
7744
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
7745
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
7746
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
7747
|
+
const rightOffset = iconRightSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
7748
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : paddingX);
|
|
7749
|
+
const iconColor = "#888888";
|
|
7750
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
7751
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7752
|
+
if (label) {
|
|
7753
|
+
svg += `
|
|
7754
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
6604
7755
|
font-family="${this.fontFamily}"
|
|
6605
7756
|
font-size="12"
|
|
6606
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
7757
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
7758
|
+
}
|
|
7759
|
+
svg += `
|
|
6607
7760
|
<rect x="${pos.x}" y="${controlY}"
|
|
6608
7761
|
width="${pos.width}" height="${controlHeight}"
|
|
6609
7762
|
rx="${radius}"
|
|
6610
7763
|
fill="${this.renderTheme.cardBg}"
|
|
6611
7764
|
stroke="#2D3748"
|
|
6612
7765
|
stroke-width="0.5"
|
|
6613
|
-
filter="url(#sketch-rough)"
|
|
6614
|
-
|
|
7766
|
+
filter="url(#sketch-rough)"/>`;
|
|
7767
|
+
if (iconLeftSvg) {
|
|
7768
|
+
svg += `
|
|
7769
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
7770
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7771
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
7772
|
+
</svg>
|
|
7773
|
+
</g>`;
|
|
7774
|
+
}
|
|
7775
|
+
if (iconRightSvg) {
|
|
7776
|
+
svg += `
|
|
7777
|
+
<g transform="translate(${pos.x + pos.width - iconPad - iconSize}, ${iconCenterY})">
|
|
7778
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7779
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
7780
|
+
</svg>
|
|
7781
|
+
</g>`;
|
|
7782
|
+
}
|
|
7783
|
+
if (placeholder) {
|
|
7784
|
+
const availWidth = pos.width - (iconLeftSvg ? leftOffset : paddingX) - (iconRightSvg ? rightOffset : paddingX);
|
|
7785
|
+
const visiblePh = this.truncateTextToWidth(placeholder, Math.max(0, availWidth), fontSize);
|
|
7786
|
+
svg += `
|
|
7787
|
+
<text x="${textX}" y="${controlY + controlHeight / 2 + 5}"
|
|
6615
7788
|
font-family="${this.fontFamily}"
|
|
6616
7789
|
font-size="${fontSize}"
|
|
6617
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
6618
|
-
|
|
7790
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePh)}</text>`;
|
|
7791
|
+
}
|
|
7792
|
+
svg += "\n </g>";
|
|
7793
|
+
return svg;
|
|
6619
7794
|
}
|
|
6620
7795
|
/**
|
|
6621
7796
|
* Render textarea with thicker border
|
|
@@ -6680,6 +7855,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6680
7855
|
*/
|
|
6681
7856
|
renderHeading(node, pos) {
|
|
6682
7857
|
const text = String(node.props.text || "Heading");
|
|
7858
|
+
const variant = String(node.props.variant || "default");
|
|
7859
|
+
const headingColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
6683
7860
|
const headingTypography = this.getHeadingTypography(node);
|
|
6684
7861
|
const fontSize = headingTypography.fontSize;
|
|
6685
7862
|
const fontWeight = headingTypography.fontWeight;
|
|
@@ -6692,7 +7869,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6692
7869
|
font-family="${this.fontFamily}"
|
|
6693
7870
|
font-size="${fontSize}"
|
|
6694
7871
|
font-weight="${fontWeight}"
|
|
6695
|
-
fill="${
|
|
7872
|
+
fill="${headingColor}">${this.escapeXml(text)}</text>
|
|
6696
7873
|
</g>`;
|
|
6697
7874
|
}
|
|
6698
7875
|
const tspans = lines.map(
|
|
@@ -6703,7 +7880,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6703
7880
|
font-family="${this.fontFamily}"
|
|
6704
7881
|
font-size="${fontSize}"
|
|
6705
7882
|
font-weight="${fontWeight}"
|
|
6706
|
-
fill="${
|
|
7883
|
+
fill="${headingColor}">${tspans}</text>
|
|
6707
7884
|
</g>`;
|
|
6708
7885
|
}
|
|
6709
7886
|
/**
|
|
@@ -6714,7 +7891,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6714
7891
|
const subtitle = String(node.props.subtitle || "");
|
|
6715
7892
|
const actions = String(node.props.actions || "");
|
|
6716
7893
|
const user = String(node.props.user || "");
|
|
6717
|
-
const
|
|
7894
|
+
const variant = String(node.props.variant || "default");
|
|
7895
|
+
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
6718
7896
|
const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
|
|
6719
7897
|
let svg = `<g${this.getDataNodeId(node)}>
|
|
6720
7898
|
<rect x="${pos.x}" y="${pos.y}"
|
|
@@ -6807,79 +7985,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6807
7985
|
* Render table with sketch filter and Comic Sans
|
|
6808
7986
|
*/
|
|
6809
7987
|
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;
|
|
7988
|
+
const standard = super.renderTable(node, pos);
|
|
7989
|
+
return standard.replace("<g", '<g filter="url(#sketch-rough)"');
|
|
6877
7990
|
}
|
|
6878
7991
|
/**
|
|
6879
7992
|
* Render text with Comic Sans
|
|
6880
7993
|
*/
|
|
6881
7994
|
renderText(node, pos) {
|
|
6882
|
-
const text = String(node.props.
|
|
7995
|
+
const text = String(node.props.text || "Text content");
|
|
6883
7996
|
const fontSize = this.tokens.text.fontSize;
|
|
6884
7997
|
const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
|
|
6885
7998
|
const lines = this.wrapTextToLines(text, pos.width, fontSize);
|
|
@@ -6931,31 +8044,67 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
6931
8044
|
renderSelect(node, pos) {
|
|
6932
8045
|
const label = String(node.props.label || "");
|
|
6933
8046
|
const placeholder = String(node.props.placeholder || "Select...");
|
|
8047
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
8048
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
6934
8049
|
const labelOffset = this.getControlLabelOffset(label);
|
|
6935
8050
|
const controlY = pos.y + labelOffset;
|
|
6936
8051
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
6937
8052
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
6938
|
-
|
|
6939
|
-
|
|
8053
|
+
const iconSize = 16;
|
|
8054
|
+
const iconPad = 12;
|
|
8055
|
+
const iconInnerGap = 8;
|
|
8056
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
8057
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
8058
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
8059
|
+
const chevronWidth = 20;
|
|
8060
|
+
const iconColor = "#888888";
|
|
8061
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
8062
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
8063
|
+
if (label) {
|
|
8064
|
+
svg += `
|
|
8065
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
6940
8066
|
font-family="${this.fontFamily}"
|
|
6941
8067
|
font-size="12"
|
|
6942
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
8068
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
8069
|
+
}
|
|
8070
|
+
svg += `
|
|
6943
8071
|
<rect x="${pos.x}" y="${controlY}"
|
|
6944
8072
|
width="${pos.width}" height="${controlHeight}"
|
|
6945
8073
|
rx="6"
|
|
6946
8074
|
fill="${this.renderTheme.cardBg}"
|
|
6947
8075
|
stroke="#2D3748"
|
|
6948
8076
|
stroke-width="0.5"
|
|
6949
|
-
filter="url(#sketch-rough)"
|
|
6950
|
-
|
|
8077
|
+
filter="url(#sketch-rough)"/>`;
|
|
8078
|
+
if (iconLeftSvg) {
|
|
8079
|
+
svg += `
|
|
8080
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
8081
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8082
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
8083
|
+
</svg>
|
|
8084
|
+
</g>`;
|
|
8085
|
+
}
|
|
8086
|
+
if (iconRightSvg) {
|
|
8087
|
+
svg += `
|
|
8088
|
+
<g transform="translate(${pos.x + pos.width - chevronWidth - iconPad - iconSize}, ${iconCenterY})">
|
|
8089
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8090
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
8091
|
+
</svg>
|
|
8092
|
+
</g>`;
|
|
8093
|
+
}
|
|
8094
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : 12);
|
|
8095
|
+
const availWidth = pos.width - (iconLeftSvg ? leftOffset : 12) - chevronWidth - (iconRightSvg ? iconPad + iconSize + iconInnerGap : 0);
|
|
8096
|
+
const visiblePh = this.truncateTextToWidth(placeholder, Math.max(0, availWidth), 14);
|
|
8097
|
+
svg += `
|
|
8098
|
+
<text x="${textX}" y="${centerY}"
|
|
6951
8099
|
font-family="${this.fontFamily}"
|
|
6952
8100
|
font-size="14"
|
|
6953
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
8101
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePh)}</text>
|
|
6954
8102
|
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
6955
8103
|
font-family="${this.fontFamily}"
|
|
6956
8104
|
font-size="16"
|
|
6957
8105
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
6958
8106
|
</g>`;
|
|
8107
|
+
return svg;
|
|
6959
8108
|
}
|
|
6960
8109
|
/**
|
|
6961
8110
|
* Render checkbox with sketch filter and Comic Sans
|
|
@@ -7354,44 +8503,43 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7354
8503
|
renderImage(node, pos) {
|
|
7355
8504
|
const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
|
|
7356
8505
|
const iconType = String(node.props.icon || "").trim();
|
|
8506
|
+
const variant = String(node.props.variant || "").trim();
|
|
7357
8507
|
const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
|
|
8508
|
+
const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
|
|
7358
8509
|
if (iconSvg) {
|
|
7359
|
-
const
|
|
7360
|
-
const
|
|
7361
|
-
const
|
|
7362
|
-
const
|
|
7363
|
-
const
|
|
7364
|
-
const
|
|
8510
|
+
const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
|
|
8511
|
+
const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
|
|
8512
|
+
const variantColor = hasVariant ? this.resolveVariantColor(variant, this.renderTheme.primary) : null;
|
|
8513
|
+
const bgColor = hasVariant ? this.hexToRgba(variantColor, 0.12) : imageBg;
|
|
8514
|
+
const iconColor = hasVariant ? variantColor : "#666666";
|
|
8515
|
+
const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
|
|
8516
|
+
const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
|
|
8517
|
+
const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
|
|
7365
8518
|
return `<g${this.getDataNodeId(node)}>
|
|
7366
|
-
<!-- Image Background -->
|
|
7367
8519
|
<rect x="${pos.x}" y="${pos.y}"
|
|
7368
8520
|
width="${pos.width}" height="${pos.height}"
|
|
7369
|
-
fill="
|
|
7370
|
-
stroke="#2D3748"
|
|
7371
|
-
stroke-width="0.5"
|
|
8521
|
+
fill="${bgColor}"
|
|
7372
8522
|
rx="4"
|
|
7373
8523
|
filter="url(#sketch-rough)"/>
|
|
7374
|
-
|
|
7375
|
-
<!-- Custom Icon Placeholder -->
|
|
7376
|
-
<rect x="${badgeX}" y="${badgeY}"
|
|
7377
|
-
width="${badgeSize}" height="${badgeSize}"
|
|
7378
|
-
rx="${Math.max(4, badgeSize * 0.2)}"
|
|
7379
|
-
fill="none"
|
|
7380
|
-
stroke="#2D3748"
|
|
7381
|
-
stroke-width="0.5"
|
|
7382
|
-
filter="url(#sketch-rough)"/>
|
|
7383
8524
|
<g transform="translate(${iconOffsetX}, ${iconOffsetY})">
|
|
7384
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
8525
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7385
8526
|
${this.extractSvgContent(iconSvg)}
|
|
7386
8527
|
</svg>
|
|
7387
8528
|
</g>
|
|
8529
|
+
<rect x="${pos.x}" y="${pos.y}"
|
|
8530
|
+
width="${pos.width}" height="${pos.height}"
|
|
8531
|
+
fill="none"
|
|
8532
|
+
stroke="#2D3748"
|
|
8533
|
+
stroke-width="0.5"
|
|
8534
|
+
rx="4"
|
|
8535
|
+
filter="url(#sketch-rough)"/>
|
|
7388
8536
|
</g>`;
|
|
7389
8537
|
}
|
|
7390
8538
|
return `<g${this.getDataNodeId(node)}>
|
|
7391
8539
|
<!-- Image Background -->
|
|
7392
8540
|
<rect x="${pos.x}" y="${pos.y}"
|
|
7393
8541
|
width="${pos.width}" height="${pos.height}"
|
|
7394
|
-
fill="
|
|
8542
|
+
fill="${imageBg}"
|
|
7395
8543
|
stroke="#2D3748"
|
|
7396
8544
|
stroke-width="0.5"
|
|
7397
8545
|
rx="4"
|
|
@@ -7446,17 +8594,23 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7446
8594
|
*/
|
|
7447
8595
|
renderSidebarMenu(node, pos) {
|
|
7448
8596
|
const itemsStr = String(node.props.items || "Item 1,Item 2,Item 3");
|
|
8597
|
+
const iconsStr = String(node.props.icons || "");
|
|
7449
8598
|
const items = itemsStr.split(",").map((s) => s.trim());
|
|
8599
|
+
const icons = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
|
|
7450
8600
|
const itemHeight = 40;
|
|
7451
8601
|
const fontSize = 14;
|
|
7452
8602
|
const activeIndex = Number(node.props.active || 0);
|
|
7453
8603
|
const accentColor = this.resolveAccentColor();
|
|
8604
|
+
const variantProp = String(node.props.variant || "").trim();
|
|
8605
|
+
const semanticVariant = variantProp ? this.getSemanticVariantColor(variantProp) : void 0;
|
|
8606
|
+
const hasVariant = variantProp.length > 0 && (semanticVariant !== void 0 || this.colorResolver.hasColor(variantProp));
|
|
8607
|
+
const activeColor = hasVariant ? this.resolveVariantColor(variantProp, accentColor) : accentColor;
|
|
7454
8608
|
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7455
8609
|
items.forEach((item, index) => {
|
|
7456
8610
|
const itemY = pos.y + index * itemHeight;
|
|
7457
8611
|
const isActive = index === activeIndex;
|
|
7458
|
-
const bgColor = isActive ? this.hexToRgba(
|
|
7459
|
-
const textColor = isActive ?
|
|
8612
|
+
const bgColor = isActive ? this.hexToRgba(activeColor, 0.15) : "transparent";
|
|
8613
|
+
const textColor = isActive ? activeColor : this.resolveTextColor();
|
|
7460
8614
|
const fontWeight = isActive ? "500" : "400";
|
|
7461
8615
|
if (isActive) {
|
|
7462
8616
|
svg += `
|
|
@@ -7466,8 +8620,24 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7466
8620
|
fill="${bgColor}"
|
|
7467
8621
|
filter="url(#sketch-rough)"/>`;
|
|
7468
8622
|
}
|
|
8623
|
+
let currentX = pos.x + 12;
|
|
8624
|
+
if (icons[index]) {
|
|
8625
|
+
const iconSvg = getIcon(icons[index]);
|
|
8626
|
+
if (iconSvg) {
|
|
8627
|
+
const iconSize = 16;
|
|
8628
|
+
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
8629
|
+
const iconColor = isActive ? activeColor : this.resolveMutedColor();
|
|
8630
|
+
svg += `
|
|
8631
|
+
<g transform="translate(${currentX}, ${iconY})">
|
|
8632
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8633
|
+
${this.extractSvgContent(iconSvg)}
|
|
8634
|
+
</svg>
|
|
8635
|
+
</g>`;
|
|
8636
|
+
currentX += iconSize + 8;
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
7469
8639
|
svg += `
|
|
7470
|
-
<text x="${
|
|
8640
|
+
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
7471
8641
|
font-family="${this.fontFamily}"
|
|
7472
8642
|
font-size="${fontSize}"
|
|
7473
8643
|
font-weight="${fontWeight}"
|
|
@@ -7480,22 +8650,23 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7480
8650
|
* Render icon (same as base, icons don't need filter)
|
|
7481
8651
|
*/
|
|
7482
8652
|
renderIcon(node, pos) {
|
|
7483
|
-
const iconType = String(node.props.
|
|
8653
|
+
const iconType = String(node.props.icon || "help-circle");
|
|
7484
8654
|
const size = String(node.props.size || "md");
|
|
8655
|
+
const variant = String(node.props.variant || "default");
|
|
7485
8656
|
const iconSvg = getIcon(iconType);
|
|
7486
8657
|
if (!iconSvg) {
|
|
7487
8658
|
return `<g${this.getDataNodeId(node)}>
|
|
7488
8659
|
<circle cx="${pos.x + pos.width / 2}" cy="${pos.y + pos.height / 2}"
|
|
7489
8660
|
r="${Math.min(pos.width, pos.height) / 2 - 2}"
|
|
7490
|
-
fill="none" stroke="
|
|
8661
|
+
fill="none" stroke="${this.resolveMutedColor()}" stroke-width="0.5"
|
|
7491
8662
|
filter="url(#sketch-rough)"/>
|
|
7492
8663
|
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
|
|
7493
8664
|
font-family="${this.fontFamily}"
|
|
7494
|
-
font-size="12" fill="
|
|
8665
|
+
font-size="12" fill="${this.resolveMutedColor()}" text-anchor="middle">?</text>
|
|
7495
8666
|
</g>`;
|
|
7496
8667
|
}
|
|
7497
8668
|
const iconSize = this.getIconSize(size);
|
|
7498
|
-
const iconColor = "
|
|
8669
|
+
const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
|
|
7499
8670
|
const offsetX = pos.x + (pos.width - iconSize) / 2;
|
|
7500
8671
|
const offsetY = pos.y + (pos.height - iconSize) / 2;
|
|
7501
8672
|
return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
|