@wireweave/core 1.0.0-beta.20260107133450 → 1.0.0-beta.20260110141318
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 +3067 -823
- package/dist/index.d.cts +168 -3
- package/dist/index.d.ts +168 -3
- package/dist/index.js +3054 -823
- package/dist/parser.cjs +636 -292
- package/dist/parser.d.cts +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/parser.js +636 -292
- package/dist/renderer.cjs +1756 -525
- package/dist/renderer.d.cts +105 -58
- package/dist/renderer.d.ts +105 -58
- package/dist/renderer.js +1756 -525
- package/dist/{types-DtovIYS6.d.cts → types-lcJzPcR0.d.cts} +40 -2
- package/dist/{types-DtovIYS6.d.ts → types-lcJzPcR0.d.ts} +40 -2
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -77,12 +77,17 @@ function generateContainerStyles(prefix) {
|
|
|
77
77
|
padding: 16px;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
/* Cards in flex rows
|
|
80
|
+
/* Cards in flex rows: respect explicit width, shrink if needed */
|
|
81
81
|
.${prefix}-row > .${prefix}-card {
|
|
82
|
-
flex:
|
|
82
|
+
flex: 0 1 auto;
|
|
83
83
|
min-width: 0;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/* Cards without explicit width should expand to fill space */
|
|
87
|
+
.${prefix}-row > .${prefix}-card-flex {
|
|
88
|
+
flex: 1 1 0%;
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
.${prefix}-card-title {
|
|
87
92
|
margin: 0 0 12px 0;
|
|
88
93
|
font-size: 18px;
|
|
@@ -236,6 +241,11 @@ function generateTextStyles(prefix) {
|
|
|
236
241
|
line-height: 1.25;
|
|
237
242
|
}
|
|
238
243
|
|
|
244
|
+
/* Remove bottom margin when title is in a row (inline with other elements) */
|
|
245
|
+
.${prefix}-row .${prefix}-title {
|
|
246
|
+
margin-bottom: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
239
249
|
h1.${prefix}-title { font-size: 36px; }
|
|
240
250
|
h2.${prefix}-title { font-size: 30px; }
|
|
241
251
|
h3.${prefix}-title { font-size: 24px; }
|
|
@@ -630,6 +640,30 @@ img.${prefix}-image {
|
|
|
630
640
|
font-size: 14px;
|
|
631
641
|
}
|
|
632
642
|
|
|
643
|
+
.${prefix}-placeholder-with-children {
|
|
644
|
+
position: relative;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.${prefix}-placeholder-label {
|
|
648
|
+
position: absolute;
|
|
649
|
+
top: 50%;
|
|
650
|
+
left: 50%;
|
|
651
|
+
transform: translate(-50%, -50%);
|
|
652
|
+
z-index: 0;
|
|
653
|
+
pointer-events: none;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.${prefix}-placeholder-overlay {
|
|
657
|
+
position: absolute;
|
|
658
|
+
top: 0;
|
|
659
|
+
left: 0;
|
|
660
|
+
right: 0;
|
|
661
|
+
bottom: 0;
|
|
662
|
+
z-index: 1;
|
|
663
|
+
display: flex;
|
|
664
|
+
flex-direction: column;
|
|
665
|
+
}
|
|
666
|
+
|
|
633
667
|
.${prefix}-avatar {
|
|
634
668
|
display: inline-flex;
|
|
635
669
|
align-items: center;
|
|
@@ -745,12 +779,12 @@ svg.${prefix}-icon {
|
|
|
745
779
|
display: block;
|
|
746
780
|
}
|
|
747
781
|
|
|
748
|
-
/* Icon size tokens */
|
|
782
|
+
/* Icon size tokens - matches SVG renderer */
|
|
749
783
|
svg.${prefix}-icon-xs { width: 12px; height: 12px; }
|
|
750
|
-
svg.${prefix}-icon-sm { width:
|
|
751
|
-
svg.${prefix}-icon-md { width:
|
|
752
|
-
svg.${prefix}-icon-lg { width:
|
|
753
|
-
svg.${prefix}-icon-xl { width:
|
|
784
|
+
svg.${prefix}-icon-sm { width: 16px; height: 16px; }
|
|
785
|
+
svg.${prefix}-icon-md { width: 20px; height: 20px; }
|
|
786
|
+
svg.${prefix}-icon-lg { width: 24px; height: 24px; }
|
|
787
|
+
svg.${prefix}-icon-xl { width: 32px; height: 32px; }
|
|
754
788
|
|
|
755
789
|
.${prefix}-icon svg {
|
|
756
790
|
display: block;
|
|
@@ -1046,6 +1080,27 @@ function generateNavigationStyles(_theme, prefix) {
|
|
|
1046
1080
|
cursor: not-allowed;
|
|
1047
1081
|
}
|
|
1048
1082
|
|
|
1083
|
+
.${prefix}-nav-group {
|
|
1084
|
+
display: flex;
|
|
1085
|
+
flex-direction: column;
|
|
1086
|
+
gap: 4px;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.${prefix}-nav-group-label {
|
|
1090
|
+
font-size: 11px;
|
|
1091
|
+
font-weight: 500;
|
|
1092
|
+
color: var(--${prefix}-muted);
|
|
1093
|
+
text-transform: uppercase;
|
|
1094
|
+
letter-spacing: 0.05em;
|
|
1095
|
+
padding: 8px 16px 4px;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.${prefix}-nav-divider {
|
|
1099
|
+
margin: 8px 0;
|
|
1100
|
+
border: none;
|
|
1101
|
+
border-top: 1px solid var(--${prefix}-border);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1049
1104
|
.${prefix}-tabs {
|
|
1050
1105
|
border-bottom: 1px solid var(--${prefix}-border);
|
|
1051
1106
|
}
|
|
@@ -1263,7 +1318,6 @@ function generateBaseStyles(prefix) {
|
|
|
1263
1318
|
font-family: var(--${prefix}-font);
|
|
1264
1319
|
color: var(--${prefix}-fg);
|
|
1265
1320
|
background: var(--${prefix}-bg);
|
|
1266
|
-
min-height: 100vh;
|
|
1267
1321
|
box-sizing: border-box;
|
|
1268
1322
|
position: relative;
|
|
1269
1323
|
display: flex;
|
|
@@ -1274,10 +1328,19 @@ function generateBaseStyles(prefix) {
|
|
|
1274
1328
|
overflow: hidden;
|
|
1275
1329
|
}
|
|
1276
1330
|
|
|
1331
|
+
/* Col direct child of page should fill page height */
|
|
1332
|
+
.${prefix}-page > .${prefix}-col {
|
|
1333
|
+
flex: 1;
|
|
1334
|
+
min-height: 0;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1277
1337
|
/* Row containing sidebar should fill remaining space */
|
|
1278
1338
|
.${prefix}-page > .${prefix}-row:has(.${prefix}-sidebar),
|
|
1279
|
-
.${prefix}-page > .${prefix}-row:has(.${prefix}-main)
|
|
1339
|
+
.${prefix}-page > .${prefix}-row:has(.${prefix}-main),
|
|
1340
|
+
.${prefix}-page > .${prefix}-col > .${prefix}-row:has(.${prefix}-sidebar),
|
|
1341
|
+
.${prefix}-page > .${prefix}-col > .${prefix}-row:has(.${prefix}-main) {
|
|
1280
1342
|
flex: 1;
|
|
1343
|
+
min-height: 0;
|
|
1281
1344
|
align-items: stretch;
|
|
1282
1345
|
}
|
|
1283
1346
|
|
|
@@ -1320,10 +1383,15 @@ function generateGridClasses(_theme, prefix) {
|
|
|
1320
1383
|
box-sizing: border-box;
|
|
1321
1384
|
}
|
|
1322
1385
|
|
|
1386
|
+
/* When explicit width is set, don't flex-grow */
|
|
1387
|
+
.${prefix}-row[style*="width:"],
|
|
1388
|
+
.${prefix}-col[style*="width:"] {
|
|
1389
|
+
flex: 0 0 auto;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1323
1392
|
`;
|
|
1324
1393
|
for (let i = 1; i <= 12; i++) {
|
|
1325
|
-
|
|
1326
|
-
css += `.${prefix}-col-${i} { flex: 0 0 auto; width: ${width}%; }
|
|
1394
|
+
css += `.${prefix}-col-${i} { flex: ${i} 0 0%; min-width: 0; }
|
|
1327
1395
|
`;
|
|
1328
1396
|
}
|
|
1329
1397
|
return css;
|
|
@@ -1491,6 +1559,20 @@ function generateLayoutClasses(prefix) {
|
|
|
1491
1559
|
.${prefix}-main {
|
|
1492
1560
|
flex: 1;
|
|
1493
1561
|
padding: 16px;
|
|
1562
|
+
display: flex;
|
|
1563
|
+
flex-direction: column;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/* Scrollable main content */
|
|
1567
|
+
.${prefix}-main.${prefix}-scroll {
|
|
1568
|
+
overflow-y: auto;
|
|
1569
|
+
overflow-x: hidden;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/* Main content should align to top, not stretch to fill */
|
|
1573
|
+
/* But allow explicit flex=1 to override */
|
|
1574
|
+
.${prefix}-main > .${prefix}-col:not(.${prefix}-flex-1) {
|
|
1575
|
+
flex: 0 0 auto;
|
|
1494
1576
|
}
|
|
1495
1577
|
|
|
1496
1578
|
.${prefix}-footer {
|
|
@@ -1507,6 +1589,7 @@ function generateLayoutClasses(prefix) {
|
|
|
1507
1589
|
border-right: 1px solid var(--${prefix}-border);
|
|
1508
1590
|
padding: 16px 16px 16px 20px;
|
|
1509
1591
|
flex-shrink: 0;
|
|
1592
|
+
align-self: stretch;
|
|
1510
1593
|
}
|
|
1511
1594
|
|
|
1512
1595
|
.${prefix}-sidebar-right {
|
|
@@ -48076,14 +48159,39 @@ var lucideIcons = {
|
|
|
48076
48159
|
]
|
|
48077
48160
|
]
|
|
48078
48161
|
};
|
|
48162
|
+
var iconAliases = {
|
|
48163
|
+
"home": "house",
|
|
48164
|
+
"plus-square": "square-plus",
|
|
48165
|
+
"minus-square": "square-minus",
|
|
48166
|
+
"x-square": "square-x",
|
|
48167
|
+
"check-square": "square-check",
|
|
48168
|
+
"edit": "pencil",
|
|
48169
|
+
"edit-2": "pencil",
|
|
48170
|
+
"edit-3": "pencil-line",
|
|
48171
|
+
"trash": "trash-2",
|
|
48172
|
+
"delete": "trash-2",
|
|
48173
|
+
"close": "x",
|
|
48174
|
+
"menu": "menu",
|
|
48175
|
+
"hamburger": "menu",
|
|
48176
|
+
"dots": "more-horizontal",
|
|
48177
|
+
"dots-vertical": "more-vertical",
|
|
48178
|
+
"cog": "settings",
|
|
48179
|
+
"gear": "settings"
|
|
48180
|
+
};
|
|
48079
48181
|
function getIconData(name) {
|
|
48080
48182
|
if (lucideIcons[name]) {
|
|
48081
48183
|
return lucideIcons[name];
|
|
48082
48184
|
}
|
|
48185
|
+
if (iconAliases[name] && lucideIcons[iconAliases[name]]) {
|
|
48186
|
+
return lucideIcons[iconAliases[name]];
|
|
48187
|
+
}
|
|
48083
48188
|
const kebabName = name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
48084
48189
|
if (lucideIcons[kebabName]) {
|
|
48085
48190
|
return lucideIcons[kebabName];
|
|
48086
48191
|
}
|
|
48192
|
+
if (iconAliases[kebabName] && lucideIcons[iconAliases[kebabName]]) {
|
|
48193
|
+
return lucideIcons[iconAliases[kebabName]];
|
|
48194
|
+
}
|
|
48087
48195
|
return void 0;
|
|
48088
48196
|
}
|
|
48089
48197
|
function renderIconSvg(data, _size = 24, strokeWidth = 2, className = "", styleAttr = "") {
|
|
@@ -48193,7 +48301,7 @@ var HtmlRenderer = class extends BaseRenderer {
|
|
|
48193
48301
|
const title = node.title ? `<title>${this.escapeHtml(node.title)}</title>
|
|
48194
48302
|
` : "";
|
|
48195
48303
|
const commonStyles = this.buildCommonStyles(node);
|
|
48196
|
-
const viewportStyle = `width: ${viewport.width}px; height: ${viewport.height}px`;
|
|
48304
|
+
const viewportStyle = `position: relative; width: ${viewport.width}px; height: ${viewport.height}px; overflow: hidden`;
|
|
48197
48305
|
const combinedStyle = commonStyles ? `${viewportStyle}; ${commonStyles}` : viewportStyle;
|
|
48198
48306
|
const dataAttrs = `data-viewport-width="${viewport.width}" data-viewport-height="${viewport.height}" data-viewport-label="${viewport.label}"`;
|
|
48199
48307
|
return `<div class="${classes}" style="${combinedStyle}" ${dataAttrs}>
|
|
@@ -48359,6 +48467,7 @@ ${children}
|
|
|
48359
48467
|
renderMain(node) {
|
|
48360
48468
|
const classes = this.buildClassString([
|
|
48361
48469
|
`${this.prefix}-main`,
|
|
48470
|
+
node.scroll ? `${this.prefix}-scroll` : void 0,
|
|
48362
48471
|
...this.getCommonClasses(node)
|
|
48363
48472
|
]);
|
|
48364
48473
|
const styles = this.buildCommonStyles(node);
|
|
@@ -48444,6 +48553,10 @@ ${children}
|
|
|
48444
48553
|
/**
|
|
48445
48554
|
* Build common inline styles for all values
|
|
48446
48555
|
*
|
|
48556
|
+
* Position values (x, y) for absolute positioning:
|
|
48557
|
+
* - When x or y is specified, element gets position: absolute
|
|
48558
|
+
* - x → left, y → top
|
|
48559
|
+
*
|
|
48447
48560
|
* Spacing values (p, m, gap) use token system:
|
|
48448
48561
|
* - number: spacing token (e.g., p=4 → padding: 16px from token table)
|
|
48449
48562
|
* - ValueWithUnit: direct CSS value (e.g., p=16px → padding: 16px)
|
|
@@ -48458,6 +48571,17 @@ ${children}
|
|
|
48458
48571
|
*/
|
|
48459
48572
|
buildCommonStyles(props) {
|
|
48460
48573
|
const styles = [];
|
|
48574
|
+
if (props.x !== void 0 || props.y !== void 0) {
|
|
48575
|
+
styles.push("position: absolute");
|
|
48576
|
+
if (props.x !== void 0) {
|
|
48577
|
+
const xValue = resolveSizeValueToCss(props.x);
|
|
48578
|
+
if (xValue) styles.push(`left: ${xValue}`);
|
|
48579
|
+
}
|
|
48580
|
+
if (props.y !== void 0) {
|
|
48581
|
+
const yValue = resolveSizeValueToCss(props.y);
|
|
48582
|
+
if (yValue) styles.push(`top: ${yValue}`);
|
|
48583
|
+
}
|
|
48584
|
+
}
|
|
48461
48585
|
const wValue = resolveSizeValueToCss(props.w);
|
|
48462
48586
|
if (wValue) {
|
|
48463
48587
|
styles.push(`width: ${wValue}`);
|
|
@@ -48567,8 +48691,10 @@ ${children}
|
|
|
48567
48691
|
// Container Node Renderers
|
|
48568
48692
|
// ===========================================
|
|
48569
48693
|
renderCard(node) {
|
|
48694
|
+
const hasExplicitWidth = node.w !== void 0;
|
|
48570
48695
|
const classes = this.buildClassString([
|
|
48571
48696
|
`${this.prefix}-card`,
|
|
48697
|
+
!hasExplicitWidth ? `${this.prefix}-card-flex` : void 0,
|
|
48572
48698
|
node.shadow ? `${this.prefix}-card-shadow-${node.shadow}` : void 0,
|
|
48573
48699
|
...this.getCommonClasses(node)
|
|
48574
48700
|
]);
|
|
@@ -48905,11 +49031,19 @@ ${slider}`;
|
|
|
48905
49031
|
renderPlaceholder(node) {
|
|
48906
49032
|
const classes = this.buildClassString([
|
|
48907
49033
|
`${this.prefix}-placeholder`,
|
|
49034
|
+
node.children && node.children.length > 0 ? `${this.prefix}-placeholder-with-children` : void 0,
|
|
48908
49035
|
...this.getCommonClasses(node)
|
|
48909
49036
|
]);
|
|
48910
49037
|
const styles = this.buildCommonStyles(node);
|
|
48911
49038
|
const styleAttr = styles ? ` style="${styles}"` : "";
|
|
48912
49039
|
const label = node.label ? this.escapeHtml(node.label) : "Placeholder";
|
|
49040
|
+
if (node.children && node.children.length > 0) {
|
|
49041
|
+
const childrenHtml = this.renderChildren(node.children);
|
|
49042
|
+
return `<div class="${classes}"${styleAttr}>
|
|
49043
|
+
<span class="${this.prefix}-placeholder-label">${label}</span>
|
|
49044
|
+
<div class="${this.prefix}-placeholder-overlay">${childrenHtml}</div>
|
|
49045
|
+
</div>`;
|
|
49046
|
+
}
|
|
48913
49047
|
return `<div class="${classes}"${styleAttr}>${label}</div>`;
|
|
48914
49048
|
}
|
|
48915
49049
|
renderAvatar(node) {
|
|
@@ -49155,6 +49289,12 @@ ${items}
|
|
|
49155
49289
|
]);
|
|
49156
49290
|
const styles = this.buildCommonStyles(node);
|
|
49157
49291
|
const styleAttr = styles ? ` style="${styles}"` : "";
|
|
49292
|
+
if (node.children && node.children.length > 0) {
|
|
49293
|
+
const content = this.renderNavChildren(node.children);
|
|
49294
|
+
return `<nav class="${classes}"${styleAttr}>
|
|
49295
|
+
${content}
|
|
49296
|
+
</nav>`;
|
|
49297
|
+
}
|
|
49158
49298
|
const items = node.items.map((item) => {
|
|
49159
49299
|
if (typeof item === "string") {
|
|
49160
49300
|
return `<a class="${this.prefix}-nav-link" href="#">${this.escapeHtml(item)}</a>`;
|
|
@@ -49164,12 +49304,48 @@ ${items}
|
|
|
49164
49304
|
item.active ? `${this.prefix}-nav-link-active` : void 0,
|
|
49165
49305
|
item.disabled ? `${this.prefix}-nav-link-disabled` : void 0
|
|
49166
49306
|
]);
|
|
49167
|
-
|
|
49307
|
+
const iconHtml = item.icon ? this.renderIconHtml(item.icon) + " " : "";
|
|
49308
|
+
return `<a class="${linkClasses}" href="${item.href || "#"}">${iconHtml}${this.escapeHtml(item.label)}</a>`;
|
|
49168
49309
|
}).join("\n");
|
|
49169
49310
|
return `<nav class="${classes}"${styleAttr}>
|
|
49170
49311
|
${items}
|
|
49171
49312
|
</nav>`;
|
|
49172
49313
|
}
|
|
49314
|
+
renderNavChildren(children) {
|
|
49315
|
+
return children.map((child) => {
|
|
49316
|
+
if (child.type === "divider") {
|
|
49317
|
+
return `<hr class="${this.prefix}-nav-divider" />`;
|
|
49318
|
+
}
|
|
49319
|
+
if (child.type === "group") {
|
|
49320
|
+
const groupItems = child.items.map((item) => {
|
|
49321
|
+
if (item.type === "divider") {
|
|
49322
|
+
return `<hr class="${this.prefix}-nav-divider" />`;
|
|
49323
|
+
}
|
|
49324
|
+
return this.renderNavItem(item);
|
|
49325
|
+
}).join("\n");
|
|
49326
|
+
return `<div class="${this.prefix}-nav-group">
|
|
49327
|
+
<div class="${this.prefix}-nav-group-label">${this.escapeHtml(child.label)}</div>
|
|
49328
|
+
${groupItems}
|
|
49329
|
+
</div>`;
|
|
49330
|
+
}
|
|
49331
|
+
if (child.type === "item") {
|
|
49332
|
+
return this.renderNavItem(child);
|
|
49333
|
+
}
|
|
49334
|
+
return "";
|
|
49335
|
+
}).join("\n");
|
|
49336
|
+
}
|
|
49337
|
+
renderNavItem(item) {
|
|
49338
|
+
const linkClasses = this.buildClassString([
|
|
49339
|
+
`${this.prefix}-nav-link`,
|
|
49340
|
+
item.active ? `${this.prefix}-nav-link-active` : void 0,
|
|
49341
|
+
item.disabled ? `${this.prefix}-nav-link-disabled` : void 0
|
|
49342
|
+
]);
|
|
49343
|
+
const iconHtml = item.icon ? this.renderIconHtml(item.icon) + " " : "";
|
|
49344
|
+
return `<a class="${linkClasses}" href="${item.href || "#"}">${iconHtml}${this.escapeHtml(item.label)}</a>`;
|
|
49345
|
+
}
|
|
49346
|
+
renderIconHtml(iconName) {
|
|
49347
|
+
return `<span class="${this.prefix}-icon" data-icon="${iconName}"></span>`;
|
|
49348
|
+
}
|
|
49173
49349
|
renderTabs(node) {
|
|
49174
49350
|
const classes = this.buildClassString([
|
|
49175
49351
|
`${this.prefix}-tabs`,
|
|
@@ -49324,13 +49500,239 @@ function createHtmlRenderer(options) {
|
|
|
49324
49500
|
return new HtmlRenderer(options);
|
|
49325
49501
|
}
|
|
49326
49502
|
|
|
49503
|
+
// src/renderer/svg/flex-layout.ts
|
|
49504
|
+
function computeFlexLayout(items, config) {
|
|
49505
|
+
const { mainSize, crossSize, justifyContent, alignItems, gap } = config;
|
|
49506
|
+
const computed = items.map((props, index) => {
|
|
49507
|
+
let flexBasis;
|
|
49508
|
+
if (props.basis === "auto" || props.basis === "content") {
|
|
49509
|
+
flexBasis = props.contentSize;
|
|
49510
|
+
} else {
|
|
49511
|
+
flexBasis = props.basis;
|
|
49512
|
+
}
|
|
49513
|
+
flexBasis = clamp(flexBasis, props.minSize, props.maxSize);
|
|
49514
|
+
return {
|
|
49515
|
+
index,
|
|
49516
|
+
props,
|
|
49517
|
+
flexBasis,
|
|
49518
|
+
mainSize: flexBasis,
|
|
49519
|
+
crossSize: 0,
|
|
49520
|
+
mainPosition: 0,
|
|
49521
|
+
crossPosition: 0,
|
|
49522
|
+
frozen: false,
|
|
49523
|
+
scaledShrinkFactor: props.shrink * flexBasis
|
|
49524
|
+
};
|
|
49525
|
+
});
|
|
49526
|
+
const totalGap = Math.max(0, (items.length - 1) * gap);
|
|
49527
|
+
const totalFlexBasis = computed.reduce((sum, item) => sum + item.flexBasis, 0);
|
|
49528
|
+
let freeSpace = mainSize - totalFlexBasis - totalGap;
|
|
49529
|
+
if (freeSpace !== 0) {
|
|
49530
|
+
resolveFlexibleLengths(computed, freeSpace);
|
|
49531
|
+
}
|
|
49532
|
+
distributeMainAxis(computed, mainSize, totalGap, gap, justifyContent);
|
|
49533
|
+
const crossSizeMax = crossSize ?? Math.max(...computed.map((item) => item.props.contentSize), 0);
|
|
49534
|
+
alignCrossAxis(computed, crossSizeMax, alignItems);
|
|
49535
|
+
const mainSizeUsed = computed.reduce((sum, item) => sum + item.mainSize, 0) + totalGap;
|
|
49536
|
+
return {
|
|
49537
|
+
items: computed,
|
|
49538
|
+
mainSizeUsed,
|
|
49539
|
+
crossSizeMax
|
|
49540
|
+
};
|
|
49541
|
+
}
|
|
49542
|
+
function resolveFlexibleLengths(items, initialFreeSpace) {
|
|
49543
|
+
items.forEach((item) => {
|
|
49544
|
+
item.frozen = false;
|
|
49545
|
+
item.mainSize = item.flexBasis;
|
|
49546
|
+
});
|
|
49547
|
+
let freeSpace = initialFreeSpace;
|
|
49548
|
+
const isGrowing = freeSpace > 0;
|
|
49549
|
+
let iteration = 0;
|
|
49550
|
+
const maxIterations = items.length + 1;
|
|
49551
|
+
while (iteration < maxIterations) {
|
|
49552
|
+
iteration++;
|
|
49553
|
+
const unfrozen = items.filter((item) => !item.frozen);
|
|
49554
|
+
if (unfrozen.length === 0) break;
|
|
49555
|
+
let flexFactorSum;
|
|
49556
|
+
if (isGrowing) {
|
|
49557
|
+
flexFactorSum = unfrozen.reduce((sum, item) => sum + item.props.grow, 0);
|
|
49558
|
+
} else {
|
|
49559
|
+
flexFactorSum = unfrozen.reduce((sum, item) => sum + item.scaledShrinkFactor, 0);
|
|
49560
|
+
}
|
|
49561
|
+
if (flexFactorSum === 0) {
|
|
49562
|
+
unfrozen.forEach((item) => item.frozen = true);
|
|
49563
|
+
break;
|
|
49564
|
+
}
|
|
49565
|
+
const usedSpace = items.reduce((sum, item) => sum + item.mainSize, 0);
|
|
49566
|
+
const containerMainSize = usedSpace + freeSpace;
|
|
49567
|
+
freeSpace = containerMainSize - usedSpace;
|
|
49568
|
+
let totalViolation = 0;
|
|
49569
|
+
for (const item of unfrozen) {
|
|
49570
|
+
let flexFraction;
|
|
49571
|
+
if (isGrowing) {
|
|
49572
|
+
flexFraction = item.props.grow / flexFactorSum;
|
|
49573
|
+
} else {
|
|
49574
|
+
flexFraction = item.scaledShrinkFactor / flexFactorSum;
|
|
49575
|
+
}
|
|
49576
|
+
const deltaSize = freeSpace * flexFraction;
|
|
49577
|
+
const targetSize = item.flexBasis + deltaSize;
|
|
49578
|
+
const clampedSize = clamp(targetSize, item.props.minSize, item.props.maxSize);
|
|
49579
|
+
const violation = clampedSize - targetSize;
|
|
49580
|
+
item.mainSize = clampedSize;
|
|
49581
|
+
totalViolation += violation;
|
|
49582
|
+
if (violation !== 0) {
|
|
49583
|
+
item.frozen = true;
|
|
49584
|
+
}
|
|
49585
|
+
}
|
|
49586
|
+
if (Math.abs(totalViolation) < 0.01) {
|
|
49587
|
+
unfrozen.forEach((item) => item.frozen = true);
|
|
49588
|
+
break;
|
|
49589
|
+
}
|
|
49590
|
+
freeSpace = containerMainSize - items.reduce((sum, item) => sum + item.mainSize, 0);
|
|
49591
|
+
}
|
|
49592
|
+
}
|
|
49593
|
+
function distributeMainAxis(items, containerSize, totalGap, gap, justifyContent) {
|
|
49594
|
+
if (items.length === 0) return;
|
|
49595
|
+
const totalItemSize = items.reduce((sum, item) => sum + item.mainSize, 0);
|
|
49596
|
+
const freeSpace = containerSize - totalItemSize - totalGap;
|
|
49597
|
+
let position = 0;
|
|
49598
|
+
let itemGap = gap;
|
|
49599
|
+
let leadingSpace = 0;
|
|
49600
|
+
switch (justifyContent) {
|
|
49601
|
+
case "flex-start":
|
|
49602
|
+
leadingSpace = 0;
|
|
49603
|
+
break;
|
|
49604
|
+
case "flex-end":
|
|
49605
|
+
leadingSpace = freeSpace;
|
|
49606
|
+
break;
|
|
49607
|
+
case "center":
|
|
49608
|
+
leadingSpace = freeSpace / 2;
|
|
49609
|
+
break;
|
|
49610
|
+
case "space-between":
|
|
49611
|
+
leadingSpace = 0;
|
|
49612
|
+
if (items.length > 1) {
|
|
49613
|
+
itemGap = gap + freeSpace / (items.length - 1);
|
|
49614
|
+
}
|
|
49615
|
+
break;
|
|
49616
|
+
case "space-around":
|
|
49617
|
+
if (items.length > 0) {
|
|
49618
|
+
const spacePerItem = freeSpace / items.length;
|
|
49619
|
+
leadingSpace = spacePerItem / 2;
|
|
49620
|
+
itemGap = gap + spacePerItem;
|
|
49621
|
+
}
|
|
49622
|
+
break;
|
|
49623
|
+
case "space-evenly":
|
|
49624
|
+
if (items.length > 0) {
|
|
49625
|
+
const totalSpaces = items.length + 1;
|
|
49626
|
+
const spaceSize = freeSpace / totalSpaces;
|
|
49627
|
+
leadingSpace = spaceSize;
|
|
49628
|
+
itemGap = gap + spaceSize;
|
|
49629
|
+
}
|
|
49630
|
+
break;
|
|
49631
|
+
}
|
|
49632
|
+
position = leadingSpace;
|
|
49633
|
+
for (let i = 0; i < items.length; i++) {
|
|
49634
|
+
items[i].mainPosition = position;
|
|
49635
|
+
position += items[i].mainSize;
|
|
49636
|
+
if (i < items.length - 1) {
|
|
49637
|
+
position += itemGap;
|
|
49638
|
+
}
|
|
49639
|
+
}
|
|
49640
|
+
}
|
|
49641
|
+
function alignCrossAxis(items, containerCrossSize, alignItems) {
|
|
49642
|
+
for (const item of items) {
|
|
49643
|
+
const align = item.props.alignSelf ?? alignItems;
|
|
49644
|
+
const itemCrossSize = item.props.contentSize;
|
|
49645
|
+
item.crossSize = align === "stretch" ? containerCrossSize : itemCrossSize;
|
|
49646
|
+
switch (align) {
|
|
49647
|
+
case "flex-start":
|
|
49648
|
+
item.crossPosition = 0;
|
|
49649
|
+
break;
|
|
49650
|
+
case "flex-end":
|
|
49651
|
+
item.crossPosition = containerCrossSize - item.crossSize;
|
|
49652
|
+
break;
|
|
49653
|
+
case "center":
|
|
49654
|
+
item.crossPosition = (containerCrossSize - item.crossSize) / 2;
|
|
49655
|
+
break;
|
|
49656
|
+
case "stretch":
|
|
49657
|
+
item.crossPosition = 0;
|
|
49658
|
+
item.crossSize = containerCrossSize;
|
|
49659
|
+
break;
|
|
49660
|
+
case "baseline":
|
|
49661
|
+
item.crossPosition = 0;
|
|
49662
|
+
break;
|
|
49663
|
+
}
|
|
49664
|
+
}
|
|
49665
|
+
}
|
|
49666
|
+
function clamp(value, min, max) {
|
|
49667
|
+
return Math.max(min, Math.min(max, value));
|
|
49668
|
+
}
|
|
49669
|
+
function toJustifyContent(justify) {
|
|
49670
|
+
switch (justify) {
|
|
49671
|
+
case "start":
|
|
49672
|
+
return "flex-start";
|
|
49673
|
+
case "end":
|
|
49674
|
+
return "flex-end";
|
|
49675
|
+
case "center":
|
|
49676
|
+
return "center";
|
|
49677
|
+
case "between":
|
|
49678
|
+
return "space-between";
|
|
49679
|
+
case "around":
|
|
49680
|
+
return "space-around";
|
|
49681
|
+
case "evenly":
|
|
49682
|
+
return "space-evenly";
|
|
49683
|
+
default:
|
|
49684
|
+
return "flex-start";
|
|
49685
|
+
}
|
|
49686
|
+
}
|
|
49687
|
+
function toAlignItems(align) {
|
|
49688
|
+
switch (align) {
|
|
49689
|
+
case "start":
|
|
49690
|
+
return "flex-start";
|
|
49691
|
+
case "end":
|
|
49692
|
+
return "flex-end";
|
|
49693
|
+
case "center":
|
|
49694
|
+
return "center";
|
|
49695
|
+
case "stretch":
|
|
49696
|
+
return "stretch";
|
|
49697
|
+
case "baseline":
|
|
49698
|
+
return "baseline";
|
|
49699
|
+
default:
|
|
49700
|
+
return "stretch";
|
|
49701
|
+
}
|
|
49702
|
+
}
|
|
49703
|
+
function createFlexItemProps(contentSize, options = {}) {
|
|
49704
|
+
return {
|
|
49705
|
+
basis: options.basis ?? "auto",
|
|
49706
|
+
grow: options.grow ?? 0,
|
|
49707
|
+
shrink: options.shrink ?? 1,
|
|
49708
|
+
minSize: options.minSize ?? 0,
|
|
49709
|
+
maxSize: options.maxSize ?? Infinity,
|
|
49710
|
+
contentSize,
|
|
49711
|
+
alignSelf: options.alignSelf
|
|
49712
|
+
};
|
|
49713
|
+
}
|
|
49714
|
+
function createFlexGrowItemProps(contentSize, options = {}) {
|
|
49715
|
+
return {
|
|
49716
|
+
basis: options.basis ?? 0,
|
|
49717
|
+
grow: options.grow ?? 1,
|
|
49718
|
+
shrink: options.shrink ?? 1,
|
|
49719
|
+
minSize: options.minSize ?? 0,
|
|
49720
|
+
maxSize: options.maxSize ?? Infinity,
|
|
49721
|
+
contentSize,
|
|
49722
|
+
alignSelf: options.alignSelf
|
|
49723
|
+
};
|
|
49724
|
+
}
|
|
49725
|
+
|
|
49327
49726
|
// src/renderer/svg/index.ts
|
|
49328
49727
|
var SvgRenderer = class {
|
|
49329
49728
|
options;
|
|
49330
49729
|
theme;
|
|
49331
|
-
|
|
49332
|
-
|
|
49333
|
-
|
|
49730
|
+
pageWidth = 0;
|
|
49731
|
+
pageHeight = 0;
|
|
49732
|
+
clipPathDefs = [];
|
|
49733
|
+
clipPathCounter = 0;
|
|
49734
|
+
// Default spacing values
|
|
49735
|
+
DEFAULT_GAP = 16;
|
|
49334
49736
|
constructor(options = {}) {
|
|
49335
49737
|
this.options = {
|
|
49336
49738
|
width: options.width ?? 800,
|
|
@@ -49341,28 +49743,41 @@ var SvgRenderer = class {
|
|
|
49341
49743
|
fontFamily: options.fontFamily ?? "system-ui, -apple-system, sans-serif"
|
|
49342
49744
|
};
|
|
49343
49745
|
this.theme = defaultTheme;
|
|
49344
|
-
this.contentWidth = this.options.width - this.options.padding * 2;
|
|
49345
49746
|
}
|
|
49346
49747
|
/**
|
|
49347
49748
|
* Render a wireframe document to SVG
|
|
49348
49749
|
*/
|
|
49349
49750
|
render(doc) {
|
|
49350
|
-
this.
|
|
49351
|
-
this.
|
|
49751
|
+
this.clipPathDefs = [];
|
|
49752
|
+
this.clipPathCounter = 0;
|
|
49352
49753
|
const firstPage = doc.children[0];
|
|
49353
49754
|
let width = this.options.width;
|
|
49354
49755
|
let height = this.options.height;
|
|
49355
|
-
if (firstPage
|
|
49356
|
-
const
|
|
49357
|
-
|
|
49358
|
-
|
|
49359
|
-
|
|
49756
|
+
if (firstPage) {
|
|
49757
|
+
const pageAny = firstPage;
|
|
49758
|
+
const hasExplicitWidth = pageAny.width !== void 0;
|
|
49759
|
+
const hasExplicitHeight = pageAny.height !== void 0;
|
|
49760
|
+
if (hasExplicitWidth || hasExplicitHeight) {
|
|
49761
|
+
if (hasExplicitWidth) {
|
|
49762
|
+
width = pageAny.width;
|
|
49763
|
+
}
|
|
49764
|
+
if (hasExplicitHeight) {
|
|
49765
|
+
height = pageAny.height;
|
|
49766
|
+
}
|
|
49767
|
+
} else if (firstPage.viewport !== void 0 || firstPage.device !== void 0) {
|
|
49768
|
+
const viewport = resolveViewport(firstPage.viewport, firstPage.device);
|
|
49769
|
+
width = viewport.width;
|
|
49770
|
+
height = viewport.height;
|
|
49771
|
+
}
|
|
49360
49772
|
}
|
|
49773
|
+
this.pageWidth = width;
|
|
49774
|
+
this.pageHeight = height;
|
|
49361
49775
|
const content = doc.children.map((page) => this.renderPage(page)).join("\n");
|
|
49776
|
+
const allDefs = this.generateDefs() + "\n" + this.clipPathDefs.join("\n");
|
|
49362
49777
|
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
49363
49778
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
49364
49779
|
<defs>
|
|
49365
|
-
${
|
|
49780
|
+
${allDefs}
|
|
49366
49781
|
</defs>
|
|
49367
49782
|
<rect width="100%" height="100%" fill="${this.options.background}"/>
|
|
49368
49783
|
<g transform="scale(${this.options.scale})">
|
|
@@ -49372,7 +49787,7 @@ var SvgRenderer = class {
|
|
|
49372
49787
|
return { svg, width, height };
|
|
49373
49788
|
}
|
|
49374
49789
|
/**
|
|
49375
|
-
* Generate SVG defs (styles
|
|
49790
|
+
* Generate SVG defs (styles)
|
|
49376
49791
|
*/
|
|
49377
49792
|
generateDefs() {
|
|
49378
49793
|
return `
|
|
@@ -49383,684 +49798,1500 @@ var SvgRenderer = class {
|
|
|
49383
49798
|
</style>
|
|
49384
49799
|
`;
|
|
49385
49800
|
}
|
|
49801
|
+
// ===========================================
|
|
49802
|
+
// Page Layout
|
|
49803
|
+
// ===========================================
|
|
49804
|
+
renderPage(page) {
|
|
49805
|
+
const padding = this.options.padding;
|
|
49806
|
+
const contentWidth = this.pageWidth - padding * 2;
|
|
49807
|
+
const contentHeight = this.pageHeight - padding * 2;
|
|
49808
|
+
const constraints = {
|
|
49809
|
+
maxWidth: contentWidth,
|
|
49810
|
+
maxHeight: contentHeight
|
|
49811
|
+
};
|
|
49812
|
+
const childBoxes = [];
|
|
49813
|
+
let currentY = padding;
|
|
49814
|
+
const isCentered = page.centered === true;
|
|
49815
|
+
const hasHeader = page.children.some((c) => c.type === "Header");
|
|
49816
|
+
const hasFooter = page.children.some((c) => c.type === "Footer");
|
|
49817
|
+
const hasMain = page.children.some((c) => c.type === "Main");
|
|
49818
|
+
if (hasHeader || hasFooter || hasMain) {
|
|
49819
|
+
return this.renderPageWithFixedLayout(page, padding, contentWidth, contentHeight);
|
|
49820
|
+
}
|
|
49821
|
+
const measurements = page.children.map((child) => this.measureNode(child, constraints));
|
|
49822
|
+
const gap = this.getGap(page) || this.DEFAULT_GAP;
|
|
49823
|
+
const totalChildrenHeight = measurements.reduce((sum, m, i) => {
|
|
49824
|
+
return sum + m.height + (i > 0 ? gap : 0);
|
|
49825
|
+
}, 0);
|
|
49826
|
+
if (isCentered) {
|
|
49827
|
+
const availableHeight = contentHeight;
|
|
49828
|
+
currentY = padding + Math.max(0, (availableHeight - totalChildrenHeight) / 2);
|
|
49829
|
+
}
|
|
49830
|
+
for (let i = 0; i < page.children.length; i++) {
|
|
49831
|
+
const child = page.children[i];
|
|
49832
|
+
const measurement = measurements[i];
|
|
49833
|
+
let childX = padding;
|
|
49834
|
+
if (isCentered || this.shouldCenterHorizontally(child)) {
|
|
49835
|
+
childX = padding + (contentWidth - measurement.width) / 2;
|
|
49836
|
+
}
|
|
49837
|
+
const box = this.layoutNode(child, childX, currentY, constraints);
|
|
49838
|
+
childBoxes.push(box);
|
|
49839
|
+
currentY += box.height + gap;
|
|
49840
|
+
}
|
|
49841
|
+
const elements = [];
|
|
49842
|
+
for (const box of childBoxes) {
|
|
49843
|
+
elements.push(this.renderBox(box));
|
|
49844
|
+
}
|
|
49845
|
+
return elements.join("\n");
|
|
49846
|
+
}
|
|
49386
49847
|
/**
|
|
49387
|
-
* Render
|
|
49848
|
+
* Render page with fixed header/footer layout
|
|
49849
|
+
* Header at top, Footer at bottom, Main fills remaining space
|
|
49388
49850
|
*/
|
|
49389
|
-
|
|
49851
|
+
renderPageWithFixedLayout(page, padding, contentWidth, contentHeight) {
|
|
49852
|
+
const constraints = {
|
|
49853
|
+
maxWidth: contentWidth,
|
|
49854
|
+
maxHeight: contentHeight
|
|
49855
|
+
};
|
|
49856
|
+
const header = page.children.find((c) => c.type === "Header");
|
|
49857
|
+
const footer = page.children.find((c) => c.type === "Footer");
|
|
49858
|
+
const otherChildren = page.children.filter((c) => c.type !== "Header" && c.type !== "Footer");
|
|
49390
49859
|
const elements = [];
|
|
49391
|
-
|
|
49392
|
-
|
|
49860
|
+
let headerHeight = 0;
|
|
49861
|
+
let footerHeight = 0;
|
|
49862
|
+
let currentY = padding;
|
|
49863
|
+
if (header) {
|
|
49864
|
+
const headerMeasure = this.measureNode(header, constraints);
|
|
49865
|
+
headerHeight = headerMeasure.height;
|
|
49866
|
+
const headerBox = this.layoutNode(header, padding, currentY, constraints);
|
|
49867
|
+
elements.push(this.renderBox(headerBox));
|
|
49393
49868
|
}
|
|
49394
|
-
|
|
49395
|
-
|
|
49869
|
+
if (footer) {
|
|
49870
|
+
const footerMeasure = this.measureNode(footer, constraints);
|
|
49871
|
+
footerHeight = footerMeasure.height;
|
|
49872
|
+
const footerY = this.pageHeight - padding - footerHeight;
|
|
49873
|
+
const footerBox = this.layoutNode(footer, padding, footerY, constraints);
|
|
49874
|
+
elements.push(this.renderBox(footerBox));
|
|
49875
|
+
}
|
|
49876
|
+
const mainStartY = currentY + headerHeight;
|
|
49877
|
+
const mainEndY = this.pageHeight - padding - footerHeight;
|
|
49878
|
+
const mainHeight = mainEndY - mainStartY;
|
|
49879
|
+
if (otherChildren.length > 0) {
|
|
49880
|
+
const mainConstraints = {
|
|
49881
|
+
maxWidth: contentWidth,
|
|
49882
|
+
maxHeight: mainHeight
|
|
49883
|
+
};
|
|
49884
|
+
const clipId = `main-clip-${this.clipPathCounter++}`;
|
|
49885
|
+
this.clipPathDefs.push(`<clipPath id="${clipId}"><rect x="${padding}" y="${mainStartY}" width="${contentWidth}" height="${mainHeight}"/></clipPath>`);
|
|
49886
|
+
const mainContent = [];
|
|
49887
|
+
mainContent.push(`<g clip-path="url(#${clipId})">`);
|
|
49888
|
+
let childY = mainStartY;
|
|
49889
|
+
const gap = this.getGap(page) || 0;
|
|
49890
|
+
for (const child of otherChildren) {
|
|
49891
|
+
const childBox = this.layoutNode(child, padding, childY, mainConstraints);
|
|
49892
|
+
mainContent.push(this.renderBox(childBox));
|
|
49893
|
+
childY += childBox.height + gap;
|
|
49894
|
+
}
|
|
49895
|
+
mainContent.push(`</g>`);
|
|
49896
|
+
elements.push(mainContent.join("\n"));
|
|
49396
49897
|
}
|
|
49397
49898
|
return elements.join("\n");
|
|
49398
49899
|
}
|
|
49399
|
-
|
|
49400
|
-
|
|
49401
|
-
*/
|
|
49402
|
-
renderPageTitle(title) {
|
|
49403
|
-
const fontSize = 24;
|
|
49404
|
-
const y = this.currentY + fontSize;
|
|
49405
|
-
this.currentY += fontSize + 16;
|
|
49406
|
-
return `<text x="${this.currentX}" y="${y}" font-size="${fontSize}" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(title)}</text>`;
|
|
49900
|
+
shouldCenterHorizontally(node) {
|
|
49901
|
+
return node.type === "Card" || node.type === "Modal";
|
|
49407
49902
|
}
|
|
49408
|
-
|
|
49409
|
-
|
|
49410
|
-
|
|
49411
|
-
|
|
49903
|
+
// ===========================================
|
|
49904
|
+
// Measurement Phase
|
|
49905
|
+
// ===========================================
|
|
49906
|
+
measureNode(node, constraints) {
|
|
49412
49907
|
switch (node.type) {
|
|
49413
|
-
// Layout nodes
|
|
49414
49908
|
case "Row":
|
|
49415
|
-
return this.
|
|
49909
|
+
return this.measureRow(node, constraints);
|
|
49416
49910
|
case "Col":
|
|
49417
|
-
return this.
|
|
49911
|
+
return this.measureCol(node, constraints);
|
|
49418
49912
|
case "Header":
|
|
49419
|
-
return this.
|
|
49420
|
-
case "Main":
|
|
49421
|
-
return this.renderMain(node);
|
|
49913
|
+
return this.measureHeader(node, constraints);
|
|
49422
49914
|
case "Footer":
|
|
49423
|
-
return this.
|
|
49915
|
+
return this.measureFooter(node, constraints);
|
|
49916
|
+
case "Main":
|
|
49917
|
+
return this.measureMain(node, constraints);
|
|
49424
49918
|
case "Sidebar":
|
|
49425
|
-
return this.
|
|
49426
|
-
// Container nodes
|
|
49919
|
+
return this.measureSidebar(node, constraints);
|
|
49427
49920
|
case "Card":
|
|
49428
|
-
return this.
|
|
49921
|
+
return this.measureCard(node, constraints);
|
|
49429
49922
|
case "Modal":
|
|
49430
|
-
return this.
|
|
49431
|
-
// Text nodes
|
|
49432
|
-
case "Text":
|
|
49433
|
-
return this.renderText(node);
|
|
49923
|
+
return this.measureModal(node, constraints);
|
|
49434
49924
|
case "Title":
|
|
49435
|
-
return this.
|
|
49436
|
-
case "
|
|
49437
|
-
return this.
|
|
49438
|
-
|
|
49925
|
+
return this.measureTitle(node);
|
|
49926
|
+
case "Text":
|
|
49927
|
+
return this.measureText(node);
|
|
49928
|
+
case "Button":
|
|
49929
|
+
return this.measureButton(node, constraints);
|
|
49439
49930
|
case "Input":
|
|
49440
|
-
return this.
|
|
49931
|
+
return this.measureInput(node, constraints);
|
|
49441
49932
|
case "Textarea":
|
|
49442
|
-
return this.
|
|
49933
|
+
return this.measureTextarea(node, constraints);
|
|
49443
49934
|
case "Select":
|
|
49444
|
-
return this.
|
|
49935
|
+
return this.measureSelect(node, constraints);
|
|
49445
49936
|
case "Checkbox":
|
|
49446
|
-
return this.
|
|
49937
|
+
return this.measureCheckbox(node);
|
|
49447
49938
|
case "Radio":
|
|
49448
|
-
return this.
|
|
49939
|
+
return this.measureRadio(node);
|
|
49449
49940
|
case "Switch":
|
|
49450
|
-
return this.
|
|
49451
|
-
|
|
49452
|
-
|
|
49453
|
-
return this.renderButton(node);
|
|
49454
|
-
// Display nodes
|
|
49941
|
+
return this.measureSwitch(node);
|
|
49942
|
+
case "Link":
|
|
49943
|
+
return this.measureLink(node);
|
|
49455
49944
|
case "Image":
|
|
49456
|
-
return this.
|
|
49945
|
+
return this.measureImage(node, constraints);
|
|
49457
49946
|
case "Placeholder":
|
|
49458
|
-
return this.
|
|
49947
|
+
return this.measurePlaceholder(node, constraints);
|
|
49459
49948
|
case "Avatar":
|
|
49460
|
-
return this.
|
|
49949
|
+
return this.measureAvatar(node, constraints);
|
|
49461
49950
|
case "Badge":
|
|
49462
|
-
return this.
|
|
49463
|
-
// Data nodes
|
|
49951
|
+
return this.measureBadge(node);
|
|
49464
49952
|
case "Table":
|
|
49465
|
-
return this.
|
|
49953
|
+
return this.measureTable(node);
|
|
49466
49954
|
case "List":
|
|
49467
|
-
return this.
|
|
49468
|
-
// Feedback nodes
|
|
49955
|
+
return this.measureList(node);
|
|
49469
49956
|
case "Alert":
|
|
49470
|
-
return this.
|
|
49957
|
+
return this.measureAlert(node, constraints);
|
|
49471
49958
|
case "Progress":
|
|
49472
|
-
return this.
|
|
49959
|
+
return this.measureProgress(node, constraints);
|
|
49473
49960
|
case "Spinner":
|
|
49474
|
-
return this.
|
|
49475
|
-
// Navigation nodes
|
|
49961
|
+
return this.measureSpinner(node);
|
|
49476
49962
|
case "Nav":
|
|
49477
|
-
return this.
|
|
49963
|
+
return this.measureNav(node);
|
|
49478
49964
|
case "Tabs":
|
|
49479
|
-
return this.
|
|
49965
|
+
return this.measureTabs(node);
|
|
49480
49966
|
case "Breadcrumb":
|
|
49481
|
-
return this.
|
|
49967
|
+
return this.measureBreadcrumb(node);
|
|
49968
|
+
case "Icon":
|
|
49969
|
+
return this.measureIcon(node);
|
|
49970
|
+
case "Divider":
|
|
49971
|
+
return this.measureDivider(node, constraints);
|
|
49482
49972
|
default:
|
|
49483
|
-
return
|
|
49973
|
+
return { width: 100, height: 40 };
|
|
49974
|
+
}
|
|
49975
|
+
}
|
|
49976
|
+
measureRow(node, constraints) {
|
|
49977
|
+
const padding = this.getPadding(node);
|
|
49978
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
49979
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
49980
|
+
const childMeasurements = node.children.map(
|
|
49981
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
49982
|
+
);
|
|
49983
|
+
const maxChildHeight = Math.max(...childMeasurements.map((m) => m.height), 0);
|
|
49984
|
+
const totalChildWidth = childMeasurements.reduce((sum, m) => sum + m.width, 0);
|
|
49985
|
+
const totalGapWidth = Math.max(0, (node.children.length - 1) * gap);
|
|
49986
|
+
const contentWidth = totalChildWidth + totalGapWidth + padding.left + padding.right;
|
|
49987
|
+
const hasExplicitWidth = "w" in node && node.w !== void 0;
|
|
49988
|
+
const width = hasExplicitWidth ? constraints.maxWidth : Math.min(contentWidth, constraints.maxWidth);
|
|
49989
|
+
return {
|
|
49990
|
+
width,
|
|
49991
|
+
height: maxChildHeight + padding.top + padding.bottom
|
|
49992
|
+
};
|
|
49993
|
+
}
|
|
49994
|
+
measureCol(node, constraints) {
|
|
49995
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
49996
|
+
const padding = this.getPadding(node);
|
|
49997
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
49998
|
+
const childMeasurements = node.children.map(
|
|
49999
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50000
|
+
);
|
|
50001
|
+
const totalHeight = childMeasurements.reduce(
|
|
50002
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50003
|
+
0
|
|
50004
|
+
);
|
|
50005
|
+
const maxChildWidth = Math.max(...childMeasurements.map((m) => m.width), 0);
|
|
50006
|
+
return {
|
|
50007
|
+
width: Math.max(maxChildWidth + padding.left + padding.right, constraints.maxWidth),
|
|
50008
|
+
height: totalHeight + padding.top + padding.bottom
|
|
50009
|
+
};
|
|
50010
|
+
}
|
|
50011
|
+
measureHeader(node, constraints) {
|
|
50012
|
+
const padding = this.getPadding(node);
|
|
50013
|
+
const height = this.resolveSize(node.h) || 56;
|
|
50014
|
+
return {
|
|
50015
|
+
width: constraints.maxWidth,
|
|
50016
|
+
height: height + padding.top + padding.bottom
|
|
50017
|
+
};
|
|
50018
|
+
}
|
|
50019
|
+
measureFooter(node, constraints) {
|
|
50020
|
+
const padding = this.getPadding(node);
|
|
50021
|
+
const height = this.resolveSize(node.h) || 60;
|
|
50022
|
+
return {
|
|
50023
|
+
width: constraints.maxWidth,
|
|
50024
|
+
height: height + padding.top + padding.bottom
|
|
50025
|
+
};
|
|
50026
|
+
}
|
|
50027
|
+
measureMain(node, constraints) {
|
|
50028
|
+
const padding = this.getPadding(node);
|
|
50029
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50030
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50031
|
+
const childMeasurements = node.children.map(
|
|
50032
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50033
|
+
);
|
|
50034
|
+
const totalHeight = childMeasurements.reduce(
|
|
50035
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50036
|
+
0
|
|
50037
|
+
);
|
|
50038
|
+
return {
|
|
50039
|
+
width: constraints.maxWidth,
|
|
50040
|
+
height: totalHeight + padding.top + padding.bottom
|
|
50041
|
+
};
|
|
50042
|
+
}
|
|
50043
|
+
measureSidebar(node, constraints) {
|
|
50044
|
+
const width = this.resolveSize(node.w) || 200;
|
|
50045
|
+
const padding = this.getPadding(node);
|
|
50046
|
+
const gap = this.getGap(node) ?? 8;
|
|
50047
|
+
const innerWidth = width - padding.left - padding.right;
|
|
50048
|
+
const childMeasurements = node.children.map(
|
|
50049
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50050
|
+
);
|
|
50051
|
+
const contentHeight = childMeasurements.reduce(
|
|
50052
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50053
|
+
0
|
|
50054
|
+
) + padding.top + padding.bottom;
|
|
50055
|
+
const explicitHeight = this.resolveSize(node.h);
|
|
50056
|
+
const height = explicitHeight || constraints.maxHeight || contentHeight;
|
|
50057
|
+
return { width, height };
|
|
50058
|
+
}
|
|
50059
|
+
measureCard(node, constraints) {
|
|
50060
|
+
let cardWidth = this.resolveSize(node.w);
|
|
50061
|
+
if (!cardWidth) {
|
|
50062
|
+
cardWidth = Math.min(360, constraints.maxWidth);
|
|
50063
|
+
} else {
|
|
50064
|
+
cardWidth = Math.min(cardWidth, constraints.maxWidth);
|
|
50065
|
+
}
|
|
50066
|
+
const explicitHeight = this.resolveSize(node.h);
|
|
50067
|
+
if (explicitHeight) {
|
|
50068
|
+
return { width: cardWidth, height: explicitHeight };
|
|
50069
|
+
}
|
|
50070
|
+
const padding = this.getPadding(node);
|
|
50071
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50072
|
+
const innerWidth = cardWidth - padding.left - padding.right;
|
|
50073
|
+
const childMeasurements = node.children.map(
|
|
50074
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50075
|
+
);
|
|
50076
|
+
let contentHeight = 0;
|
|
50077
|
+
if (node.title) {
|
|
50078
|
+
contentHeight += 28;
|
|
50079
|
+
}
|
|
50080
|
+
contentHeight += childMeasurements.reduce(
|
|
50081
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50082
|
+
0
|
|
50083
|
+
);
|
|
50084
|
+
return {
|
|
50085
|
+
width: cardWidth,
|
|
50086
|
+
height: contentHeight + padding.top + padding.bottom
|
|
50087
|
+
};
|
|
50088
|
+
}
|
|
50089
|
+
measureModal(node, constraints) {
|
|
50090
|
+
const width = this.resolveSize(node.w) || 400;
|
|
50091
|
+
const padding = this.getPadding(node);
|
|
50092
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50093
|
+
const innerWidth = width - padding.left - padding.right;
|
|
50094
|
+
const childMeasurements = node.children.map(
|
|
50095
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50096
|
+
);
|
|
50097
|
+
let contentHeight = node.title ? 40 : 0;
|
|
50098
|
+
contentHeight += childMeasurements.reduce(
|
|
50099
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50100
|
+
0
|
|
50101
|
+
);
|
|
50102
|
+
return {
|
|
50103
|
+
width,
|
|
50104
|
+
height: contentHeight + padding.top + padding.bottom
|
|
50105
|
+
};
|
|
50106
|
+
}
|
|
50107
|
+
measureTitle(node) {
|
|
50108
|
+
const level = node.level || 1;
|
|
50109
|
+
const fontSize = this.getTitleFontSize(level);
|
|
50110
|
+
const textWidth = this.estimateTextWidth(node.content, fontSize);
|
|
50111
|
+
return { width: textWidth, height: fontSize + 8 };
|
|
50112
|
+
}
|
|
50113
|
+
measureText(node) {
|
|
50114
|
+
const fontSize = this.resolveFontSize(node.size);
|
|
50115
|
+
const textWidth = this.estimateTextWidth(node.content, fontSize);
|
|
50116
|
+
return { width: textWidth, height: fontSize + 8 };
|
|
50117
|
+
}
|
|
50118
|
+
measureButton(node, constraints) {
|
|
50119
|
+
const hasIcon = !!node.icon;
|
|
50120
|
+
const isIconOnly = hasIcon && !node.content.trim();
|
|
50121
|
+
const iconSize = 16;
|
|
50122
|
+
const padding = 16;
|
|
50123
|
+
let width;
|
|
50124
|
+
if (this.isFullWidth(node)) {
|
|
50125
|
+
width = constraints.maxWidth;
|
|
50126
|
+
} else if (isIconOnly) {
|
|
50127
|
+
width = iconSize + padding * 2;
|
|
50128
|
+
} else if (hasIcon) {
|
|
50129
|
+
width = Math.max(80, this.estimateTextWidth(node.content, 14) + iconSize + 48);
|
|
50130
|
+
} else {
|
|
50131
|
+
width = Math.max(80, this.estimateTextWidth(node.content, 14) + 32);
|
|
50132
|
+
}
|
|
50133
|
+
return { width, height: 40 };
|
|
50134
|
+
}
|
|
50135
|
+
measureInput(node, constraints) {
|
|
50136
|
+
let width;
|
|
50137
|
+
if (this.resolveSize(node.w)) {
|
|
50138
|
+
width = this.resolveSize(node.w);
|
|
50139
|
+
} else if (constraints.inHeader) {
|
|
50140
|
+
const placeholderWidth = node.placeholder ? this.estimateTextWidth(node.placeholder, 14) : 60;
|
|
50141
|
+
width = Math.max(120, placeholderWidth + 24 + 100);
|
|
50142
|
+
} else {
|
|
50143
|
+
width = constraints.maxWidth;
|
|
50144
|
+
}
|
|
50145
|
+
let height = 36;
|
|
50146
|
+
if (node.label) height += 24;
|
|
50147
|
+
return { width, height };
|
|
50148
|
+
}
|
|
50149
|
+
measureTextarea(node, constraints) {
|
|
50150
|
+
const width = this.resolveSize(node.w) || constraints.maxWidth;
|
|
50151
|
+
let height = node.rows ? node.rows * 24 : 100;
|
|
50152
|
+
if (node.label) height += 24;
|
|
50153
|
+
return { width, height };
|
|
50154
|
+
}
|
|
50155
|
+
measureSelect(node, constraints) {
|
|
50156
|
+
const width = this.resolveSize(node.w) || constraints.maxWidth;
|
|
50157
|
+
let height = 40;
|
|
50158
|
+
if (node.label) height += 24;
|
|
50159
|
+
return { width, height };
|
|
50160
|
+
}
|
|
50161
|
+
measureCheckbox(node) {
|
|
50162
|
+
const labelWidth = node.label ? this.estimateTextWidth(node.label, 14) + 8 : 0;
|
|
50163
|
+
return { width: 18 + labelWidth, height: 24 };
|
|
50164
|
+
}
|
|
50165
|
+
measureRadio(node) {
|
|
50166
|
+
const labelWidth = node.label ? this.estimateTextWidth(node.label, 14) + 8 : 0;
|
|
50167
|
+
return { width: 18 + labelWidth, height: 24 };
|
|
50168
|
+
}
|
|
50169
|
+
measureSwitch(node) {
|
|
50170
|
+
const labelWidth = node.label ? this.estimateTextWidth(node.label, 14) + 8 : 0;
|
|
50171
|
+
return { width: 44 + labelWidth, height: 28 };
|
|
50172
|
+
}
|
|
50173
|
+
measureLink(node) {
|
|
50174
|
+
const fontSize = 14;
|
|
50175
|
+
const textWidth = this.estimateTextWidth(node.content, fontSize);
|
|
50176
|
+
return { width: textWidth, height: fontSize + 8 };
|
|
50177
|
+
}
|
|
50178
|
+
measureImage(node, constraints) {
|
|
50179
|
+
let width;
|
|
50180
|
+
if (this.isFullWidth(node)) {
|
|
50181
|
+
width = constraints.maxWidth;
|
|
50182
|
+
} else {
|
|
50183
|
+
width = this.resolveSize(node.w) || 200;
|
|
50184
|
+
}
|
|
50185
|
+
const height = this.resolveSize(node.h) || 150;
|
|
50186
|
+
return { width, height };
|
|
50187
|
+
}
|
|
50188
|
+
measurePlaceholder(node, constraints) {
|
|
50189
|
+
let width;
|
|
50190
|
+
if (node.w !== void 0) {
|
|
50191
|
+
width = this.isFullWidth(node) ? constraints.maxWidth : this.resolveSize(node.w) || 200;
|
|
50192
|
+
} else {
|
|
50193
|
+
width = constraints.maxWidth || 200;
|
|
50194
|
+
}
|
|
50195
|
+
const height = this.resolveSize(node.h) || 100;
|
|
50196
|
+
return { width, height };
|
|
50197
|
+
}
|
|
50198
|
+
measureAvatar(node, constraints) {
|
|
50199
|
+
const sizes = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };
|
|
50200
|
+
const defaultSize = constraints?.inHeader ? "sm" : "md";
|
|
50201
|
+
const size = sizes[node.size || defaultSize] || 40;
|
|
50202
|
+
return { width: size, height: size };
|
|
50203
|
+
}
|
|
50204
|
+
measureBadge(node) {
|
|
50205
|
+
const width = Math.max(24, this.estimateTextWidth(node.content, 12) + 16);
|
|
50206
|
+
return { width, height: 22 };
|
|
50207
|
+
}
|
|
50208
|
+
measureTable(node) {
|
|
50209
|
+
const columns = node.columns || [];
|
|
50210
|
+
const rows = node.rows || [];
|
|
50211
|
+
const rowCount = rows.length || 3;
|
|
50212
|
+
const colWidth = 120;
|
|
50213
|
+
const rowHeight = 40;
|
|
50214
|
+
return {
|
|
50215
|
+
width: columns.length * colWidth,
|
|
50216
|
+
height: (rowCount + 1) * rowHeight
|
|
50217
|
+
};
|
|
50218
|
+
}
|
|
50219
|
+
measureList(node) {
|
|
50220
|
+
const items = node.items || [];
|
|
50221
|
+
const maxItemWidth = Math.max(...items.map((item) => {
|
|
50222
|
+
const content = typeof item === "string" ? item : item.content;
|
|
50223
|
+
return this.estimateTextWidth(content, 14);
|
|
50224
|
+
}), 100);
|
|
50225
|
+
return { width: maxItemWidth + 24, height: items.length * 28 };
|
|
50226
|
+
}
|
|
50227
|
+
measureAlert(_node, constraints) {
|
|
50228
|
+
const width = Math.min(400, constraints.maxWidth);
|
|
50229
|
+
return { width, height: 48 };
|
|
50230
|
+
}
|
|
50231
|
+
measureProgress(node, constraints) {
|
|
50232
|
+
const width = Math.min(200, constraints.maxWidth);
|
|
50233
|
+
let height = 8;
|
|
50234
|
+
if (node.label) height += 24;
|
|
50235
|
+
return { width, height };
|
|
50236
|
+
}
|
|
50237
|
+
measureSpinner(node) {
|
|
50238
|
+
const sizes = { xs: 16, sm: 20, md: 24, lg: 32, xl: 40 };
|
|
50239
|
+
const size = sizes[node.size || "md"] || 24;
|
|
50240
|
+
return { width: size, height: size };
|
|
50241
|
+
}
|
|
50242
|
+
measureNav(node) {
|
|
50243
|
+
const items = node.items || [];
|
|
50244
|
+
if (node.vertical) {
|
|
50245
|
+
const maxWidth = Math.max(...items.map((item) => {
|
|
50246
|
+
const label = typeof item === "string" ? item : item.label;
|
|
50247
|
+
return this.estimateTextWidth(label, 14);
|
|
50248
|
+
}), 100);
|
|
50249
|
+
return { width: maxWidth, height: items.length * 32 };
|
|
50250
|
+
} else {
|
|
50251
|
+
const totalWidth = items.reduce((sum, item) => {
|
|
50252
|
+
const label = typeof item === "string" ? item : item.label;
|
|
50253
|
+
return sum + this.estimateTextWidth(label, 14) + 24;
|
|
50254
|
+
}, 0);
|
|
50255
|
+
return { width: totalWidth, height: 32 };
|
|
49484
50256
|
}
|
|
49485
50257
|
}
|
|
50258
|
+
measureTabs(node) {
|
|
50259
|
+
const items = node.items || [];
|
|
50260
|
+
const totalWidth = items.reduce((sum, item) => {
|
|
50261
|
+
const label = typeof item === "string" ? item : item;
|
|
50262
|
+
return sum + this.estimateTextWidth(label, 14) + 32;
|
|
50263
|
+
}, 0);
|
|
50264
|
+
return { width: totalWidth, height: 44 };
|
|
50265
|
+
}
|
|
50266
|
+
measureBreadcrumb(node) {
|
|
50267
|
+
const items = node.items || [];
|
|
50268
|
+
const totalWidth = items.reduce((sum, item, idx) => {
|
|
50269
|
+
const label = typeof item === "string" ? item : item.label;
|
|
50270
|
+
return sum + this.estimateTextWidth(label, 14) + (idx < items.length - 1 ? 24 : 0);
|
|
50271
|
+
}, 0);
|
|
50272
|
+
return { width: totalWidth, height: 28 };
|
|
50273
|
+
}
|
|
50274
|
+
measureIcon(node) {
|
|
50275
|
+
const sizes = { xs: 12, sm: 16, md: 20, lg: 24, xl: 32 };
|
|
50276
|
+
const size = sizes[node.size || "md"] || 20;
|
|
50277
|
+
return { width: size, height: size };
|
|
50278
|
+
}
|
|
50279
|
+
measureDivider(_node, constraints) {
|
|
50280
|
+
return { width: constraints.maxWidth, height: 1 };
|
|
50281
|
+
}
|
|
49486
50282
|
// ===========================================
|
|
49487
|
-
// Layout
|
|
50283
|
+
// Layout Phase
|
|
49488
50284
|
// ===========================================
|
|
49489
|
-
|
|
49490
|
-
|
|
49491
|
-
|
|
49492
|
-
|
|
49493
|
-
|
|
49494
|
-
|
|
49495
|
-
|
|
50285
|
+
layoutNode(node, x, y, constraints) {
|
|
50286
|
+
switch (node.type) {
|
|
50287
|
+
case "Row":
|
|
50288
|
+
return this.layoutRow(node, x, y, constraints);
|
|
50289
|
+
case "Col":
|
|
50290
|
+
return this.layoutCol(node, x, y, constraints);
|
|
50291
|
+
case "Header":
|
|
50292
|
+
return this.layoutHeader(node, x, y, constraints);
|
|
50293
|
+
case "Footer":
|
|
50294
|
+
return this.layoutFooter(node, x, y, constraints);
|
|
50295
|
+
case "Main":
|
|
50296
|
+
return this.layoutMain(node, x, y, constraints);
|
|
50297
|
+
case "Sidebar":
|
|
50298
|
+
return this.layoutSidebar(node, x, y, constraints);
|
|
50299
|
+
case "Card":
|
|
50300
|
+
return this.layoutCard(node, x, y, constraints);
|
|
50301
|
+
default:
|
|
50302
|
+
const size = this.measureNode(node, constraints);
|
|
50303
|
+
return {
|
|
50304
|
+
x,
|
|
50305
|
+
y,
|
|
50306
|
+
width: size.width,
|
|
50307
|
+
height: size.height,
|
|
50308
|
+
node,
|
|
50309
|
+
children: []
|
|
50310
|
+
};
|
|
50311
|
+
}
|
|
50312
|
+
}
|
|
50313
|
+
layoutRow(node, x, y, constraints) {
|
|
50314
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50315
|
+
const padding = this.getPadding(node);
|
|
50316
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50317
|
+
const justify = node.justify || "start";
|
|
50318
|
+
const align = node.align || (constraints.inHeader ? "center" : "start");
|
|
50319
|
+
const childMeasurements = node.children.map(
|
|
50320
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50321
|
+
);
|
|
50322
|
+
const flexItems = node.children.map((child, i) => {
|
|
50323
|
+
const measurement = childMeasurements[i];
|
|
50324
|
+
if ("w" in child && child.w !== void 0 && child.w !== "full") {
|
|
50325
|
+
const resolved = this.resolveSize(child.w);
|
|
50326
|
+
if (resolved !== void 0) {
|
|
50327
|
+
return createFlexItemProps(measurement.height, {
|
|
50328
|
+
basis: resolved,
|
|
50329
|
+
grow: 0,
|
|
50330
|
+
shrink: 0,
|
|
50331
|
+
minSize: resolved,
|
|
50332
|
+
maxSize: resolved,
|
|
50333
|
+
contentSize: measurement.width
|
|
50334
|
+
});
|
|
50335
|
+
}
|
|
49496
50336
|
}
|
|
49497
|
-
|
|
49498
|
-
|
|
49499
|
-
|
|
49500
|
-
|
|
50337
|
+
if (this.isFlexContainer(child)) {
|
|
50338
|
+
return createFlexGrowItemProps(measurement.height, {
|
|
50339
|
+
basis: 0,
|
|
50340
|
+
grow: 1,
|
|
50341
|
+
shrink: 1,
|
|
50342
|
+
minSize: 0,
|
|
50343
|
+
maxSize: Infinity,
|
|
50344
|
+
contentSize: measurement.width
|
|
50345
|
+
});
|
|
50346
|
+
}
|
|
50347
|
+
if (constraints.inHeader && child.type === "Input") {
|
|
50348
|
+
return createFlexGrowItemProps(measurement.height, {
|
|
50349
|
+
basis: measurement.width,
|
|
50350
|
+
// auto = content size
|
|
50351
|
+
grow: 1,
|
|
50352
|
+
shrink: 1,
|
|
50353
|
+
minSize: 120,
|
|
50354
|
+
// min-width: 120px
|
|
50355
|
+
maxSize: Infinity,
|
|
50356
|
+
contentSize: measurement.width
|
|
50357
|
+
});
|
|
50358
|
+
}
|
|
50359
|
+
return createFlexItemProps(measurement.height, {
|
|
50360
|
+
basis: measurement.width,
|
|
50361
|
+
// auto = content size
|
|
50362
|
+
grow: 0,
|
|
50363
|
+
shrink: 1,
|
|
50364
|
+
minSize: 0,
|
|
50365
|
+
maxSize: Infinity,
|
|
50366
|
+
contentSize: measurement.width
|
|
50367
|
+
});
|
|
50368
|
+
});
|
|
50369
|
+
const flexConfig = {
|
|
50370
|
+
mainSize: innerWidth,
|
|
50371
|
+
crossSize: void 0,
|
|
50372
|
+
// Will use max child height
|
|
50373
|
+
direction: "row",
|
|
50374
|
+
justifyContent: toJustifyContent(justify),
|
|
50375
|
+
alignItems: toAlignItems(align),
|
|
50376
|
+
gap
|
|
50377
|
+
};
|
|
50378
|
+
const flexResult = computeFlexLayout(flexItems, flexConfig);
|
|
50379
|
+
const maxChildHeight = Math.max(...childMeasurements.map((m) => m.height), 0);
|
|
50380
|
+
const innerMaxHeight = constraints.maxHeight ? constraints.maxHeight - padding.top - padding.bottom : void 0;
|
|
50381
|
+
const effectiveHeight = innerMaxHeight || maxChildHeight;
|
|
50382
|
+
const rowHeight = effectiveHeight + padding.top + padding.bottom;
|
|
50383
|
+
const children = [];
|
|
50384
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
50385
|
+
const child = node.children[i];
|
|
50386
|
+
const flexItem = flexResult.items[i];
|
|
50387
|
+
const measurement = childMeasurements[i];
|
|
50388
|
+
const childX = x + padding.left + flexItem.mainPosition;
|
|
50389
|
+
let childY = y + padding.top;
|
|
50390
|
+
switch (align) {
|
|
50391
|
+
case "center":
|
|
50392
|
+
childY = y + padding.top + (maxChildHeight - measurement.height) / 2;
|
|
50393
|
+
break;
|
|
50394
|
+
case "end":
|
|
50395
|
+
childY = y + padding.top + (maxChildHeight - measurement.height);
|
|
50396
|
+
break;
|
|
50397
|
+
}
|
|
50398
|
+
const childBox = this.layoutNode(child, childX, childY, {
|
|
50399
|
+
...constraints,
|
|
50400
|
+
maxWidth: flexItem.mainSize,
|
|
50401
|
+
maxHeight: effectiveHeight
|
|
50402
|
+
});
|
|
50403
|
+
children.push(childBox);
|
|
50404
|
+
}
|
|
50405
|
+
return {
|
|
50406
|
+
x,
|
|
50407
|
+
y,
|
|
50408
|
+
width: constraints.maxWidth,
|
|
50409
|
+
height: rowHeight,
|
|
50410
|
+
node,
|
|
50411
|
+
children,
|
|
50412
|
+
padding
|
|
50413
|
+
};
|
|
50414
|
+
}
|
|
50415
|
+
layoutCol(node, x, y, constraints) {
|
|
50416
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50417
|
+
const padding = this.getPadding(node);
|
|
50418
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50419
|
+
const innerHeight = constraints.maxHeight ? constraints.maxHeight - padding.top - padding.bottom : void 0;
|
|
50420
|
+
const align = node.align || "stretch";
|
|
50421
|
+
const childMeasurements = node.children.map(
|
|
50422
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50423
|
+
);
|
|
50424
|
+
const flexItems = node.children.map((child, i) => {
|
|
50425
|
+
const measurement = childMeasurements[i];
|
|
50426
|
+
if ("h" in child && child.h !== void 0) {
|
|
50427
|
+
const resolved = this.resolveSize(child.h);
|
|
50428
|
+
if (resolved !== void 0) {
|
|
50429
|
+
return createFlexItemProps(measurement.width, {
|
|
50430
|
+
basis: resolved,
|
|
50431
|
+
grow: 0,
|
|
50432
|
+
shrink: 0,
|
|
50433
|
+
minSize: resolved,
|
|
50434
|
+
maxSize: resolved,
|
|
50435
|
+
contentSize: measurement.height
|
|
50436
|
+
});
|
|
50437
|
+
}
|
|
50438
|
+
}
|
|
50439
|
+
if (child.type === "Row" && this.rowContainsSidebarOrMain(child)) {
|
|
50440
|
+
return createFlexGrowItemProps(measurement.width, {
|
|
50441
|
+
basis: 0,
|
|
50442
|
+
grow: 1,
|
|
50443
|
+
shrink: 1,
|
|
50444
|
+
minSize: 0,
|
|
50445
|
+
maxSize: Infinity,
|
|
50446
|
+
contentSize: measurement.height
|
|
50447
|
+
});
|
|
50448
|
+
}
|
|
50449
|
+
return createFlexItemProps(measurement.width, {
|
|
50450
|
+
basis: measurement.height,
|
|
50451
|
+
// auto = content size
|
|
50452
|
+
grow: 0,
|
|
50453
|
+
shrink: 1,
|
|
50454
|
+
minSize: 0,
|
|
50455
|
+
maxSize: Infinity,
|
|
50456
|
+
contentSize: measurement.height
|
|
50457
|
+
});
|
|
50458
|
+
});
|
|
50459
|
+
const flexConfig = {
|
|
50460
|
+
mainSize: innerHeight ?? childMeasurements.reduce(
|
|
50461
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50462
|
+
0
|
|
50463
|
+
),
|
|
50464
|
+
crossSize: innerWidth,
|
|
50465
|
+
direction: "column",
|
|
50466
|
+
justifyContent: "flex-start",
|
|
50467
|
+
alignItems: toAlignItems(align),
|
|
50468
|
+
gap
|
|
50469
|
+
};
|
|
50470
|
+
const flexResult = computeFlexLayout(flexItems, flexConfig);
|
|
50471
|
+
const children = [];
|
|
50472
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
50473
|
+
const child = node.children[i];
|
|
50474
|
+
const flexItem = flexResult.items[i];
|
|
50475
|
+
const measurement = childMeasurements[i];
|
|
50476
|
+
let childX = x + padding.left;
|
|
50477
|
+
let childWidth = align === "stretch" ? innerWidth : measurement.width;
|
|
50478
|
+
switch (align) {
|
|
50479
|
+
case "center":
|
|
50480
|
+
childX = x + padding.left + (innerWidth - measurement.width) / 2;
|
|
50481
|
+
break;
|
|
50482
|
+
case "end":
|
|
50483
|
+
childX = x + padding.left + (innerWidth - measurement.width);
|
|
50484
|
+
break;
|
|
50485
|
+
}
|
|
50486
|
+
const childY = y + padding.top + flexItem.mainPosition;
|
|
50487
|
+
const childBox = this.layoutNode(child, childX, childY, {
|
|
50488
|
+
...constraints,
|
|
50489
|
+
maxWidth: childWidth,
|
|
50490
|
+
maxHeight: flexItem.mainSize
|
|
50491
|
+
});
|
|
50492
|
+
children.push(childBox);
|
|
50493
|
+
}
|
|
50494
|
+
const totalHeight = flexResult.mainSizeUsed;
|
|
50495
|
+
return {
|
|
50496
|
+
x,
|
|
50497
|
+
y,
|
|
50498
|
+
width: constraints.maxWidth,
|
|
50499
|
+
height: totalHeight + padding.top + padding.bottom,
|
|
50500
|
+
node,
|
|
50501
|
+
children,
|
|
50502
|
+
padding
|
|
50503
|
+
};
|
|
50504
|
+
}
|
|
50505
|
+
rowContainsSidebarOrMain(row) {
|
|
50506
|
+
return row.children.some(
|
|
50507
|
+
(child) => child.type === "Sidebar" || child.type === "Main"
|
|
50508
|
+
);
|
|
50509
|
+
}
|
|
50510
|
+
layoutHeader(node, x, y, constraints) {
|
|
50511
|
+
const padding = this.getPadding(node);
|
|
50512
|
+
const height = this.resolveSize(node.h) || 56;
|
|
50513
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50514
|
+
const children = [];
|
|
50515
|
+
let currentY = y + padding.top;
|
|
49501
50516
|
for (const child of node.children) {
|
|
49502
|
-
const
|
|
49503
|
-
|
|
49504
|
-
|
|
49505
|
-
|
|
49506
|
-
|
|
49507
|
-
|
|
49508
|
-
|
|
49509
|
-
|
|
50517
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50518
|
+
...constraints,
|
|
50519
|
+
maxWidth: innerWidth,
|
|
50520
|
+
maxHeight: void 0,
|
|
50521
|
+
// Children use natural height
|
|
50522
|
+
inHeader: true
|
|
50523
|
+
// Pass header context for child sizing (e.g., smaller avatars)
|
|
50524
|
+
});
|
|
50525
|
+
const verticalOffset = (height - childBox.height) / 2;
|
|
50526
|
+
childBox.y = y + padding.top + verticalOffset;
|
|
50527
|
+
this.adjustChildrenY(childBox, verticalOffset);
|
|
50528
|
+
children.push(childBox);
|
|
50529
|
+
currentY += childBox.height;
|
|
49510
50530
|
}
|
|
49511
|
-
|
|
49512
|
-
|
|
49513
|
-
|
|
50531
|
+
return {
|
|
50532
|
+
x,
|
|
50533
|
+
y,
|
|
50534
|
+
width: constraints.maxWidth,
|
|
50535
|
+
height: height + padding.top + padding.bottom,
|
|
50536
|
+
node,
|
|
50537
|
+
children,
|
|
50538
|
+
padding
|
|
50539
|
+
};
|
|
49514
50540
|
}
|
|
49515
|
-
|
|
49516
|
-
|
|
50541
|
+
adjustChildrenY(box, offset) {
|
|
50542
|
+
for (const child of box.children) {
|
|
50543
|
+
child.y += offset;
|
|
50544
|
+
this.adjustChildrenY(child, offset);
|
|
50545
|
+
}
|
|
49517
50546
|
}
|
|
49518
|
-
|
|
49519
|
-
|
|
49520
|
-
|
|
49521
|
-
|
|
49522
|
-
|
|
49523
|
-
|
|
49524
|
-
|
|
49525
|
-
|
|
49526
|
-
return `
|
|
49527
|
-
<g transform="translate(${x}, ${y})">
|
|
49528
|
-
<rect width="${this.contentWidth}" height="${height}" fill="${this.theme.colors.background}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49529
|
-
${children}
|
|
49530
|
-
</g>`;
|
|
50547
|
+
/**
|
|
50548
|
+
* Check if a node should flex to fill available space in a Row
|
|
50549
|
+
* Only layout containers without explicit width should flex
|
|
50550
|
+
*/
|
|
50551
|
+
isFlexContainer(node) {
|
|
50552
|
+
if (node.type === "Main") return true;
|
|
50553
|
+
if (node.type === "Col" && !("w" in node && node.w !== void 0)) return true;
|
|
50554
|
+
return false;
|
|
49531
50555
|
}
|
|
49532
|
-
|
|
49533
|
-
|
|
50556
|
+
layoutFooter(node, x, y, constraints) {
|
|
50557
|
+
const padding = this.getPadding(node);
|
|
50558
|
+
const height = this.resolveSize(node.h) || 60;
|
|
50559
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50560
|
+
const children = [];
|
|
50561
|
+
let currentY = y + padding.top;
|
|
50562
|
+
for (const child of node.children) {
|
|
50563
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50564
|
+
...constraints,
|
|
50565
|
+
maxWidth: innerWidth,
|
|
50566
|
+
maxHeight: void 0
|
|
50567
|
+
// Children use natural height
|
|
50568
|
+
});
|
|
50569
|
+
const verticalOffset = (height - childBox.height) / 2;
|
|
50570
|
+
childBox.y = y + padding.top + verticalOffset;
|
|
50571
|
+
this.adjustChildrenY(childBox, verticalOffset);
|
|
50572
|
+
children.push(childBox);
|
|
50573
|
+
currentY += childBox.height;
|
|
50574
|
+
}
|
|
50575
|
+
return {
|
|
50576
|
+
x,
|
|
50577
|
+
y,
|
|
50578
|
+
width: constraints.maxWidth,
|
|
50579
|
+
height: height + padding.top + padding.bottom,
|
|
50580
|
+
node,
|
|
50581
|
+
children,
|
|
50582
|
+
padding
|
|
50583
|
+
};
|
|
49534
50584
|
}
|
|
49535
|
-
|
|
49536
|
-
const
|
|
49537
|
-
const
|
|
49538
|
-
const
|
|
49539
|
-
const
|
|
49540
|
-
|
|
49541
|
-
|
|
49542
|
-
|
|
49543
|
-
|
|
49544
|
-
|
|
49545
|
-
|
|
49546
|
-
|
|
49547
|
-
|
|
50585
|
+
layoutMain(node, x, y, constraints) {
|
|
50586
|
+
const padding = this.getPadding(node);
|
|
50587
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50588
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50589
|
+
const childMeasurements = node.children.map(
|
|
50590
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50591
|
+
);
|
|
50592
|
+
const children = [];
|
|
50593
|
+
let currentY = y + padding.top;
|
|
50594
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
50595
|
+
const child = node.children[i];
|
|
50596
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50597
|
+
...constraints,
|
|
50598
|
+
maxWidth: innerWidth
|
|
50599
|
+
});
|
|
50600
|
+
children.push(childBox);
|
|
50601
|
+
currentY += childBox.height + gap;
|
|
50602
|
+
}
|
|
50603
|
+
const totalHeight = childMeasurements.reduce(
|
|
50604
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50605
|
+
0
|
|
50606
|
+
);
|
|
50607
|
+
return {
|
|
50608
|
+
x,
|
|
50609
|
+
y,
|
|
50610
|
+
width: constraints.maxWidth,
|
|
50611
|
+
height: totalHeight + padding.top + padding.bottom,
|
|
50612
|
+
node,
|
|
50613
|
+
children,
|
|
50614
|
+
padding
|
|
50615
|
+
};
|
|
49548
50616
|
}
|
|
49549
|
-
|
|
49550
|
-
const width = 200;
|
|
49551
|
-
const
|
|
49552
|
-
const
|
|
49553
|
-
const
|
|
49554
|
-
const
|
|
49555
|
-
|
|
49556
|
-
const
|
|
49557
|
-
|
|
49558
|
-
|
|
49559
|
-
|
|
49560
|
-
|
|
49561
|
-
|
|
49562
|
-
|
|
50617
|
+
layoutSidebar(node, x, y, constraints) {
|
|
50618
|
+
const width = this.resolveSize(node.w) || 200;
|
|
50619
|
+
const padding = this.getPadding(node);
|
|
50620
|
+
const gap = this.getGap(node) ?? 8;
|
|
50621
|
+
const innerWidth = width - padding.left - padding.right;
|
|
50622
|
+
const children = [];
|
|
50623
|
+
let currentY = y + padding.top;
|
|
50624
|
+
for (const child of node.children) {
|
|
50625
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50626
|
+
...constraints,
|
|
50627
|
+
maxWidth: innerWidth
|
|
50628
|
+
});
|
|
50629
|
+
children.push(childBox);
|
|
50630
|
+
currentY += childBox.height + gap;
|
|
50631
|
+
}
|
|
50632
|
+
const contentHeight = currentY - y - padding.top - gap + padding.bottom;
|
|
50633
|
+
const height = constraints.maxHeight || Math.max(contentHeight, 200);
|
|
50634
|
+
return {
|
|
50635
|
+
x,
|
|
50636
|
+
y,
|
|
50637
|
+
width,
|
|
50638
|
+
height,
|
|
50639
|
+
node,
|
|
50640
|
+
children,
|
|
50641
|
+
padding
|
|
50642
|
+
};
|
|
50643
|
+
}
|
|
50644
|
+
layoutCard(node, x, y, constraints) {
|
|
50645
|
+
let cardWidth = this.resolveSize(node.w);
|
|
50646
|
+
if (!cardWidth) {
|
|
50647
|
+
cardWidth = Math.min(360, constraints.maxWidth);
|
|
50648
|
+
}
|
|
50649
|
+
const padding = this.getPadding(node);
|
|
50650
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50651
|
+
const innerWidth = cardWidth - padding.left - padding.right;
|
|
50652
|
+
const children = [];
|
|
50653
|
+
let currentY = y + padding.top;
|
|
50654
|
+
if (node.title) {
|
|
50655
|
+
currentY += 28;
|
|
50656
|
+
}
|
|
50657
|
+
for (const child of node.children) {
|
|
50658
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50659
|
+
...constraints,
|
|
50660
|
+
maxWidth: innerWidth
|
|
50661
|
+
});
|
|
50662
|
+
children.push(childBox);
|
|
50663
|
+
currentY += childBox.height + gap;
|
|
50664
|
+
}
|
|
50665
|
+
const totalHeight = currentY - y - gap + padding.bottom;
|
|
50666
|
+
return {
|
|
50667
|
+
x,
|
|
50668
|
+
y,
|
|
50669
|
+
width: cardWidth,
|
|
50670
|
+
height: totalHeight,
|
|
50671
|
+
node,
|
|
50672
|
+
children,
|
|
50673
|
+
padding
|
|
50674
|
+
};
|
|
49563
50675
|
}
|
|
49564
50676
|
// ===========================================
|
|
49565
|
-
//
|
|
50677
|
+
// Render Phase
|
|
49566
50678
|
// ===========================================
|
|
49567
|
-
|
|
49568
|
-
|
|
49569
|
-
|
|
49570
|
-
|
|
49571
|
-
|
|
49572
|
-
|
|
49573
|
-
|
|
50679
|
+
renderBox(box) {
|
|
50680
|
+
if (!box.node) return "";
|
|
50681
|
+
switch (box.node.type) {
|
|
50682
|
+
case "Row":
|
|
50683
|
+
return this.renderRowBox(box);
|
|
50684
|
+
case "Col":
|
|
50685
|
+
return this.renderColBox(box);
|
|
50686
|
+
case "Header":
|
|
50687
|
+
return this.renderHeaderBox(box);
|
|
50688
|
+
case "Footer":
|
|
50689
|
+
return this.renderFooterBox(box);
|
|
50690
|
+
case "Main":
|
|
50691
|
+
return this.renderMainBox(box);
|
|
50692
|
+
case "Sidebar":
|
|
50693
|
+
return this.renderSidebarBox(box);
|
|
50694
|
+
case "Card":
|
|
50695
|
+
return this.renderCardBox(box);
|
|
50696
|
+
case "Modal":
|
|
50697
|
+
return this.renderModalBox(box);
|
|
50698
|
+
case "Title":
|
|
50699
|
+
return this.renderTitleBox(box);
|
|
50700
|
+
case "Text":
|
|
50701
|
+
return this.renderTextBox(box);
|
|
50702
|
+
case "Button":
|
|
50703
|
+
return this.renderButtonBox(box);
|
|
50704
|
+
case "Input":
|
|
50705
|
+
return this.renderInputBox(box);
|
|
50706
|
+
case "Textarea":
|
|
50707
|
+
return this.renderTextareaBox(box);
|
|
50708
|
+
case "Select":
|
|
50709
|
+
return this.renderSelectBox(box);
|
|
50710
|
+
case "Checkbox":
|
|
50711
|
+
return this.renderCheckboxBox(box);
|
|
50712
|
+
case "Radio":
|
|
50713
|
+
return this.renderRadioBox(box);
|
|
50714
|
+
case "Switch":
|
|
50715
|
+
return this.renderSwitchBox(box);
|
|
50716
|
+
case "Link":
|
|
50717
|
+
return this.renderLinkBox(box);
|
|
50718
|
+
case "Image":
|
|
50719
|
+
return this.renderImageBox(box);
|
|
50720
|
+
case "Placeholder":
|
|
50721
|
+
return this.renderPlaceholderBox(box);
|
|
50722
|
+
case "Avatar":
|
|
50723
|
+
return this.renderAvatarBox(box);
|
|
50724
|
+
case "Badge":
|
|
50725
|
+
return this.renderBadgeBox(box);
|
|
50726
|
+
case "Table":
|
|
50727
|
+
return this.renderTableBox(box);
|
|
50728
|
+
case "List":
|
|
50729
|
+
return this.renderListBox(box);
|
|
50730
|
+
case "Alert":
|
|
50731
|
+
return this.renderAlertBox(box);
|
|
50732
|
+
case "Progress":
|
|
50733
|
+
return this.renderProgressBox(box);
|
|
50734
|
+
case "Spinner":
|
|
50735
|
+
return this.renderSpinnerBox(box);
|
|
50736
|
+
case "Nav":
|
|
50737
|
+
return this.renderNavBox(box);
|
|
50738
|
+
case "Tabs":
|
|
50739
|
+
return this.renderTabsBox(box);
|
|
50740
|
+
case "Breadcrumb":
|
|
50741
|
+
return this.renderBreadcrumbBox(box);
|
|
50742
|
+
case "Icon":
|
|
50743
|
+
return this.renderIconBox(box);
|
|
50744
|
+
case "Divider":
|
|
50745
|
+
return this.renderDividerBox(box);
|
|
50746
|
+
default:
|
|
50747
|
+
return `<!-- Unsupported: ${box.node.type} -->`;
|
|
50748
|
+
}
|
|
50749
|
+
}
|
|
50750
|
+
renderRowBox(box) {
|
|
50751
|
+
const childrenSvg = box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50752
|
+
return childrenSvg;
|
|
50753
|
+
}
|
|
50754
|
+
renderColBox(box) {
|
|
50755
|
+
const childrenSvg = box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50756
|
+
return childrenSvg;
|
|
50757
|
+
}
|
|
50758
|
+
renderHeaderBox(box) {
|
|
50759
|
+
const node = box.node;
|
|
50760
|
+
const hasBorder = node.border !== false;
|
|
50761
|
+
let svg = `<g>
|
|
50762
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" fill="${this.theme.colors.background}"/>`;
|
|
50763
|
+
if (hasBorder) {
|
|
50764
|
+
svg += `<line x1="${box.x}" y1="${box.y + box.height}" x2="${box.x + box.width}" y2="${box.y + box.height}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50765
|
+
}
|
|
50766
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50767
|
+
svg += `</g>`;
|
|
50768
|
+
return svg;
|
|
50769
|
+
}
|
|
50770
|
+
renderFooterBox(box) {
|
|
50771
|
+
const node = box.node;
|
|
50772
|
+
const hasBorder = node.border !== false;
|
|
50773
|
+
let svg = `<g>
|
|
50774
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" fill="${this.theme.colors.background}"`;
|
|
50775
|
+
if (hasBorder) {
|
|
50776
|
+
svg += ` stroke="${this.theme.colors.border}" stroke-width="1"`;
|
|
50777
|
+
}
|
|
50778
|
+
svg += `/>`;
|
|
50779
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50780
|
+
svg += `</g>`;
|
|
50781
|
+
return svg;
|
|
50782
|
+
}
|
|
50783
|
+
renderMainBox(box) {
|
|
50784
|
+
const childrenSvg = box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50785
|
+
return childrenSvg;
|
|
50786
|
+
}
|
|
50787
|
+
renderSidebarBox(box) {
|
|
50788
|
+
let svg = `<g>
|
|
50789
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" fill="${this.theme.colors.background}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50790
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50791
|
+
svg += `</g>`;
|
|
50792
|
+
return svg;
|
|
50793
|
+
}
|
|
50794
|
+
renderCardBox(box) {
|
|
50795
|
+
const node = box.node;
|
|
50796
|
+
const shadowAttr = node.shadow ? 'filter="url(#shadow)"' : "";
|
|
50797
|
+
let svg = `<g>
|
|
50798
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1" ${shadowAttr}/>`;
|
|
49574
50799
|
if (node.title) {
|
|
49575
|
-
const
|
|
49576
|
-
|
|
49577
|
-
this.currentY += titleFontSize + 8;
|
|
50800
|
+
const padding = this.getPadding(node);
|
|
50801
|
+
svg += `<text x="${box.x + padding.left}" y="${box.y + padding.top + 18}" font-size="16" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(node.title)}</text>`;
|
|
49578
50802
|
}
|
|
49579
|
-
|
|
49580
|
-
|
|
49581
|
-
|
|
49582
|
-
this.currentY = savedY + contentHeight + 16;
|
|
49583
|
-
return `
|
|
49584
|
-
<g transform="translate(${x}, ${y})">
|
|
49585
|
-
<rect width="${width}" height="${contentHeight}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49586
|
-
${titleSvg}
|
|
49587
|
-
<g transform="translate(16, ${childStartY})">
|
|
49588
|
-
${children}
|
|
49589
|
-
</g>
|
|
49590
|
-
</g>`;
|
|
50803
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50804
|
+
svg += `</g>`;
|
|
50805
|
+
return svg;
|
|
49591
50806
|
}
|
|
49592
|
-
|
|
49593
|
-
const
|
|
49594
|
-
const
|
|
49595
|
-
const
|
|
49596
|
-
|
|
49597
|
-
|
|
50807
|
+
renderModalBox(box) {
|
|
50808
|
+
const node = box.node;
|
|
50809
|
+
const modalX = (this.pageWidth - box.width) / 2;
|
|
50810
|
+
const modalY = (this.pageHeight - box.height) / 2;
|
|
50811
|
+
let svg = `<g>
|
|
50812
|
+
<rect width="${this.pageWidth}" height="${this.pageHeight}" fill="rgba(0,0,0,0.5)"/>
|
|
50813
|
+
<rect x="${modalX}" y="${modalY}" width="${box.width}" height="${box.height}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49598
50814
|
if (node.title) {
|
|
49599
|
-
|
|
50815
|
+
svg += `<text x="${modalX + 20}" y="${modalY + 30}" font-size="18" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(node.title)}</text>`;
|
|
49600
50816
|
}
|
|
49601
|
-
const
|
|
49602
|
-
|
|
49603
|
-
|
|
49604
|
-
|
|
49605
|
-
|
|
49606
|
-
|
|
49607
|
-
this.currentY = savedY;
|
|
49608
|
-
return `
|
|
49609
|
-
<g>
|
|
49610
|
-
<rect width="100%" height="100%" fill="rgba(0,0,0,0.5)" opacity="0.5"/>
|
|
49611
|
-
<g transform="translate(${x}, ${y})">
|
|
49612
|
-
<rect width="${width}" height="${height}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49613
|
-
${titleSvg}
|
|
49614
|
-
${children}
|
|
49615
|
-
</g>
|
|
49616
|
-
</g>`;
|
|
50817
|
+
for (const child of box.children) {
|
|
50818
|
+
const adjustedChild = { ...child, x: modalX + (child.x - box.x), y: modalY + (child.y - box.y) };
|
|
50819
|
+
svg += this.renderBox(adjustedChild);
|
|
50820
|
+
}
|
|
50821
|
+
svg += `</g>`;
|
|
50822
|
+
return svg;
|
|
49617
50823
|
}
|
|
49618
|
-
|
|
49619
|
-
|
|
49620
|
-
|
|
49621
|
-
|
|
50824
|
+
renderTitleBox(box) {
|
|
50825
|
+
const node = box.node;
|
|
50826
|
+
const level = node.level || 1;
|
|
50827
|
+
const fontSize = this.getTitleFontSize(level);
|
|
50828
|
+
const textY = box.y + fontSize;
|
|
50829
|
+
let textX = box.x;
|
|
50830
|
+
let anchor = "start";
|
|
50831
|
+
if (node.align === "center") {
|
|
50832
|
+
textX = box.x + box.width / 2;
|
|
50833
|
+
anchor = "middle";
|
|
50834
|
+
} else if (node.align === "right") {
|
|
50835
|
+
textX = box.x + box.width;
|
|
50836
|
+
anchor = "end";
|
|
50837
|
+
}
|
|
50838
|
+
return `<text x="${textX}" y="${textY}" font-size="${fontSize}" font-weight="600" fill="${this.theme.colors.foreground}" text-anchor="${anchor}">${this.escapeXml(node.content)}</text>`;
|
|
50839
|
+
}
|
|
50840
|
+
renderTextBox(box) {
|
|
50841
|
+
const node = box.node;
|
|
49622
50842
|
const fontSize = this.resolveFontSize(node.size);
|
|
49623
50843
|
const fill = node.muted ? this.theme.colors.muted : this.theme.colors.foreground;
|
|
49624
50844
|
const fontWeight = node.weight || "normal";
|
|
49625
|
-
const
|
|
49626
|
-
|
|
49627
|
-
|
|
49628
|
-
|
|
49629
|
-
|
|
49630
|
-
|
|
49631
|
-
|
|
49632
|
-
|
|
49633
|
-
|
|
49634
|
-
|
|
50845
|
+
const textY = box.y + fontSize;
|
|
50846
|
+
let textX = box.x;
|
|
50847
|
+
let anchor = "start";
|
|
50848
|
+
if (node.align === "center") {
|
|
50849
|
+
textX = box.x + box.width / 2;
|
|
50850
|
+
anchor = "middle";
|
|
50851
|
+
} else if (node.align === "right") {
|
|
50852
|
+
textX = box.x + box.width;
|
|
50853
|
+
anchor = "end";
|
|
50854
|
+
}
|
|
50855
|
+
return `<text x="${textX}" y="${textY}" font-size="${fontSize}" font-weight="${fontWeight}" fill="${fill}" text-anchor="${anchor}">${this.escapeXml(node.content)}</text>`;
|
|
49635
50856
|
}
|
|
49636
|
-
|
|
49637
|
-
const
|
|
49638
|
-
const
|
|
49639
|
-
|
|
49640
|
-
|
|
50857
|
+
renderButtonBox(box) {
|
|
50858
|
+
const node = box.node;
|
|
50859
|
+
const hasIcon = !!node.icon;
|
|
50860
|
+
const isIconOnly = hasIcon && !node.content.trim();
|
|
50861
|
+
let fill = this.theme.colors.primary;
|
|
50862
|
+
let textFill = "#ffffff";
|
|
50863
|
+
let strokeAttr = "";
|
|
50864
|
+
if (node.secondary) {
|
|
50865
|
+
fill = this.theme.colors.secondary;
|
|
50866
|
+
} else if (node.outline) {
|
|
50867
|
+
fill = "white";
|
|
50868
|
+
textFill = this.theme.colors.foreground;
|
|
50869
|
+
strokeAttr = `stroke="${this.theme.colors.border}" stroke-width="1"`;
|
|
50870
|
+
} else if (node.ghost) {
|
|
50871
|
+
fill = "transparent";
|
|
50872
|
+
textFill = this.theme.colors.foreground;
|
|
50873
|
+
}
|
|
50874
|
+
let svg = `<g>
|
|
50875
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="4" fill="${fill}" ${strokeAttr}/>`;
|
|
50876
|
+
if (hasIcon) {
|
|
50877
|
+
const iconData = getIconData(node.icon);
|
|
50878
|
+
if (iconData) {
|
|
50879
|
+
const iconSize = 16;
|
|
50880
|
+
const iconX = isIconOnly ? box.x + (box.width - iconSize) / 2 : box.x + 16;
|
|
50881
|
+
const iconY = box.y + (box.height - iconSize) / 2;
|
|
50882
|
+
svg += this.renderIconPaths(iconData, iconX, iconY, iconSize, textFill);
|
|
50883
|
+
}
|
|
50884
|
+
}
|
|
50885
|
+
if (!isIconOnly) {
|
|
50886
|
+
const textX = hasIcon ? box.x + 36 + (box.width - 52) / 2 : box.x + box.width / 2;
|
|
50887
|
+
svg += `<text x="${textX}" y="${box.y + box.height / 2 + 5}" font-size="14" fill="${textFill}" text-anchor="middle">${this.escapeXml(node.content)}</text>`;
|
|
50888
|
+
}
|
|
50889
|
+
svg += `</g>`;
|
|
50890
|
+
return svg;
|
|
49641
50891
|
}
|
|
49642
|
-
|
|
49643
|
-
|
|
49644
|
-
|
|
49645
|
-
|
|
49646
|
-
const width = 280;
|
|
49647
|
-
const height = 40;
|
|
49648
|
-
const x = this.currentX;
|
|
49649
|
-
let y = this.currentY;
|
|
49650
|
-
let result = "";
|
|
50892
|
+
renderInputBox(box) {
|
|
50893
|
+
const node = box.node;
|
|
50894
|
+
let y = box.y;
|
|
50895
|
+
let svg = "<g>";
|
|
49651
50896
|
if (node.label) {
|
|
49652
|
-
|
|
50897
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49653
50898
|
y += 24;
|
|
49654
50899
|
}
|
|
50900
|
+
const inputHeight = 36;
|
|
49655
50901
|
const placeholder = node.placeholder || "";
|
|
49656
|
-
|
|
49657
|
-
|
|
49658
|
-
|
|
49659
|
-
|
|
49660
|
-
</g>`;
|
|
49661
|
-
this.currentY = y + height + 12;
|
|
49662
|
-
return result;
|
|
50902
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${inputHeight}" rx="4" fill="#f4f4f5"/>`;
|
|
50903
|
+
svg += `<text x="${box.x + 12}" y="${y + inputHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>`;
|
|
50904
|
+
svg += "</g>";
|
|
50905
|
+
return svg;
|
|
49663
50906
|
}
|
|
49664
|
-
|
|
49665
|
-
const
|
|
49666
|
-
|
|
49667
|
-
|
|
49668
|
-
let y = this.currentY;
|
|
49669
|
-
let result = "";
|
|
50907
|
+
renderTextareaBox(box) {
|
|
50908
|
+
const node = box.node;
|
|
50909
|
+
let y = box.y;
|
|
50910
|
+
let svg = "<g>";
|
|
49670
50911
|
if (node.label) {
|
|
49671
|
-
|
|
50912
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49672
50913
|
y += 24;
|
|
49673
50914
|
}
|
|
50915
|
+
const textareaHeight = box.height - (node.label ? 24 : 0);
|
|
49674
50916
|
const placeholder = node.placeholder || "";
|
|
49675
|
-
|
|
49676
|
-
|
|
49677
|
-
|
|
49678
|
-
|
|
49679
|
-
</g>`;
|
|
49680
|
-
this.currentY = y + height + 12;
|
|
49681
|
-
return result;
|
|
50917
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${textareaHeight}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50918
|
+
svg += `<text x="${box.x + 12}" y="${y + 24}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>`;
|
|
50919
|
+
svg += "</g>";
|
|
50920
|
+
return svg;
|
|
49682
50921
|
}
|
|
49683
|
-
|
|
49684
|
-
const
|
|
49685
|
-
|
|
49686
|
-
|
|
49687
|
-
let y = this.currentY;
|
|
49688
|
-
let result = "";
|
|
50922
|
+
renderSelectBox(box) {
|
|
50923
|
+
const node = box.node;
|
|
50924
|
+
let y = box.y;
|
|
50925
|
+
let svg = "<g>";
|
|
49689
50926
|
if (node.label) {
|
|
49690
|
-
|
|
50927
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49691
50928
|
y += 24;
|
|
49692
50929
|
}
|
|
50930
|
+
const selectHeight = 40;
|
|
49693
50931
|
const placeholder = node.placeholder || "Select...";
|
|
49694
|
-
|
|
49695
|
-
|
|
49696
|
-
|
|
49697
|
-
|
|
49698
|
-
|
|
49699
|
-
</g>`;
|
|
49700
|
-
this.currentY = y + height + 12;
|
|
49701
|
-
return result;
|
|
50932
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${selectHeight}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50933
|
+
svg += `<text x="${box.x + 12}" y="${y + selectHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>`;
|
|
50934
|
+
svg += `<path d="M${box.x + box.width - 24} ${y + selectHeight / 2 - 3} l6 6 l6 -6" fill="none" stroke="${this.theme.colors.muted}" stroke-width="1.5"/>`;
|
|
50935
|
+
svg += "</g>";
|
|
50936
|
+
return svg;
|
|
49702
50937
|
}
|
|
49703
|
-
|
|
49704
|
-
const
|
|
49705
|
-
const y = this.currentY;
|
|
50938
|
+
renderCheckboxBox(box) {
|
|
50939
|
+
const node = box.node;
|
|
49706
50940
|
const size = 18;
|
|
49707
|
-
let
|
|
49708
|
-
|
|
49709
|
-
<rect width="${size}" height="${size}" rx="3" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50941
|
+
let svg = `<g>
|
|
50942
|
+
<rect x="${box.x}" y="${box.y}" width="${size}" height="${size}" rx="3" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49710
50943
|
if (node.checked) {
|
|
49711
|
-
|
|
50944
|
+
svg += `<path d="M${box.x + 4} ${box.y + 9} L${box.x + 7} ${box.y + 12} L${box.x + 14} ${box.y + 5}" fill="none" stroke="${this.theme.colors.foreground}" stroke-width="2"/>`;
|
|
49712
50945
|
}
|
|
49713
50946
|
if (node.label) {
|
|
49714
|
-
|
|
50947
|
+
svg += `<text x="${box.x + size + 8}" y="${box.y + size - 3}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49715
50948
|
}
|
|
49716
|
-
|
|
49717
|
-
|
|
49718
|
-
return result;
|
|
50949
|
+
svg += "</g>";
|
|
50950
|
+
return svg;
|
|
49719
50951
|
}
|
|
49720
|
-
|
|
49721
|
-
const
|
|
49722
|
-
const y = this.currentY;
|
|
50952
|
+
renderRadioBox(box) {
|
|
50953
|
+
const node = box.node;
|
|
49723
50954
|
const size = 18;
|
|
49724
50955
|
const radius = size / 2;
|
|
49725
|
-
|
|
49726
|
-
|
|
49727
|
-
|
|
50956
|
+
const cx = box.x + radius;
|
|
50957
|
+
const cy = box.y + radius;
|
|
50958
|
+
let svg = `<g>
|
|
50959
|
+
<circle cx="${cx}" cy="${cy}" r="${radius - 1}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49728
50960
|
if (node.checked) {
|
|
49729
|
-
|
|
50961
|
+
svg += `<circle cx="${cx}" cy="${cy}" r="${radius - 5}" fill="${this.theme.colors.foreground}"/>`;
|
|
49730
50962
|
}
|
|
49731
50963
|
if (node.label) {
|
|
49732
|
-
|
|
50964
|
+
svg += `<text x="${box.x + size + 8}" y="${box.y + size - 3}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49733
50965
|
}
|
|
49734
|
-
|
|
49735
|
-
|
|
49736
|
-
return result;
|
|
50966
|
+
svg += "</g>";
|
|
50967
|
+
return svg;
|
|
49737
50968
|
}
|
|
49738
|
-
|
|
49739
|
-
const
|
|
49740
|
-
const y = this.currentY;
|
|
50969
|
+
renderSwitchBox(box) {
|
|
50970
|
+
const node = box.node;
|
|
49741
50971
|
const width = 44;
|
|
49742
50972
|
const height = 24;
|
|
49743
50973
|
const radius = height / 2;
|
|
49744
50974
|
const isOn = node.checked;
|
|
49745
50975
|
const bgColor = isOn ? this.theme.colors.primary : this.theme.colors.border;
|
|
49746
|
-
const knobX = isOn ? width - radius : radius;
|
|
49747
|
-
let
|
|
49748
|
-
|
|
49749
|
-
<
|
|
49750
|
-
<circle cx="${knobX}" cy="${radius}" r="${radius - 3}" fill="white"/>`;
|
|
50976
|
+
const knobX = isOn ? box.x + width - radius : box.x + radius;
|
|
50977
|
+
let svg = `<g>
|
|
50978
|
+
<rect x="${box.x}" y="${box.y}" width="${width}" height="${height}" rx="${radius}" fill="${bgColor}"/>
|
|
50979
|
+
<circle cx="${knobX}" cy="${box.y + radius}" r="${radius - 3}" fill="white"/>`;
|
|
49751
50980
|
if (node.label) {
|
|
49752
|
-
|
|
49753
|
-
}
|
|
49754
|
-
result += "</g>";
|
|
49755
|
-
this.currentY += height + 12;
|
|
49756
|
-
return result;
|
|
49757
|
-
}
|
|
49758
|
-
// ===========================================
|
|
49759
|
-
// Button Renderer
|
|
49760
|
-
// ===========================================
|
|
49761
|
-
renderButton(node) {
|
|
49762
|
-
const content = node.content;
|
|
49763
|
-
const hasIcon = !!node.icon;
|
|
49764
|
-
const isIconOnly = hasIcon && !content.trim();
|
|
49765
|
-
const iconSize = 16;
|
|
49766
|
-
const padding = isIconOnly ? 8 : 16;
|
|
49767
|
-
let width;
|
|
49768
|
-
if (isIconOnly) {
|
|
49769
|
-
width = iconSize + padding * 2;
|
|
49770
|
-
} else if (hasIcon) {
|
|
49771
|
-
width = Math.max(80, content.length * 10 + iconSize + 40);
|
|
49772
|
-
} else {
|
|
49773
|
-
width = Math.max(80, content.length * 10 + 32);
|
|
49774
|
-
}
|
|
49775
|
-
const height = 36;
|
|
49776
|
-
const x = this.currentX;
|
|
49777
|
-
const y = this.currentY;
|
|
49778
|
-
let fill = this.theme.colors.primary;
|
|
49779
|
-
let textFill = "#ffffff";
|
|
49780
|
-
let isOutline = false;
|
|
49781
|
-
if (node.secondary) {
|
|
49782
|
-
fill = this.theme.colors.secondary;
|
|
49783
|
-
} else if (node.outline) {
|
|
49784
|
-
fill = "white";
|
|
49785
|
-
textFill = this.theme.colors.foreground;
|
|
49786
|
-
isOutline = true;
|
|
49787
|
-
} else if (node.ghost) {
|
|
49788
|
-
fill = "transparent";
|
|
49789
|
-
textFill = this.theme.colors.foreground;
|
|
50981
|
+
svg += `<text x="${box.x + width + 8}" y="${box.y + height - 6}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49790
50982
|
}
|
|
49791
|
-
|
|
49792
|
-
|
|
49793
|
-
let iconSvg = "";
|
|
49794
|
-
if (hasIcon) {
|
|
49795
|
-
const iconData = getIconData(node.icon);
|
|
49796
|
-
if (iconData) {
|
|
49797
|
-
const iconX = isIconOnly ? (width - iconSize) / 2 : padding;
|
|
49798
|
-
const iconY = (height - iconSize) / 2;
|
|
49799
|
-
iconSvg = this.renderIconPaths(iconData, iconX, iconY, iconSize, textFill);
|
|
49800
|
-
}
|
|
49801
|
-
}
|
|
49802
|
-
const textX = hasIcon && !isIconOnly ? padding + iconSize + 8 + (width - padding - iconSize - 8 - padding) / 2 : width / 2;
|
|
49803
|
-
const textContent = isIconOnly ? "" : `<text x="${textX}" y="${height / 2 + 5}" font-size="14" fill="${textFill}" text-anchor="middle">${this.escapeXml(content)}</text>`;
|
|
49804
|
-
return `
|
|
49805
|
-
<g transform="translate(${x}, ${y})">
|
|
49806
|
-
<rect width="${width}" height="${height}" rx="4" fill="${fill}" ${strokeAttr}/>
|
|
49807
|
-
${iconSvg}
|
|
49808
|
-
${textContent}
|
|
49809
|
-
</g>`;
|
|
50983
|
+
svg += "</g>";
|
|
50984
|
+
return svg;
|
|
49810
50985
|
}
|
|
49811
|
-
|
|
49812
|
-
|
|
49813
|
-
|
|
49814
|
-
|
|
49815
|
-
|
|
49816
|
-
const paths = data.map(([tag, attrs]) => {
|
|
49817
|
-
const attrStr = Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
49818
|
-
return `<${tag} ${attrStr} />`;
|
|
49819
|
-
}).join("");
|
|
49820
|
-
return `<g transform="translate(${x}, ${y}) scale(${scale})" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</g>`;
|
|
50986
|
+
renderLinkBox(box) {
|
|
50987
|
+
const node = box.node;
|
|
50988
|
+
const fontSize = 14;
|
|
50989
|
+
const textY = box.y + fontSize;
|
|
50990
|
+
return `<text x="${box.x}" y="${textY}" font-size="${fontSize}" fill="${this.theme.colors.primary}" text-decoration="underline">${this.escapeXml(node.content)}</text>`;
|
|
49821
50991
|
}
|
|
49822
|
-
|
|
49823
|
-
|
|
49824
|
-
|
|
49825
|
-
|
|
49826
|
-
|
|
49827
|
-
|
|
49828
|
-
|
|
49829
|
-
const y = this.currentY;
|
|
49830
|
-
this.currentY += height + 12;
|
|
49831
|
-
return `
|
|
49832
|
-
<g transform="translate(${x}, ${y})">
|
|
49833
|
-
<rect width="${width}" height="${height}" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49834
|
-
<line x1="0" y1="0" x2="${width}" y2="${height}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49835
|
-
<line x1="${width}" y1="0" x2="0" y2="${height}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49836
|
-
<text x="${width / 2}" y="${height / 2 + 5}" font-size="14" fill="${this.theme.colors.foreground}" text-anchor="middle">${this.escapeXml(node.alt || "Image")}</text>
|
|
50992
|
+
renderImageBox(box) {
|
|
50993
|
+
const node = box.node;
|
|
50994
|
+
return `<g>
|
|
50995
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
50996
|
+
<line x1="${box.x}" y1="${box.y}" x2="${box.x + box.width}" y2="${box.y + box.height}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
50997
|
+
<line x1="${box.x + box.width}" y1="${box.y}" x2="${box.x}" y2="${box.y + box.height}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
50998
|
+
<text x="${box.x + box.width / 2}" y="${box.y + box.height / 2 + 5}" font-size="14" fill="${this.theme.colors.foreground}" text-anchor="middle">${this.escapeXml(node.alt || "Image")}</text>
|
|
49837
50999
|
</g>`;
|
|
49838
51000
|
}
|
|
49839
|
-
|
|
49840
|
-
const
|
|
49841
|
-
const
|
|
49842
|
-
const
|
|
49843
|
-
|
|
49844
|
-
|
|
49845
|
-
|
|
49846
|
-
|
|
49847
|
-
|
|
49848
|
-
<
|
|
51001
|
+
renderPlaceholderBox(box) {
|
|
51002
|
+
const node = box.node;
|
|
51003
|
+
const patternId = `placeholder-pattern-${this.clipPathCounter++}`;
|
|
51004
|
+
const patternDef = `<pattern id="${patternId}" width="14" height="14" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
|
51005
|
+
<rect width="14" height="14" fill="#f9f9f9"/>
|
|
51006
|
+
<rect width="7" height="14" fill="rgba(0,0,0,0.03)"/>
|
|
51007
|
+
</pattern>`;
|
|
51008
|
+
this.clipPathDefs.push(patternDef);
|
|
51009
|
+
return `<g>
|
|
51010
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="4" fill="url(#${patternId})" stroke="${this.theme.colors.muted}" stroke-width="1" stroke-dasharray="4,2"/>
|
|
51011
|
+
<text x="${box.x + box.width / 2}" y="${box.y + box.height / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}" text-anchor="middle">${this.escapeXml(node.label || "Placeholder")}</text>
|
|
49849
51012
|
</g>`;
|
|
49850
51013
|
}
|
|
49851
|
-
|
|
49852
|
-
const
|
|
49853
|
-
const
|
|
49854
|
-
const
|
|
49855
|
-
const
|
|
49856
|
-
const y = this.currentY;
|
|
51014
|
+
renderAvatarBox(box) {
|
|
51015
|
+
const node = box.node;
|
|
51016
|
+
const radius = box.width / 2;
|
|
51017
|
+
const cx = box.x + radius;
|
|
51018
|
+
const cy = box.y + radius;
|
|
49857
51019
|
const initial = node.name ? node.name.charAt(0).toUpperCase() : "?";
|
|
49858
|
-
|
|
49859
|
-
|
|
49860
|
-
|
|
49861
|
-
<circle cx="${radius}" cy="${radius}" r="${radius}" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49862
|
-
<text x="${radius}" y="${radius + 5}" font-size="${size / 2.5}" fill="${this.theme.colors.foreground}" text-anchor="middle">${initial}</text>
|
|
51020
|
+
return `<g>
|
|
51021
|
+
<circle cx="${cx}" cy="${cy}" r="${radius}" fill="${this.theme.colors.foreground}"/>
|
|
51022
|
+
<text x="${cx}" y="${cy + 5}" font-size="${box.width / 2.5}" fill="${this.theme.colors.background}" text-anchor="middle">${initial}</text>
|
|
49863
51023
|
</g>`;
|
|
49864
51024
|
}
|
|
49865
|
-
|
|
49866
|
-
const
|
|
49867
|
-
|
|
49868
|
-
|
|
49869
|
-
|
|
49870
|
-
const y = this.currentY;
|
|
49871
|
-
const fill = this.theme.colors.muted;
|
|
49872
|
-
const textFill = this.theme.colors.foreground;
|
|
49873
|
-
this.currentY += height + 8;
|
|
49874
|
-
return `
|
|
49875
|
-
<g transform="translate(${x}, ${y})">
|
|
49876
|
-
<rect width="${width}" height="${height}" rx="11" fill="${fill}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49877
|
-
<text x="${width / 2}" y="${height / 2 + 4}" font-size="12" fill="${textFill}" text-anchor="middle">${this.escapeXml(content)}</text>
|
|
51025
|
+
renderBadgeBox(box) {
|
|
51026
|
+
const node = box.node;
|
|
51027
|
+
return `<g>
|
|
51028
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="11" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
51029
|
+
<text x="${box.x + box.width / 2}" y="${box.y + box.height / 2 + 4}" font-size="12" fill="${this.theme.colors.foreground}" text-anchor="middle">${this.escapeXml(node.content)}</text>
|
|
49878
51030
|
</g>`;
|
|
49879
51031
|
}
|
|
49880
|
-
|
|
49881
|
-
|
|
49882
|
-
// ===========================================
|
|
49883
|
-
renderTable(node) {
|
|
51032
|
+
renderTableBox(box) {
|
|
51033
|
+
const node = box.node;
|
|
49884
51034
|
const columns = node.columns || [];
|
|
49885
51035
|
const rows = node.rows || [];
|
|
49886
51036
|
const rowCount = rows.length || 3;
|
|
49887
51037
|
const colWidth = 120;
|
|
49888
51038
|
const rowHeight = 40;
|
|
49889
|
-
|
|
49890
|
-
|
|
49891
|
-
let svg = `<g transform="translate(${x}, ${y})">`;
|
|
49892
|
-
svg += `<rect width="${columns.length * colWidth}" height="${rowHeight}" fill="${this.theme.colors.muted}"/>`;
|
|
51039
|
+
let svg = `<g>`;
|
|
51040
|
+
svg += `<rect x="${box.x}" y="${box.y}" width="${columns.length * colWidth}" height="${rowHeight}" fill="${this.theme.colors.muted}"/>`;
|
|
49893
51041
|
columns.forEach((col, i) => {
|
|
49894
|
-
svg += `<text x="${i * colWidth + 12}" y="${rowHeight / 2 + 5}" font-size="14" font-weight="600">${this.escapeXml(col)}</text>`;
|
|
51042
|
+
svg += `<text x="${box.x + i * colWidth + 12}" y="${box.y + rowHeight / 2 + 5}" font-size="14" font-weight="600">${this.escapeXml(col)}</text>`;
|
|
49895
51043
|
});
|
|
49896
|
-
const displayRowCount = rows.length > 0 ? rows.length :
|
|
51044
|
+
const displayRowCount = rows.length > 0 ? rows.length : rowCount;
|
|
49897
51045
|
for (let rowIdx = 0; rowIdx < displayRowCount; rowIdx++) {
|
|
49898
|
-
const rowY = (rowIdx + 1) * rowHeight;
|
|
49899
|
-
svg += `<rect y="${rowY}" width="${columns.length * colWidth}" height="${rowHeight}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51046
|
+
const rowY = box.y + (rowIdx + 1) * rowHeight;
|
|
51047
|
+
svg += `<rect x="${box.x}" y="${rowY}" width="${columns.length * colWidth}" height="${rowHeight}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49900
51048
|
columns.forEach((_, colIdx) => {
|
|
49901
51049
|
const cellContent = rows[rowIdx] && rows[rowIdx][colIdx] ? String(typeof rows[rowIdx][colIdx] === "object" ? "..." : rows[rowIdx][colIdx]) : "\u2014";
|
|
49902
|
-
svg += `<text x="${colIdx * colWidth + 12}" y="${rowY + rowHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(cellContent)}</text>`;
|
|
51050
|
+
svg += `<text x="${box.x + colIdx * colWidth + 12}" y="${rowY + rowHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(cellContent)}</text>`;
|
|
49903
51051
|
});
|
|
49904
51052
|
}
|
|
49905
51053
|
svg += "</g>";
|
|
49906
|
-
this.currentY += (displayRowCount + 1) * rowHeight + 16;
|
|
49907
51054
|
return svg;
|
|
49908
51055
|
}
|
|
49909
|
-
|
|
49910
|
-
const
|
|
49911
|
-
let y = this.currentY;
|
|
51056
|
+
renderListBox(box) {
|
|
51057
|
+
const node = box.node;
|
|
49912
51058
|
const items = node.items || [];
|
|
49913
51059
|
const ordered = node.ordered || false;
|
|
49914
|
-
let svg =
|
|
51060
|
+
let svg = "<g>";
|
|
49915
51061
|
items.forEach((item, idx) => {
|
|
49916
51062
|
const marker = ordered ? `${idx + 1}.` : "\u2022";
|
|
49917
51063
|
const content = typeof item === "string" ? item : item.content;
|
|
49918
|
-
svg += `<text x="
|
|
51064
|
+
svg += `<text x="${box.x}" y="${box.y + idx * 28 + 16}" font-size="14" fill="${this.theme.colors.foreground}">${marker} ${this.escapeXml(content)}</text>`;
|
|
49919
51065
|
});
|
|
49920
51066
|
svg += "</g>";
|
|
49921
|
-
this.currentY += items.length * 24 + 12;
|
|
49922
51067
|
return svg;
|
|
49923
51068
|
}
|
|
49924
|
-
|
|
49925
|
-
|
|
49926
|
-
|
|
49927
|
-
|
|
49928
|
-
|
|
49929
|
-
const height = 48;
|
|
49930
|
-
const x = this.currentX;
|
|
49931
|
-
const y = this.currentY;
|
|
49932
|
-
this.currentY += height + 12;
|
|
49933
|
-
return `
|
|
49934
|
-
<g transform="translate(${x}, ${y})">
|
|
49935
|
-
<rect width="${width}" height="${height}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49936
|
-
<text x="16" y="${height / 2 + 5}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.content)}</text>
|
|
51069
|
+
renderAlertBox(box) {
|
|
51070
|
+
const node = box.node;
|
|
51071
|
+
return `<g>
|
|
51072
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
51073
|
+
<text x="${box.x + 16}" y="${box.y + box.height / 2 + 5}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.content)}</text>
|
|
49937
51074
|
</g>`;
|
|
49938
51075
|
}
|
|
49939
|
-
|
|
49940
|
-
const
|
|
49941
|
-
|
|
49942
|
-
|
|
49943
|
-
let y = this.currentY;
|
|
49944
|
-
let result = "";
|
|
51076
|
+
renderProgressBox(box) {
|
|
51077
|
+
const node = box.node;
|
|
51078
|
+
let y = box.y;
|
|
51079
|
+
let svg = "<g>";
|
|
49945
51080
|
if (node.label) {
|
|
49946
|
-
|
|
51081
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49947
51082
|
y += 24;
|
|
49948
51083
|
}
|
|
51084
|
+
const barHeight = 8;
|
|
49949
51085
|
const value = node.value || 0;
|
|
49950
51086
|
const max = node.max || 100;
|
|
49951
51087
|
const percent = Math.min(100, Math.max(0, value / max * 100));
|
|
49952
|
-
|
|
49953
|
-
|
|
49954
|
-
|
|
49955
|
-
|
|
49956
|
-
</g>`;
|
|
49957
|
-
this.currentY = y + height + 12;
|
|
49958
|
-
return result;
|
|
51088
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${barHeight}" rx="${barHeight / 2}" fill="${this.theme.colors.muted}"/>`;
|
|
51089
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width * percent / 100}" height="${barHeight}" rx="${barHeight / 2}" fill="${this.theme.colors.primary}"/>`;
|
|
51090
|
+
svg += "</g>";
|
|
51091
|
+
return svg;
|
|
49959
51092
|
}
|
|
49960
|
-
|
|
49961
|
-
const
|
|
49962
|
-
const
|
|
49963
|
-
const
|
|
49964
|
-
|
|
49965
|
-
|
|
49966
|
-
|
|
49967
|
-
return `
|
|
49968
|
-
<g transform="translate(${x}, ${y})">
|
|
49969
|
-
<circle r="${radius}" fill="none" stroke="${this.theme.colors.muted}" stroke-width="2"/>
|
|
49970
|
-
<path d="M0,-${radius} A${radius},${radius} 0 0,1 ${radius},0" fill="none" stroke="${this.theme.colors.primary}" stroke-width="2" stroke-linecap="round"/>
|
|
51093
|
+
renderSpinnerBox(box) {
|
|
51094
|
+
const cx = box.x + box.width / 2;
|
|
51095
|
+
const cy = box.y + box.height / 2;
|
|
51096
|
+
const radius = box.width / 2 - 2;
|
|
51097
|
+
return `<g>
|
|
51098
|
+
<circle cx="${cx}" cy="${cy}" r="${radius}" fill="none" stroke="${this.theme.colors.muted}" stroke-width="2"/>
|
|
51099
|
+
<path d="M${cx},${cy - radius} A${radius},${radius} 0 0,1 ${cx + radius},${cy}" fill="none" stroke="${this.theme.colors.primary}" stroke-width="2" stroke-linecap="round"/>
|
|
49971
51100
|
</g>`;
|
|
49972
51101
|
}
|
|
49973
|
-
|
|
49974
|
-
|
|
49975
|
-
// ===========================================
|
|
49976
|
-
renderNav(node) {
|
|
49977
|
-
const items = node.items || [];
|
|
49978
|
-
const x = this.currentX;
|
|
49979
|
-
const y = this.currentY;
|
|
51102
|
+
renderNavBox(box) {
|
|
51103
|
+
const node = box.node;
|
|
49980
51104
|
const vertical = node.vertical || false;
|
|
49981
|
-
let svg =
|
|
51105
|
+
let svg = "<g>";
|
|
51106
|
+
if (node.children && node.children.length > 0) {
|
|
51107
|
+
let offsetY = 0;
|
|
51108
|
+
node.children.forEach((child) => {
|
|
51109
|
+
if (child.type === "divider") {
|
|
51110
|
+
offsetY += 16;
|
|
51111
|
+
svg += `<line x1="${box.x}" y1="${box.y + offsetY}" x2="${box.x + box.width - 32}" y2="${box.y + offsetY}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51112
|
+
offsetY += 8;
|
|
51113
|
+
} else if (child.type === "group") {
|
|
51114
|
+
svg += `<text x="${box.x}" y="${box.y + offsetY + 16}" font-size="11" font-weight="500" fill="${this.theme.colors.muted}" text-transform="uppercase">${this.escapeXml(child.label)}</text>`;
|
|
51115
|
+
offsetY += 28;
|
|
51116
|
+
child.items.forEach((item) => {
|
|
51117
|
+
if (item.type === "divider") {
|
|
51118
|
+
offsetY += 8;
|
|
51119
|
+
svg += `<line x1="${box.x}" y1="${box.y + offsetY}" x2="${box.x + box.width - 32}" y2="${box.y + offsetY}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51120
|
+
offsetY += 8;
|
|
51121
|
+
} else {
|
|
51122
|
+
const fill = item.active ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
51123
|
+
const fontWeight = item.active ? 'font-weight="500"' : "";
|
|
51124
|
+
svg += `<text x="${box.x}" y="${box.y + offsetY + 16}" font-size="14" ${fontWeight} fill="${fill}">${this.escapeXml(item.label)}</text>`;
|
|
51125
|
+
offsetY += 32;
|
|
51126
|
+
}
|
|
51127
|
+
});
|
|
51128
|
+
} else if (child.type === "item") {
|
|
51129
|
+
const fill = child.active ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
51130
|
+
const fontWeight = child.active ? 'font-weight="500"' : "";
|
|
51131
|
+
svg += `<text x="${box.x}" y="${box.y + offsetY + 16}" font-size="14" ${fontWeight} fill="${fill}">${this.escapeXml(child.label)}</text>`;
|
|
51132
|
+
offsetY += 32;
|
|
51133
|
+
}
|
|
51134
|
+
});
|
|
51135
|
+
svg += "</g>";
|
|
51136
|
+
return svg;
|
|
51137
|
+
}
|
|
51138
|
+
const items = node.items || [];
|
|
49982
51139
|
if (vertical) {
|
|
49983
51140
|
items.forEach((item, idx) => {
|
|
49984
51141
|
const label = typeof item === "string" ? item : item.label;
|
|
49985
51142
|
const isActive = typeof item === "object" && item.active;
|
|
49986
51143
|
const fill = isActive ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
49987
|
-
svg += `<text x="
|
|
51144
|
+
svg += `<text x="${box.x}" y="${box.y + idx * 32 + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
49988
51145
|
});
|
|
49989
|
-
this.currentY += items.length * 32 + 12;
|
|
49990
51146
|
} else {
|
|
49991
51147
|
let offsetX = 0;
|
|
49992
51148
|
items.forEach((item) => {
|
|
49993
51149
|
const label = typeof item === "string" ? item : item.label;
|
|
49994
51150
|
const isActive = typeof item === "object" && item.active;
|
|
49995
51151
|
const fill = isActive ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
49996
|
-
svg += `<text x="${offsetX}" y="16" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
49997
|
-
offsetX += label
|
|
51152
|
+
svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
51153
|
+
offsetX += this.estimateTextWidth(label, 14) + 24;
|
|
49998
51154
|
});
|
|
49999
|
-
this.currentY += 32;
|
|
50000
51155
|
}
|
|
50001
51156
|
svg += "</g>";
|
|
50002
51157
|
return svg;
|
|
50003
51158
|
}
|
|
50004
|
-
|
|
51159
|
+
renderTabsBox(box) {
|
|
51160
|
+
const node = box.node;
|
|
50005
51161
|
const items = node.items || [];
|
|
50006
|
-
const x = this.currentX;
|
|
50007
|
-
const y = this.currentY;
|
|
50008
51162
|
const tabHeight = 40;
|
|
50009
|
-
let svg =
|
|
51163
|
+
let svg = "<g>";
|
|
50010
51164
|
let offsetX = 0;
|
|
50011
|
-
items.forEach((item) => {
|
|
51165
|
+
items.forEach((item, idx) => {
|
|
50012
51166
|
const label = typeof item === "string" ? item : item;
|
|
50013
|
-
const tabWidth = label
|
|
50014
|
-
|
|
50015
|
-
svg += `<
|
|
51167
|
+
const tabWidth = this.estimateTextWidth(label, 14) + 32;
|
|
51168
|
+
const isFirst = idx === 0;
|
|
51169
|
+
svg += `<rect x="${box.x + offsetX}" y="${box.y}" width="${tabWidth}" height="${tabHeight}" fill="${isFirst ? "white" : this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51170
|
+
svg += `<text x="${box.x + offsetX + tabWidth / 2}" y="${box.y + tabHeight / 2 + 5}" font-size="14" text-anchor="middle" fill="${this.theme.colors.foreground}">${this.escapeXml(label)}</text>`;
|
|
50016
51171
|
offsetX += tabWidth;
|
|
50017
51172
|
});
|
|
50018
51173
|
svg += "</g>";
|
|
50019
|
-
this.currentY += tabHeight + 12;
|
|
50020
51174
|
return svg;
|
|
50021
51175
|
}
|
|
50022
|
-
|
|
51176
|
+
renderBreadcrumbBox(box) {
|
|
51177
|
+
const node = box.node;
|
|
50023
51178
|
const items = node.items || [];
|
|
50024
51179
|
const separator = "/";
|
|
50025
|
-
|
|
50026
|
-
const y = this.currentY;
|
|
50027
|
-
let svg = `<g transform="translate(${x}, ${y})">`;
|
|
51180
|
+
let svg = "<g>";
|
|
50028
51181
|
let offsetX = 0;
|
|
50029
51182
|
items.forEach((item, idx) => {
|
|
50030
51183
|
const label = typeof item === "string" ? item : item.label;
|
|
50031
51184
|
const isLast = idx === items.length - 1;
|
|
50032
51185
|
const fill = isLast ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
50033
|
-
svg += `<text x="${offsetX}" y="16" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
50034
|
-
offsetX += label
|
|
51186
|
+
svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
51187
|
+
offsetX += this.estimateTextWidth(label, 14) + 8;
|
|
50035
51188
|
if (!isLast) {
|
|
50036
|
-
svg += `<text x="${offsetX}" y="16" font-size="14" fill="${this.theme.colors.muted}">${separator}</text>`;
|
|
51189
|
+
svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${this.theme.colors.muted}">${separator}</text>`;
|
|
50037
51190
|
offsetX += 16;
|
|
50038
51191
|
}
|
|
50039
51192
|
});
|
|
50040
51193
|
svg += "</g>";
|
|
50041
|
-
this.currentY += 28;
|
|
50042
51194
|
return svg;
|
|
50043
51195
|
}
|
|
51196
|
+
renderIconBox(box) {
|
|
51197
|
+
const node = box.node;
|
|
51198
|
+
const iconData = getIconData(node.name);
|
|
51199
|
+
if (!iconData) {
|
|
51200
|
+
return `<!-- Icon not found: ${node.name} -->`;
|
|
51201
|
+
}
|
|
51202
|
+
const color = this.theme.colors.foreground;
|
|
51203
|
+
return this.renderIconPaths(iconData, box.x, box.y, box.width, color);
|
|
51204
|
+
}
|
|
51205
|
+
renderDividerBox(box) {
|
|
51206
|
+
return `<line x1="${box.x}" y1="${box.y}" x2="${box.x + box.width}" y2="${box.y}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51207
|
+
}
|
|
50044
51208
|
// ===========================================
|
|
50045
51209
|
// Utility Methods
|
|
50046
51210
|
// ===========================================
|
|
50047
|
-
|
|
50048
|
-
const
|
|
50049
|
-
|
|
50050
|
-
|
|
50051
|
-
|
|
50052
|
-
|
|
50053
|
-
|
|
50054
|
-
|
|
50055
|
-
|
|
50056
|
-
|
|
50057
|
-
|
|
50058
|
-
|
|
51211
|
+
renderIconPaths(data, x, y, size, color) {
|
|
51212
|
+
const scale = size / 24;
|
|
51213
|
+
const paths = data.map(([tag, attrs]) => {
|
|
51214
|
+
const attrStr = Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
51215
|
+
return `<${tag} ${attrStr} />`;
|
|
51216
|
+
}).join("");
|
|
51217
|
+
return `<g transform="translate(${x}, ${y}) scale(${scale})" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</g>`;
|
|
51218
|
+
}
|
|
51219
|
+
getPadding(node) {
|
|
51220
|
+
const defaultPadding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
51221
|
+
if (!("p" in node) && !("px" in node) && !("py" in node)) {
|
|
51222
|
+
if (node.type === "Card" || node.type === "Header" || node.type === "Footer" || node.type === "Sidebar") {
|
|
51223
|
+
return { top: 16, right: 16, bottom: 16, left: 16 };
|
|
51224
|
+
}
|
|
51225
|
+
return defaultPadding;
|
|
51226
|
+
}
|
|
51227
|
+
let p = 0;
|
|
51228
|
+
if ("p" in node && node.p !== void 0) {
|
|
51229
|
+
p = this.resolveSpacing(node.p);
|
|
51230
|
+
}
|
|
51231
|
+
let px = p;
|
|
51232
|
+
let py = p;
|
|
51233
|
+
if ("px" in node && node.px !== void 0) {
|
|
51234
|
+
px = this.resolveSpacing(node.px);
|
|
51235
|
+
}
|
|
51236
|
+
if ("py" in node && node.py !== void 0) {
|
|
51237
|
+
py = this.resolveSpacing(node.py);
|
|
51238
|
+
}
|
|
51239
|
+
if (node.type === "Header") {
|
|
51240
|
+
return { top: 0, right: px, bottom: 0, left: px };
|
|
51241
|
+
}
|
|
51242
|
+
return { top: py, right: px, bottom: py, left: px };
|
|
51243
|
+
}
|
|
51244
|
+
getGap(node) {
|
|
51245
|
+
if ("gap" in node && node.gap !== void 0) {
|
|
51246
|
+
return this.resolveSpacing(node.gap);
|
|
51247
|
+
}
|
|
51248
|
+
return void 0;
|
|
51249
|
+
}
|
|
51250
|
+
resolveSpacing(value) {
|
|
51251
|
+
if (typeof value === "number") {
|
|
51252
|
+
return value * 4;
|
|
51253
|
+
}
|
|
51254
|
+
if (typeof value === "object" && value && "value" in value) {
|
|
51255
|
+
return value.value;
|
|
51256
|
+
}
|
|
51257
|
+
return 0;
|
|
51258
|
+
}
|
|
51259
|
+
resolveSize(value) {
|
|
51260
|
+
if (value === void 0 || value === null) return void 0;
|
|
51261
|
+
if (typeof value === "number") return value;
|
|
51262
|
+
if (typeof value === "string") {
|
|
51263
|
+
if (value === "full") return void 0;
|
|
51264
|
+
const num = parseFloat(value);
|
|
51265
|
+
if (!isNaN(num)) return num;
|
|
51266
|
+
}
|
|
51267
|
+
if (typeof value === "object" && value && "value" in value) {
|
|
51268
|
+
return value.value;
|
|
51269
|
+
}
|
|
51270
|
+
return void 0;
|
|
51271
|
+
}
|
|
51272
|
+
isFullWidth(node) {
|
|
51273
|
+
if ("w" in node) {
|
|
51274
|
+
return node.w === "full";
|
|
51275
|
+
}
|
|
51276
|
+
return false;
|
|
51277
|
+
}
|
|
51278
|
+
estimateTextWidth(text, fontSize) {
|
|
51279
|
+
return text.length * fontSize * 0.6;
|
|
50059
51280
|
}
|
|
50060
51281
|
resolveFontSize(size) {
|
|
50061
51282
|
if (!size) return 16;
|
|
50062
51283
|
if (typeof size === "string") {
|
|
50063
|
-
|
|
51284
|
+
const sizes = {
|
|
51285
|
+
xs: 12,
|
|
51286
|
+
sm: 14,
|
|
51287
|
+
base: 16,
|
|
51288
|
+
md: 16,
|
|
51289
|
+
lg: 18,
|
|
51290
|
+
xl: 20,
|
|
51291
|
+
"2xl": 24,
|
|
51292
|
+
"3xl": 30
|
|
51293
|
+
};
|
|
51294
|
+
return sizes[size] || 16;
|
|
50064
51295
|
}
|
|
50065
51296
|
if (typeof size === "object" && "value" in size) {
|
|
50066
51297
|
if (size.unit === "px") return size.value;
|
|
@@ -50071,15 +51302,8 @@ var SvgRenderer = class {
|
|
|
50071
51302
|
return 16;
|
|
50072
51303
|
}
|
|
50073
51304
|
getTitleFontSize(level) {
|
|
50074
|
-
const sizes = {
|
|
50075
|
-
|
|
50076
|
-
2: 28,
|
|
50077
|
-
3: 24,
|
|
50078
|
-
4: 20,
|
|
50079
|
-
5: 18,
|
|
50080
|
-
6: 16
|
|
50081
|
-
};
|
|
50082
|
-
return sizes[level] || 24;
|
|
51305
|
+
const sizes = { 1: 36, 2: 30, 3: 24, 4: 20, 5: 18, 6: 16 };
|
|
51306
|
+
return sizes[level] || 20;
|
|
50083
51307
|
}
|
|
50084
51308
|
escapeXml(str) {
|
|
50085
51309
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -50140,15 +51364,22 @@ function renderToSvg2(document, options = {}) {
|
|
|
50140
51364
|
height = viewport.height;
|
|
50141
51365
|
}
|
|
50142
51366
|
}
|
|
50143
|
-
const padding = options.padding ?? 20;
|
|
50144
51367
|
const background = options.background ?? "#ffffff";
|
|
50145
51368
|
const { html, css } = render(document, { theme: "light" });
|
|
50146
51369
|
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
50147
51370
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
50148
51371
|
viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
50149
51372
|
<rect width="100%" height="100%" fill="${background}"/>
|
|
50150
|
-
<foreignObject x="
|
|
50151
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="
|
|
51373
|
+
<foreignObject x="0" y="0" width="${width}" height="${height}">
|
|
51374
|
+
<div xmlns="http://www.w3.org/1999/xhtml" style="
|
|
51375
|
+
width: ${width}px;
|
|
51376
|
+
height: ${height}px;
|
|
51377
|
+
overflow: hidden;
|
|
51378
|
+
display: flex;
|
|
51379
|
+
justify-content: center;
|
|
51380
|
+
align-items: center;
|
|
51381
|
+
box-sizing: border-box;
|
|
51382
|
+
">
|
|
50152
51383
|
<style type="text/css">
|
|
50153
51384
|
${css}
|
|
50154
51385
|
</style>
|