@wireweave/core 1.0.0-beta.20260107134058 → 1.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +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.cjs
CHANGED
|
@@ -118,12 +118,17 @@ function generateContainerStyles(prefix) {
|
|
|
118
118
|
padding: 16px;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/* Cards in flex rows
|
|
121
|
+
/* Cards in flex rows: respect explicit width, shrink if needed */
|
|
122
122
|
.${prefix}-row > .${prefix}-card {
|
|
123
|
-
flex:
|
|
123
|
+
flex: 0 1 auto;
|
|
124
124
|
min-width: 0;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/* Cards without explicit width should expand to fill space */
|
|
128
|
+
.${prefix}-row > .${prefix}-card-flex {
|
|
129
|
+
flex: 1 1 0%;
|
|
130
|
+
}
|
|
131
|
+
|
|
127
132
|
.${prefix}-card-title {
|
|
128
133
|
margin: 0 0 12px 0;
|
|
129
134
|
font-size: 18px;
|
|
@@ -277,6 +282,11 @@ function generateTextStyles(prefix) {
|
|
|
277
282
|
line-height: 1.25;
|
|
278
283
|
}
|
|
279
284
|
|
|
285
|
+
/* Remove bottom margin when title is in a row (inline with other elements) */
|
|
286
|
+
.${prefix}-row .${prefix}-title {
|
|
287
|
+
margin-bottom: 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
280
290
|
h1.${prefix}-title { font-size: 36px; }
|
|
281
291
|
h2.${prefix}-title { font-size: 30px; }
|
|
282
292
|
h3.${prefix}-title { font-size: 24px; }
|
|
@@ -671,6 +681,30 @@ img.${prefix}-image {
|
|
|
671
681
|
font-size: 14px;
|
|
672
682
|
}
|
|
673
683
|
|
|
684
|
+
.${prefix}-placeholder-with-children {
|
|
685
|
+
position: relative;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.${prefix}-placeholder-label {
|
|
689
|
+
position: absolute;
|
|
690
|
+
top: 50%;
|
|
691
|
+
left: 50%;
|
|
692
|
+
transform: translate(-50%, -50%);
|
|
693
|
+
z-index: 0;
|
|
694
|
+
pointer-events: none;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.${prefix}-placeholder-overlay {
|
|
698
|
+
position: absolute;
|
|
699
|
+
top: 0;
|
|
700
|
+
left: 0;
|
|
701
|
+
right: 0;
|
|
702
|
+
bottom: 0;
|
|
703
|
+
z-index: 1;
|
|
704
|
+
display: flex;
|
|
705
|
+
flex-direction: column;
|
|
706
|
+
}
|
|
707
|
+
|
|
674
708
|
.${prefix}-avatar {
|
|
675
709
|
display: inline-flex;
|
|
676
710
|
align-items: center;
|
|
@@ -786,12 +820,12 @@ svg.${prefix}-icon {
|
|
|
786
820
|
display: block;
|
|
787
821
|
}
|
|
788
822
|
|
|
789
|
-
/* Icon size tokens */
|
|
823
|
+
/* Icon size tokens - matches SVG renderer */
|
|
790
824
|
svg.${prefix}-icon-xs { width: 12px; height: 12px; }
|
|
791
|
-
svg.${prefix}-icon-sm { width:
|
|
792
|
-
svg.${prefix}-icon-md { width:
|
|
793
|
-
svg.${prefix}-icon-lg { width:
|
|
794
|
-
svg.${prefix}-icon-xl { width:
|
|
825
|
+
svg.${prefix}-icon-sm { width: 16px; height: 16px; }
|
|
826
|
+
svg.${prefix}-icon-md { width: 20px; height: 20px; }
|
|
827
|
+
svg.${prefix}-icon-lg { width: 24px; height: 24px; }
|
|
828
|
+
svg.${prefix}-icon-xl { width: 32px; height: 32px; }
|
|
795
829
|
|
|
796
830
|
.${prefix}-icon svg {
|
|
797
831
|
display: block;
|
|
@@ -1087,6 +1121,27 @@ function generateNavigationStyles(_theme, prefix) {
|
|
|
1087
1121
|
cursor: not-allowed;
|
|
1088
1122
|
}
|
|
1089
1123
|
|
|
1124
|
+
.${prefix}-nav-group {
|
|
1125
|
+
display: flex;
|
|
1126
|
+
flex-direction: column;
|
|
1127
|
+
gap: 4px;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.${prefix}-nav-group-label {
|
|
1131
|
+
font-size: 11px;
|
|
1132
|
+
font-weight: 500;
|
|
1133
|
+
color: var(--${prefix}-muted);
|
|
1134
|
+
text-transform: uppercase;
|
|
1135
|
+
letter-spacing: 0.05em;
|
|
1136
|
+
padding: 8px 16px 4px;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.${prefix}-nav-divider {
|
|
1140
|
+
margin: 8px 0;
|
|
1141
|
+
border: none;
|
|
1142
|
+
border-top: 1px solid var(--${prefix}-border);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1090
1145
|
.${prefix}-tabs {
|
|
1091
1146
|
border-bottom: 1px solid var(--${prefix}-border);
|
|
1092
1147
|
}
|
|
@@ -1304,7 +1359,6 @@ function generateBaseStyles(prefix) {
|
|
|
1304
1359
|
font-family: var(--${prefix}-font);
|
|
1305
1360
|
color: var(--${prefix}-fg);
|
|
1306
1361
|
background: var(--${prefix}-bg);
|
|
1307
|
-
min-height: 100vh;
|
|
1308
1362
|
box-sizing: border-box;
|
|
1309
1363
|
position: relative;
|
|
1310
1364
|
display: flex;
|
|
@@ -1315,10 +1369,19 @@ function generateBaseStyles(prefix) {
|
|
|
1315
1369
|
overflow: hidden;
|
|
1316
1370
|
}
|
|
1317
1371
|
|
|
1372
|
+
/* Col direct child of page should fill page height */
|
|
1373
|
+
.${prefix}-page > .${prefix}-col {
|
|
1374
|
+
flex: 1;
|
|
1375
|
+
min-height: 0;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1318
1378
|
/* Row containing sidebar should fill remaining space */
|
|
1319
1379
|
.${prefix}-page > .${prefix}-row:has(.${prefix}-sidebar),
|
|
1320
|
-
.${prefix}-page > .${prefix}-row:has(.${prefix}-main)
|
|
1380
|
+
.${prefix}-page > .${prefix}-row:has(.${prefix}-main),
|
|
1381
|
+
.${prefix}-page > .${prefix}-col > .${prefix}-row:has(.${prefix}-sidebar),
|
|
1382
|
+
.${prefix}-page > .${prefix}-col > .${prefix}-row:has(.${prefix}-main) {
|
|
1321
1383
|
flex: 1;
|
|
1384
|
+
min-height: 0;
|
|
1322
1385
|
align-items: stretch;
|
|
1323
1386
|
}
|
|
1324
1387
|
|
|
@@ -1361,10 +1424,15 @@ function generateGridClasses(_theme, prefix) {
|
|
|
1361
1424
|
box-sizing: border-box;
|
|
1362
1425
|
}
|
|
1363
1426
|
|
|
1427
|
+
/* When explicit width is set, don't flex-grow */
|
|
1428
|
+
.${prefix}-row[style*="width:"],
|
|
1429
|
+
.${prefix}-col[style*="width:"] {
|
|
1430
|
+
flex: 0 0 auto;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1364
1433
|
`;
|
|
1365
1434
|
for (let i = 1; i <= 12; i++) {
|
|
1366
|
-
|
|
1367
|
-
css += `.${prefix}-col-${i} { flex: 0 0 auto; width: ${width}%; }
|
|
1435
|
+
css += `.${prefix}-col-${i} { flex: ${i} 0 0%; min-width: 0; }
|
|
1368
1436
|
`;
|
|
1369
1437
|
}
|
|
1370
1438
|
return css;
|
|
@@ -1532,6 +1600,20 @@ function generateLayoutClasses(prefix) {
|
|
|
1532
1600
|
.${prefix}-main {
|
|
1533
1601
|
flex: 1;
|
|
1534
1602
|
padding: 16px;
|
|
1603
|
+
display: flex;
|
|
1604
|
+
flex-direction: column;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/* Scrollable main content */
|
|
1608
|
+
.${prefix}-main.${prefix}-scroll {
|
|
1609
|
+
overflow-y: auto;
|
|
1610
|
+
overflow-x: hidden;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/* Main content should align to top, not stretch to fill */
|
|
1614
|
+
/* But allow explicit flex=1 to override */
|
|
1615
|
+
.${prefix}-main > .${prefix}-col:not(.${prefix}-flex-1) {
|
|
1616
|
+
flex: 0 0 auto;
|
|
1535
1617
|
}
|
|
1536
1618
|
|
|
1537
1619
|
.${prefix}-footer {
|
|
@@ -1548,6 +1630,7 @@ function generateLayoutClasses(prefix) {
|
|
|
1548
1630
|
border-right: 1px solid var(--${prefix}-border);
|
|
1549
1631
|
padding: 16px 16px 16px 20px;
|
|
1550
1632
|
flex-shrink: 0;
|
|
1633
|
+
align-self: stretch;
|
|
1551
1634
|
}
|
|
1552
1635
|
|
|
1553
1636
|
.${prefix}-sidebar-right {
|
|
@@ -48117,14 +48200,39 @@ var lucideIcons = {
|
|
|
48117
48200
|
]
|
|
48118
48201
|
]
|
|
48119
48202
|
};
|
|
48203
|
+
var iconAliases = {
|
|
48204
|
+
"home": "house",
|
|
48205
|
+
"plus-square": "square-plus",
|
|
48206
|
+
"minus-square": "square-minus",
|
|
48207
|
+
"x-square": "square-x",
|
|
48208
|
+
"check-square": "square-check",
|
|
48209
|
+
"edit": "pencil",
|
|
48210
|
+
"edit-2": "pencil",
|
|
48211
|
+
"edit-3": "pencil-line",
|
|
48212
|
+
"trash": "trash-2",
|
|
48213
|
+
"delete": "trash-2",
|
|
48214
|
+
"close": "x",
|
|
48215
|
+
"menu": "menu",
|
|
48216
|
+
"hamburger": "menu",
|
|
48217
|
+
"dots": "more-horizontal",
|
|
48218
|
+
"dots-vertical": "more-vertical",
|
|
48219
|
+
"cog": "settings",
|
|
48220
|
+
"gear": "settings"
|
|
48221
|
+
};
|
|
48120
48222
|
function getIconData(name) {
|
|
48121
48223
|
if (lucideIcons[name]) {
|
|
48122
48224
|
return lucideIcons[name];
|
|
48123
48225
|
}
|
|
48226
|
+
if (iconAliases[name] && lucideIcons[iconAliases[name]]) {
|
|
48227
|
+
return lucideIcons[iconAliases[name]];
|
|
48228
|
+
}
|
|
48124
48229
|
const kebabName = name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
48125
48230
|
if (lucideIcons[kebabName]) {
|
|
48126
48231
|
return lucideIcons[kebabName];
|
|
48127
48232
|
}
|
|
48233
|
+
if (iconAliases[kebabName] && lucideIcons[iconAliases[kebabName]]) {
|
|
48234
|
+
return lucideIcons[iconAliases[kebabName]];
|
|
48235
|
+
}
|
|
48128
48236
|
return void 0;
|
|
48129
48237
|
}
|
|
48130
48238
|
function renderIconSvg(data, _size = 24, strokeWidth = 2, className = "", styleAttr = "") {
|
|
@@ -48234,7 +48342,7 @@ var HtmlRenderer = class extends BaseRenderer {
|
|
|
48234
48342
|
const title = node.title ? `<title>${this.escapeHtml(node.title)}</title>
|
|
48235
48343
|
` : "";
|
|
48236
48344
|
const commonStyles = this.buildCommonStyles(node);
|
|
48237
|
-
const viewportStyle = `width: ${viewport.width}px; height: ${viewport.height}px`;
|
|
48345
|
+
const viewportStyle = `position: relative; width: ${viewport.width}px; height: ${viewport.height}px; overflow: hidden`;
|
|
48238
48346
|
const combinedStyle = commonStyles ? `${viewportStyle}; ${commonStyles}` : viewportStyle;
|
|
48239
48347
|
const dataAttrs = `data-viewport-width="${viewport.width}" data-viewport-height="${viewport.height}" data-viewport-label="${viewport.label}"`;
|
|
48240
48348
|
return `<div class="${classes}" style="${combinedStyle}" ${dataAttrs}>
|
|
@@ -48400,6 +48508,7 @@ ${children}
|
|
|
48400
48508
|
renderMain(node) {
|
|
48401
48509
|
const classes = this.buildClassString([
|
|
48402
48510
|
`${this.prefix}-main`,
|
|
48511
|
+
node.scroll ? `${this.prefix}-scroll` : void 0,
|
|
48403
48512
|
...this.getCommonClasses(node)
|
|
48404
48513
|
]);
|
|
48405
48514
|
const styles = this.buildCommonStyles(node);
|
|
@@ -48485,6 +48594,10 @@ ${children}
|
|
|
48485
48594
|
/**
|
|
48486
48595
|
* Build common inline styles for all values
|
|
48487
48596
|
*
|
|
48597
|
+
* Position values (x, y) for absolute positioning:
|
|
48598
|
+
* - When x or y is specified, element gets position: absolute
|
|
48599
|
+
* - x → left, y → top
|
|
48600
|
+
*
|
|
48488
48601
|
* Spacing values (p, m, gap) use token system:
|
|
48489
48602
|
* - number: spacing token (e.g., p=4 → padding: 16px from token table)
|
|
48490
48603
|
* - ValueWithUnit: direct CSS value (e.g., p=16px → padding: 16px)
|
|
@@ -48499,6 +48612,17 @@ ${children}
|
|
|
48499
48612
|
*/
|
|
48500
48613
|
buildCommonStyles(props) {
|
|
48501
48614
|
const styles = [];
|
|
48615
|
+
if (props.x !== void 0 || props.y !== void 0) {
|
|
48616
|
+
styles.push("position: absolute");
|
|
48617
|
+
if (props.x !== void 0) {
|
|
48618
|
+
const xValue = resolveSizeValueToCss(props.x);
|
|
48619
|
+
if (xValue) styles.push(`left: ${xValue}`);
|
|
48620
|
+
}
|
|
48621
|
+
if (props.y !== void 0) {
|
|
48622
|
+
const yValue = resolveSizeValueToCss(props.y);
|
|
48623
|
+
if (yValue) styles.push(`top: ${yValue}`);
|
|
48624
|
+
}
|
|
48625
|
+
}
|
|
48502
48626
|
const wValue = resolveSizeValueToCss(props.w);
|
|
48503
48627
|
if (wValue) {
|
|
48504
48628
|
styles.push(`width: ${wValue}`);
|
|
@@ -48608,8 +48732,10 @@ ${children}
|
|
|
48608
48732
|
// Container Node Renderers
|
|
48609
48733
|
// ===========================================
|
|
48610
48734
|
renderCard(node) {
|
|
48735
|
+
const hasExplicitWidth = node.w !== void 0;
|
|
48611
48736
|
const classes = this.buildClassString([
|
|
48612
48737
|
`${this.prefix}-card`,
|
|
48738
|
+
!hasExplicitWidth ? `${this.prefix}-card-flex` : void 0,
|
|
48613
48739
|
node.shadow ? `${this.prefix}-card-shadow-${node.shadow}` : void 0,
|
|
48614
48740
|
...this.getCommonClasses(node)
|
|
48615
48741
|
]);
|
|
@@ -48946,11 +49072,19 @@ ${slider}`;
|
|
|
48946
49072
|
renderPlaceholder(node) {
|
|
48947
49073
|
const classes = this.buildClassString([
|
|
48948
49074
|
`${this.prefix}-placeholder`,
|
|
49075
|
+
node.children && node.children.length > 0 ? `${this.prefix}-placeholder-with-children` : void 0,
|
|
48949
49076
|
...this.getCommonClasses(node)
|
|
48950
49077
|
]);
|
|
48951
49078
|
const styles = this.buildCommonStyles(node);
|
|
48952
49079
|
const styleAttr = styles ? ` style="${styles}"` : "";
|
|
48953
49080
|
const label = node.label ? this.escapeHtml(node.label) : "Placeholder";
|
|
49081
|
+
if (node.children && node.children.length > 0) {
|
|
49082
|
+
const childrenHtml = this.renderChildren(node.children);
|
|
49083
|
+
return `<div class="${classes}"${styleAttr}>
|
|
49084
|
+
<span class="${this.prefix}-placeholder-label">${label}</span>
|
|
49085
|
+
<div class="${this.prefix}-placeholder-overlay">${childrenHtml}</div>
|
|
49086
|
+
</div>`;
|
|
49087
|
+
}
|
|
48954
49088
|
return `<div class="${classes}"${styleAttr}>${label}</div>`;
|
|
48955
49089
|
}
|
|
48956
49090
|
renderAvatar(node) {
|
|
@@ -49196,6 +49330,12 @@ ${items}
|
|
|
49196
49330
|
]);
|
|
49197
49331
|
const styles = this.buildCommonStyles(node);
|
|
49198
49332
|
const styleAttr = styles ? ` style="${styles}"` : "";
|
|
49333
|
+
if (node.children && node.children.length > 0) {
|
|
49334
|
+
const content = this.renderNavChildren(node.children);
|
|
49335
|
+
return `<nav class="${classes}"${styleAttr}>
|
|
49336
|
+
${content}
|
|
49337
|
+
</nav>`;
|
|
49338
|
+
}
|
|
49199
49339
|
const items = node.items.map((item) => {
|
|
49200
49340
|
if (typeof item === "string") {
|
|
49201
49341
|
return `<a class="${this.prefix}-nav-link" href="#">${this.escapeHtml(item)}</a>`;
|
|
@@ -49205,12 +49345,48 @@ ${items}
|
|
|
49205
49345
|
item.active ? `${this.prefix}-nav-link-active` : void 0,
|
|
49206
49346
|
item.disabled ? `${this.prefix}-nav-link-disabled` : void 0
|
|
49207
49347
|
]);
|
|
49208
|
-
|
|
49348
|
+
const iconHtml = item.icon ? this.renderIconHtml(item.icon) + " " : "";
|
|
49349
|
+
return `<a class="${linkClasses}" href="${item.href || "#"}">${iconHtml}${this.escapeHtml(item.label)}</a>`;
|
|
49209
49350
|
}).join("\n");
|
|
49210
49351
|
return `<nav class="${classes}"${styleAttr}>
|
|
49211
49352
|
${items}
|
|
49212
49353
|
</nav>`;
|
|
49213
49354
|
}
|
|
49355
|
+
renderNavChildren(children) {
|
|
49356
|
+
return children.map((child) => {
|
|
49357
|
+
if (child.type === "divider") {
|
|
49358
|
+
return `<hr class="${this.prefix}-nav-divider" />`;
|
|
49359
|
+
}
|
|
49360
|
+
if (child.type === "group") {
|
|
49361
|
+
const groupItems = child.items.map((item) => {
|
|
49362
|
+
if (item.type === "divider") {
|
|
49363
|
+
return `<hr class="${this.prefix}-nav-divider" />`;
|
|
49364
|
+
}
|
|
49365
|
+
return this.renderNavItem(item);
|
|
49366
|
+
}).join("\n");
|
|
49367
|
+
return `<div class="${this.prefix}-nav-group">
|
|
49368
|
+
<div class="${this.prefix}-nav-group-label">${this.escapeHtml(child.label)}</div>
|
|
49369
|
+
${groupItems}
|
|
49370
|
+
</div>`;
|
|
49371
|
+
}
|
|
49372
|
+
if (child.type === "item") {
|
|
49373
|
+
return this.renderNavItem(child);
|
|
49374
|
+
}
|
|
49375
|
+
return "";
|
|
49376
|
+
}).join("\n");
|
|
49377
|
+
}
|
|
49378
|
+
renderNavItem(item) {
|
|
49379
|
+
const linkClasses = this.buildClassString([
|
|
49380
|
+
`${this.prefix}-nav-link`,
|
|
49381
|
+
item.active ? `${this.prefix}-nav-link-active` : void 0,
|
|
49382
|
+
item.disabled ? `${this.prefix}-nav-link-disabled` : void 0
|
|
49383
|
+
]);
|
|
49384
|
+
const iconHtml = item.icon ? this.renderIconHtml(item.icon) + " " : "";
|
|
49385
|
+
return `<a class="${linkClasses}" href="${item.href || "#"}">${iconHtml}${this.escapeHtml(item.label)}</a>`;
|
|
49386
|
+
}
|
|
49387
|
+
renderIconHtml(iconName) {
|
|
49388
|
+
return `<span class="${this.prefix}-icon" data-icon="${iconName}"></span>`;
|
|
49389
|
+
}
|
|
49214
49390
|
renderTabs(node) {
|
|
49215
49391
|
const classes = this.buildClassString([
|
|
49216
49392
|
`${this.prefix}-tabs`,
|
|
@@ -49365,13 +49541,239 @@ function createHtmlRenderer(options) {
|
|
|
49365
49541
|
return new HtmlRenderer(options);
|
|
49366
49542
|
}
|
|
49367
49543
|
|
|
49544
|
+
// src/renderer/svg/flex-layout.ts
|
|
49545
|
+
function computeFlexLayout(items, config) {
|
|
49546
|
+
const { mainSize, crossSize, justifyContent, alignItems, gap } = config;
|
|
49547
|
+
const computed = items.map((props, index) => {
|
|
49548
|
+
let flexBasis;
|
|
49549
|
+
if (props.basis === "auto" || props.basis === "content") {
|
|
49550
|
+
flexBasis = props.contentSize;
|
|
49551
|
+
} else {
|
|
49552
|
+
flexBasis = props.basis;
|
|
49553
|
+
}
|
|
49554
|
+
flexBasis = clamp(flexBasis, props.minSize, props.maxSize);
|
|
49555
|
+
return {
|
|
49556
|
+
index,
|
|
49557
|
+
props,
|
|
49558
|
+
flexBasis,
|
|
49559
|
+
mainSize: flexBasis,
|
|
49560
|
+
crossSize: 0,
|
|
49561
|
+
mainPosition: 0,
|
|
49562
|
+
crossPosition: 0,
|
|
49563
|
+
frozen: false,
|
|
49564
|
+
scaledShrinkFactor: props.shrink * flexBasis
|
|
49565
|
+
};
|
|
49566
|
+
});
|
|
49567
|
+
const totalGap = Math.max(0, (items.length - 1) * gap);
|
|
49568
|
+
const totalFlexBasis = computed.reduce((sum, item) => sum + item.flexBasis, 0);
|
|
49569
|
+
let freeSpace = mainSize - totalFlexBasis - totalGap;
|
|
49570
|
+
if (freeSpace !== 0) {
|
|
49571
|
+
resolveFlexibleLengths(computed, freeSpace);
|
|
49572
|
+
}
|
|
49573
|
+
distributeMainAxis(computed, mainSize, totalGap, gap, justifyContent);
|
|
49574
|
+
const crossSizeMax = crossSize ?? Math.max(...computed.map((item) => item.props.contentSize), 0);
|
|
49575
|
+
alignCrossAxis(computed, crossSizeMax, alignItems);
|
|
49576
|
+
const mainSizeUsed = computed.reduce((sum, item) => sum + item.mainSize, 0) + totalGap;
|
|
49577
|
+
return {
|
|
49578
|
+
items: computed,
|
|
49579
|
+
mainSizeUsed,
|
|
49580
|
+
crossSizeMax
|
|
49581
|
+
};
|
|
49582
|
+
}
|
|
49583
|
+
function resolveFlexibleLengths(items, initialFreeSpace) {
|
|
49584
|
+
items.forEach((item) => {
|
|
49585
|
+
item.frozen = false;
|
|
49586
|
+
item.mainSize = item.flexBasis;
|
|
49587
|
+
});
|
|
49588
|
+
let freeSpace = initialFreeSpace;
|
|
49589
|
+
const isGrowing = freeSpace > 0;
|
|
49590
|
+
let iteration = 0;
|
|
49591
|
+
const maxIterations = items.length + 1;
|
|
49592
|
+
while (iteration < maxIterations) {
|
|
49593
|
+
iteration++;
|
|
49594
|
+
const unfrozen = items.filter((item) => !item.frozen);
|
|
49595
|
+
if (unfrozen.length === 0) break;
|
|
49596
|
+
let flexFactorSum;
|
|
49597
|
+
if (isGrowing) {
|
|
49598
|
+
flexFactorSum = unfrozen.reduce((sum, item) => sum + item.props.grow, 0);
|
|
49599
|
+
} else {
|
|
49600
|
+
flexFactorSum = unfrozen.reduce((sum, item) => sum + item.scaledShrinkFactor, 0);
|
|
49601
|
+
}
|
|
49602
|
+
if (flexFactorSum === 0) {
|
|
49603
|
+
unfrozen.forEach((item) => item.frozen = true);
|
|
49604
|
+
break;
|
|
49605
|
+
}
|
|
49606
|
+
const usedSpace = items.reduce((sum, item) => sum + item.mainSize, 0);
|
|
49607
|
+
const containerMainSize = usedSpace + freeSpace;
|
|
49608
|
+
freeSpace = containerMainSize - usedSpace;
|
|
49609
|
+
let totalViolation = 0;
|
|
49610
|
+
for (const item of unfrozen) {
|
|
49611
|
+
let flexFraction;
|
|
49612
|
+
if (isGrowing) {
|
|
49613
|
+
flexFraction = item.props.grow / flexFactorSum;
|
|
49614
|
+
} else {
|
|
49615
|
+
flexFraction = item.scaledShrinkFactor / flexFactorSum;
|
|
49616
|
+
}
|
|
49617
|
+
const deltaSize = freeSpace * flexFraction;
|
|
49618
|
+
const targetSize = item.flexBasis + deltaSize;
|
|
49619
|
+
const clampedSize = clamp(targetSize, item.props.minSize, item.props.maxSize);
|
|
49620
|
+
const violation = clampedSize - targetSize;
|
|
49621
|
+
item.mainSize = clampedSize;
|
|
49622
|
+
totalViolation += violation;
|
|
49623
|
+
if (violation !== 0) {
|
|
49624
|
+
item.frozen = true;
|
|
49625
|
+
}
|
|
49626
|
+
}
|
|
49627
|
+
if (Math.abs(totalViolation) < 0.01) {
|
|
49628
|
+
unfrozen.forEach((item) => item.frozen = true);
|
|
49629
|
+
break;
|
|
49630
|
+
}
|
|
49631
|
+
freeSpace = containerMainSize - items.reduce((sum, item) => sum + item.mainSize, 0);
|
|
49632
|
+
}
|
|
49633
|
+
}
|
|
49634
|
+
function distributeMainAxis(items, containerSize, totalGap, gap, justifyContent) {
|
|
49635
|
+
if (items.length === 0) return;
|
|
49636
|
+
const totalItemSize = items.reduce((sum, item) => sum + item.mainSize, 0);
|
|
49637
|
+
const freeSpace = containerSize - totalItemSize - totalGap;
|
|
49638
|
+
let position = 0;
|
|
49639
|
+
let itemGap = gap;
|
|
49640
|
+
let leadingSpace = 0;
|
|
49641
|
+
switch (justifyContent) {
|
|
49642
|
+
case "flex-start":
|
|
49643
|
+
leadingSpace = 0;
|
|
49644
|
+
break;
|
|
49645
|
+
case "flex-end":
|
|
49646
|
+
leadingSpace = freeSpace;
|
|
49647
|
+
break;
|
|
49648
|
+
case "center":
|
|
49649
|
+
leadingSpace = freeSpace / 2;
|
|
49650
|
+
break;
|
|
49651
|
+
case "space-between":
|
|
49652
|
+
leadingSpace = 0;
|
|
49653
|
+
if (items.length > 1) {
|
|
49654
|
+
itemGap = gap + freeSpace / (items.length - 1);
|
|
49655
|
+
}
|
|
49656
|
+
break;
|
|
49657
|
+
case "space-around":
|
|
49658
|
+
if (items.length > 0) {
|
|
49659
|
+
const spacePerItem = freeSpace / items.length;
|
|
49660
|
+
leadingSpace = spacePerItem / 2;
|
|
49661
|
+
itemGap = gap + spacePerItem;
|
|
49662
|
+
}
|
|
49663
|
+
break;
|
|
49664
|
+
case "space-evenly":
|
|
49665
|
+
if (items.length > 0) {
|
|
49666
|
+
const totalSpaces = items.length + 1;
|
|
49667
|
+
const spaceSize = freeSpace / totalSpaces;
|
|
49668
|
+
leadingSpace = spaceSize;
|
|
49669
|
+
itemGap = gap + spaceSize;
|
|
49670
|
+
}
|
|
49671
|
+
break;
|
|
49672
|
+
}
|
|
49673
|
+
position = leadingSpace;
|
|
49674
|
+
for (let i = 0; i < items.length; i++) {
|
|
49675
|
+
items[i].mainPosition = position;
|
|
49676
|
+
position += items[i].mainSize;
|
|
49677
|
+
if (i < items.length - 1) {
|
|
49678
|
+
position += itemGap;
|
|
49679
|
+
}
|
|
49680
|
+
}
|
|
49681
|
+
}
|
|
49682
|
+
function alignCrossAxis(items, containerCrossSize, alignItems) {
|
|
49683
|
+
for (const item of items) {
|
|
49684
|
+
const align = item.props.alignSelf ?? alignItems;
|
|
49685
|
+
const itemCrossSize = item.props.contentSize;
|
|
49686
|
+
item.crossSize = align === "stretch" ? containerCrossSize : itemCrossSize;
|
|
49687
|
+
switch (align) {
|
|
49688
|
+
case "flex-start":
|
|
49689
|
+
item.crossPosition = 0;
|
|
49690
|
+
break;
|
|
49691
|
+
case "flex-end":
|
|
49692
|
+
item.crossPosition = containerCrossSize - item.crossSize;
|
|
49693
|
+
break;
|
|
49694
|
+
case "center":
|
|
49695
|
+
item.crossPosition = (containerCrossSize - item.crossSize) / 2;
|
|
49696
|
+
break;
|
|
49697
|
+
case "stretch":
|
|
49698
|
+
item.crossPosition = 0;
|
|
49699
|
+
item.crossSize = containerCrossSize;
|
|
49700
|
+
break;
|
|
49701
|
+
case "baseline":
|
|
49702
|
+
item.crossPosition = 0;
|
|
49703
|
+
break;
|
|
49704
|
+
}
|
|
49705
|
+
}
|
|
49706
|
+
}
|
|
49707
|
+
function clamp(value, min, max) {
|
|
49708
|
+
return Math.max(min, Math.min(max, value));
|
|
49709
|
+
}
|
|
49710
|
+
function toJustifyContent(justify) {
|
|
49711
|
+
switch (justify) {
|
|
49712
|
+
case "start":
|
|
49713
|
+
return "flex-start";
|
|
49714
|
+
case "end":
|
|
49715
|
+
return "flex-end";
|
|
49716
|
+
case "center":
|
|
49717
|
+
return "center";
|
|
49718
|
+
case "between":
|
|
49719
|
+
return "space-between";
|
|
49720
|
+
case "around":
|
|
49721
|
+
return "space-around";
|
|
49722
|
+
case "evenly":
|
|
49723
|
+
return "space-evenly";
|
|
49724
|
+
default:
|
|
49725
|
+
return "flex-start";
|
|
49726
|
+
}
|
|
49727
|
+
}
|
|
49728
|
+
function toAlignItems(align) {
|
|
49729
|
+
switch (align) {
|
|
49730
|
+
case "start":
|
|
49731
|
+
return "flex-start";
|
|
49732
|
+
case "end":
|
|
49733
|
+
return "flex-end";
|
|
49734
|
+
case "center":
|
|
49735
|
+
return "center";
|
|
49736
|
+
case "stretch":
|
|
49737
|
+
return "stretch";
|
|
49738
|
+
case "baseline":
|
|
49739
|
+
return "baseline";
|
|
49740
|
+
default:
|
|
49741
|
+
return "stretch";
|
|
49742
|
+
}
|
|
49743
|
+
}
|
|
49744
|
+
function createFlexItemProps(contentSize, options = {}) {
|
|
49745
|
+
return {
|
|
49746
|
+
basis: options.basis ?? "auto",
|
|
49747
|
+
grow: options.grow ?? 0,
|
|
49748
|
+
shrink: options.shrink ?? 1,
|
|
49749
|
+
minSize: options.minSize ?? 0,
|
|
49750
|
+
maxSize: options.maxSize ?? Infinity,
|
|
49751
|
+
contentSize,
|
|
49752
|
+
alignSelf: options.alignSelf
|
|
49753
|
+
};
|
|
49754
|
+
}
|
|
49755
|
+
function createFlexGrowItemProps(contentSize, options = {}) {
|
|
49756
|
+
return {
|
|
49757
|
+
basis: options.basis ?? 0,
|
|
49758
|
+
grow: options.grow ?? 1,
|
|
49759
|
+
shrink: options.shrink ?? 1,
|
|
49760
|
+
minSize: options.minSize ?? 0,
|
|
49761
|
+
maxSize: options.maxSize ?? Infinity,
|
|
49762
|
+
contentSize,
|
|
49763
|
+
alignSelf: options.alignSelf
|
|
49764
|
+
};
|
|
49765
|
+
}
|
|
49766
|
+
|
|
49368
49767
|
// src/renderer/svg/index.ts
|
|
49369
49768
|
var SvgRenderer = class {
|
|
49370
49769
|
options;
|
|
49371
49770
|
theme;
|
|
49372
|
-
|
|
49373
|
-
|
|
49374
|
-
|
|
49771
|
+
pageWidth = 0;
|
|
49772
|
+
pageHeight = 0;
|
|
49773
|
+
clipPathDefs = [];
|
|
49774
|
+
clipPathCounter = 0;
|
|
49775
|
+
// Default spacing values
|
|
49776
|
+
DEFAULT_GAP = 16;
|
|
49375
49777
|
constructor(options = {}) {
|
|
49376
49778
|
this.options = {
|
|
49377
49779
|
width: options.width ?? 800,
|
|
@@ -49382,28 +49784,41 @@ var SvgRenderer = class {
|
|
|
49382
49784
|
fontFamily: options.fontFamily ?? "system-ui, -apple-system, sans-serif"
|
|
49383
49785
|
};
|
|
49384
49786
|
this.theme = defaultTheme;
|
|
49385
|
-
this.contentWidth = this.options.width - this.options.padding * 2;
|
|
49386
49787
|
}
|
|
49387
49788
|
/**
|
|
49388
49789
|
* Render a wireframe document to SVG
|
|
49389
49790
|
*/
|
|
49390
49791
|
render(doc) {
|
|
49391
|
-
this.
|
|
49392
|
-
this.
|
|
49792
|
+
this.clipPathDefs = [];
|
|
49793
|
+
this.clipPathCounter = 0;
|
|
49393
49794
|
const firstPage = doc.children[0];
|
|
49394
49795
|
let width = this.options.width;
|
|
49395
49796
|
let height = this.options.height;
|
|
49396
|
-
if (firstPage
|
|
49397
|
-
const
|
|
49398
|
-
|
|
49399
|
-
|
|
49400
|
-
|
|
49797
|
+
if (firstPage) {
|
|
49798
|
+
const pageAny = firstPage;
|
|
49799
|
+
const hasExplicitWidth = pageAny.width !== void 0;
|
|
49800
|
+
const hasExplicitHeight = pageAny.height !== void 0;
|
|
49801
|
+
if (hasExplicitWidth || hasExplicitHeight) {
|
|
49802
|
+
if (hasExplicitWidth) {
|
|
49803
|
+
width = pageAny.width;
|
|
49804
|
+
}
|
|
49805
|
+
if (hasExplicitHeight) {
|
|
49806
|
+
height = pageAny.height;
|
|
49807
|
+
}
|
|
49808
|
+
} else if (firstPage.viewport !== void 0 || firstPage.device !== void 0) {
|
|
49809
|
+
const viewport = resolveViewport(firstPage.viewport, firstPage.device);
|
|
49810
|
+
width = viewport.width;
|
|
49811
|
+
height = viewport.height;
|
|
49812
|
+
}
|
|
49401
49813
|
}
|
|
49814
|
+
this.pageWidth = width;
|
|
49815
|
+
this.pageHeight = height;
|
|
49402
49816
|
const content = doc.children.map((page) => this.renderPage(page)).join("\n");
|
|
49817
|
+
const allDefs = this.generateDefs() + "\n" + this.clipPathDefs.join("\n");
|
|
49403
49818
|
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
49404
49819
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
49405
49820
|
<defs>
|
|
49406
|
-
${
|
|
49821
|
+
${allDefs}
|
|
49407
49822
|
</defs>
|
|
49408
49823
|
<rect width="100%" height="100%" fill="${this.options.background}"/>
|
|
49409
49824
|
<g transform="scale(${this.options.scale})">
|
|
@@ -49413,7 +49828,7 @@ var SvgRenderer = class {
|
|
|
49413
49828
|
return { svg, width, height };
|
|
49414
49829
|
}
|
|
49415
49830
|
/**
|
|
49416
|
-
* Generate SVG defs (styles
|
|
49831
|
+
* Generate SVG defs (styles)
|
|
49417
49832
|
*/
|
|
49418
49833
|
generateDefs() {
|
|
49419
49834
|
return `
|
|
@@ -49424,684 +49839,1500 @@ var SvgRenderer = class {
|
|
|
49424
49839
|
</style>
|
|
49425
49840
|
`;
|
|
49426
49841
|
}
|
|
49842
|
+
// ===========================================
|
|
49843
|
+
// Page Layout
|
|
49844
|
+
// ===========================================
|
|
49845
|
+
renderPage(page) {
|
|
49846
|
+
const padding = this.options.padding;
|
|
49847
|
+
const contentWidth = this.pageWidth - padding * 2;
|
|
49848
|
+
const contentHeight = this.pageHeight - padding * 2;
|
|
49849
|
+
const constraints = {
|
|
49850
|
+
maxWidth: contentWidth,
|
|
49851
|
+
maxHeight: contentHeight
|
|
49852
|
+
};
|
|
49853
|
+
const childBoxes = [];
|
|
49854
|
+
let currentY = padding;
|
|
49855
|
+
const isCentered = page.centered === true;
|
|
49856
|
+
const hasHeader = page.children.some((c) => c.type === "Header");
|
|
49857
|
+
const hasFooter = page.children.some((c) => c.type === "Footer");
|
|
49858
|
+
const hasMain = page.children.some((c) => c.type === "Main");
|
|
49859
|
+
if (hasHeader || hasFooter || hasMain) {
|
|
49860
|
+
return this.renderPageWithFixedLayout(page, padding, contentWidth, contentHeight);
|
|
49861
|
+
}
|
|
49862
|
+
const measurements = page.children.map((child) => this.measureNode(child, constraints));
|
|
49863
|
+
const gap = this.getGap(page) || this.DEFAULT_GAP;
|
|
49864
|
+
const totalChildrenHeight = measurements.reduce((sum, m, i) => {
|
|
49865
|
+
return sum + m.height + (i > 0 ? gap : 0);
|
|
49866
|
+
}, 0);
|
|
49867
|
+
if (isCentered) {
|
|
49868
|
+
const availableHeight = contentHeight;
|
|
49869
|
+
currentY = padding + Math.max(0, (availableHeight - totalChildrenHeight) / 2);
|
|
49870
|
+
}
|
|
49871
|
+
for (let i = 0; i < page.children.length; i++) {
|
|
49872
|
+
const child = page.children[i];
|
|
49873
|
+
const measurement = measurements[i];
|
|
49874
|
+
let childX = padding;
|
|
49875
|
+
if (isCentered || this.shouldCenterHorizontally(child)) {
|
|
49876
|
+
childX = padding + (contentWidth - measurement.width) / 2;
|
|
49877
|
+
}
|
|
49878
|
+
const box = this.layoutNode(child, childX, currentY, constraints);
|
|
49879
|
+
childBoxes.push(box);
|
|
49880
|
+
currentY += box.height + gap;
|
|
49881
|
+
}
|
|
49882
|
+
const elements = [];
|
|
49883
|
+
for (const box of childBoxes) {
|
|
49884
|
+
elements.push(this.renderBox(box));
|
|
49885
|
+
}
|
|
49886
|
+
return elements.join("\n");
|
|
49887
|
+
}
|
|
49427
49888
|
/**
|
|
49428
|
-
* Render
|
|
49889
|
+
* Render page with fixed header/footer layout
|
|
49890
|
+
* Header at top, Footer at bottom, Main fills remaining space
|
|
49429
49891
|
*/
|
|
49430
|
-
|
|
49892
|
+
renderPageWithFixedLayout(page, padding, contentWidth, contentHeight) {
|
|
49893
|
+
const constraints = {
|
|
49894
|
+
maxWidth: contentWidth,
|
|
49895
|
+
maxHeight: contentHeight
|
|
49896
|
+
};
|
|
49897
|
+
const header = page.children.find((c) => c.type === "Header");
|
|
49898
|
+
const footer = page.children.find((c) => c.type === "Footer");
|
|
49899
|
+
const otherChildren = page.children.filter((c) => c.type !== "Header" && c.type !== "Footer");
|
|
49431
49900
|
const elements = [];
|
|
49432
|
-
|
|
49433
|
-
|
|
49901
|
+
let headerHeight = 0;
|
|
49902
|
+
let footerHeight = 0;
|
|
49903
|
+
let currentY = padding;
|
|
49904
|
+
if (header) {
|
|
49905
|
+
const headerMeasure = this.measureNode(header, constraints);
|
|
49906
|
+
headerHeight = headerMeasure.height;
|
|
49907
|
+
const headerBox = this.layoutNode(header, padding, currentY, constraints);
|
|
49908
|
+
elements.push(this.renderBox(headerBox));
|
|
49434
49909
|
}
|
|
49435
|
-
|
|
49436
|
-
|
|
49910
|
+
if (footer) {
|
|
49911
|
+
const footerMeasure = this.measureNode(footer, constraints);
|
|
49912
|
+
footerHeight = footerMeasure.height;
|
|
49913
|
+
const footerY = this.pageHeight - padding - footerHeight;
|
|
49914
|
+
const footerBox = this.layoutNode(footer, padding, footerY, constraints);
|
|
49915
|
+
elements.push(this.renderBox(footerBox));
|
|
49916
|
+
}
|
|
49917
|
+
const mainStartY = currentY + headerHeight;
|
|
49918
|
+
const mainEndY = this.pageHeight - padding - footerHeight;
|
|
49919
|
+
const mainHeight = mainEndY - mainStartY;
|
|
49920
|
+
if (otherChildren.length > 0) {
|
|
49921
|
+
const mainConstraints = {
|
|
49922
|
+
maxWidth: contentWidth,
|
|
49923
|
+
maxHeight: mainHeight
|
|
49924
|
+
};
|
|
49925
|
+
const clipId = `main-clip-${this.clipPathCounter++}`;
|
|
49926
|
+
this.clipPathDefs.push(`<clipPath id="${clipId}"><rect x="${padding}" y="${mainStartY}" width="${contentWidth}" height="${mainHeight}"/></clipPath>`);
|
|
49927
|
+
const mainContent = [];
|
|
49928
|
+
mainContent.push(`<g clip-path="url(#${clipId})">`);
|
|
49929
|
+
let childY = mainStartY;
|
|
49930
|
+
const gap = this.getGap(page) || 0;
|
|
49931
|
+
for (const child of otherChildren) {
|
|
49932
|
+
const childBox = this.layoutNode(child, padding, childY, mainConstraints);
|
|
49933
|
+
mainContent.push(this.renderBox(childBox));
|
|
49934
|
+
childY += childBox.height + gap;
|
|
49935
|
+
}
|
|
49936
|
+
mainContent.push(`</g>`);
|
|
49937
|
+
elements.push(mainContent.join("\n"));
|
|
49437
49938
|
}
|
|
49438
49939
|
return elements.join("\n");
|
|
49439
49940
|
}
|
|
49440
|
-
|
|
49441
|
-
|
|
49442
|
-
*/
|
|
49443
|
-
renderPageTitle(title) {
|
|
49444
|
-
const fontSize = 24;
|
|
49445
|
-
const y = this.currentY + fontSize;
|
|
49446
|
-
this.currentY += fontSize + 16;
|
|
49447
|
-
return `<text x="${this.currentX}" y="${y}" font-size="${fontSize}" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(title)}</text>`;
|
|
49941
|
+
shouldCenterHorizontally(node) {
|
|
49942
|
+
return node.type === "Card" || node.type === "Modal";
|
|
49448
49943
|
}
|
|
49449
|
-
|
|
49450
|
-
|
|
49451
|
-
|
|
49452
|
-
|
|
49944
|
+
// ===========================================
|
|
49945
|
+
// Measurement Phase
|
|
49946
|
+
// ===========================================
|
|
49947
|
+
measureNode(node, constraints) {
|
|
49453
49948
|
switch (node.type) {
|
|
49454
|
-
// Layout nodes
|
|
49455
49949
|
case "Row":
|
|
49456
|
-
return this.
|
|
49950
|
+
return this.measureRow(node, constraints);
|
|
49457
49951
|
case "Col":
|
|
49458
|
-
return this.
|
|
49952
|
+
return this.measureCol(node, constraints);
|
|
49459
49953
|
case "Header":
|
|
49460
|
-
return this.
|
|
49461
|
-
case "Main":
|
|
49462
|
-
return this.renderMain(node);
|
|
49954
|
+
return this.measureHeader(node, constraints);
|
|
49463
49955
|
case "Footer":
|
|
49464
|
-
return this.
|
|
49956
|
+
return this.measureFooter(node, constraints);
|
|
49957
|
+
case "Main":
|
|
49958
|
+
return this.measureMain(node, constraints);
|
|
49465
49959
|
case "Sidebar":
|
|
49466
|
-
return this.
|
|
49467
|
-
// Container nodes
|
|
49960
|
+
return this.measureSidebar(node, constraints);
|
|
49468
49961
|
case "Card":
|
|
49469
|
-
return this.
|
|
49962
|
+
return this.measureCard(node, constraints);
|
|
49470
49963
|
case "Modal":
|
|
49471
|
-
return this.
|
|
49472
|
-
// Text nodes
|
|
49473
|
-
case "Text":
|
|
49474
|
-
return this.renderText(node);
|
|
49964
|
+
return this.measureModal(node, constraints);
|
|
49475
49965
|
case "Title":
|
|
49476
|
-
return this.
|
|
49477
|
-
case "
|
|
49478
|
-
return this.
|
|
49479
|
-
|
|
49966
|
+
return this.measureTitle(node);
|
|
49967
|
+
case "Text":
|
|
49968
|
+
return this.measureText(node);
|
|
49969
|
+
case "Button":
|
|
49970
|
+
return this.measureButton(node, constraints);
|
|
49480
49971
|
case "Input":
|
|
49481
|
-
return this.
|
|
49972
|
+
return this.measureInput(node, constraints);
|
|
49482
49973
|
case "Textarea":
|
|
49483
|
-
return this.
|
|
49974
|
+
return this.measureTextarea(node, constraints);
|
|
49484
49975
|
case "Select":
|
|
49485
|
-
return this.
|
|
49976
|
+
return this.measureSelect(node, constraints);
|
|
49486
49977
|
case "Checkbox":
|
|
49487
|
-
return this.
|
|
49978
|
+
return this.measureCheckbox(node);
|
|
49488
49979
|
case "Radio":
|
|
49489
|
-
return this.
|
|
49980
|
+
return this.measureRadio(node);
|
|
49490
49981
|
case "Switch":
|
|
49491
|
-
return this.
|
|
49492
|
-
|
|
49493
|
-
|
|
49494
|
-
return this.renderButton(node);
|
|
49495
|
-
// Display nodes
|
|
49982
|
+
return this.measureSwitch(node);
|
|
49983
|
+
case "Link":
|
|
49984
|
+
return this.measureLink(node);
|
|
49496
49985
|
case "Image":
|
|
49497
|
-
return this.
|
|
49986
|
+
return this.measureImage(node, constraints);
|
|
49498
49987
|
case "Placeholder":
|
|
49499
|
-
return this.
|
|
49988
|
+
return this.measurePlaceholder(node, constraints);
|
|
49500
49989
|
case "Avatar":
|
|
49501
|
-
return this.
|
|
49990
|
+
return this.measureAvatar(node, constraints);
|
|
49502
49991
|
case "Badge":
|
|
49503
|
-
return this.
|
|
49504
|
-
// Data nodes
|
|
49992
|
+
return this.measureBadge(node);
|
|
49505
49993
|
case "Table":
|
|
49506
|
-
return this.
|
|
49994
|
+
return this.measureTable(node);
|
|
49507
49995
|
case "List":
|
|
49508
|
-
return this.
|
|
49509
|
-
// Feedback nodes
|
|
49996
|
+
return this.measureList(node);
|
|
49510
49997
|
case "Alert":
|
|
49511
|
-
return this.
|
|
49998
|
+
return this.measureAlert(node, constraints);
|
|
49512
49999
|
case "Progress":
|
|
49513
|
-
return this.
|
|
50000
|
+
return this.measureProgress(node, constraints);
|
|
49514
50001
|
case "Spinner":
|
|
49515
|
-
return this.
|
|
49516
|
-
// Navigation nodes
|
|
50002
|
+
return this.measureSpinner(node);
|
|
49517
50003
|
case "Nav":
|
|
49518
|
-
return this.
|
|
50004
|
+
return this.measureNav(node);
|
|
49519
50005
|
case "Tabs":
|
|
49520
|
-
return this.
|
|
50006
|
+
return this.measureTabs(node);
|
|
49521
50007
|
case "Breadcrumb":
|
|
49522
|
-
return this.
|
|
50008
|
+
return this.measureBreadcrumb(node);
|
|
50009
|
+
case "Icon":
|
|
50010
|
+
return this.measureIcon(node);
|
|
50011
|
+
case "Divider":
|
|
50012
|
+
return this.measureDivider(node, constraints);
|
|
49523
50013
|
default:
|
|
49524
|
-
return
|
|
50014
|
+
return { width: 100, height: 40 };
|
|
50015
|
+
}
|
|
50016
|
+
}
|
|
50017
|
+
measureRow(node, constraints) {
|
|
50018
|
+
const padding = this.getPadding(node);
|
|
50019
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50020
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50021
|
+
const childMeasurements = node.children.map(
|
|
50022
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50023
|
+
);
|
|
50024
|
+
const maxChildHeight = Math.max(...childMeasurements.map((m) => m.height), 0);
|
|
50025
|
+
const totalChildWidth = childMeasurements.reduce((sum, m) => sum + m.width, 0);
|
|
50026
|
+
const totalGapWidth = Math.max(0, (node.children.length - 1) * gap);
|
|
50027
|
+
const contentWidth = totalChildWidth + totalGapWidth + padding.left + padding.right;
|
|
50028
|
+
const hasExplicitWidth = "w" in node && node.w !== void 0;
|
|
50029
|
+
const width = hasExplicitWidth ? constraints.maxWidth : Math.min(contentWidth, constraints.maxWidth);
|
|
50030
|
+
return {
|
|
50031
|
+
width,
|
|
50032
|
+
height: maxChildHeight + padding.top + padding.bottom
|
|
50033
|
+
};
|
|
50034
|
+
}
|
|
50035
|
+
measureCol(node, constraints) {
|
|
50036
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50037
|
+
const padding = this.getPadding(node);
|
|
50038
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50039
|
+
const childMeasurements = node.children.map(
|
|
50040
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50041
|
+
);
|
|
50042
|
+
const totalHeight = childMeasurements.reduce(
|
|
50043
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50044
|
+
0
|
|
50045
|
+
);
|
|
50046
|
+
const maxChildWidth = Math.max(...childMeasurements.map((m) => m.width), 0);
|
|
50047
|
+
return {
|
|
50048
|
+
width: Math.max(maxChildWidth + padding.left + padding.right, constraints.maxWidth),
|
|
50049
|
+
height: totalHeight + padding.top + padding.bottom
|
|
50050
|
+
};
|
|
50051
|
+
}
|
|
50052
|
+
measureHeader(node, constraints) {
|
|
50053
|
+
const padding = this.getPadding(node);
|
|
50054
|
+
const height = this.resolveSize(node.h) || 56;
|
|
50055
|
+
return {
|
|
50056
|
+
width: constraints.maxWidth,
|
|
50057
|
+
height: height + padding.top + padding.bottom
|
|
50058
|
+
};
|
|
50059
|
+
}
|
|
50060
|
+
measureFooter(node, constraints) {
|
|
50061
|
+
const padding = this.getPadding(node);
|
|
50062
|
+
const height = this.resolveSize(node.h) || 60;
|
|
50063
|
+
return {
|
|
50064
|
+
width: constraints.maxWidth,
|
|
50065
|
+
height: height + padding.top + padding.bottom
|
|
50066
|
+
};
|
|
50067
|
+
}
|
|
50068
|
+
measureMain(node, constraints) {
|
|
50069
|
+
const padding = this.getPadding(node);
|
|
50070
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50071
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50072
|
+
const childMeasurements = node.children.map(
|
|
50073
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50074
|
+
);
|
|
50075
|
+
const totalHeight = childMeasurements.reduce(
|
|
50076
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50077
|
+
0
|
|
50078
|
+
);
|
|
50079
|
+
return {
|
|
50080
|
+
width: constraints.maxWidth,
|
|
50081
|
+
height: totalHeight + padding.top + padding.bottom
|
|
50082
|
+
};
|
|
50083
|
+
}
|
|
50084
|
+
measureSidebar(node, constraints) {
|
|
50085
|
+
const width = this.resolveSize(node.w) || 200;
|
|
50086
|
+
const padding = this.getPadding(node);
|
|
50087
|
+
const gap = this.getGap(node) ?? 8;
|
|
50088
|
+
const innerWidth = width - padding.left - padding.right;
|
|
50089
|
+
const childMeasurements = node.children.map(
|
|
50090
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50091
|
+
);
|
|
50092
|
+
const contentHeight = childMeasurements.reduce(
|
|
50093
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50094
|
+
0
|
|
50095
|
+
) + padding.top + padding.bottom;
|
|
50096
|
+
const explicitHeight = this.resolveSize(node.h);
|
|
50097
|
+
const height = explicitHeight || constraints.maxHeight || contentHeight;
|
|
50098
|
+
return { width, height };
|
|
50099
|
+
}
|
|
50100
|
+
measureCard(node, constraints) {
|
|
50101
|
+
let cardWidth = this.resolveSize(node.w);
|
|
50102
|
+
if (!cardWidth) {
|
|
50103
|
+
cardWidth = Math.min(360, constraints.maxWidth);
|
|
50104
|
+
} else {
|
|
50105
|
+
cardWidth = Math.min(cardWidth, constraints.maxWidth);
|
|
50106
|
+
}
|
|
50107
|
+
const explicitHeight = this.resolveSize(node.h);
|
|
50108
|
+
if (explicitHeight) {
|
|
50109
|
+
return { width: cardWidth, height: explicitHeight };
|
|
50110
|
+
}
|
|
50111
|
+
const padding = this.getPadding(node);
|
|
50112
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50113
|
+
const innerWidth = cardWidth - padding.left - padding.right;
|
|
50114
|
+
const childMeasurements = node.children.map(
|
|
50115
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50116
|
+
);
|
|
50117
|
+
let contentHeight = 0;
|
|
50118
|
+
if (node.title) {
|
|
50119
|
+
contentHeight += 28;
|
|
50120
|
+
}
|
|
50121
|
+
contentHeight += childMeasurements.reduce(
|
|
50122
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50123
|
+
0
|
|
50124
|
+
);
|
|
50125
|
+
return {
|
|
50126
|
+
width: cardWidth,
|
|
50127
|
+
height: contentHeight + padding.top + padding.bottom
|
|
50128
|
+
};
|
|
50129
|
+
}
|
|
50130
|
+
measureModal(node, constraints) {
|
|
50131
|
+
const width = this.resolveSize(node.w) || 400;
|
|
50132
|
+
const padding = this.getPadding(node);
|
|
50133
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50134
|
+
const innerWidth = width - padding.left - padding.right;
|
|
50135
|
+
const childMeasurements = node.children.map(
|
|
50136
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50137
|
+
);
|
|
50138
|
+
let contentHeight = node.title ? 40 : 0;
|
|
50139
|
+
contentHeight += childMeasurements.reduce(
|
|
50140
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50141
|
+
0
|
|
50142
|
+
);
|
|
50143
|
+
return {
|
|
50144
|
+
width,
|
|
50145
|
+
height: contentHeight + padding.top + padding.bottom
|
|
50146
|
+
};
|
|
50147
|
+
}
|
|
50148
|
+
measureTitle(node) {
|
|
50149
|
+
const level = node.level || 1;
|
|
50150
|
+
const fontSize = this.getTitleFontSize(level);
|
|
50151
|
+
const textWidth = this.estimateTextWidth(node.content, fontSize);
|
|
50152
|
+
return { width: textWidth, height: fontSize + 8 };
|
|
50153
|
+
}
|
|
50154
|
+
measureText(node) {
|
|
50155
|
+
const fontSize = this.resolveFontSize(node.size);
|
|
50156
|
+
const textWidth = this.estimateTextWidth(node.content, fontSize);
|
|
50157
|
+
return { width: textWidth, height: fontSize + 8 };
|
|
50158
|
+
}
|
|
50159
|
+
measureButton(node, constraints) {
|
|
50160
|
+
const hasIcon = !!node.icon;
|
|
50161
|
+
const isIconOnly = hasIcon && !node.content.trim();
|
|
50162
|
+
const iconSize = 16;
|
|
50163
|
+
const padding = 16;
|
|
50164
|
+
let width;
|
|
50165
|
+
if (this.isFullWidth(node)) {
|
|
50166
|
+
width = constraints.maxWidth;
|
|
50167
|
+
} else if (isIconOnly) {
|
|
50168
|
+
width = iconSize + padding * 2;
|
|
50169
|
+
} else if (hasIcon) {
|
|
50170
|
+
width = Math.max(80, this.estimateTextWidth(node.content, 14) + iconSize + 48);
|
|
50171
|
+
} else {
|
|
50172
|
+
width = Math.max(80, this.estimateTextWidth(node.content, 14) + 32);
|
|
50173
|
+
}
|
|
50174
|
+
return { width, height: 40 };
|
|
50175
|
+
}
|
|
50176
|
+
measureInput(node, constraints) {
|
|
50177
|
+
let width;
|
|
50178
|
+
if (this.resolveSize(node.w)) {
|
|
50179
|
+
width = this.resolveSize(node.w);
|
|
50180
|
+
} else if (constraints.inHeader) {
|
|
50181
|
+
const placeholderWidth = node.placeholder ? this.estimateTextWidth(node.placeholder, 14) : 60;
|
|
50182
|
+
width = Math.max(120, placeholderWidth + 24 + 100);
|
|
50183
|
+
} else {
|
|
50184
|
+
width = constraints.maxWidth;
|
|
50185
|
+
}
|
|
50186
|
+
let height = 36;
|
|
50187
|
+
if (node.label) height += 24;
|
|
50188
|
+
return { width, height };
|
|
50189
|
+
}
|
|
50190
|
+
measureTextarea(node, constraints) {
|
|
50191
|
+
const width = this.resolveSize(node.w) || constraints.maxWidth;
|
|
50192
|
+
let height = node.rows ? node.rows * 24 : 100;
|
|
50193
|
+
if (node.label) height += 24;
|
|
50194
|
+
return { width, height };
|
|
50195
|
+
}
|
|
50196
|
+
measureSelect(node, constraints) {
|
|
50197
|
+
const width = this.resolveSize(node.w) || constraints.maxWidth;
|
|
50198
|
+
let height = 40;
|
|
50199
|
+
if (node.label) height += 24;
|
|
50200
|
+
return { width, height };
|
|
50201
|
+
}
|
|
50202
|
+
measureCheckbox(node) {
|
|
50203
|
+
const labelWidth = node.label ? this.estimateTextWidth(node.label, 14) + 8 : 0;
|
|
50204
|
+
return { width: 18 + labelWidth, height: 24 };
|
|
50205
|
+
}
|
|
50206
|
+
measureRadio(node) {
|
|
50207
|
+
const labelWidth = node.label ? this.estimateTextWidth(node.label, 14) + 8 : 0;
|
|
50208
|
+
return { width: 18 + labelWidth, height: 24 };
|
|
50209
|
+
}
|
|
50210
|
+
measureSwitch(node) {
|
|
50211
|
+
const labelWidth = node.label ? this.estimateTextWidth(node.label, 14) + 8 : 0;
|
|
50212
|
+
return { width: 44 + labelWidth, height: 28 };
|
|
50213
|
+
}
|
|
50214
|
+
measureLink(node) {
|
|
50215
|
+
const fontSize = 14;
|
|
50216
|
+
const textWidth = this.estimateTextWidth(node.content, fontSize);
|
|
50217
|
+
return { width: textWidth, height: fontSize + 8 };
|
|
50218
|
+
}
|
|
50219
|
+
measureImage(node, constraints) {
|
|
50220
|
+
let width;
|
|
50221
|
+
if (this.isFullWidth(node)) {
|
|
50222
|
+
width = constraints.maxWidth;
|
|
50223
|
+
} else {
|
|
50224
|
+
width = this.resolveSize(node.w) || 200;
|
|
50225
|
+
}
|
|
50226
|
+
const height = this.resolveSize(node.h) || 150;
|
|
50227
|
+
return { width, height };
|
|
50228
|
+
}
|
|
50229
|
+
measurePlaceholder(node, constraints) {
|
|
50230
|
+
let width;
|
|
50231
|
+
if (node.w !== void 0) {
|
|
50232
|
+
width = this.isFullWidth(node) ? constraints.maxWidth : this.resolveSize(node.w) || 200;
|
|
50233
|
+
} else {
|
|
50234
|
+
width = constraints.maxWidth || 200;
|
|
50235
|
+
}
|
|
50236
|
+
const height = this.resolveSize(node.h) || 100;
|
|
50237
|
+
return { width, height };
|
|
50238
|
+
}
|
|
50239
|
+
measureAvatar(node, constraints) {
|
|
50240
|
+
const sizes = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };
|
|
50241
|
+
const defaultSize = constraints?.inHeader ? "sm" : "md";
|
|
50242
|
+
const size = sizes[node.size || defaultSize] || 40;
|
|
50243
|
+
return { width: size, height: size };
|
|
50244
|
+
}
|
|
50245
|
+
measureBadge(node) {
|
|
50246
|
+
const width = Math.max(24, this.estimateTextWidth(node.content, 12) + 16);
|
|
50247
|
+
return { width, height: 22 };
|
|
50248
|
+
}
|
|
50249
|
+
measureTable(node) {
|
|
50250
|
+
const columns = node.columns || [];
|
|
50251
|
+
const rows = node.rows || [];
|
|
50252
|
+
const rowCount = rows.length || 3;
|
|
50253
|
+
const colWidth = 120;
|
|
50254
|
+
const rowHeight = 40;
|
|
50255
|
+
return {
|
|
50256
|
+
width: columns.length * colWidth,
|
|
50257
|
+
height: (rowCount + 1) * rowHeight
|
|
50258
|
+
};
|
|
50259
|
+
}
|
|
50260
|
+
measureList(node) {
|
|
50261
|
+
const items = node.items || [];
|
|
50262
|
+
const maxItemWidth = Math.max(...items.map((item) => {
|
|
50263
|
+
const content = typeof item === "string" ? item : item.content;
|
|
50264
|
+
return this.estimateTextWidth(content, 14);
|
|
50265
|
+
}), 100);
|
|
50266
|
+
return { width: maxItemWidth + 24, height: items.length * 28 };
|
|
50267
|
+
}
|
|
50268
|
+
measureAlert(_node, constraints) {
|
|
50269
|
+
const width = Math.min(400, constraints.maxWidth);
|
|
50270
|
+
return { width, height: 48 };
|
|
50271
|
+
}
|
|
50272
|
+
measureProgress(node, constraints) {
|
|
50273
|
+
const width = Math.min(200, constraints.maxWidth);
|
|
50274
|
+
let height = 8;
|
|
50275
|
+
if (node.label) height += 24;
|
|
50276
|
+
return { width, height };
|
|
50277
|
+
}
|
|
50278
|
+
measureSpinner(node) {
|
|
50279
|
+
const sizes = { xs: 16, sm: 20, md: 24, lg: 32, xl: 40 };
|
|
50280
|
+
const size = sizes[node.size || "md"] || 24;
|
|
50281
|
+
return { width: size, height: size };
|
|
50282
|
+
}
|
|
50283
|
+
measureNav(node) {
|
|
50284
|
+
const items = node.items || [];
|
|
50285
|
+
if (node.vertical) {
|
|
50286
|
+
const maxWidth = Math.max(...items.map((item) => {
|
|
50287
|
+
const label = typeof item === "string" ? item : item.label;
|
|
50288
|
+
return this.estimateTextWidth(label, 14);
|
|
50289
|
+
}), 100);
|
|
50290
|
+
return { width: maxWidth, height: items.length * 32 };
|
|
50291
|
+
} else {
|
|
50292
|
+
const totalWidth = items.reduce((sum, item) => {
|
|
50293
|
+
const label = typeof item === "string" ? item : item.label;
|
|
50294
|
+
return sum + this.estimateTextWidth(label, 14) + 24;
|
|
50295
|
+
}, 0);
|
|
50296
|
+
return { width: totalWidth, height: 32 };
|
|
49525
50297
|
}
|
|
49526
50298
|
}
|
|
50299
|
+
measureTabs(node) {
|
|
50300
|
+
const items = node.items || [];
|
|
50301
|
+
const totalWidth = items.reduce((sum, item) => {
|
|
50302
|
+
const label = typeof item === "string" ? item : item;
|
|
50303
|
+
return sum + this.estimateTextWidth(label, 14) + 32;
|
|
50304
|
+
}, 0);
|
|
50305
|
+
return { width: totalWidth, height: 44 };
|
|
50306
|
+
}
|
|
50307
|
+
measureBreadcrumb(node) {
|
|
50308
|
+
const items = node.items || [];
|
|
50309
|
+
const totalWidth = items.reduce((sum, item, idx) => {
|
|
50310
|
+
const label = typeof item === "string" ? item : item.label;
|
|
50311
|
+
return sum + this.estimateTextWidth(label, 14) + (idx < items.length - 1 ? 24 : 0);
|
|
50312
|
+
}, 0);
|
|
50313
|
+
return { width: totalWidth, height: 28 };
|
|
50314
|
+
}
|
|
50315
|
+
measureIcon(node) {
|
|
50316
|
+
const sizes = { xs: 12, sm: 16, md: 20, lg: 24, xl: 32 };
|
|
50317
|
+
const size = sizes[node.size || "md"] || 20;
|
|
50318
|
+
return { width: size, height: size };
|
|
50319
|
+
}
|
|
50320
|
+
measureDivider(_node, constraints) {
|
|
50321
|
+
return { width: constraints.maxWidth, height: 1 };
|
|
50322
|
+
}
|
|
49527
50323
|
// ===========================================
|
|
49528
|
-
// Layout
|
|
50324
|
+
// Layout Phase
|
|
49529
50325
|
// ===========================================
|
|
49530
|
-
|
|
49531
|
-
|
|
49532
|
-
|
|
49533
|
-
|
|
49534
|
-
|
|
49535
|
-
|
|
49536
|
-
|
|
50326
|
+
layoutNode(node, x, y, constraints) {
|
|
50327
|
+
switch (node.type) {
|
|
50328
|
+
case "Row":
|
|
50329
|
+
return this.layoutRow(node, x, y, constraints);
|
|
50330
|
+
case "Col":
|
|
50331
|
+
return this.layoutCol(node, x, y, constraints);
|
|
50332
|
+
case "Header":
|
|
50333
|
+
return this.layoutHeader(node, x, y, constraints);
|
|
50334
|
+
case "Footer":
|
|
50335
|
+
return this.layoutFooter(node, x, y, constraints);
|
|
50336
|
+
case "Main":
|
|
50337
|
+
return this.layoutMain(node, x, y, constraints);
|
|
50338
|
+
case "Sidebar":
|
|
50339
|
+
return this.layoutSidebar(node, x, y, constraints);
|
|
50340
|
+
case "Card":
|
|
50341
|
+
return this.layoutCard(node, x, y, constraints);
|
|
50342
|
+
default:
|
|
50343
|
+
const size = this.measureNode(node, constraints);
|
|
50344
|
+
return {
|
|
50345
|
+
x,
|
|
50346
|
+
y,
|
|
50347
|
+
width: size.width,
|
|
50348
|
+
height: size.height,
|
|
50349
|
+
node,
|
|
50350
|
+
children: []
|
|
50351
|
+
};
|
|
50352
|
+
}
|
|
50353
|
+
}
|
|
50354
|
+
layoutRow(node, x, y, constraints) {
|
|
50355
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50356
|
+
const padding = this.getPadding(node);
|
|
50357
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50358
|
+
const justify = node.justify || "start";
|
|
50359
|
+
const align = node.align || (constraints.inHeader ? "center" : "start");
|
|
50360
|
+
const childMeasurements = node.children.map(
|
|
50361
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50362
|
+
);
|
|
50363
|
+
const flexItems = node.children.map((child, i) => {
|
|
50364
|
+
const measurement = childMeasurements[i];
|
|
50365
|
+
if ("w" in child && child.w !== void 0 && child.w !== "full") {
|
|
50366
|
+
const resolved = this.resolveSize(child.w);
|
|
50367
|
+
if (resolved !== void 0) {
|
|
50368
|
+
return createFlexItemProps(measurement.height, {
|
|
50369
|
+
basis: resolved,
|
|
50370
|
+
grow: 0,
|
|
50371
|
+
shrink: 0,
|
|
50372
|
+
minSize: resolved,
|
|
50373
|
+
maxSize: resolved,
|
|
50374
|
+
contentSize: measurement.width
|
|
50375
|
+
});
|
|
50376
|
+
}
|
|
49537
50377
|
}
|
|
49538
|
-
|
|
49539
|
-
|
|
49540
|
-
|
|
49541
|
-
|
|
50378
|
+
if (this.isFlexContainer(child)) {
|
|
50379
|
+
return createFlexGrowItemProps(measurement.height, {
|
|
50380
|
+
basis: 0,
|
|
50381
|
+
grow: 1,
|
|
50382
|
+
shrink: 1,
|
|
50383
|
+
minSize: 0,
|
|
50384
|
+
maxSize: Infinity,
|
|
50385
|
+
contentSize: measurement.width
|
|
50386
|
+
});
|
|
50387
|
+
}
|
|
50388
|
+
if (constraints.inHeader && child.type === "Input") {
|
|
50389
|
+
return createFlexGrowItemProps(measurement.height, {
|
|
50390
|
+
basis: measurement.width,
|
|
50391
|
+
// auto = content size
|
|
50392
|
+
grow: 1,
|
|
50393
|
+
shrink: 1,
|
|
50394
|
+
minSize: 120,
|
|
50395
|
+
// min-width: 120px
|
|
50396
|
+
maxSize: Infinity,
|
|
50397
|
+
contentSize: measurement.width
|
|
50398
|
+
});
|
|
50399
|
+
}
|
|
50400
|
+
return createFlexItemProps(measurement.height, {
|
|
50401
|
+
basis: measurement.width,
|
|
50402
|
+
// auto = content size
|
|
50403
|
+
grow: 0,
|
|
50404
|
+
shrink: 1,
|
|
50405
|
+
minSize: 0,
|
|
50406
|
+
maxSize: Infinity,
|
|
50407
|
+
contentSize: measurement.width
|
|
50408
|
+
});
|
|
50409
|
+
});
|
|
50410
|
+
const flexConfig = {
|
|
50411
|
+
mainSize: innerWidth,
|
|
50412
|
+
crossSize: void 0,
|
|
50413
|
+
// Will use max child height
|
|
50414
|
+
direction: "row",
|
|
50415
|
+
justifyContent: toJustifyContent(justify),
|
|
50416
|
+
alignItems: toAlignItems(align),
|
|
50417
|
+
gap
|
|
50418
|
+
};
|
|
50419
|
+
const flexResult = computeFlexLayout(flexItems, flexConfig);
|
|
50420
|
+
const maxChildHeight = Math.max(...childMeasurements.map((m) => m.height), 0);
|
|
50421
|
+
const innerMaxHeight = constraints.maxHeight ? constraints.maxHeight - padding.top - padding.bottom : void 0;
|
|
50422
|
+
const effectiveHeight = innerMaxHeight || maxChildHeight;
|
|
50423
|
+
const rowHeight = effectiveHeight + padding.top + padding.bottom;
|
|
50424
|
+
const children = [];
|
|
50425
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
50426
|
+
const child = node.children[i];
|
|
50427
|
+
const flexItem = flexResult.items[i];
|
|
50428
|
+
const measurement = childMeasurements[i];
|
|
50429
|
+
const childX = x + padding.left + flexItem.mainPosition;
|
|
50430
|
+
let childY = y + padding.top;
|
|
50431
|
+
switch (align) {
|
|
50432
|
+
case "center":
|
|
50433
|
+
childY = y + padding.top + (maxChildHeight - measurement.height) / 2;
|
|
50434
|
+
break;
|
|
50435
|
+
case "end":
|
|
50436
|
+
childY = y + padding.top + (maxChildHeight - measurement.height);
|
|
50437
|
+
break;
|
|
50438
|
+
}
|
|
50439
|
+
const childBox = this.layoutNode(child, childX, childY, {
|
|
50440
|
+
...constraints,
|
|
50441
|
+
maxWidth: flexItem.mainSize,
|
|
50442
|
+
maxHeight: effectiveHeight
|
|
50443
|
+
});
|
|
50444
|
+
children.push(childBox);
|
|
50445
|
+
}
|
|
50446
|
+
return {
|
|
50447
|
+
x,
|
|
50448
|
+
y,
|
|
50449
|
+
width: constraints.maxWidth,
|
|
50450
|
+
height: rowHeight,
|
|
50451
|
+
node,
|
|
50452
|
+
children,
|
|
50453
|
+
padding
|
|
50454
|
+
};
|
|
50455
|
+
}
|
|
50456
|
+
layoutCol(node, x, y, constraints) {
|
|
50457
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50458
|
+
const padding = this.getPadding(node);
|
|
50459
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50460
|
+
const innerHeight = constraints.maxHeight ? constraints.maxHeight - padding.top - padding.bottom : void 0;
|
|
50461
|
+
const align = node.align || "stretch";
|
|
50462
|
+
const childMeasurements = node.children.map(
|
|
50463
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50464
|
+
);
|
|
50465
|
+
const flexItems = node.children.map((child, i) => {
|
|
50466
|
+
const measurement = childMeasurements[i];
|
|
50467
|
+
if ("h" in child && child.h !== void 0) {
|
|
50468
|
+
const resolved = this.resolveSize(child.h);
|
|
50469
|
+
if (resolved !== void 0) {
|
|
50470
|
+
return createFlexItemProps(measurement.width, {
|
|
50471
|
+
basis: resolved,
|
|
50472
|
+
grow: 0,
|
|
50473
|
+
shrink: 0,
|
|
50474
|
+
minSize: resolved,
|
|
50475
|
+
maxSize: resolved,
|
|
50476
|
+
contentSize: measurement.height
|
|
50477
|
+
});
|
|
50478
|
+
}
|
|
50479
|
+
}
|
|
50480
|
+
if (child.type === "Row" && this.rowContainsSidebarOrMain(child)) {
|
|
50481
|
+
return createFlexGrowItemProps(measurement.width, {
|
|
50482
|
+
basis: 0,
|
|
50483
|
+
grow: 1,
|
|
50484
|
+
shrink: 1,
|
|
50485
|
+
minSize: 0,
|
|
50486
|
+
maxSize: Infinity,
|
|
50487
|
+
contentSize: measurement.height
|
|
50488
|
+
});
|
|
50489
|
+
}
|
|
50490
|
+
return createFlexItemProps(measurement.width, {
|
|
50491
|
+
basis: measurement.height,
|
|
50492
|
+
// auto = content size
|
|
50493
|
+
grow: 0,
|
|
50494
|
+
shrink: 1,
|
|
50495
|
+
minSize: 0,
|
|
50496
|
+
maxSize: Infinity,
|
|
50497
|
+
contentSize: measurement.height
|
|
50498
|
+
});
|
|
50499
|
+
});
|
|
50500
|
+
const flexConfig = {
|
|
50501
|
+
mainSize: innerHeight ?? childMeasurements.reduce(
|
|
50502
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50503
|
+
0
|
|
50504
|
+
),
|
|
50505
|
+
crossSize: innerWidth,
|
|
50506
|
+
direction: "column",
|
|
50507
|
+
justifyContent: "flex-start",
|
|
50508
|
+
alignItems: toAlignItems(align),
|
|
50509
|
+
gap
|
|
50510
|
+
};
|
|
50511
|
+
const flexResult = computeFlexLayout(flexItems, flexConfig);
|
|
50512
|
+
const children = [];
|
|
50513
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
50514
|
+
const child = node.children[i];
|
|
50515
|
+
const flexItem = flexResult.items[i];
|
|
50516
|
+
const measurement = childMeasurements[i];
|
|
50517
|
+
let childX = x + padding.left;
|
|
50518
|
+
let childWidth = align === "stretch" ? innerWidth : measurement.width;
|
|
50519
|
+
switch (align) {
|
|
50520
|
+
case "center":
|
|
50521
|
+
childX = x + padding.left + (innerWidth - measurement.width) / 2;
|
|
50522
|
+
break;
|
|
50523
|
+
case "end":
|
|
50524
|
+
childX = x + padding.left + (innerWidth - measurement.width);
|
|
50525
|
+
break;
|
|
50526
|
+
}
|
|
50527
|
+
const childY = y + padding.top + flexItem.mainPosition;
|
|
50528
|
+
const childBox = this.layoutNode(child, childX, childY, {
|
|
50529
|
+
...constraints,
|
|
50530
|
+
maxWidth: childWidth,
|
|
50531
|
+
maxHeight: flexItem.mainSize
|
|
50532
|
+
});
|
|
50533
|
+
children.push(childBox);
|
|
50534
|
+
}
|
|
50535
|
+
const totalHeight = flexResult.mainSizeUsed;
|
|
50536
|
+
return {
|
|
50537
|
+
x,
|
|
50538
|
+
y,
|
|
50539
|
+
width: constraints.maxWidth,
|
|
50540
|
+
height: totalHeight + padding.top + padding.bottom,
|
|
50541
|
+
node,
|
|
50542
|
+
children,
|
|
50543
|
+
padding
|
|
50544
|
+
};
|
|
50545
|
+
}
|
|
50546
|
+
rowContainsSidebarOrMain(row) {
|
|
50547
|
+
return row.children.some(
|
|
50548
|
+
(child) => child.type === "Sidebar" || child.type === "Main"
|
|
50549
|
+
);
|
|
50550
|
+
}
|
|
50551
|
+
layoutHeader(node, x, y, constraints) {
|
|
50552
|
+
const padding = this.getPadding(node);
|
|
50553
|
+
const height = this.resolveSize(node.h) || 56;
|
|
50554
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50555
|
+
const children = [];
|
|
50556
|
+
let currentY = y + padding.top;
|
|
49542
50557
|
for (const child of node.children) {
|
|
49543
|
-
const
|
|
49544
|
-
|
|
49545
|
-
|
|
49546
|
-
|
|
49547
|
-
|
|
49548
|
-
|
|
49549
|
-
|
|
49550
|
-
|
|
50558
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50559
|
+
...constraints,
|
|
50560
|
+
maxWidth: innerWidth,
|
|
50561
|
+
maxHeight: void 0,
|
|
50562
|
+
// Children use natural height
|
|
50563
|
+
inHeader: true
|
|
50564
|
+
// Pass header context for child sizing (e.g., smaller avatars)
|
|
50565
|
+
});
|
|
50566
|
+
const verticalOffset = (height - childBox.height) / 2;
|
|
50567
|
+
childBox.y = y + padding.top + verticalOffset;
|
|
50568
|
+
this.adjustChildrenY(childBox, verticalOffset);
|
|
50569
|
+
children.push(childBox);
|
|
50570
|
+
currentY += childBox.height;
|
|
49551
50571
|
}
|
|
49552
|
-
|
|
49553
|
-
|
|
49554
|
-
|
|
50572
|
+
return {
|
|
50573
|
+
x,
|
|
50574
|
+
y,
|
|
50575
|
+
width: constraints.maxWidth,
|
|
50576
|
+
height: height + padding.top + padding.bottom,
|
|
50577
|
+
node,
|
|
50578
|
+
children,
|
|
50579
|
+
padding
|
|
50580
|
+
};
|
|
49555
50581
|
}
|
|
49556
|
-
|
|
49557
|
-
|
|
50582
|
+
adjustChildrenY(box, offset) {
|
|
50583
|
+
for (const child of box.children) {
|
|
50584
|
+
child.y += offset;
|
|
50585
|
+
this.adjustChildrenY(child, offset);
|
|
50586
|
+
}
|
|
49558
50587
|
}
|
|
49559
|
-
|
|
49560
|
-
|
|
49561
|
-
|
|
49562
|
-
|
|
49563
|
-
|
|
49564
|
-
|
|
49565
|
-
|
|
49566
|
-
|
|
49567
|
-
return `
|
|
49568
|
-
<g transform="translate(${x}, ${y})">
|
|
49569
|
-
<rect width="${this.contentWidth}" height="${height}" fill="${this.theme.colors.background}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49570
|
-
${children}
|
|
49571
|
-
</g>`;
|
|
50588
|
+
/**
|
|
50589
|
+
* Check if a node should flex to fill available space in a Row
|
|
50590
|
+
* Only layout containers without explicit width should flex
|
|
50591
|
+
*/
|
|
50592
|
+
isFlexContainer(node) {
|
|
50593
|
+
if (node.type === "Main") return true;
|
|
50594
|
+
if (node.type === "Col" && !("w" in node && node.w !== void 0)) return true;
|
|
50595
|
+
return false;
|
|
49572
50596
|
}
|
|
49573
|
-
|
|
49574
|
-
|
|
50597
|
+
layoutFooter(node, x, y, constraints) {
|
|
50598
|
+
const padding = this.getPadding(node);
|
|
50599
|
+
const height = this.resolveSize(node.h) || 60;
|
|
50600
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50601
|
+
const children = [];
|
|
50602
|
+
let currentY = y + padding.top;
|
|
50603
|
+
for (const child of node.children) {
|
|
50604
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50605
|
+
...constraints,
|
|
50606
|
+
maxWidth: innerWidth,
|
|
50607
|
+
maxHeight: void 0
|
|
50608
|
+
// Children use natural height
|
|
50609
|
+
});
|
|
50610
|
+
const verticalOffset = (height - childBox.height) / 2;
|
|
50611
|
+
childBox.y = y + padding.top + verticalOffset;
|
|
50612
|
+
this.adjustChildrenY(childBox, verticalOffset);
|
|
50613
|
+
children.push(childBox);
|
|
50614
|
+
currentY += childBox.height;
|
|
50615
|
+
}
|
|
50616
|
+
return {
|
|
50617
|
+
x,
|
|
50618
|
+
y,
|
|
50619
|
+
width: constraints.maxWidth,
|
|
50620
|
+
height: height + padding.top + padding.bottom,
|
|
50621
|
+
node,
|
|
50622
|
+
children,
|
|
50623
|
+
padding
|
|
50624
|
+
};
|
|
49575
50625
|
}
|
|
49576
|
-
|
|
49577
|
-
const
|
|
49578
|
-
const
|
|
49579
|
-
const
|
|
49580
|
-
const
|
|
49581
|
-
|
|
49582
|
-
|
|
49583
|
-
|
|
49584
|
-
|
|
49585
|
-
|
|
49586
|
-
|
|
49587
|
-
|
|
49588
|
-
|
|
50626
|
+
layoutMain(node, x, y, constraints) {
|
|
50627
|
+
const padding = this.getPadding(node);
|
|
50628
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50629
|
+
const innerWidth = constraints.maxWidth - padding.left - padding.right;
|
|
50630
|
+
const childMeasurements = node.children.map(
|
|
50631
|
+
(child) => this.measureNode(child, { ...constraints, maxWidth: innerWidth })
|
|
50632
|
+
);
|
|
50633
|
+
const children = [];
|
|
50634
|
+
let currentY = y + padding.top;
|
|
50635
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
50636
|
+
const child = node.children[i];
|
|
50637
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50638
|
+
...constraints,
|
|
50639
|
+
maxWidth: innerWidth
|
|
50640
|
+
});
|
|
50641
|
+
children.push(childBox);
|
|
50642
|
+
currentY += childBox.height + gap;
|
|
50643
|
+
}
|
|
50644
|
+
const totalHeight = childMeasurements.reduce(
|
|
50645
|
+
(sum, m, i) => sum + m.height + (i > 0 ? gap : 0),
|
|
50646
|
+
0
|
|
50647
|
+
);
|
|
50648
|
+
return {
|
|
50649
|
+
x,
|
|
50650
|
+
y,
|
|
50651
|
+
width: constraints.maxWidth,
|
|
50652
|
+
height: totalHeight + padding.top + padding.bottom,
|
|
50653
|
+
node,
|
|
50654
|
+
children,
|
|
50655
|
+
padding
|
|
50656
|
+
};
|
|
49589
50657
|
}
|
|
49590
|
-
|
|
49591
|
-
const width = 200;
|
|
49592
|
-
const
|
|
49593
|
-
const
|
|
49594
|
-
const
|
|
49595
|
-
const
|
|
49596
|
-
|
|
49597
|
-
const
|
|
49598
|
-
|
|
49599
|
-
|
|
49600
|
-
|
|
49601
|
-
|
|
49602
|
-
|
|
49603
|
-
|
|
50658
|
+
layoutSidebar(node, x, y, constraints) {
|
|
50659
|
+
const width = this.resolveSize(node.w) || 200;
|
|
50660
|
+
const padding = this.getPadding(node);
|
|
50661
|
+
const gap = this.getGap(node) ?? 8;
|
|
50662
|
+
const innerWidth = width - padding.left - padding.right;
|
|
50663
|
+
const children = [];
|
|
50664
|
+
let currentY = y + padding.top;
|
|
50665
|
+
for (const child of node.children) {
|
|
50666
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50667
|
+
...constraints,
|
|
50668
|
+
maxWidth: innerWidth
|
|
50669
|
+
});
|
|
50670
|
+
children.push(childBox);
|
|
50671
|
+
currentY += childBox.height + gap;
|
|
50672
|
+
}
|
|
50673
|
+
const contentHeight = currentY - y - padding.top - gap + padding.bottom;
|
|
50674
|
+
const height = constraints.maxHeight || Math.max(contentHeight, 200);
|
|
50675
|
+
return {
|
|
50676
|
+
x,
|
|
50677
|
+
y,
|
|
50678
|
+
width,
|
|
50679
|
+
height,
|
|
50680
|
+
node,
|
|
50681
|
+
children,
|
|
50682
|
+
padding
|
|
50683
|
+
};
|
|
50684
|
+
}
|
|
50685
|
+
layoutCard(node, x, y, constraints) {
|
|
50686
|
+
let cardWidth = this.resolveSize(node.w);
|
|
50687
|
+
if (!cardWidth) {
|
|
50688
|
+
cardWidth = Math.min(360, constraints.maxWidth);
|
|
50689
|
+
}
|
|
50690
|
+
const padding = this.getPadding(node);
|
|
50691
|
+
const gap = this.getGap(node) ?? this.DEFAULT_GAP;
|
|
50692
|
+
const innerWidth = cardWidth - padding.left - padding.right;
|
|
50693
|
+
const children = [];
|
|
50694
|
+
let currentY = y + padding.top;
|
|
50695
|
+
if (node.title) {
|
|
50696
|
+
currentY += 28;
|
|
50697
|
+
}
|
|
50698
|
+
for (const child of node.children) {
|
|
50699
|
+
const childBox = this.layoutNode(child, x + padding.left, currentY, {
|
|
50700
|
+
...constraints,
|
|
50701
|
+
maxWidth: innerWidth
|
|
50702
|
+
});
|
|
50703
|
+
children.push(childBox);
|
|
50704
|
+
currentY += childBox.height + gap;
|
|
50705
|
+
}
|
|
50706
|
+
const totalHeight = currentY - y - gap + padding.bottom;
|
|
50707
|
+
return {
|
|
50708
|
+
x,
|
|
50709
|
+
y,
|
|
50710
|
+
width: cardWidth,
|
|
50711
|
+
height: totalHeight,
|
|
50712
|
+
node,
|
|
50713
|
+
children,
|
|
50714
|
+
padding
|
|
50715
|
+
};
|
|
49604
50716
|
}
|
|
49605
50717
|
// ===========================================
|
|
49606
|
-
//
|
|
50718
|
+
// Render Phase
|
|
49607
50719
|
// ===========================================
|
|
49608
|
-
|
|
49609
|
-
|
|
49610
|
-
|
|
49611
|
-
|
|
49612
|
-
|
|
49613
|
-
|
|
49614
|
-
|
|
50720
|
+
renderBox(box) {
|
|
50721
|
+
if (!box.node) return "";
|
|
50722
|
+
switch (box.node.type) {
|
|
50723
|
+
case "Row":
|
|
50724
|
+
return this.renderRowBox(box);
|
|
50725
|
+
case "Col":
|
|
50726
|
+
return this.renderColBox(box);
|
|
50727
|
+
case "Header":
|
|
50728
|
+
return this.renderHeaderBox(box);
|
|
50729
|
+
case "Footer":
|
|
50730
|
+
return this.renderFooterBox(box);
|
|
50731
|
+
case "Main":
|
|
50732
|
+
return this.renderMainBox(box);
|
|
50733
|
+
case "Sidebar":
|
|
50734
|
+
return this.renderSidebarBox(box);
|
|
50735
|
+
case "Card":
|
|
50736
|
+
return this.renderCardBox(box);
|
|
50737
|
+
case "Modal":
|
|
50738
|
+
return this.renderModalBox(box);
|
|
50739
|
+
case "Title":
|
|
50740
|
+
return this.renderTitleBox(box);
|
|
50741
|
+
case "Text":
|
|
50742
|
+
return this.renderTextBox(box);
|
|
50743
|
+
case "Button":
|
|
50744
|
+
return this.renderButtonBox(box);
|
|
50745
|
+
case "Input":
|
|
50746
|
+
return this.renderInputBox(box);
|
|
50747
|
+
case "Textarea":
|
|
50748
|
+
return this.renderTextareaBox(box);
|
|
50749
|
+
case "Select":
|
|
50750
|
+
return this.renderSelectBox(box);
|
|
50751
|
+
case "Checkbox":
|
|
50752
|
+
return this.renderCheckboxBox(box);
|
|
50753
|
+
case "Radio":
|
|
50754
|
+
return this.renderRadioBox(box);
|
|
50755
|
+
case "Switch":
|
|
50756
|
+
return this.renderSwitchBox(box);
|
|
50757
|
+
case "Link":
|
|
50758
|
+
return this.renderLinkBox(box);
|
|
50759
|
+
case "Image":
|
|
50760
|
+
return this.renderImageBox(box);
|
|
50761
|
+
case "Placeholder":
|
|
50762
|
+
return this.renderPlaceholderBox(box);
|
|
50763
|
+
case "Avatar":
|
|
50764
|
+
return this.renderAvatarBox(box);
|
|
50765
|
+
case "Badge":
|
|
50766
|
+
return this.renderBadgeBox(box);
|
|
50767
|
+
case "Table":
|
|
50768
|
+
return this.renderTableBox(box);
|
|
50769
|
+
case "List":
|
|
50770
|
+
return this.renderListBox(box);
|
|
50771
|
+
case "Alert":
|
|
50772
|
+
return this.renderAlertBox(box);
|
|
50773
|
+
case "Progress":
|
|
50774
|
+
return this.renderProgressBox(box);
|
|
50775
|
+
case "Spinner":
|
|
50776
|
+
return this.renderSpinnerBox(box);
|
|
50777
|
+
case "Nav":
|
|
50778
|
+
return this.renderNavBox(box);
|
|
50779
|
+
case "Tabs":
|
|
50780
|
+
return this.renderTabsBox(box);
|
|
50781
|
+
case "Breadcrumb":
|
|
50782
|
+
return this.renderBreadcrumbBox(box);
|
|
50783
|
+
case "Icon":
|
|
50784
|
+
return this.renderIconBox(box);
|
|
50785
|
+
case "Divider":
|
|
50786
|
+
return this.renderDividerBox(box);
|
|
50787
|
+
default:
|
|
50788
|
+
return `<!-- Unsupported: ${box.node.type} -->`;
|
|
50789
|
+
}
|
|
50790
|
+
}
|
|
50791
|
+
renderRowBox(box) {
|
|
50792
|
+
const childrenSvg = box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50793
|
+
return childrenSvg;
|
|
50794
|
+
}
|
|
50795
|
+
renderColBox(box) {
|
|
50796
|
+
const childrenSvg = box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50797
|
+
return childrenSvg;
|
|
50798
|
+
}
|
|
50799
|
+
renderHeaderBox(box) {
|
|
50800
|
+
const node = box.node;
|
|
50801
|
+
const hasBorder = node.border !== false;
|
|
50802
|
+
let svg = `<g>
|
|
50803
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" fill="${this.theme.colors.background}"/>`;
|
|
50804
|
+
if (hasBorder) {
|
|
50805
|
+
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"/>`;
|
|
50806
|
+
}
|
|
50807
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50808
|
+
svg += `</g>`;
|
|
50809
|
+
return svg;
|
|
50810
|
+
}
|
|
50811
|
+
renderFooterBox(box) {
|
|
50812
|
+
const node = box.node;
|
|
50813
|
+
const hasBorder = node.border !== false;
|
|
50814
|
+
let svg = `<g>
|
|
50815
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" fill="${this.theme.colors.background}"`;
|
|
50816
|
+
if (hasBorder) {
|
|
50817
|
+
svg += ` stroke="${this.theme.colors.border}" stroke-width="1"`;
|
|
50818
|
+
}
|
|
50819
|
+
svg += `/>`;
|
|
50820
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50821
|
+
svg += `</g>`;
|
|
50822
|
+
return svg;
|
|
50823
|
+
}
|
|
50824
|
+
renderMainBox(box) {
|
|
50825
|
+
const childrenSvg = box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50826
|
+
return childrenSvg;
|
|
50827
|
+
}
|
|
50828
|
+
renderSidebarBox(box) {
|
|
50829
|
+
let svg = `<g>
|
|
50830
|
+
<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"/>`;
|
|
50831
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50832
|
+
svg += `</g>`;
|
|
50833
|
+
return svg;
|
|
50834
|
+
}
|
|
50835
|
+
renderCardBox(box) {
|
|
50836
|
+
const node = box.node;
|
|
50837
|
+
const shadowAttr = node.shadow ? 'filter="url(#shadow)"' : "";
|
|
50838
|
+
let svg = `<g>
|
|
50839
|
+
<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}/>`;
|
|
49615
50840
|
if (node.title) {
|
|
49616
|
-
const
|
|
49617
|
-
|
|
49618
|
-
this.currentY += titleFontSize + 8;
|
|
50841
|
+
const padding = this.getPadding(node);
|
|
50842
|
+
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>`;
|
|
49619
50843
|
}
|
|
49620
|
-
|
|
49621
|
-
|
|
49622
|
-
|
|
49623
|
-
this.currentY = savedY + contentHeight + 16;
|
|
49624
|
-
return `
|
|
49625
|
-
<g transform="translate(${x}, ${y})">
|
|
49626
|
-
<rect width="${width}" height="${contentHeight}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49627
|
-
${titleSvg}
|
|
49628
|
-
<g transform="translate(16, ${childStartY})">
|
|
49629
|
-
${children}
|
|
49630
|
-
</g>
|
|
49631
|
-
</g>`;
|
|
50844
|
+
svg += box.children.map((child) => this.renderBox(child)).join("\n");
|
|
50845
|
+
svg += `</g>`;
|
|
50846
|
+
return svg;
|
|
49632
50847
|
}
|
|
49633
|
-
|
|
49634
|
-
const
|
|
49635
|
-
const
|
|
49636
|
-
const
|
|
49637
|
-
|
|
49638
|
-
|
|
50848
|
+
renderModalBox(box) {
|
|
50849
|
+
const node = box.node;
|
|
50850
|
+
const modalX = (this.pageWidth - box.width) / 2;
|
|
50851
|
+
const modalY = (this.pageHeight - box.height) / 2;
|
|
50852
|
+
let svg = `<g>
|
|
50853
|
+
<rect width="${this.pageWidth}" height="${this.pageHeight}" fill="rgba(0,0,0,0.5)"/>
|
|
50854
|
+
<rect x="${modalX}" y="${modalY}" width="${box.width}" height="${box.height}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49639
50855
|
if (node.title) {
|
|
49640
|
-
|
|
50856
|
+
svg += `<text x="${modalX + 20}" y="${modalY + 30}" font-size="18" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(node.title)}</text>`;
|
|
49641
50857
|
}
|
|
49642
|
-
const
|
|
49643
|
-
|
|
49644
|
-
|
|
49645
|
-
|
|
49646
|
-
|
|
49647
|
-
|
|
49648
|
-
this.currentY = savedY;
|
|
49649
|
-
return `
|
|
49650
|
-
<g>
|
|
49651
|
-
<rect width="100%" height="100%" fill="rgba(0,0,0,0.5)" opacity="0.5"/>
|
|
49652
|
-
<g transform="translate(${x}, ${y})">
|
|
49653
|
-
<rect width="${width}" height="${height}" rx="8" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49654
|
-
${titleSvg}
|
|
49655
|
-
${children}
|
|
49656
|
-
</g>
|
|
49657
|
-
</g>`;
|
|
50858
|
+
for (const child of box.children) {
|
|
50859
|
+
const adjustedChild = { ...child, x: modalX + (child.x - box.x), y: modalY + (child.y - box.y) };
|
|
50860
|
+
svg += this.renderBox(adjustedChild);
|
|
50861
|
+
}
|
|
50862
|
+
svg += `</g>`;
|
|
50863
|
+
return svg;
|
|
49658
50864
|
}
|
|
49659
|
-
|
|
49660
|
-
|
|
49661
|
-
|
|
49662
|
-
|
|
50865
|
+
renderTitleBox(box) {
|
|
50866
|
+
const node = box.node;
|
|
50867
|
+
const level = node.level || 1;
|
|
50868
|
+
const fontSize = this.getTitleFontSize(level);
|
|
50869
|
+
const textY = box.y + fontSize;
|
|
50870
|
+
let textX = box.x;
|
|
50871
|
+
let anchor = "start";
|
|
50872
|
+
if (node.align === "center") {
|
|
50873
|
+
textX = box.x + box.width / 2;
|
|
50874
|
+
anchor = "middle";
|
|
50875
|
+
} else if (node.align === "right") {
|
|
50876
|
+
textX = box.x + box.width;
|
|
50877
|
+
anchor = "end";
|
|
50878
|
+
}
|
|
50879
|
+
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>`;
|
|
50880
|
+
}
|
|
50881
|
+
renderTextBox(box) {
|
|
50882
|
+
const node = box.node;
|
|
49663
50883
|
const fontSize = this.resolveFontSize(node.size);
|
|
49664
50884
|
const fill = node.muted ? this.theme.colors.muted : this.theme.colors.foreground;
|
|
49665
50885
|
const fontWeight = node.weight || "normal";
|
|
49666
|
-
const
|
|
49667
|
-
|
|
49668
|
-
|
|
49669
|
-
|
|
49670
|
-
|
|
49671
|
-
|
|
49672
|
-
|
|
49673
|
-
|
|
49674
|
-
|
|
49675
|
-
|
|
50886
|
+
const textY = box.y + fontSize;
|
|
50887
|
+
let textX = box.x;
|
|
50888
|
+
let anchor = "start";
|
|
50889
|
+
if (node.align === "center") {
|
|
50890
|
+
textX = box.x + box.width / 2;
|
|
50891
|
+
anchor = "middle";
|
|
50892
|
+
} else if (node.align === "right") {
|
|
50893
|
+
textX = box.x + box.width;
|
|
50894
|
+
anchor = "end";
|
|
50895
|
+
}
|
|
50896
|
+
return `<text x="${textX}" y="${textY}" font-size="${fontSize}" font-weight="${fontWeight}" fill="${fill}" text-anchor="${anchor}">${this.escapeXml(node.content)}</text>`;
|
|
49676
50897
|
}
|
|
49677
|
-
|
|
49678
|
-
const
|
|
49679
|
-
const
|
|
49680
|
-
|
|
49681
|
-
|
|
50898
|
+
renderButtonBox(box) {
|
|
50899
|
+
const node = box.node;
|
|
50900
|
+
const hasIcon = !!node.icon;
|
|
50901
|
+
const isIconOnly = hasIcon && !node.content.trim();
|
|
50902
|
+
let fill = this.theme.colors.primary;
|
|
50903
|
+
let textFill = "#ffffff";
|
|
50904
|
+
let strokeAttr = "";
|
|
50905
|
+
if (node.secondary) {
|
|
50906
|
+
fill = this.theme.colors.secondary;
|
|
50907
|
+
} else if (node.outline) {
|
|
50908
|
+
fill = "white";
|
|
50909
|
+
textFill = this.theme.colors.foreground;
|
|
50910
|
+
strokeAttr = `stroke="${this.theme.colors.border}" stroke-width="1"`;
|
|
50911
|
+
} else if (node.ghost) {
|
|
50912
|
+
fill = "transparent";
|
|
50913
|
+
textFill = this.theme.colors.foreground;
|
|
50914
|
+
}
|
|
50915
|
+
let svg = `<g>
|
|
50916
|
+
<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="4" fill="${fill}" ${strokeAttr}/>`;
|
|
50917
|
+
if (hasIcon) {
|
|
50918
|
+
const iconData = getIconData(node.icon);
|
|
50919
|
+
if (iconData) {
|
|
50920
|
+
const iconSize = 16;
|
|
50921
|
+
const iconX = isIconOnly ? box.x + (box.width - iconSize) / 2 : box.x + 16;
|
|
50922
|
+
const iconY = box.y + (box.height - iconSize) / 2;
|
|
50923
|
+
svg += this.renderIconPaths(iconData, iconX, iconY, iconSize, textFill);
|
|
50924
|
+
}
|
|
50925
|
+
}
|
|
50926
|
+
if (!isIconOnly) {
|
|
50927
|
+
const textX = hasIcon ? box.x + 36 + (box.width - 52) / 2 : box.x + box.width / 2;
|
|
50928
|
+
svg += `<text x="${textX}" y="${box.y + box.height / 2 + 5}" font-size="14" fill="${textFill}" text-anchor="middle">${this.escapeXml(node.content)}</text>`;
|
|
50929
|
+
}
|
|
50930
|
+
svg += `</g>`;
|
|
50931
|
+
return svg;
|
|
49682
50932
|
}
|
|
49683
|
-
|
|
49684
|
-
|
|
49685
|
-
|
|
49686
|
-
|
|
49687
|
-
const width = 280;
|
|
49688
|
-
const height = 40;
|
|
49689
|
-
const x = this.currentX;
|
|
49690
|
-
let y = this.currentY;
|
|
49691
|
-
let result = "";
|
|
50933
|
+
renderInputBox(box) {
|
|
50934
|
+
const node = box.node;
|
|
50935
|
+
let y = box.y;
|
|
50936
|
+
let svg = "<g>";
|
|
49692
50937
|
if (node.label) {
|
|
49693
|
-
|
|
50938
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49694
50939
|
y += 24;
|
|
49695
50940
|
}
|
|
50941
|
+
const inputHeight = 36;
|
|
49696
50942
|
const placeholder = node.placeholder || "";
|
|
49697
|
-
|
|
49698
|
-
|
|
49699
|
-
|
|
49700
|
-
|
|
49701
|
-
</g>`;
|
|
49702
|
-
this.currentY = y + height + 12;
|
|
49703
|
-
return result;
|
|
50943
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${inputHeight}" rx="4" fill="#f4f4f5"/>`;
|
|
50944
|
+
svg += `<text x="${box.x + 12}" y="${y + inputHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>`;
|
|
50945
|
+
svg += "</g>";
|
|
50946
|
+
return svg;
|
|
49704
50947
|
}
|
|
49705
|
-
|
|
49706
|
-
const
|
|
49707
|
-
|
|
49708
|
-
|
|
49709
|
-
let y = this.currentY;
|
|
49710
|
-
let result = "";
|
|
50948
|
+
renderTextareaBox(box) {
|
|
50949
|
+
const node = box.node;
|
|
50950
|
+
let y = box.y;
|
|
50951
|
+
let svg = "<g>";
|
|
49711
50952
|
if (node.label) {
|
|
49712
|
-
|
|
50953
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49713
50954
|
y += 24;
|
|
49714
50955
|
}
|
|
50956
|
+
const textareaHeight = box.height - (node.label ? 24 : 0);
|
|
49715
50957
|
const placeholder = node.placeholder || "";
|
|
49716
|
-
|
|
49717
|
-
|
|
49718
|
-
|
|
49719
|
-
|
|
49720
|
-
</g>`;
|
|
49721
|
-
this.currentY = y + height + 12;
|
|
49722
|
-
return result;
|
|
50958
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${textareaHeight}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50959
|
+
svg += `<text x="${box.x + 12}" y="${y + 24}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>`;
|
|
50960
|
+
svg += "</g>";
|
|
50961
|
+
return svg;
|
|
49723
50962
|
}
|
|
49724
|
-
|
|
49725
|
-
const
|
|
49726
|
-
|
|
49727
|
-
|
|
49728
|
-
let y = this.currentY;
|
|
49729
|
-
let result = "";
|
|
50963
|
+
renderSelectBox(box) {
|
|
50964
|
+
const node = box.node;
|
|
50965
|
+
let y = box.y;
|
|
50966
|
+
let svg = "<g>";
|
|
49730
50967
|
if (node.label) {
|
|
49731
|
-
|
|
50968
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49732
50969
|
y += 24;
|
|
49733
50970
|
}
|
|
50971
|
+
const selectHeight = 40;
|
|
49734
50972
|
const placeholder = node.placeholder || "Select...";
|
|
49735
|
-
|
|
49736
|
-
|
|
49737
|
-
|
|
49738
|
-
|
|
49739
|
-
|
|
49740
|
-
</g>`;
|
|
49741
|
-
this.currentY = y + height + 12;
|
|
49742
|
-
return result;
|
|
50973
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${selectHeight}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50974
|
+
svg += `<text x="${box.x + 12}" y="${y + selectHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>`;
|
|
50975
|
+
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"/>`;
|
|
50976
|
+
svg += "</g>";
|
|
50977
|
+
return svg;
|
|
49743
50978
|
}
|
|
49744
|
-
|
|
49745
|
-
const
|
|
49746
|
-
const y = this.currentY;
|
|
50979
|
+
renderCheckboxBox(box) {
|
|
50980
|
+
const node = box.node;
|
|
49747
50981
|
const size = 18;
|
|
49748
|
-
let
|
|
49749
|
-
|
|
49750
|
-
<rect width="${size}" height="${size}" rx="3" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
50982
|
+
let svg = `<g>
|
|
50983
|
+
<rect x="${box.x}" y="${box.y}" width="${size}" height="${size}" rx="3" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49751
50984
|
if (node.checked) {
|
|
49752
|
-
|
|
50985
|
+
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"/>`;
|
|
49753
50986
|
}
|
|
49754
50987
|
if (node.label) {
|
|
49755
|
-
|
|
50988
|
+
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>`;
|
|
49756
50989
|
}
|
|
49757
|
-
|
|
49758
|
-
|
|
49759
|
-
return result;
|
|
50990
|
+
svg += "</g>";
|
|
50991
|
+
return svg;
|
|
49760
50992
|
}
|
|
49761
|
-
|
|
49762
|
-
const
|
|
49763
|
-
const y = this.currentY;
|
|
50993
|
+
renderRadioBox(box) {
|
|
50994
|
+
const node = box.node;
|
|
49764
50995
|
const size = 18;
|
|
49765
50996
|
const radius = size / 2;
|
|
49766
|
-
|
|
49767
|
-
|
|
49768
|
-
|
|
50997
|
+
const cx = box.x + radius;
|
|
50998
|
+
const cy = box.y + radius;
|
|
50999
|
+
let svg = `<g>
|
|
51000
|
+
<circle cx="${cx}" cy="${cy}" r="${radius - 1}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49769
51001
|
if (node.checked) {
|
|
49770
|
-
|
|
51002
|
+
svg += `<circle cx="${cx}" cy="${cy}" r="${radius - 5}" fill="${this.theme.colors.foreground}"/>`;
|
|
49771
51003
|
}
|
|
49772
51004
|
if (node.label) {
|
|
49773
|
-
|
|
51005
|
+
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>`;
|
|
49774
51006
|
}
|
|
49775
|
-
|
|
49776
|
-
|
|
49777
|
-
return result;
|
|
51007
|
+
svg += "</g>";
|
|
51008
|
+
return svg;
|
|
49778
51009
|
}
|
|
49779
|
-
|
|
49780
|
-
const
|
|
49781
|
-
const y = this.currentY;
|
|
51010
|
+
renderSwitchBox(box) {
|
|
51011
|
+
const node = box.node;
|
|
49782
51012
|
const width = 44;
|
|
49783
51013
|
const height = 24;
|
|
49784
51014
|
const radius = height / 2;
|
|
49785
51015
|
const isOn = node.checked;
|
|
49786
51016
|
const bgColor = isOn ? this.theme.colors.primary : this.theme.colors.border;
|
|
49787
|
-
const knobX = isOn ? width - radius : radius;
|
|
49788
|
-
let
|
|
49789
|
-
|
|
49790
|
-
<
|
|
49791
|
-
<circle cx="${knobX}" cy="${radius}" r="${radius - 3}" fill="white"/>`;
|
|
51017
|
+
const knobX = isOn ? box.x + width - radius : box.x + radius;
|
|
51018
|
+
let svg = `<g>
|
|
51019
|
+
<rect x="${box.x}" y="${box.y}" width="${width}" height="${height}" rx="${radius}" fill="${bgColor}"/>
|
|
51020
|
+
<circle cx="${knobX}" cy="${box.y + radius}" r="${radius - 3}" fill="white"/>`;
|
|
49792
51021
|
if (node.label) {
|
|
49793
|
-
|
|
49794
|
-
}
|
|
49795
|
-
result += "</g>";
|
|
49796
|
-
this.currentY += height + 12;
|
|
49797
|
-
return result;
|
|
49798
|
-
}
|
|
49799
|
-
// ===========================================
|
|
49800
|
-
// Button Renderer
|
|
49801
|
-
// ===========================================
|
|
49802
|
-
renderButton(node) {
|
|
49803
|
-
const content = node.content;
|
|
49804
|
-
const hasIcon = !!node.icon;
|
|
49805
|
-
const isIconOnly = hasIcon && !content.trim();
|
|
49806
|
-
const iconSize = 16;
|
|
49807
|
-
const padding = isIconOnly ? 8 : 16;
|
|
49808
|
-
let width;
|
|
49809
|
-
if (isIconOnly) {
|
|
49810
|
-
width = iconSize + padding * 2;
|
|
49811
|
-
} else if (hasIcon) {
|
|
49812
|
-
width = Math.max(80, content.length * 10 + iconSize + 40);
|
|
49813
|
-
} else {
|
|
49814
|
-
width = Math.max(80, content.length * 10 + 32);
|
|
49815
|
-
}
|
|
49816
|
-
const height = 36;
|
|
49817
|
-
const x = this.currentX;
|
|
49818
|
-
const y = this.currentY;
|
|
49819
|
-
let fill = this.theme.colors.primary;
|
|
49820
|
-
let textFill = "#ffffff";
|
|
49821
|
-
let isOutline = false;
|
|
49822
|
-
if (node.secondary) {
|
|
49823
|
-
fill = this.theme.colors.secondary;
|
|
49824
|
-
} else if (node.outline) {
|
|
49825
|
-
fill = "white";
|
|
49826
|
-
textFill = this.theme.colors.foreground;
|
|
49827
|
-
isOutline = true;
|
|
49828
|
-
} else if (node.ghost) {
|
|
49829
|
-
fill = "transparent";
|
|
49830
|
-
textFill = this.theme.colors.foreground;
|
|
51022
|
+
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>`;
|
|
49831
51023
|
}
|
|
49832
|
-
|
|
49833
|
-
|
|
49834
|
-
let iconSvg = "";
|
|
49835
|
-
if (hasIcon) {
|
|
49836
|
-
const iconData = getIconData(node.icon);
|
|
49837
|
-
if (iconData) {
|
|
49838
|
-
const iconX = isIconOnly ? (width - iconSize) / 2 : padding;
|
|
49839
|
-
const iconY = (height - iconSize) / 2;
|
|
49840
|
-
iconSvg = this.renderIconPaths(iconData, iconX, iconY, iconSize, textFill);
|
|
49841
|
-
}
|
|
49842
|
-
}
|
|
49843
|
-
const textX = hasIcon && !isIconOnly ? padding + iconSize + 8 + (width - padding - iconSize - 8 - padding) / 2 : width / 2;
|
|
49844
|
-
const textContent = isIconOnly ? "" : `<text x="${textX}" y="${height / 2 + 5}" font-size="14" fill="${textFill}" text-anchor="middle">${this.escapeXml(content)}</text>`;
|
|
49845
|
-
return `
|
|
49846
|
-
<g transform="translate(${x}, ${y})">
|
|
49847
|
-
<rect width="${width}" height="${height}" rx="4" fill="${fill}" ${strokeAttr}/>
|
|
49848
|
-
${iconSvg}
|
|
49849
|
-
${textContent}
|
|
49850
|
-
</g>`;
|
|
51024
|
+
svg += "</g>";
|
|
51025
|
+
return svg;
|
|
49851
51026
|
}
|
|
49852
|
-
|
|
49853
|
-
|
|
49854
|
-
|
|
49855
|
-
|
|
49856
|
-
|
|
49857
|
-
const paths = data.map(([tag, attrs]) => {
|
|
49858
|
-
const attrStr = Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
49859
|
-
return `<${tag} ${attrStr} />`;
|
|
49860
|
-
}).join("");
|
|
49861
|
-
return `<g transform="translate(${x}, ${y}) scale(${scale})" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</g>`;
|
|
51027
|
+
renderLinkBox(box) {
|
|
51028
|
+
const node = box.node;
|
|
51029
|
+
const fontSize = 14;
|
|
51030
|
+
const textY = box.y + fontSize;
|
|
51031
|
+
return `<text x="${box.x}" y="${textY}" font-size="${fontSize}" fill="${this.theme.colors.primary}" text-decoration="underline">${this.escapeXml(node.content)}</text>`;
|
|
49862
51032
|
}
|
|
49863
|
-
|
|
49864
|
-
|
|
49865
|
-
|
|
49866
|
-
|
|
49867
|
-
|
|
49868
|
-
|
|
49869
|
-
|
|
49870
|
-
const y = this.currentY;
|
|
49871
|
-
this.currentY += height + 12;
|
|
49872
|
-
return `
|
|
49873
|
-
<g transform="translate(${x}, ${y})">
|
|
49874
|
-
<rect width="${width}" height="${height}" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49875
|
-
<line x1="0" y1="0" x2="${width}" y2="${height}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49876
|
-
<line x1="${width}" y1="0" x2="0" y2="${height}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49877
|
-
<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>
|
|
51033
|
+
renderImageBox(box) {
|
|
51034
|
+
const node = box.node;
|
|
51035
|
+
return `<g>
|
|
51036
|
+
<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"/>
|
|
51037
|
+
<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"/>
|
|
51038
|
+
<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"/>
|
|
51039
|
+
<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>
|
|
49878
51040
|
</g>`;
|
|
49879
51041
|
}
|
|
49880
|
-
|
|
49881
|
-
const
|
|
49882
|
-
const
|
|
49883
|
-
const
|
|
49884
|
-
|
|
49885
|
-
|
|
49886
|
-
|
|
49887
|
-
|
|
49888
|
-
|
|
49889
|
-
<
|
|
51042
|
+
renderPlaceholderBox(box) {
|
|
51043
|
+
const node = box.node;
|
|
51044
|
+
const patternId = `placeholder-pattern-${this.clipPathCounter++}`;
|
|
51045
|
+
const patternDef = `<pattern id="${patternId}" width="14" height="14" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
|
51046
|
+
<rect width="14" height="14" fill="#f9f9f9"/>
|
|
51047
|
+
<rect width="7" height="14" fill="rgba(0,0,0,0.03)"/>
|
|
51048
|
+
</pattern>`;
|
|
51049
|
+
this.clipPathDefs.push(patternDef);
|
|
51050
|
+
return `<g>
|
|
51051
|
+
<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"/>
|
|
51052
|
+
<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>
|
|
49890
51053
|
</g>`;
|
|
49891
51054
|
}
|
|
49892
|
-
|
|
49893
|
-
const
|
|
49894
|
-
const
|
|
49895
|
-
const
|
|
49896
|
-
const
|
|
49897
|
-
const y = this.currentY;
|
|
51055
|
+
renderAvatarBox(box) {
|
|
51056
|
+
const node = box.node;
|
|
51057
|
+
const radius = box.width / 2;
|
|
51058
|
+
const cx = box.x + radius;
|
|
51059
|
+
const cy = box.y + radius;
|
|
49898
51060
|
const initial = node.name ? node.name.charAt(0).toUpperCase() : "?";
|
|
49899
|
-
|
|
49900
|
-
|
|
49901
|
-
|
|
49902
|
-
<circle cx="${radius}" cy="${radius}" r="${radius}" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49903
|
-
<text x="${radius}" y="${radius + 5}" font-size="${size / 2.5}" fill="${this.theme.colors.foreground}" text-anchor="middle">${initial}</text>
|
|
51061
|
+
return `<g>
|
|
51062
|
+
<circle cx="${cx}" cy="${cy}" r="${radius}" fill="${this.theme.colors.foreground}"/>
|
|
51063
|
+
<text x="${cx}" y="${cy + 5}" font-size="${box.width / 2.5}" fill="${this.theme.colors.background}" text-anchor="middle">${initial}</text>
|
|
49904
51064
|
</g>`;
|
|
49905
51065
|
}
|
|
49906
|
-
|
|
49907
|
-
const
|
|
49908
|
-
|
|
49909
|
-
|
|
49910
|
-
|
|
49911
|
-
const y = this.currentY;
|
|
49912
|
-
const fill = this.theme.colors.muted;
|
|
49913
|
-
const textFill = this.theme.colors.foreground;
|
|
49914
|
-
this.currentY += height + 8;
|
|
49915
|
-
return `
|
|
49916
|
-
<g transform="translate(${x}, ${y})">
|
|
49917
|
-
<rect width="${width}" height="${height}" rx="11" fill="${fill}" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49918
|
-
<text x="${width / 2}" y="${height / 2 + 4}" font-size="12" fill="${textFill}" text-anchor="middle">${this.escapeXml(content)}</text>
|
|
51066
|
+
renderBadgeBox(box) {
|
|
51067
|
+
const node = box.node;
|
|
51068
|
+
return `<g>
|
|
51069
|
+
<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"/>
|
|
51070
|
+
<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>
|
|
49919
51071
|
</g>`;
|
|
49920
51072
|
}
|
|
49921
|
-
|
|
49922
|
-
|
|
49923
|
-
// ===========================================
|
|
49924
|
-
renderTable(node) {
|
|
51073
|
+
renderTableBox(box) {
|
|
51074
|
+
const node = box.node;
|
|
49925
51075
|
const columns = node.columns || [];
|
|
49926
51076
|
const rows = node.rows || [];
|
|
49927
51077
|
const rowCount = rows.length || 3;
|
|
49928
51078
|
const colWidth = 120;
|
|
49929
51079
|
const rowHeight = 40;
|
|
49930
|
-
|
|
49931
|
-
|
|
49932
|
-
let svg = `<g transform="translate(${x}, ${y})">`;
|
|
49933
|
-
svg += `<rect width="${columns.length * colWidth}" height="${rowHeight}" fill="${this.theme.colors.muted}"/>`;
|
|
51080
|
+
let svg = `<g>`;
|
|
51081
|
+
svg += `<rect x="${box.x}" y="${box.y}" width="${columns.length * colWidth}" height="${rowHeight}" fill="${this.theme.colors.muted}"/>`;
|
|
49934
51082
|
columns.forEach((col, i) => {
|
|
49935
|
-
svg += `<text x="${i * colWidth + 12}" y="${rowHeight / 2 + 5}" font-size="14" font-weight="600">${this.escapeXml(col)}</text>`;
|
|
51083
|
+
svg += `<text x="${box.x + i * colWidth + 12}" y="${box.y + rowHeight / 2 + 5}" font-size="14" font-weight="600">${this.escapeXml(col)}</text>`;
|
|
49936
51084
|
});
|
|
49937
|
-
const displayRowCount = rows.length > 0 ? rows.length :
|
|
51085
|
+
const displayRowCount = rows.length > 0 ? rows.length : rowCount;
|
|
49938
51086
|
for (let rowIdx = 0; rowIdx < displayRowCount; rowIdx++) {
|
|
49939
|
-
const rowY = (rowIdx + 1) * rowHeight;
|
|
49940
|
-
svg += `<rect y="${rowY}" width="${columns.length * colWidth}" height="${rowHeight}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51087
|
+
const rowY = box.y + (rowIdx + 1) * rowHeight;
|
|
51088
|
+
svg += `<rect x="${box.x}" y="${rowY}" width="${columns.length * colWidth}" height="${rowHeight}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
49941
51089
|
columns.forEach((_, colIdx) => {
|
|
49942
51090
|
const cellContent = rows[rowIdx] && rows[rowIdx][colIdx] ? String(typeof rows[rowIdx][colIdx] === "object" ? "..." : rows[rowIdx][colIdx]) : "\u2014";
|
|
49943
|
-
svg += `<text x="${colIdx * colWidth + 12}" y="${rowY + rowHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(cellContent)}</text>`;
|
|
51091
|
+
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>`;
|
|
49944
51092
|
});
|
|
49945
51093
|
}
|
|
49946
51094
|
svg += "</g>";
|
|
49947
|
-
this.currentY += (displayRowCount + 1) * rowHeight + 16;
|
|
49948
51095
|
return svg;
|
|
49949
51096
|
}
|
|
49950
|
-
|
|
49951
|
-
const
|
|
49952
|
-
let y = this.currentY;
|
|
51097
|
+
renderListBox(box) {
|
|
51098
|
+
const node = box.node;
|
|
49953
51099
|
const items = node.items || [];
|
|
49954
51100
|
const ordered = node.ordered || false;
|
|
49955
|
-
let svg =
|
|
51101
|
+
let svg = "<g>";
|
|
49956
51102
|
items.forEach((item, idx) => {
|
|
49957
51103
|
const marker = ordered ? `${idx + 1}.` : "\u2022";
|
|
49958
51104
|
const content = typeof item === "string" ? item : item.content;
|
|
49959
|
-
svg += `<text x="
|
|
51105
|
+
svg += `<text x="${box.x}" y="${box.y + idx * 28 + 16}" font-size="14" fill="${this.theme.colors.foreground}">${marker} ${this.escapeXml(content)}</text>`;
|
|
49960
51106
|
});
|
|
49961
51107
|
svg += "</g>";
|
|
49962
|
-
this.currentY += items.length * 24 + 12;
|
|
49963
51108
|
return svg;
|
|
49964
51109
|
}
|
|
49965
|
-
|
|
49966
|
-
|
|
49967
|
-
|
|
49968
|
-
|
|
49969
|
-
|
|
49970
|
-
const height = 48;
|
|
49971
|
-
const x = this.currentX;
|
|
49972
|
-
const y = this.currentY;
|
|
49973
|
-
this.currentY += height + 12;
|
|
49974
|
-
return `
|
|
49975
|
-
<g transform="translate(${x}, ${y})">
|
|
49976
|
-
<rect width="${width}" height="${height}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
|
|
49977
|
-
<text x="16" y="${height / 2 + 5}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.content)}</text>
|
|
51110
|
+
renderAlertBox(box) {
|
|
51111
|
+
const node = box.node;
|
|
51112
|
+
return `<g>
|
|
51113
|
+
<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"/>
|
|
51114
|
+
<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>
|
|
49978
51115
|
</g>`;
|
|
49979
51116
|
}
|
|
49980
|
-
|
|
49981
|
-
const
|
|
49982
|
-
|
|
49983
|
-
|
|
49984
|
-
let y = this.currentY;
|
|
49985
|
-
let result = "";
|
|
51117
|
+
renderProgressBox(box) {
|
|
51118
|
+
const node = box.node;
|
|
51119
|
+
let y = box.y;
|
|
51120
|
+
let svg = "<g>";
|
|
49986
51121
|
if (node.label) {
|
|
49987
|
-
|
|
51122
|
+
svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
|
|
49988
51123
|
y += 24;
|
|
49989
51124
|
}
|
|
51125
|
+
const barHeight = 8;
|
|
49990
51126
|
const value = node.value || 0;
|
|
49991
51127
|
const max = node.max || 100;
|
|
49992
51128
|
const percent = Math.min(100, Math.max(0, value / max * 100));
|
|
49993
|
-
|
|
49994
|
-
|
|
49995
|
-
|
|
49996
|
-
|
|
49997
|
-
</g>`;
|
|
49998
|
-
this.currentY = y + height + 12;
|
|
49999
|
-
return result;
|
|
51129
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width}" height="${barHeight}" rx="${barHeight / 2}" fill="${this.theme.colors.muted}"/>`;
|
|
51130
|
+
svg += `<rect x="${box.x}" y="${y}" width="${box.width * percent / 100}" height="${barHeight}" rx="${barHeight / 2}" fill="${this.theme.colors.primary}"/>`;
|
|
51131
|
+
svg += "</g>";
|
|
51132
|
+
return svg;
|
|
50000
51133
|
}
|
|
50001
|
-
|
|
50002
|
-
const
|
|
50003
|
-
const
|
|
50004
|
-
const
|
|
50005
|
-
|
|
50006
|
-
|
|
50007
|
-
|
|
50008
|
-
return `
|
|
50009
|
-
<g transform="translate(${x}, ${y})">
|
|
50010
|
-
<circle r="${radius}" fill="none" stroke="${this.theme.colors.muted}" stroke-width="2"/>
|
|
50011
|
-
<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"/>
|
|
51134
|
+
renderSpinnerBox(box) {
|
|
51135
|
+
const cx = box.x + box.width / 2;
|
|
51136
|
+
const cy = box.y + box.height / 2;
|
|
51137
|
+
const radius = box.width / 2 - 2;
|
|
51138
|
+
return `<g>
|
|
51139
|
+
<circle cx="${cx}" cy="${cy}" r="${radius}" fill="none" stroke="${this.theme.colors.muted}" stroke-width="2"/>
|
|
51140
|
+
<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"/>
|
|
50012
51141
|
</g>`;
|
|
50013
51142
|
}
|
|
50014
|
-
|
|
50015
|
-
|
|
50016
|
-
// ===========================================
|
|
50017
|
-
renderNav(node) {
|
|
50018
|
-
const items = node.items || [];
|
|
50019
|
-
const x = this.currentX;
|
|
50020
|
-
const y = this.currentY;
|
|
51143
|
+
renderNavBox(box) {
|
|
51144
|
+
const node = box.node;
|
|
50021
51145
|
const vertical = node.vertical || false;
|
|
50022
|
-
let svg =
|
|
51146
|
+
let svg = "<g>";
|
|
51147
|
+
if (node.children && node.children.length > 0) {
|
|
51148
|
+
let offsetY = 0;
|
|
51149
|
+
node.children.forEach((child) => {
|
|
51150
|
+
if (child.type === "divider") {
|
|
51151
|
+
offsetY += 16;
|
|
51152
|
+
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"/>`;
|
|
51153
|
+
offsetY += 8;
|
|
51154
|
+
} else if (child.type === "group") {
|
|
51155
|
+
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>`;
|
|
51156
|
+
offsetY += 28;
|
|
51157
|
+
child.items.forEach((item) => {
|
|
51158
|
+
if (item.type === "divider") {
|
|
51159
|
+
offsetY += 8;
|
|
51160
|
+
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"/>`;
|
|
51161
|
+
offsetY += 8;
|
|
51162
|
+
} else {
|
|
51163
|
+
const fill = item.active ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
51164
|
+
const fontWeight = item.active ? 'font-weight="500"' : "";
|
|
51165
|
+
svg += `<text x="${box.x}" y="${box.y + offsetY + 16}" font-size="14" ${fontWeight} fill="${fill}">${this.escapeXml(item.label)}</text>`;
|
|
51166
|
+
offsetY += 32;
|
|
51167
|
+
}
|
|
51168
|
+
});
|
|
51169
|
+
} else if (child.type === "item") {
|
|
51170
|
+
const fill = child.active ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
51171
|
+
const fontWeight = child.active ? 'font-weight="500"' : "";
|
|
51172
|
+
svg += `<text x="${box.x}" y="${box.y + offsetY + 16}" font-size="14" ${fontWeight} fill="${fill}">${this.escapeXml(child.label)}</text>`;
|
|
51173
|
+
offsetY += 32;
|
|
51174
|
+
}
|
|
51175
|
+
});
|
|
51176
|
+
svg += "</g>";
|
|
51177
|
+
return svg;
|
|
51178
|
+
}
|
|
51179
|
+
const items = node.items || [];
|
|
50023
51180
|
if (vertical) {
|
|
50024
51181
|
items.forEach((item, idx) => {
|
|
50025
51182
|
const label = typeof item === "string" ? item : item.label;
|
|
50026
51183
|
const isActive = typeof item === "object" && item.active;
|
|
50027
51184
|
const fill = isActive ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
50028
|
-
svg += `<text x="
|
|
51185
|
+
svg += `<text x="${box.x}" y="${box.y + idx * 32 + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
50029
51186
|
});
|
|
50030
|
-
this.currentY += items.length * 32 + 12;
|
|
50031
51187
|
} else {
|
|
50032
51188
|
let offsetX = 0;
|
|
50033
51189
|
items.forEach((item) => {
|
|
50034
51190
|
const label = typeof item === "string" ? item : item.label;
|
|
50035
51191
|
const isActive = typeof item === "object" && item.active;
|
|
50036
51192
|
const fill = isActive ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
50037
|
-
svg += `<text x="${offsetX}" y="16" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
50038
|
-
offsetX += label
|
|
51193
|
+
svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
51194
|
+
offsetX += this.estimateTextWidth(label, 14) + 24;
|
|
50039
51195
|
});
|
|
50040
|
-
this.currentY += 32;
|
|
50041
51196
|
}
|
|
50042
51197
|
svg += "</g>";
|
|
50043
51198
|
return svg;
|
|
50044
51199
|
}
|
|
50045
|
-
|
|
51200
|
+
renderTabsBox(box) {
|
|
51201
|
+
const node = box.node;
|
|
50046
51202
|
const items = node.items || [];
|
|
50047
|
-
const x = this.currentX;
|
|
50048
|
-
const y = this.currentY;
|
|
50049
51203
|
const tabHeight = 40;
|
|
50050
|
-
let svg =
|
|
51204
|
+
let svg = "<g>";
|
|
50051
51205
|
let offsetX = 0;
|
|
50052
|
-
items.forEach((item) => {
|
|
51206
|
+
items.forEach((item, idx) => {
|
|
50053
51207
|
const label = typeof item === "string" ? item : item;
|
|
50054
|
-
const tabWidth = label
|
|
50055
|
-
|
|
50056
|
-
svg += `<
|
|
51208
|
+
const tabWidth = this.estimateTextWidth(label, 14) + 32;
|
|
51209
|
+
const isFirst = idx === 0;
|
|
51210
|
+
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"/>`;
|
|
51211
|
+
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>`;
|
|
50057
51212
|
offsetX += tabWidth;
|
|
50058
51213
|
});
|
|
50059
51214
|
svg += "</g>";
|
|
50060
|
-
this.currentY += tabHeight + 12;
|
|
50061
51215
|
return svg;
|
|
50062
51216
|
}
|
|
50063
|
-
|
|
51217
|
+
renderBreadcrumbBox(box) {
|
|
51218
|
+
const node = box.node;
|
|
50064
51219
|
const items = node.items || [];
|
|
50065
51220
|
const separator = "/";
|
|
50066
|
-
|
|
50067
|
-
const y = this.currentY;
|
|
50068
|
-
let svg = `<g transform="translate(${x}, ${y})">`;
|
|
51221
|
+
let svg = "<g>";
|
|
50069
51222
|
let offsetX = 0;
|
|
50070
51223
|
items.forEach((item, idx) => {
|
|
50071
51224
|
const label = typeof item === "string" ? item : item.label;
|
|
50072
51225
|
const isLast = idx === items.length - 1;
|
|
50073
51226
|
const fill = isLast ? this.theme.colors.foreground : this.theme.colors.muted;
|
|
50074
|
-
svg += `<text x="${offsetX}" y="16" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
50075
|
-
offsetX += label
|
|
51227
|
+
svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
|
|
51228
|
+
offsetX += this.estimateTextWidth(label, 14) + 8;
|
|
50076
51229
|
if (!isLast) {
|
|
50077
|
-
svg += `<text x="${offsetX}" y="16" font-size="14" fill="${this.theme.colors.muted}">${separator}</text>`;
|
|
51230
|
+
svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${this.theme.colors.muted}">${separator}</text>`;
|
|
50078
51231
|
offsetX += 16;
|
|
50079
51232
|
}
|
|
50080
51233
|
});
|
|
50081
51234
|
svg += "</g>";
|
|
50082
|
-
this.currentY += 28;
|
|
50083
51235
|
return svg;
|
|
50084
51236
|
}
|
|
51237
|
+
renderIconBox(box) {
|
|
51238
|
+
const node = box.node;
|
|
51239
|
+
const iconData = getIconData(node.name);
|
|
51240
|
+
if (!iconData) {
|
|
51241
|
+
return `<!-- Icon not found: ${node.name} -->`;
|
|
51242
|
+
}
|
|
51243
|
+
const color = this.theme.colors.foreground;
|
|
51244
|
+
return this.renderIconPaths(iconData, box.x, box.y, box.width, color);
|
|
51245
|
+
}
|
|
51246
|
+
renderDividerBox(box) {
|
|
51247
|
+
return `<line x1="${box.x}" y1="${box.y}" x2="${box.x + box.width}" y2="${box.y}" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
|
|
51248
|
+
}
|
|
50085
51249
|
// ===========================================
|
|
50086
51250
|
// Utility Methods
|
|
50087
51251
|
// ===========================================
|
|
50088
|
-
|
|
50089
|
-
const
|
|
50090
|
-
|
|
50091
|
-
|
|
50092
|
-
|
|
50093
|
-
|
|
50094
|
-
|
|
50095
|
-
|
|
50096
|
-
|
|
50097
|
-
|
|
50098
|
-
|
|
50099
|
-
|
|
51252
|
+
renderIconPaths(data, x, y, size, color) {
|
|
51253
|
+
const scale = size / 24;
|
|
51254
|
+
const paths = data.map(([tag, attrs]) => {
|
|
51255
|
+
const attrStr = Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
51256
|
+
return `<${tag} ${attrStr} />`;
|
|
51257
|
+
}).join("");
|
|
51258
|
+
return `<g transform="translate(${x}, ${y}) scale(${scale})" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</g>`;
|
|
51259
|
+
}
|
|
51260
|
+
getPadding(node) {
|
|
51261
|
+
const defaultPadding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
51262
|
+
if (!("p" in node) && !("px" in node) && !("py" in node)) {
|
|
51263
|
+
if (node.type === "Card" || node.type === "Header" || node.type === "Footer" || node.type === "Sidebar") {
|
|
51264
|
+
return { top: 16, right: 16, bottom: 16, left: 16 };
|
|
51265
|
+
}
|
|
51266
|
+
return defaultPadding;
|
|
51267
|
+
}
|
|
51268
|
+
let p = 0;
|
|
51269
|
+
if ("p" in node && node.p !== void 0) {
|
|
51270
|
+
p = this.resolveSpacing(node.p);
|
|
51271
|
+
}
|
|
51272
|
+
let px = p;
|
|
51273
|
+
let py = p;
|
|
51274
|
+
if ("px" in node && node.px !== void 0) {
|
|
51275
|
+
px = this.resolveSpacing(node.px);
|
|
51276
|
+
}
|
|
51277
|
+
if ("py" in node && node.py !== void 0) {
|
|
51278
|
+
py = this.resolveSpacing(node.py);
|
|
51279
|
+
}
|
|
51280
|
+
if (node.type === "Header") {
|
|
51281
|
+
return { top: 0, right: px, bottom: 0, left: px };
|
|
51282
|
+
}
|
|
51283
|
+
return { top: py, right: px, bottom: py, left: px };
|
|
51284
|
+
}
|
|
51285
|
+
getGap(node) {
|
|
51286
|
+
if ("gap" in node && node.gap !== void 0) {
|
|
51287
|
+
return this.resolveSpacing(node.gap);
|
|
51288
|
+
}
|
|
51289
|
+
return void 0;
|
|
51290
|
+
}
|
|
51291
|
+
resolveSpacing(value) {
|
|
51292
|
+
if (typeof value === "number") {
|
|
51293
|
+
return value * 4;
|
|
51294
|
+
}
|
|
51295
|
+
if (typeof value === "object" && value && "value" in value) {
|
|
51296
|
+
return value.value;
|
|
51297
|
+
}
|
|
51298
|
+
return 0;
|
|
51299
|
+
}
|
|
51300
|
+
resolveSize(value) {
|
|
51301
|
+
if (value === void 0 || value === null) return void 0;
|
|
51302
|
+
if (typeof value === "number") return value;
|
|
51303
|
+
if (typeof value === "string") {
|
|
51304
|
+
if (value === "full") return void 0;
|
|
51305
|
+
const num = parseFloat(value);
|
|
51306
|
+
if (!isNaN(num)) return num;
|
|
51307
|
+
}
|
|
51308
|
+
if (typeof value === "object" && value && "value" in value) {
|
|
51309
|
+
return value.value;
|
|
51310
|
+
}
|
|
51311
|
+
return void 0;
|
|
51312
|
+
}
|
|
51313
|
+
isFullWidth(node) {
|
|
51314
|
+
if ("w" in node) {
|
|
51315
|
+
return node.w === "full";
|
|
51316
|
+
}
|
|
51317
|
+
return false;
|
|
51318
|
+
}
|
|
51319
|
+
estimateTextWidth(text, fontSize) {
|
|
51320
|
+
return text.length * fontSize * 0.6;
|
|
50100
51321
|
}
|
|
50101
51322
|
resolveFontSize(size) {
|
|
50102
51323
|
if (!size) return 16;
|
|
50103
51324
|
if (typeof size === "string") {
|
|
50104
|
-
|
|
51325
|
+
const sizes = {
|
|
51326
|
+
xs: 12,
|
|
51327
|
+
sm: 14,
|
|
51328
|
+
base: 16,
|
|
51329
|
+
md: 16,
|
|
51330
|
+
lg: 18,
|
|
51331
|
+
xl: 20,
|
|
51332
|
+
"2xl": 24,
|
|
51333
|
+
"3xl": 30
|
|
51334
|
+
};
|
|
51335
|
+
return sizes[size] || 16;
|
|
50105
51336
|
}
|
|
50106
51337
|
if (typeof size === "object" && "value" in size) {
|
|
50107
51338
|
if (size.unit === "px") return size.value;
|
|
@@ -50112,15 +51343,8 @@ var SvgRenderer = class {
|
|
|
50112
51343
|
return 16;
|
|
50113
51344
|
}
|
|
50114
51345
|
getTitleFontSize(level) {
|
|
50115
|
-
const sizes = {
|
|
50116
|
-
|
|
50117
|
-
2: 28,
|
|
50118
|
-
3: 24,
|
|
50119
|
-
4: 20,
|
|
50120
|
-
5: 18,
|
|
50121
|
-
6: 16
|
|
50122
|
-
};
|
|
50123
|
-
return sizes[level] || 24;
|
|
51346
|
+
const sizes = { 1: 36, 2: 30, 3: 24, 4: 20, 5: 18, 6: 16 };
|
|
51347
|
+
return sizes[level] || 20;
|
|
50124
51348
|
}
|
|
50125
51349
|
escapeXml(str) {
|
|
50126
51350
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -50181,15 +51405,22 @@ function renderToSvg2(document, options = {}) {
|
|
|
50181
51405
|
height = viewport.height;
|
|
50182
51406
|
}
|
|
50183
51407
|
}
|
|
50184
|
-
const padding = options.padding ?? 20;
|
|
50185
51408
|
const background = options.background ?? "#ffffff";
|
|
50186
51409
|
const { html, css } = render(document, { theme: "light" });
|
|
50187
51410
|
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
50188
51411
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
50189
51412
|
viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
50190
51413
|
<rect width="100%" height="100%" fill="${background}"/>
|
|
50191
|
-
<foreignObject x="
|
|
50192
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="
|
|
51414
|
+
<foreignObject x="0" y="0" width="${width}" height="${height}">
|
|
51415
|
+
<div xmlns="http://www.w3.org/1999/xhtml" style="
|
|
51416
|
+
width: ${width}px;
|
|
51417
|
+
height: ${height}px;
|
|
51418
|
+
overflow: hidden;
|
|
51419
|
+
display: flex;
|
|
51420
|
+
justify-content: center;
|
|
51421
|
+
align-items: center;
|
|
51422
|
+
box-sizing: border-box;
|
|
51423
|
+
">
|
|
50193
51424
|
<style type="text/css">
|
|
50194
51425
|
${css}
|
|
50195
51426
|
</style>
|