@wireweave/core 1.0.0-beta.20260110141318 → 1.1.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/renderer.cjs CHANGED
@@ -118,15 +118,10 @@ function generateContainerStyles(prefix) {
118
118
  padding: 16px;
119
119
  }
120
120
 
121
- /* Cards in flex rows: respect explicit width, shrink if needed */
121
+ /* Cards in flex rows should expand equally */
122
122
  .${prefix}-row > .${prefix}-card {
123
- flex: 0 1 auto;
124
- min-width: 0;
125
- }
126
-
127
- /* Cards without explicit width should expand to fill space */
128
- .${prefix}-row > .${prefix}-card-flex {
129
123
  flex: 1 1 0%;
124
+ min-width: 0;
130
125
  }
131
126
 
132
127
  .${prefix}-card-title {
@@ -282,11 +277,6 @@ function generateTextStyles(prefix) {
282
277
  line-height: 1.25;
283
278
  }
284
279
 
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
-
290
280
  h1.${prefix}-title { font-size: 36px; }
291
281
  h2.${prefix}-title { font-size: 30px; }
292
282
  h3.${prefix}-title { font-size: 24px; }
@@ -681,30 +671,6 @@ img.${prefix}-image {
681
671
  font-size: 14px;
682
672
  }
683
673
 
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
-
708
674
  .${prefix}-avatar {
709
675
  display: inline-flex;
710
676
  align-items: center;
@@ -820,12 +786,12 @@ svg.${prefix}-icon {
820
786
  display: block;
821
787
  }
822
788
 
823
- /* Icon size tokens - matches SVG renderer */
789
+ /* Icon size tokens */
824
790
  svg.${prefix}-icon-xs { width: 12px; height: 12px; }
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; }
791
+ svg.${prefix}-icon-sm { width: 14px; height: 14px; }
792
+ svg.${prefix}-icon-md { width: 16px; height: 16px; }
793
+ svg.${prefix}-icon-lg { width: 20px; height: 20px; }
794
+ svg.${prefix}-icon-xl { width: 24px; height: 24px; }
829
795
 
830
796
  .${prefix}-icon svg {
831
797
  display: block;
@@ -1121,27 +1087,6 @@ function generateNavigationStyles(_theme, prefix) {
1121
1087
  cursor: not-allowed;
1122
1088
  }
1123
1089
 
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
-
1145
1090
  .${prefix}-tabs {
1146
1091
  border-bottom: 1px solid var(--${prefix}-border);
1147
1092
  }
@@ -1359,6 +1304,7 @@ function generateBaseStyles(prefix) {
1359
1304
  font-family: var(--${prefix}-font);
1360
1305
  color: var(--${prefix}-fg);
1361
1306
  background: var(--${prefix}-bg);
1307
+ min-height: 100vh;
1362
1308
  box-sizing: border-box;
1363
1309
  position: relative;
1364
1310
  display: flex;
@@ -1369,19 +1315,10 @@ function generateBaseStyles(prefix) {
1369
1315
  overflow: hidden;
1370
1316
  }
1371
1317
 
1372
- /* Col direct child of page should fill page height */
1373
- .${prefix}-page > .${prefix}-col {
1374
- flex: 1;
1375
- min-height: 0;
1376
- }
1377
-
1378
1318
  /* Row containing sidebar should fill remaining space */
1379
1319
  .${prefix}-page > .${prefix}-row:has(.${prefix}-sidebar),
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) {
1320
+ .${prefix}-page > .${prefix}-row:has(.${prefix}-main) {
1383
1321
  flex: 1;
1384
- min-height: 0;
1385
1322
  align-items: stretch;
1386
1323
  }
1387
1324
 
@@ -1424,15 +1361,10 @@ function generateGridClasses(_theme, prefix) {
1424
1361
  box-sizing: border-box;
1425
1362
  }
1426
1363
 
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
-
1433
1364
  `;
1434
1365
  for (let i = 1; i <= 12; i++) {
1435
- css += `.${prefix}-col-${i} { flex: ${i} 0 0%; min-width: 0; }
1366
+ const width = (i / 12 * 100).toFixed(4);
1367
+ css += `.${prefix}-col-${i} { flex: 0 0 auto; width: ${width}%; }
1436
1368
  `;
1437
1369
  }
1438
1370
  return css;
@@ -1600,20 +1532,6 @@ function generateLayoutClasses(prefix) {
1600
1532
  .${prefix}-main {
1601
1533
  flex: 1;
1602
1534
  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;
1617
1535
  }
1618
1536
 
1619
1537
  .${prefix}-footer {
@@ -1630,7 +1548,6 @@ function generateLayoutClasses(prefix) {
1630
1548
  border-right: 1px solid var(--${prefix}-border);
1631
1549
  padding: 16px 16px 16px 20px;
1632
1550
  flex-shrink: 0;
1633
- align-self: stretch;
1634
1551
  }
1635
1552
 
1636
1553
  .${prefix}-sidebar-right {
@@ -48200,39 +48117,14 @@ var lucideIcons = {
48200
48117
  ]
48201
48118
  ]
48202
48119
  };
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
- };
48222
48120
  function getIconData(name) {
48223
48121
  if (lucideIcons[name]) {
48224
48122
  return lucideIcons[name];
48225
48123
  }
48226
- if (iconAliases[name] && lucideIcons[iconAliases[name]]) {
48227
- return lucideIcons[iconAliases[name]];
48228
- }
48229
48124
  const kebabName = name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
48230
48125
  if (lucideIcons[kebabName]) {
48231
48126
  return lucideIcons[kebabName];
48232
48127
  }
48233
- if (iconAliases[kebabName] && lucideIcons[iconAliases[kebabName]]) {
48234
- return lucideIcons[iconAliases[kebabName]];
48235
- }
48236
48128
  return void 0;
48237
48129
  }
48238
48130
  function renderIconSvg(data, _size = 24, strokeWidth = 2, className = "", styleAttr = "") {
@@ -48342,7 +48234,7 @@ var HtmlRenderer = class extends BaseRenderer {
48342
48234
  const title = node.title ? `<title>${this.escapeHtml(node.title)}</title>
48343
48235
  ` : "";
48344
48236
  const commonStyles = this.buildCommonStyles(node);
48345
- const viewportStyle = `position: relative; width: ${viewport.width}px; height: ${viewport.height}px; overflow: hidden`;
48237
+ const viewportStyle = `width: ${viewport.width}px; height: ${viewport.height}px`;
48346
48238
  const combinedStyle = commonStyles ? `${viewportStyle}; ${commonStyles}` : viewportStyle;
48347
48239
  const dataAttrs = `data-viewport-width="${viewport.width}" data-viewport-height="${viewport.height}" data-viewport-label="${viewport.label}"`;
48348
48240
  return `<div class="${classes}" style="${combinedStyle}" ${dataAttrs}>
@@ -48508,7 +48400,6 @@ ${children}
48508
48400
  renderMain(node) {
48509
48401
  const classes = this.buildClassString([
48510
48402
  `${this.prefix}-main`,
48511
- node.scroll ? `${this.prefix}-scroll` : void 0,
48512
48403
  ...this.getCommonClasses(node)
48513
48404
  ]);
48514
48405
  const styles = this.buildCommonStyles(node);
@@ -48594,10 +48485,6 @@ ${children}
48594
48485
  /**
48595
48486
  * Build common inline styles for all values
48596
48487
  *
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
- *
48601
48488
  * Spacing values (p, m, gap) use token system:
48602
48489
  * - number: spacing token (e.g., p=4 → padding: 16px from token table)
48603
48490
  * - ValueWithUnit: direct CSS value (e.g., p=16px → padding: 16px)
@@ -48612,17 +48499,6 @@ ${children}
48612
48499
  */
48613
48500
  buildCommonStyles(props) {
48614
48501
  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
- }
48626
48502
  const wValue = resolveSizeValueToCss(props.w);
48627
48503
  if (wValue) {
48628
48504
  styles.push(`width: ${wValue}`);
@@ -48732,10 +48608,8 @@ ${children}
48732
48608
  // Container Node Renderers
48733
48609
  // ===========================================
48734
48610
  renderCard(node) {
48735
- const hasExplicitWidth = node.w !== void 0;
48736
48611
  const classes = this.buildClassString([
48737
48612
  `${this.prefix}-card`,
48738
- !hasExplicitWidth ? `${this.prefix}-card-flex` : void 0,
48739
48613
  node.shadow ? `${this.prefix}-card-shadow-${node.shadow}` : void 0,
48740
48614
  ...this.getCommonClasses(node)
48741
48615
  ]);
@@ -49072,19 +48946,11 @@ ${slider}`;
49072
48946
  renderPlaceholder(node) {
49073
48947
  const classes = this.buildClassString([
49074
48948
  `${this.prefix}-placeholder`,
49075
- node.children && node.children.length > 0 ? `${this.prefix}-placeholder-with-children` : void 0,
49076
48949
  ...this.getCommonClasses(node)
49077
48950
  ]);
49078
48951
  const styles = this.buildCommonStyles(node);
49079
48952
  const styleAttr = styles ? ` style="${styles}"` : "";
49080
48953
  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
- }
49088
48954
  return `<div class="${classes}"${styleAttr}>${label}</div>`;
49089
48955
  }
49090
48956
  renderAvatar(node) {
@@ -49330,12 +49196,6 @@ ${items}
49330
49196
  ]);
49331
49197
  const styles = this.buildCommonStyles(node);
49332
49198
  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
- }
49339
49199
  const items = node.items.map((item) => {
49340
49200
  if (typeof item === "string") {
49341
49201
  return `<a class="${this.prefix}-nav-link" href="#">${this.escapeHtml(item)}</a>`;
@@ -49345,48 +49205,12 @@ ${content}
49345
49205
  item.active ? `${this.prefix}-nav-link-active` : void 0,
49346
49206
  item.disabled ? `${this.prefix}-nav-link-disabled` : void 0
49347
49207
  ]);
49348
- const iconHtml = item.icon ? this.renderIconHtml(item.icon) + " " : "";
49349
- return `<a class="${linkClasses}" href="${item.href || "#"}">${iconHtml}${this.escapeHtml(item.label)}</a>`;
49208
+ return `<a class="${linkClasses}" href="${item.href || "#"}">${this.escapeHtml(item.label)}</a>`;
49350
49209
  }).join("\n");
49351
49210
  return `<nav class="${classes}"${styleAttr}>
49352
49211
  ${items}
49353
49212
  </nav>`;
49354
49213
  }
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
- }
49390
49214
  renderTabs(node) {
49391
49215
  const classes = this.buildClassString([
49392
49216
  `${this.prefix}-tabs`,
@@ -49541,239 +49365,13 @@ function createHtmlRenderer(options) {
49541
49365
  return new HtmlRenderer(options);
49542
49366
  }
49543
49367
 
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
-
49767
49368
  // src/renderer/svg/index.ts
49768
49369
  var SvgRenderer = class {
49769
49370
  options;
49770
49371
  theme;
49771
- pageWidth = 0;
49772
- pageHeight = 0;
49773
- clipPathDefs = [];
49774
- clipPathCounter = 0;
49775
- // Default spacing values
49776
- DEFAULT_GAP = 16;
49372
+ currentX = 0;
49373
+ currentY = 0;
49374
+ contentWidth = 0;
49777
49375
  constructor(options = {}) {
49778
49376
  this.options = {
49779
49377
  width: options.width ?? 800,
@@ -49784,41 +49382,28 @@ var SvgRenderer = class {
49784
49382
  fontFamily: options.fontFamily ?? "system-ui, -apple-system, sans-serif"
49785
49383
  };
49786
49384
  this.theme = defaultTheme;
49385
+ this.contentWidth = this.options.width - this.options.padding * 2;
49787
49386
  }
49788
49387
  /**
49789
49388
  * Render a wireframe document to SVG
49790
49389
  */
49791
49390
  render(doc) {
49792
- this.clipPathDefs = [];
49793
- this.clipPathCounter = 0;
49391
+ this.currentX = this.options.padding;
49392
+ this.currentY = this.options.padding;
49794
49393
  const firstPage = doc.children[0];
49795
49394
  let width = this.options.width;
49796
49395
  let height = this.options.height;
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
- }
49396
+ if (firstPage && (firstPage.viewport !== void 0 || firstPage.device !== void 0)) {
49397
+ const viewport = resolveViewport(firstPage.viewport, firstPage.device);
49398
+ width = viewport.width;
49399
+ height = viewport.height;
49400
+ this.contentWidth = width - this.options.padding * 2;
49813
49401
  }
49814
- this.pageWidth = width;
49815
- this.pageHeight = height;
49816
49402
  const content = doc.children.map((page) => this.renderPage(page)).join("\n");
49817
- const allDefs = this.generateDefs() + "\n" + this.clipPathDefs.join("\n");
49818
49403
  const svg = `<?xml version="1.0" encoding="UTF-8"?>
49819
49404
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
49820
49405
  <defs>
49821
- ${allDefs}
49406
+ ${this.generateDefs()}
49822
49407
  </defs>
49823
49408
  <rect width="100%" height="100%" fill="${this.options.background}"/>
49824
49409
  <g transform="scale(${this.options.scale})">
@@ -49828,7 +49413,7 @@ var SvgRenderer = class {
49828
49413
  return { svg, width, height };
49829
49414
  }
49830
49415
  /**
49831
- * Generate SVG defs (styles)
49416
+ * Generate SVG defs (styles, patterns, etc.)
49832
49417
  */
49833
49418
  generateDefs() {
49834
49419
  return `
@@ -49839,1500 +49424,684 @@ var SvgRenderer = class {
49839
49424
  </style>
49840
49425
  `;
49841
49426
  }
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
- }
49888
49427
  /**
49889
- * Render page with fixed header/footer layout
49890
- * Header at top, Footer at bottom, Main fills remaining space
49428
+ * Render a page node
49891
49429
  */
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");
49430
+ renderPage(node) {
49900
49431
  const elements = [];
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));
49909
- }
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));
49432
+ if (node.title) {
49433
+ elements.push(this.renderPageTitle(node.title));
49916
49434
  }
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"));
49435
+ for (const child of node.children) {
49436
+ elements.push(this.renderNode(child));
49938
49437
  }
49939
49438
  return elements.join("\n");
49940
49439
  }
49941
- shouldCenterHorizontally(node) {
49942
- return node.type === "Card" || node.type === "Modal";
49440
+ /**
49441
+ * Render page title
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>`;
49943
49448
  }
49944
- // ===========================================
49945
- // Measurement Phase
49946
- // ===========================================
49947
- measureNode(node, constraints) {
49449
+ /**
49450
+ * Render any AST node
49451
+ */
49452
+ renderNode(node) {
49948
49453
  switch (node.type) {
49454
+ // Layout nodes
49949
49455
  case "Row":
49950
- return this.measureRow(node, constraints);
49456
+ return this.renderRow(node);
49951
49457
  case "Col":
49952
- return this.measureCol(node, constraints);
49458
+ return this.renderCol(node);
49953
49459
  case "Header":
49954
- return this.measureHeader(node, constraints);
49955
- case "Footer":
49956
- return this.measureFooter(node, constraints);
49460
+ return this.renderHeader(node);
49957
49461
  case "Main":
49958
- return this.measureMain(node, constraints);
49462
+ return this.renderMain(node);
49463
+ case "Footer":
49464
+ return this.renderFooter(node);
49959
49465
  case "Sidebar":
49960
- return this.measureSidebar(node, constraints);
49466
+ return this.renderSidebar(node);
49467
+ // Container nodes
49961
49468
  case "Card":
49962
- return this.measureCard(node, constraints);
49469
+ return this.renderCard(node);
49963
49470
  case "Modal":
49964
- return this.measureModal(node, constraints);
49965
- case "Title":
49966
- return this.measureTitle(node);
49471
+ return this.renderModal(node);
49472
+ // Text nodes
49967
49473
  case "Text":
49968
- return this.measureText(node);
49969
- case "Button":
49970
- return this.measureButton(node, constraints);
49474
+ return this.renderText(node);
49475
+ case "Title":
49476
+ return this.renderTitle(node);
49477
+ case "Link":
49478
+ return this.renderLink(node);
49479
+ // Input nodes
49971
49480
  case "Input":
49972
- return this.measureInput(node, constraints);
49481
+ return this.renderInput(node);
49973
49482
  case "Textarea":
49974
- return this.measureTextarea(node, constraints);
49483
+ return this.renderTextarea(node);
49975
49484
  case "Select":
49976
- return this.measureSelect(node, constraints);
49485
+ return this.renderSelect(node);
49977
49486
  case "Checkbox":
49978
- return this.measureCheckbox(node);
49487
+ return this.renderCheckbox(node);
49979
49488
  case "Radio":
49980
- return this.measureRadio(node);
49489
+ return this.renderRadio(node);
49981
49490
  case "Switch":
49982
- return this.measureSwitch(node);
49983
- case "Link":
49984
- return this.measureLink(node);
49491
+ return this.renderSwitch(node);
49492
+ // Button
49493
+ case "Button":
49494
+ return this.renderButton(node);
49495
+ // Display nodes
49985
49496
  case "Image":
49986
- return this.measureImage(node, constraints);
49497
+ return this.renderImage(node);
49987
49498
  case "Placeholder":
49988
- return this.measurePlaceholder(node, constraints);
49499
+ return this.renderPlaceholder(node);
49989
49500
  case "Avatar":
49990
- return this.measureAvatar(node, constraints);
49501
+ return this.renderAvatar(node);
49991
49502
  case "Badge":
49992
- return this.measureBadge(node);
49503
+ return this.renderBadge(node);
49504
+ // Data nodes
49993
49505
  case "Table":
49994
- return this.measureTable(node);
49506
+ return this.renderTable(node);
49995
49507
  case "List":
49996
- return this.measureList(node);
49508
+ return this.renderList(node);
49509
+ // Feedback nodes
49997
49510
  case "Alert":
49998
- return this.measureAlert(node, constraints);
49511
+ return this.renderAlert(node);
49999
49512
  case "Progress":
50000
- return this.measureProgress(node, constraints);
49513
+ return this.renderProgress(node);
50001
49514
  case "Spinner":
50002
- return this.measureSpinner(node);
49515
+ return this.renderSpinner(node);
49516
+ // Navigation nodes
50003
49517
  case "Nav":
50004
- return this.measureNav(node);
49518
+ return this.renderNav(node);
50005
49519
  case "Tabs":
50006
- return this.measureTabs(node);
49520
+ return this.renderTabs(node);
50007
49521
  case "Breadcrumb":
50008
- return this.measureBreadcrumb(node);
50009
- case "Icon":
50010
- return this.measureIcon(node);
50011
- case "Divider":
50012
- return this.measureDivider(node, constraints);
49522
+ return this.renderBreadcrumb(node);
50013
49523
  default:
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 };
49524
+ return `<!-- Unsupported: ${node.type} -->`;
50297
49525
  }
50298
49526
  }
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
- }
50323
49527
  // ===========================================
50324
- // Layout Phase
49528
+ // Layout Renderers
50325
49529
  // ===========================================
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
- }
50377
- }
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
- }
49530
+ renderRow(node) {
49531
+ const savedX = this.currentX;
49532
+ const savedY = this.currentY;
49533
+ const elements = [];
49534
+ const totalSpan = node.children.reduce((sum, child) => {
49535
+ if ("span" in child && typeof child.span === "number") {
49536
+ return sum + child.span;
50479
49537
  }
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;
49538
+ return sum + 1;
49539
+ }, 0);
49540
+ const colWidth = this.contentWidth / Math.max(totalSpan, 1);
49541
+ let maxHeight = 0;
50557
49542
  for (const child of node.children) {
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;
50571
- }
50572
- return {
50573
- x,
50574
- y,
50575
- width: constraints.maxWidth,
50576
- height: height + padding.top + padding.bottom,
50577
- node,
50578
- children,
50579
- padding
50580
- };
50581
- }
50582
- adjustChildrenY(box, offset) {
50583
- for (const child of box.children) {
50584
- child.y += offset;
50585
- this.adjustChildrenY(child, offset);
49543
+ const span = "span" in child && typeof child.span === "number" ? child.span : 1;
49544
+ const childWidth = colWidth * span;
49545
+ const startY = this.currentY;
49546
+ elements.push(this.renderNode(child));
49547
+ const childHeight = this.currentY - startY;
49548
+ maxHeight = Math.max(maxHeight, childHeight);
49549
+ this.currentX += childWidth;
49550
+ this.currentY = savedY;
50586
49551
  }
49552
+ this.currentX = savedX;
49553
+ this.currentY = savedY + maxHeight;
49554
+ return elements.join("\n");
50587
49555
  }
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;
49556
+ renderCol(node) {
49557
+ return node.children.map((child) => this.renderNode(child)).join("\n");
50596
49558
  }
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
- };
49559
+ renderHeader(node) {
49560
+ const height = 60;
49561
+ const x = this.currentX;
49562
+ const y = this.currentY;
49563
+ const savedY = this.currentY;
49564
+ this.currentY += 16;
49565
+ const children = node.children.map((c) => this.renderNode(c)).join("\n");
49566
+ this.currentY = savedY + height + 8;
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>`;
50625
49572
  }
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
- };
49573
+ renderMain(node) {
49574
+ return node.children.map((c) => this.renderNode(c)).join("\n");
50657
49575
  }
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
- };
49576
+ renderFooter(node) {
49577
+ const height = 60;
49578
+ const x = this.currentX;
49579
+ const y = this.currentY;
49580
+ const savedY = this.currentY;
49581
+ this.currentY += 16;
49582
+ const children = node.children.map((c) => this.renderNode(c)).join("\n");
49583
+ this.currentY = savedY + height + 8;
49584
+ return `
49585
+ <g transform="translate(${x}, ${y})">
49586
+ <rect width="${this.contentWidth}" height="${height}" fill="${this.theme.colors.background}" stroke="${this.theme.colors.border}" stroke-width="1"/>
49587
+ ${children}
49588
+ </g>`;
50684
49589
  }
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
- };
49590
+ renderSidebar(node) {
49591
+ const width = 200;
49592
+ const height = 300;
49593
+ const x = this.currentX;
49594
+ const y = this.currentY;
49595
+ const savedY = this.currentY;
49596
+ this.currentY += 16;
49597
+ const children = node.children.map((c) => this.renderNode(c)).join("\n");
49598
+ this.currentY = savedY + height + 8;
49599
+ return `
49600
+ <g transform="translate(${x}, ${y})">
49601
+ <rect width="${width}" height="${height}" fill="${this.theme.colors.background}" stroke="${this.theme.colors.border}" stroke-width="1"/>
49602
+ ${children}
49603
+ </g>`;
50716
49604
  }
50717
49605
  // ===========================================
50718
- // Render Phase
49606
+ // Container Renderers
50719
49607
  // ===========================================
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}/>`;
49608
+ renderCard(node) {
49609
+ const width = Math.min(300, this.contentWidth);
49610
+ const x = this.currentX;
49611
+ const y = this.currentY;
49612
+ const savedY = this.currentY;
49613
+ this.currentY += 16;
49614
+ let titleSvg = "";
50840
49615
  if (node.title) {
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>`;
49616
+ const titleFontSize = 16;
49617
+ titleSvg = `<text x="16" y="${titleFontSize + 12}" font-size="${titleFontSize}" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(node.title)}</text>`;
49618
+ this.currentY += titleFontSize + 8;
50843
49619
  }
50844
- svg += box.children.map((child) => this.renderBox(child)).join("\n");
50845
- svg += `</g>`;
50846
- return svg;
49620
+ const childStartY = this.currentY - savedY;
49621
+ const children = node.children.map((c) => this.renderNode(c)).join("\n");
49622
+ const contentHeight = Math.max(this.currentY - savedY, 100);
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>`;
50847
49632
  }
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"/>`;
49633
+ renderModal(node) {
49634
+ const width = 400;
49635
+ const height = 300;
49636
+ const x = (this.options.width - width) / 2;
49637
+ const y = (this.options.height - height) / 2;
49638
+ let titleSvg = "";
50855
49639
  if (node.title) {
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>`;
50857
- }
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;
50864
- }
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";
49640
+ titleSvg = `<text x="20" y="30" font-size="18" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(node.title)}</text>`;
50878
49641
  }
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>`;
49642
+ const savedX = this.currentX;
49643
+ const savedY = this.currentY;
49644
+ this.currentX = 20;
49645
+ this.currentY = 50;
49646
+ const children = node.children.map((c) => this.renderNode(c)).join("\n");
49647
+ this.currentX = savedX;
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>`;
50880
49658
  }
50881
- renderTextBox(box) {
50882
- const node = box.node;
49659
+ // ===========================================
49660
+ // Text Renderers
49661
+ // ===========================================
49662
+ renderText(node) {
50883
49663
  const fontSize = this.resolveFontSize(node.size);
50884
49664
  const fill = node.muted ? this.theme.colors.muted : this.theme.colors.foreground;
50885
49665
  const fontWeight = node.weight || "normal";
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>`;
49666
+ const y = this.currentY + fontSize;
49667
+ this.currentY += fontSize + 8;
49668
+ return `<text x="${this.currentX}" y="${y}" font-size="${fontSize}" font-weight="${fontWeight}" fill="${fill}">${this.escapeXml(node.content)}</text>`;
50897
49669
  }
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;
49670
+ renderTitle(node) {
49671
+ const level = node.level || 1;
49672
+ const fontSize = this.getTitleFontSize(level);
49673
+ const y = this.currentY + fontSize;
49674
+ this.currentY += fontSize + 12;
49675
+ return `<text x="${this.currentX}" y="${y}" font-size="${fontSize}" font-weight="600" fill="${this.theme.colors.foreground}">${this.escapeXml(node.content)}</text>`;
50932
49676
  }
50933
- renderInputBox(box) {
50934
- const node = box.node;
50935
- let y = box.y;
50936
- let svg = "<g>";
49677
+ renderLink(node) {
49678
+ const fontSize = 14;
49679
+ const y = this.currentY + fontSize;
49680
+ this.currentY += fontSize + 8;
49681
+ return `<text x="${this.currentX}" y="${y}" font-size="${fontSize}" fill="${this.theme.colors.primary}" text-decoration="underline">${this.escapeXml(node.content)}</text>`;
49682
+ }
49683
+ // ===========================================
49684
+ // Input Renderers
49685
+ // ===========================================
49686
+ renderInput(node) {
49687
+ const width = 280;
49688
+ const height = 40;
49689
+ const x = this.currentX;
49690
+ let y = this.currentY;
49691
+ let result = "";
50937
49692
  if (node.label) {
50938
- svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
49693
+ result += `<text x="${x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
50939
49694
  y += 24;
50940
49695
  }
50941
- const inputHeight = 36;
50942
49696
  const placeholder = node.placeholder || "";
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;
49697
+ result += `
49698
+ <g transform="translate(${x}, ${y})">
49699
+ <rect width="${width}" height="${height}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
49700
+ <text x="12" y="${height / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>
49701
+ </g>`;
49702
+ this.currentY = y + height + 12;
49703
+ return result;
50947
49704
  }
50948
- renderTextareaBox(box) {
50949
- const node = box.node;
50950
- let y = box.y;
50951
- let svg = "<g>";
49705
+ renderTextarea(node) {
49706
+ const width = 280;
49707
+ const height = 100;
49708
+ const x = this.currentX;
49709
+ let y = this.currentY;
49710
+ let result = "";
50952
49711
  if (node.label) {
50953
- svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
49712
+ result += `<text x="${x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
50954
49713
  y += 24;
50955
49714
  }
50956
- const textareaHeight = box.height - (node.label ? 24 : 0);
50957
49715
  const placeholder = node.placeholder || "";
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;
49716
+ result += `
49717
+ <g transform="translate(${x}, ${y})">
49718
+ <rect width="${width}" height="${height}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
49719
+ <text x="12" y="24" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>
49720
+ </g>`;
49721
+ this.currentY = y + height + 12;
49722
+ return result;
50962
49723
  }
50963
- renderSelectBox(box) {
50964
- const node = box.node;
50965
- let y = box.y;
50966
- let svg = "<g>";
49724
+ renderSelect(node) {
49725
+ const width = 280;
49726
+ const height = 40;
49727
+ const x = this.currentX;
49728
+ let y = this.currentY;
49729
+ let result = "";
50967
49730
  if (node.label) {
50968
- svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
49731
+ result += `<text x="${x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
50969
49732
  y += 24;
50970
49733
  }
50971
- const selectHeight = 40;
50972
49734
  const placeholder = node.placeholder || "Select...";
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;
49735
+ result += `
49736
+ <g transform="translate(${x}, ${y})">
49737
+ <rect width="${width}" height="${height}" rx="4" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>
49738
+ <text x="12" y="${height / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(placeholder)}</text>
49739
+ <path d="M${width - 24} ${height / 2 - 3} l6 6 l6 -6" fill="none" stroke="${this.theme.colors.muted}" stroke-width="1.5"/>
49740
+ </g>`;
49741
+ this.currentY = y + height + 12;
49742
+ return result;
50978
49743
  }
50979
- renderCheckboxBox(box) {
50980
- const node = box.node;
49744
+ renderCheckbox(node) {
49745
+ const x = this.currentX;
49746
+ const y = this.currentY;
50981
49747
  const size = 18;
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"/>`;
49748
+ let result = `
49749
+ <g transform="translate(${x}, ${y})">
49750
+ <rect width="${size}" height="${size}" rx="3" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
50984
49751
  if (node.checked) {
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"/>`;
49752
+ result += `<path d="M4 9 L7 12 L14 5" fill="none" stroke="${this.theme.colors.foreground}" stroke-width="2"/>`;
50986
49753
  }
50987
49754
  if (node.label) {
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>`;
49755
+ result += `<text x="${size + 8}" y="${size - 3}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
50989
49756
  }
50990
- svg += "</g>";
50991
- return svg;
49757
+ result += "</g>";
49758
+ this.currentY += size + 12;
49759
+ return result;
50992
49760
  }
50993
- renderRadioBox(box) {
50994
- const node = box.node;
49761
+ renderRadio(node) {
49762
+ const x = this.currentX;
49763
+ const y = this.currentY;
50995
49764
  const size = 18;
50996
49765
  const radius = size / 2;
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"/>`;
49766
+ let result = `
49767
+ <g transform="translate(${x}, ${y})">
49768
+ <circle cx="${radius}" cy="${radius}" r="${radius - 1}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
51001
49769
  if (node.checked) {
51002
- svg += `<circle cx="${cx}" cy="${cy}" r="${radius - 5}" fill="${this.theme.colors.foreground}"/>`;
49770
+ result += `<circle cx="${radius}" cy="${radius}" r="${radius - 5}" fill="${this.theme.colors.foreground}"/>`;
51003
49771
  }
51004
49772
  if (node.label) {
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>`;
49773
+ result += `<text x="${size + 8}" y="${size - 3}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
51006
49774
  }
51007
- svg += "</g>";
51008
- return svg;
49775
+ result += "</g>";
49776
+ this.currentY += size + 12;
49777
+ return result;
51009
49778
  }
51010
- renderSwitchBox(box) {
51011
- const node = box.node;
49779
+ renderSwitch(node) {
49780
+ const x = this.currentX;
49781
+ const y = this.currentY;
51012
49782
  const width = 44;
51013
49783
  const height = 24;
51014
49784
  const radius = height / 2;
51015
49785
  const isOn = node.checked;
51016
49786
  const bgColor = isOn ? this.theme.colors.primary : this.theme.colors.border;
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"/>`;
49787
+ const knobX = isOn ? width - radius : radius;
49788
+ let result = `
49789
+ <g transform="translate(${x}, ${y})">
49790
+ <rect width="${width}" height="${height}" rx="${radius}" fill="${bgColor}"/>
49791
+ <circle cx="${knobX}" cy="${radius}" r="${radius - 3}" fill="white"/>`;
51021
49792
  if (node.label) {
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>`;
49793
+ result += `<text x="${width + 8}" y="${height - 6}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
51023
49794
  }
51024
- svg += "</g>";
51025
- return svg;
49795
+ result += "</g>";
49796
+ this.currentY += height + 12;
49797
+ return result;
51026
49798
  }
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>`;
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;
49831
+ }
49832
+ this.currentY += height + 8;
49833
+ const strokeAttr = isOutline ? `stroke="${this.theme.colors.border}" stroke-width="1"` : "";
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>`;
49851
+ }
49852
+ /**
49853
+ * Render icon paths for SVG
49854
+ */
49855
+ renderIconPaths(data, x, y, size, color) {
49856
+ const scale = size / 24;
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>`;
51032
49862
  }
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>
49863
+ // ===========================================
49864
+ // Display Renderers
49865
+ // ===========================================
49866
+ renderImage(node) {
49867
+ const width = node.w && typeof node.w === "number" ? node.w : 200;
49868
+ const height = node.h && typeof node.h === "number" ? node.h : 150;
49869
+ const x = this.currentX;
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>
51040
49878
  </g>`;
51041
49879
  }
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>
49880
+ renderPlaceholder(node) {
49881
+ const width = node.w && typeof node.w === "number" ? node.w : 200;
49882
+ const height = node.h && typeof node.h === "number" ? node.h : 100;
49883
+ const x = this.currentX;
49884
+ const y = this.currentY;
49885
+ this.currentY += height + 12;
49886
+ return `
49887
+ <g transform="translate(${x}, ${y})">
49888
+ <rect width="${width}" height="${height}" fill="${this.theme.colors.muted}" stroke="${this.theme.colors.border}" stroke-width="1" stroke-dasharray="4,4"/>
49889
+ <text x="${width / 2}" y="${height / 2 + 5}" font-size="14" fill="${this.theme.colors.foreground}" text-anchor="middle">${this.escapeXml(node.label || "Placeholder")}</text>
51053
49890
  </g>`;
51054
49891
  }
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;
49892
+ renderAvatar(node) {
49893
+ const sizes = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };
49894
+ const size = sizes[node.size || "md"] || 40;
49895
+ const radius = size / 2;
49896
+ const x = this.currentX;
49897
+ const y = this.currentY;
51060
49898
  const initial = node.name ? node.name.charAt(0).toUpperCase() : "?";
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>
49899
+ this.currentY += size + 12;
49900
+ return `
49901
+ <g transform="translate(${x}, ${y})">
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>
51064
49904
  </g>`;
51065
49905
  }
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>
49906
+ renderBadge(node) {
49907
+ const content = node.content;
49908
+ const width = Math.max(24, content.length * 8 + 16);
49909
+ const height = 22;
49910
+ const x = this.currentX;
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>
51071
49919
  </g>`;
51072
49920
  }
51073
- renderTableBox(box) {
51074
- const node = box.node;
49921
+ // ===========================================
49922
+ // Data Renderers
49923
+ // ===========================================
49924
+ renderTable(node) {
51075
49925
  const columns = node.columns || [];
51076
49926
  const rows = node.rows || [];
51077
49927
  const rowCount = rows.length || 3;
51078
49928
  const colWidth = 120;
51079
49929
  const rowHeight = 40;
51080
- let svg = `<g>`;
51081
- svg += `<rect x="${box.x}" y="${box.y}" width="${columns.length * colWidth}" height="${rowHeight}" fill="${this.theme.colors.muted}"/>`;
49930
+ const x = this.currentX;
49931
+ const y = this.currentY;
49932
+ let svg = `<g transform="translate(${x}, ${y})">`;
49933
+ svg += `<rect width="${columns.length * colWidth}" height="${rowHeight}" fill="${this.theme.colors.muted}"/>`;
51082
49934
  columns.forEach((col, i) => {
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>`;
49935
+ svg += `<text x="${i * colWidth + 12}" y="${rowHeight / 2 + 5}" font-size="14" font-weight="600">${this.escapeXml(col)}</text>`;
51084
49936
  });
51085
- const displayRowCount = rows.length > 0 ? rows.length : rowCount;
49937
+ const displayRowCount = rows.length > 0 ? rows.length : Math.max(rowCount, 3);
51086
49938
  for (let rowIdx = 0; rowIdx < displayRowCount; rowIdx++) {
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"/>`;
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"/>`;
51089
49941
  columns.forEach((_, colIdx) => {
51090
49942
  const cellContent = rows[rowIdx] && rows[rowIdx][colIdx] ? String(typeof rows[rowIdx][colIdx] === "object" ? "..." : rows[rowIdx][colIdx]) : "\u2014";
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>`;
49943
+ svg += `<text x="${colIdx * colWidth + 12}" y="${rowY + rowHeight / 2 + 5}" font-size="14" fill="${this.theme.colors.muted}">${this.escapeXml(cellContent)}</text>`;
51092
49944
  });
51093
49945
  }
51094
49946
  svg += "</g>";
49947
+ this.currentY += (displayRowCount + 1) * rowHeight + 16;
51095
49948
  return svg;
51096
49949
  }
51097
- renderListBox(box) {
51098
- const node = box.node;
49950
+ renderList(node) {
49951
+ const x = this.currentX;
49952
+ let y = this.currentY;
51099
49953
  const items = node.items || [];
51100
49954
  const ordered = node.ordered || false;
51101
- let svg = "<g>";
49955
+ let svg = `<g transform="translate(${x}, ${y})">`;
51102
49956
  items.forEach((item, idx) => {
51103
49957
  const marker = ordered ? `${idx + 1}.` : "\u2022";
51104
49958
  const content = typeof item === "string" ? item : item.content;
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>`;
49959
+ svg += `<text x="0" y="${idx * 24 + 16}" font-size="14" fill="${this.theme.colors.foreground}">${marker} ${this.escapeXml(content)}</text>`;
51106
49960
  });
51107
49961
  svg += "</g>";
49962
+ this.currentY += items.length * 24 + 12;
51108
49963
  return svg;
51109
49964
  }
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>
49965
+ // ===========================================
49966
+ // Feedback Renderers
49967
+ // ===========================================
49968
+ renderAlert(node) {
49969
+ const width = Math.min(400, this.contentWidth);
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>
51115
49978
  </g>`;
51116
49979
  }
51117
- renderProgressBox(box) {
51118
- const node = box.node;
51119
- let y = box.y;
51120
- let svg = "<g>";
49980
+ renderProgress(node) {
49981
+ const width = 200;
49982
+ const height = 8;
49983
+ const x = this.currentX;
49984
+ let y = this.currentY;
49985
+ let result = "";
51121
49986
  if (node.label) {
51122
- svg += `<text x="${box.x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
49987
+ result += `<text x="${x}" y="${y + 14}" font-size="14" fill="${this.theme.colors.foreground}">${this.escapeXml(node.label)}</text>`;
51123
49988
  y += 24;
51124
49989
  }
51125
- const barHeight = 8;
51126
49990
  const value = node.value || 0;
51127
49991
  const max = node.max || 100;
51128
49992
  const percent = Math.min(100, Math.max(0, value / max * 100));
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;
49993
+ result += `
49994
+ <g transform="translate(${x}, ${y})">
49995
+ <rect width="${width}" height="${height}" rx="${height / 2}" fill="${this.theme.colors.muted}"/>
49996
+ <rect width="${width * percent / 100}" height="${height}" rx="${height / 2}" fill="${this.theme.colors.primary}"/>
49997
+ </g>`;
49998
+ this.currentY = y + height + 12;
49999
+ return result;
51133
50000
  }
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"/>
50001
+ renderSpinner(node) {
50002
+ const sizes = { xs: 16, sm: 20, md: 24, lg: 32, xl: 40 };
50003
+ const size = sizes[node.size || "md"] || 24;
50004
+ const x = this.currentX + size / 2;
50005
+ const y = this.currentY + size / 2;
50006
+ const radius = size / 2 - 2;
50007
+ this.currentY += size + 12;
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"/>
51141
50012
  </g>`;
51142
50013
  }
51143
- renderNavBox(box) {
51144
- const node = box.node;
51145
- const vertical = node.vertical || false;
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
- }
50014
+ // ===========================================
50015
+ // Navigation Renderers
50016
+ // ===========================================
50017
+ renderNav(node) {
51179
50018
  const items = node.items || [];
50019
+ const x = this.currentX;
50020
+ const y = this.currentY;
50021
+ const vertical = node.vertical || false;
50022
+ let svg = `<g transform="translate(${x}, ${y})">`;
51180
50023
  if (vertical) {
51181
50024
  items.forEach((item, idx) => {
51182
50025
  const label = typeof item === "string" ? item : item.label;
51183
50026
  const isActive = typeof item === "object" && item.active;
51184
50027
  const fill = isActive ? this.theme.colors.foreground : this.theme.colors.muted;
51185
- svg += `<text x="${box.x}" y="${box.y + idx * 32 + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
50028
+ svg += `<text x="0" y="${idx * 32 + 16}" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
51186
50029
  });
50030
+ this.currentY += items.length * 32 + 12;
51187
50031
  } else {
51188
50032
  let offsetX = 0;
51189
50033
  items.forEach((item) => {
51190
50034
  const label = typeof item === "string" ? item : item.label;
51191
50035
  const isActive = typeof item === "object" && item.active;
51192
50036
  const fill = isActive ? this.theme.colors.foreground : this.theme.colors.muted;
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;
50037
+ svg += `<text x="${offsetX}" y="16" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
50038
+ offsetX += label.length * 8 + 24;
51195
50039
  });
50040
+ this.currentY += 32;
51196
50041
  }
51197
50042
  svg += "</g>";
51198
50043
  return svg;
51199
50044
  }
51200
- renderTabsBox(box) {
51201
- const node = box.node;
50045
+ renderTabs(node) {
51202
50046
  const items = node.items || [];
50047
+ const x = this.currentX;
50048
+ const y = this.currentY;
51203
50049
  const tabHeight = 40;
51204
- let svg = "<g>";
50050
+ let svg = `<g transform="translate(${x}, ${y})">`;
51205
50051
  let offsetX = 0;
51206
- items.forEach((item, idx) => {
50052
+ items.forEach((item) => {
51207
50053
  const label = typeof item === "string" ? item : item;
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>`;
50054
+ const tabWidth = label.length * 10 + 24;
50055
+ svg += `<rect x="${offsetX}" width="${tabWidth}" height="${tabHeight}" fill="white" stroke="${this.theme.colors.border}" stroke-width="1"/>`;
50056
+ svg += `<text x="${offsetX + tabWidth / 2}" y="${tabHeight / 2 + 5}" font-size="14" text-anchor="middle">${this.escapeXml(label)}</text>`;
51212
50057
  offsetX += tabWidth;
51213
50058
  });
51214
50059
  svg += "</g>";
50060
+ this.currentY += tabHeight + 12;
51215
50061
  return svg;
51216
50062
  }
51217
- renderBreadcrumbBox(box) {
51218
- const node = box.node;
50063
+ renderBreadcrumb(node) {
51219
50064
  const items = node.items || [];
51220
50065
  const separator = "/";
51221
- let svg = "<g>";
50066
+ const x = this.currentX;
50067
+ const y = this.currentY;
50068
+ let svg = `<g transform="translate(${x}, ${y})">`;
51222
50069
  let offsetX = 0;
51223
50070
  items.forEach((item, idx) => {
51224
50071
  const label = typeof item === "string" ? item : item.label;
51225
50072
  const isLast = idx === items.length - 1;
51226
50073
  const fill = isLast ? this.theme.colors.foreground : this.theme.colors.muted;
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;
50074
+ svg += `<text x="${offsetX}" y="16" font-size="14" fill="${fill}">${this.escapeXml(label)}</text>`;
50075
+ offsetX += label.length * 8 + 8;
51229
50076
  if (!isLast) {
51230
- svg += `<text x="${box.x + offsetX}" y="${box.y + 16}" font-size="14" fill="${this.theme.colors.muted}">${separator}</text>`;
50077
+ svg += `<text x="${offsetX}" y="16" font-size="14" fill="${this.theme.colors.muted}">${separator}</text>`;
51231
50078
  offsetX += 16;
51232
50079
  }
51233
50080
  });
51234
50081
  svg += "</g>";
50082
+ this.currentY += 28;
51235
50083
  return svg;
51236
50084
  }
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
- }
51249
50085
  // ===========================================
51250
50086
  // Utility Methods
51251
50087
  // ===========================================
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;
50088
+ getFontSize(size) {
50089
+ const sizes = {
50090
+ xs: 12,
50091
+ sm: 14,
50092
+ base: 16,
50093
+ md: 16,
50094
+ lg: 18,
50095
+ xl: 20,
50096
+ "2xl": 24,
50097
+ "3xl": 30
50098
+ };
50099
+ return sizes[size] || 16;
51321
50100
  }
51322
50101
  resolveFontSize(size) {
51323
50102
  if (!size) return 16;
51324
50103
  if (typeof size === "string") {
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;
50104
+ return this.getFontSize(size);
51336
50105
  }
51337
50106
  if (typeof size === "object" && "value" in size) {
51338
50107
  if (size.unit === "px") return size.value;
@@ -51343,8 +50112,15 @@ var SvgRenderer = class {
51343
50112
  return 16;
51344
50113
  }
51345
50114
  getTitleFontSize(level) {
51346
- const sizes = { 1: 36, 2: 30, 3: 24, 4: 20, 5: 18, 6: 16 };
51347
- return sizes[level] || 20;
50115
+ const sizes = {
50116
+ 1: 32,
50117
+ 2: 28,
50118
+ 3: 24,
50119
+ 4: 20,
50120
+ 5: 18,
50121
+ 6: 16
50122
+ };
50123
+ return sizes[level] || 24;
51348
50124
  }
51349
50125
  escapeXml(str) {
51350
50126
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
@@ -51405,22 +50181,15 @@ function renderToSvg2(document, options = {}) {
51405
50181
  height = viewport.height;
51406
50182
  }
51407
50183
  }
50184
+ const padding = options.padding ?? 20;
51408
50185
  const background = options.background ?? "#ffffff";
51409
50186
  const { html, css } = render(document, { theme: "light" });
51410
50187
  const svg = `<?xml version="1.0" encoding="UTF-8"?>
51411
50188
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
51412
50189
  viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
51413
50190
  <rect width="100%" height="100%" fill="${background}"/>
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
- ">
50191
+ <foreignObject x="${padding}" y="${padding}" width="${width - padding * 2}" height="${height - padding * 2}">
50192
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%; overflow: hidden;">
51424
50193
  <style type="text/css">
51425
50194
  ${css}
51426
50195
  </style>