@wire-dsl/engine 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2174,8 +2174,8 @@ var IRStyleSchema = z.object({
2174
2174
  var IRNodeStyleSchema = z.object({
2175
2175
  padding: z.string().optional(),
2176
2176
  gap: z.string().optional(),
2177
- align: z.enum(["left", "center", "right", "justify"]).optional(),
2178
- justify: z.enum(["start", "center", "end"]).optional(),
2177
+ justify: z.enum(["start", "center", "end", "stretch", "spaceBetween", "spaceAround"]).optional(),
2178
+ align: z.enum(["start", "center", "end"]).optional(),
2179
2179
  background: z.string().optional()
2180
2180
  });
2181
2181
  var IRMetaSchema = z.object({
@@ -2461,6 +2461,9 @@ ${messages}`);
2461
2461
  if (layoutParams.gap !== void 0) {
2462
2462
  style.gap = String(layoutParams.gap);
2463
2463
  }
2464
+ if (layoutParams.justify !== void 0) {
2465
+ style.justify = layoutParams.justify;
2466
+ }
2464
2467
  if (layoutParams.align !== void 0) {
2465
2468
  style.align = layoutParams.align;
2466
2469
  }
@@ -2540,13 +2543,13 @@ ${messages}`);
2540
2543
  "Heading",
2541
2544
  "Text",
2542
2545
  "Label",
2546
+ "Paragraph",
2543
2547
  "Image",
2544
2548
  "Card",
2545
2549
  "Stat",
2546
2550
  "Topbar",
2547
2551
  "Table",
2548
2552
  "Chart",
2549
- "ChartPlaceholder",
2550
2553
  "Textarea",
2551
2554
  "Select",
2552
2555
  "Checkbox",
@@ -2881,19 +2884,19 @@ var ICON_SIZES_BY_DENSITY = {
2881
2884
  comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
2882
2885
  };
2883
2886
  var ICON_BUTTON_SIZES_BY_DENSITY = {
2884
- compact: { sm: 20, md: 24, lg: 32 },
2885
- normal: { sm: 24, md: 32, lg: 40 },
2886
- comfortable: { sm: 28, md: 40, lg: 48 }
2887
+ compact: { xs: 16, sm: 20, md: 24, lg: 32, xl: 40 },
2888
+ normal: { xs: 20, sm: 24, md: 32, lg: 40, xl: 48 },
2889
+ comfortable: { xs: 24, sm: 28, md: 40, lg: 48, xl: 56 }
2887
2890
  };
2888
2891
  var CONTROL_HEIGHTS_BY_DENSITY = {
2889
- compact: { sm: 28, md: 32, lg: 36 },
2890
- normal: { sm: 36, md: 40, lg: 48 },
2891
- comfortable: { sm: 40, md: 48, lg: 56 }
2892
+ compact: { xs: 24, sm: 28, md: 32, lg: 36, xl: 44 },
2893
+ normal: { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 },
2894
+ comfortable: { xs: 32, sm: 40, md: 48, lg: 56, xl: 64 }
2892
2895
  };
2893
2896
  var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
2894
- compact: { sm: 20, md: 24, lg: 32 },
2895
- normal: { sm: 24, md: 32, lg: 40 },
2896
- comfortable: { sm: 28, md: 40, lg: 48 }
2897
+ compact: { xs: 24, sm: 28, md: 32, lg: 36, xl: 44 },
2898
+ normal: { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 },
2899
+ comfortable: { xs: 32, sm: 40, md: 48, lg: 56, xl: 64 }
2897
2900
  };
2898
2901
  var CONTROL_PADDING_BY_DENSITY = {
2899
2902
  compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
@@ -3107,8 +3110,9 @@ var LayoutEngine = class {
3107
3110
  }
3108
3111
  });
3109
3112
  } else {
3110
- const align = node.style.align || "justify";
3111
- if (align === "justify") {
3113
+ const justify = node.style.justify || "stretch";
3114
+ const crossAlign = node.style.align || "start";
3115
+ if (justify === "stretch") {
3112
3116
  let currentX = x;
3113
3117
  const childWidth = this.calculateChildWidth(children.length, width, gap);
3114
3118
  let stackHeight = 0;
@@ -3130,43 +3134,44 @@ var LayoutEngine = class {
3130
3134
  });
3131
3135
  } else {
3132
3136
  const childWidths = [];
3137
+ const childHeights = [];
3133
3138
  const explicitHeightFlags = [];
3134
- const blockButtonIndices = /* @__PURE__ */ new Set();
3139
+ const flexIndices = /* @__PURE__ */ new Set();
3135
3140
  let stackHeight = 0;
3136
3141
  children.forEach((childRef, index) => {
3137
3142
  const childNode = this.nodes[childRef.ref];
3138
- let childWidth = this.getIntrinsicComponentWidth(childNode);
3139
- let childHeight = this.getComponentHeight();
3140
3143
  const hasExplicitHeight = childNode?.kind === "component" && !!childNode.props.height;
3141
3144
  const hasExplicitWidth = childNode?.kind === "component" && !!childNode.props.width;
3142
3145
  const isBlockButton = childNode?.kind === "component" && childNode.componentType === "Button" && !hasExplicitWidth && this.parseBooleanProp(childNode.props.block, false);
3143
- if (isBlockButton) {
3146
+ const isFlexContainer = !hasExplicitWidth && childNode?.kind === "container" && !this.containerHasIntrinsicWidth(childNode);
3147
+ let childWidth;
3148
+ if (isBlockButton || isFlexContainer) {
3144
3149
  childWidth = 0;
3145
- blockButtonIndices.add(index);
3150
+ flexIndices.add(index);
3146
3151
  } else if (hasExplicitWidth) {
3147
3152
  childWidth = Number(childNode.props.width);
3148
- }
3149
- if (hasExplicitHeight) {
3150
- childHeight = Number(childNode.props.height);
3153
+ } else {
3154
+ childWidth = this.getIntrinsicWidth(childNode, width);
3151
3155
  }
3152
3156
  childWidths.push(childWidth);
3157
+ childHeights.push(this.getComponentHeight());
3153
3158
  explicitHeightFlags.push(hasExplicitHeight);
3154
3159
  });
3155
3160
  const totalGapWidth = gap * Math.max(0, children.length - 1);
3156
- if (blockButtonIndices.size > 0) {
3161
+ if (flexIndices.size > 0) {
3157
3162
  const fixedWidth = childWidths.reduce((sum, w, idx) => {
3158
- return blockButtonIndices.has(idx) ? sum : sum + w;
3163
+ return flexIndices.has(idx) ? sum : sum + w;
3159
3164
  }, 0);
3160
3165
  const remainingWidth = width - totalGapWidth - fixedWidth;
3161
- const widthPerBlock = Math.max(1, remainingWidth / blockButtonIndices.size);
3162
- blockButtonIndices.forEach((index) => {
3163
- childWidths[index] = widthPerBlock;
3166
+ const widthPerFlex = Math.max(1, remainingWidth / flexIndices.size);
3167
+ flexIndices.forEach((idx) => {
3168
+ childWidths[idx] = widthPerFlex;
3164
3169
  });
3165
3170
  }
3166
3171
  children.forEach((childRef, index) => {
3167
3172
  const childNode = this.nodes[childRef.ref];
3168
- let childHeight = this.getComponentHeight();
3169
3173
  const childWidth = childWidths[index];
3174
+ let childHeight = this.getComponentHeight();
3170
3175
  if (explicitHeightFlags[index] && childNode?.kind === "component") {
3171
3176
  childHeight = Number(childNode.props.height);
3172
3177
  } else if (childNode?.kind === "container") {
@@ -3174,21 +3179,37 @@ var LayoutEngine = class {
3174
3179
  } else if (childNode?.kind === "component") {
3175
3180
  childHeight = this.getIntrinsicComponentHeight(childNode, childWidth);
3176
3181
  }
3182
+ childHeights[index] = childHeight;
3177
3183
  stackHeight = Math.max(stackHeight, childHeight);
3178
3184
  });
3179
3185
  const totalChildWidth = childWidths.reduce((sum, w) => sum + w, 0);
3180
3186
  const totalContentWidth = totalChildWidth + totalGapWidth;
3181
3187
  let startX = x;
3182
- if (align === "center") {
3188
+ let dynamicGap = gap;
3189
+ if (justify === "center") {
3183
3190
  startX = x + (width - totalContentWidth) / 2;
3184
- } else if (align === "right") {
3191
+ } else if (justify === "end") {
3185
3192
  startX = x + width - totalContentWidth;
3193
+ } else if (justify === "spaceBetween") {
3194
+ startX = x;
3195
+ dynamicGap = children.length > 1 ? (width - totalChildWidth) / (children.length - 1) : 0;
3196
+ } else if (justify === "spaceAround") {
3197
+ const spacing = children.length > 0 ? (width - totalChildWidth) / children.length : 0;
3198
+ startX = x + spacing / 2;
3199
+ dynamicGap = spacing;
3186
3200
  }
3187
3201
  let currentX = startX;
3188
3202
  children.forEach((childRef, index) => {
3189
3203
  const childWidth = childWidths[index];
3190
- this.calculateNode(childRef.ref, currentX, y, childWidth, stackHeight, "stack");
3191
- currentX += childWidth + gap;
3204
+ const childHeight = childHeights[index];
3205
+ let childY = y;
3206
+ if (crossAlign === "center") {
3207
+ childY = y + Math.round((stackHeight - childHeight) / 2);
3208
+ } else if (crossAlign === "end") {
3209
+ childY = y + stackHeight - childHeight;
3210
+ }
3211
+ this.calculateNode(childRef.ref, currentX, childY, childWidth, childHeight, "stack");
3212
+ currentX += childWidth + dynamicGap;
3192
3213
  });
3193
3214
  }
3194
3215
  }
@@ -3548,7 +3569,7 @@ var LayoutEngine = class {
3548
3569
  wrapTextToLines(text, maxWidth, fontSize) {
3549
3570
  const normalized = text.replace(/\r\n/g, "\n");
3550
3571
  const paragraphs = normalized.split("\n");
3551
- const charWidth = fontSize * 0.6;
3572
+ const charWidth = fontSize * 0.5;
3552
3573
  const safeWidth = Math.max(maxWidth, charWidth);
3553
3574
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
3554
3575
  const lines = [];
@@ -3586,12 +3607,14 @@ var LayoutEngine = class {
3586
3607
  getIntrinsicComponentHeight(node, availableWidth) {
3587
3608
  if (node.kind !== "component") return this.getComponentHeight();
3588
3609
  const controlSize = String(node.props.size || "md");
3610
+ const actionControlSize = String(node.props.size || "sm");
3589
3611
  const density = this.style.density || "normal";
3590
3612
  const inputControlHeight = resolveControlHeight(controlSize, density);
3591
- const actionControlHeight = resolveActionControlHeight(controlSize, density);
3613
+ const actionControlHeight = resolveActionControlHeight(actionControlSize, density);
3592
3614
  const controlLabelOffset = node.componentType === "Input" || node.componentType === "Textarea" || node.componentType === "Select" ? this.getControlLabelOffset(String(node.props.label || "")) : (node.componentType === "Button" || node.componentType === "IconButton") && this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
3593
3615
  if (node.componentType === "Image") {
3594
3616
  const placeholder = String(node.props.placeholder || "landscape");
3617
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
3595
3618
  const aspectRatios = {
3596
3619
  landscape: 16 / 9,
3597
3620
  portrait: 2 / 3,
@@ -3599,7 +3622,7 @@ var LayoutEngine = class {
3599
3622
  icon: 1,
3600
3623
  avatar: 1
3601
3624
  };
3602
- const ratio = aspectRatios[placeholder] || 16 / 9;
3625
+ const ratio = isCircle ? 1 : aspectRatios[placeholder] || 16 / 9;
3603
3626
  const explicitHeight = Number(node.props.height);
3604
3627
  if (!isNaN(explicitHeight) && explicitHeight > 0) {
3605
3628
  return explicitHeight;
@@ -3651,13 +3674,19 @@ var LayoutEngine = class {
3651
3674
  }
3652
3675
  return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
3653
3676
  }
3654
- if (node.componentType === "Text") {
3677
+ if (node.componentType === "Text" || node.componentType === "Paragraph") {
3655
3678
  const content = String(node.props.text || "");
3656
- const { fontSize, lineHeight } = this.getTextMetricsForDensity();
3679
+ const { fontSize: defaultFontSize, lineHeight } = this.getTextMetricsForDensity();
3680
+ const sizeProp = String(node.props.size || "");
3681
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
3682
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
3657
3683
  const lineHeightPx = Math.ceil(fontSize * lineHeight);
3658
3684
  const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
3659
3685
  const lines = this.wrapTextToLines(content, maxWidth, fontSize);
3660
3686
  const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
3687
+ if (sizeProp) {
3688
+ return wrappedHeight;
3689
+ }
3661
3690
  return Math.max(this.getComponentHeight(), wrappedHeight);
3662
3691
  }
3663
3692
  if (node.componentType === "Alert") {
@@ -3686,7 +3715,7 @@ var LayoutEngine = class {
3686
3715
  if (node.componentType === "Modal") return 300;
3687
3716
  if (node.componentType === "Card") return 120;
3688
3717
  if (node.componentType === "Stat") return 120;
3689
- if (node.componentType === "Chart" || node.componentType === "ChartPlaceholder") return 250;
3718
+ if (node.componentType === "Chart") return 250;
3690
3719
  if (node.componentType === "List") {
3691
3720
  const itemsFromProps = String(node.props.items || "").split(",").map((item) => item.trim()).filter(Boolean);
3692
3721
  const parsedItemsMock = Number(node.props.itemsMock ?? 4);
@@ -3697,7 +3726,14 @@ var LayoutEngine = class {
3697
3726
  const contentHeight = titleHeight + itemCount * itemHeight;
3698
3727
  return Math.max(this.getComponentHeight(), contentHeight);
3699
3728
  }
3700
- if (node.componentType === "Topbar") return 56;
3729
+ if (node.componentType === "Topbar") {
3730
+ const TOPBAR_HEIGHTS = { sm: 44, md: 56, lg: 72 };
3731
+ return TOPBAR_HEIGHTS[String(node.props.size || "md")] ?? 56;
3732
+ }
3733
+ if (node.componentType === "Tabs") {
3734
+ const TABS_HEIGHTS = { sm: 32, md: 44, lg: 52 };
3735
+ return TABS_HEIGHTS[String(node.props.size || "md")] ?? 44;
3736
+ }
3701
3737
  if (node.componentType === "Divider") return 1;
3702
3738
  if (node.componentType === "Separate") return this.getSeparateSize(node);
3703
3739
  if (node.componentType === "Input" || node.componentType === "Select") {
@@ -3706,11 +3742,57 @@ var LayoutEngine = class {
3706
3742
  if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
3707
3743
  return actionControlHeight + controlLabelOffset;
3708
3744
  }
3745
+ if (node.componentType === "Badge" || node.componentType === "Chip") {
3746
+ const BADGE_HEIGHTS = { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 };
3747
+ const badgeSize = String(node.props.size || "md");
3748
+ return BADGE_HEIGHTS[badgeSize] ?? 40;
3749
+ }
3709
3750
  return this.getComponentHeight();
3710
3751
  }
3711
3752
  getControlLabelOffset(label) {
3712
3753
  return label.trim().length > 0 ? 18 : 0;
3713
3754
  }
3755
+ /**
3756
+ * Returns true when a container's width can be calculated from its children
3757
+ * (i.e. it is a horizontal non-stretch stack). False means the container
3758
+ * behaves like `flex-grow:1` and should absorb remaining space.
3759
+ */
3760
+ containerHasIntrinsicWidth(node) {
3761
+ if (node.kind !== "container") return false;
3762
+ return node.containerType === "stack" && String(node.params.direction || "vertical") === "horizontal" && (node.style.justify || "stretch") !== "stretch";
3763
+ }
3764
+ /**
3765
+ * Returns the natural (intrinsic) width of any node — component or container.
3766
+ * For horizontal non-stretch containers the width is the sum of their children's
3767
+ * intrinsic widths plus gaps, capped at `availableWidth`. All other containers
3768
+ * are assumed to take the full available width (they stretch or grow).
3769
+ */
3770
+ getIntrinsicWidth(node, availableWidth) {
3771
+ if (!node) return 120;
3772
+ if (node.kind === "component") {
3773
+ return this.getIntrinsicComponentWidth(node);
3774
+ }
3775
+ if (node.kind === "container") {
3776
+ if (this.containerHasIntrinsicWidth(node)) {
3777
+ const gap = this.resolveSpacing(node.style.gap);
3778
+ const padding = this.resolveSpacing(node.style.padding);
3779
+ const innerAvailable = Math.max(0, availableWidth - padding * 2);
3780
+ const children = node.children ?? [];
3781
+ let total = padding * 2;
3782
+ children.forEach((childRef, idx) => {
3783
+ const child = this.nodes[childRef.ref];
3784
+ total += this.getIntrinsicWidth(child, innerAvailable);
3785
+ if (idx < children.length - 1) total += gap;
3786
+ });
3787
+ return Math.min(total, availableWidth);
3788
+ }
3789
+ return availableWidth;
3790
+ }
3791
+ if (node.kind === "instance") {
3792
+ return availableWidth;
3793
+ }
3794
+ return 120;
3795
+ }
3714
3796
  getIntrinsicComponentWidth(node) {
3715
3797
  if (!node || node.kind !== "component") {
3716
3798
  return 120;
@@ -3720,7 +3802,7 @@ var LayoutEngine = class {
3720
3802
  return resolveIconSize(size, this.style.density || "normal");
3721
3803
  }
3722
3804
  if (node.componentType === "IconButton") {
3723
- const size = String(node.props.size || "md");
3805
+ const size = String(node.props.size || "sm");
3724
3806
  const density = this.style.density || "normal";
3725
3807
  const baseSize = resolveIconButtonSize(size, density);
3726
3808
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -3778,7 +3860,13 @@ var LayoutEngine = class {
3778
3860
  }
3779
3861
  if (node.componentType === "Badge" || node.componentType === "Chip") {
3780
3862
  const text = String(node.props.text || "");
3781
- return Math.max(50, text.length * 7 + 16);
3863
+ const size = String(node.props.size || "md");
3864
+ const BADGE_CHAR_W = { xs: 6, sm: 6.5, md: 7, lg: 7.5, xl: 8.5 };
3865
+ const BADGE_PAD_X = { xs: 5, sm: 6, md: 8, lg: 10, xl: 14 };
3866
+ const customPadding = node.props.padding !== void 0 ? Number(node.props.padding) : void 0;
3867
+ const padX = customPadding !== void 0 && !isNaN(customPadding) ? customPadding : BADGE_PAD_X[size] ?? 8;
3868
+ const charW = BADGE_CHAR_W[size] ?? 7;
3869
+ return Math.max(padX * 4, text.length * charW + padX * 2);
3782
3870
  }
3783
3871
  return 120;
3784
3872
  }
@@ -4062,34 +4150,358 @@ MockDataGenerator.HEADER_TO_MOCK = {
4062
4150
  var ColorResolver = class {
4063
4151
  constructor() {
4064
4152
  this.customColors = {};
4065
- // Named colors palette
4153
+ // Named colors palette — Full Material Design + utility aliases
4066
4154
  this.namedColors = {
4067
- // Grayscale
4155
+ // ── Utility ──────────────────────────────────────────────────────────────
4068
4156
  white: "#FFFFFF",
4069
4157
  black: "#000000",
4070
- gray: "#6B7280",
4071
- slate: "#64748B",
4158
+ gray: "#9E9E9E",
4159
+ // alias for grey 500
4160
+ grey: "#9E9E9E",
4161
+ slate: "#607D8B",
4162
+ // alias for blue_grey 500
4072
4163
  zinc: "#71717A",
4073
- // Colors
4074
- red: "#EF4444",
4075
- orange: "#F97316",
4076
- yellow: "#EAB308",
4077
- lime: "#84CC16",
4078
- green: "#22C55E",
4164
+ // Extra well-known non-Material names (kept for backward compat)
4079
4165
  emerald: "#10B981",
4080
- teal: "#14B8A6",
4081
- cyan: "#06B6D4",
4082
- blue: "#3B82F6",
4083
- indigo: "#6366F1",
4084
4166
  violet: "#8B5CF6",
4085
- purple: "#A855F7",
4086
4167
  fuchsia: "#D946EF",
4087
- pink: "#EC4899",
4088
4168
  rose: "#F43F5E",
4089
- // Light variants
4090
- "red-light": "#FEE2E2",
4091
- "blue-light": "#DBEAFE",
4092
- "green-light": "#DCFCE7"
4169
+ // Light utility variants
4170
+ "red-light": "#FFCDD2",
4171
+ "blue-light": "#BBDEFB",
4172
+ "green-light": "#C8E6C9",
4173
+ // ── Red ──────────────────────────────────────────────────────────────────
4174
+ red: "#F44336",
4175
+ // 500
4176
+ red_50: "#FFEBEE",
4177
+ red_100: "#FFCDD2",
4178
+ red_200: "#EF9A9A",
4179
+ red_300: "#E57373",
4180
+ red_400: "#EF5350",
4181
+ red_500: "#F44336",
4182
+ red_600: "#E53935",
4183
+ red_700: "#D32F2F",
4184
+ red_800: "#C62828",
4185
+ red_900: "#B71C1C",
4186
+ red_A100: "#FF8A80",
4187
+ red_A200: "#FF5252",
4188
+ red_A400: "#FF1744",
4189
+ red_A700: "#D50000",
4190
+ // ── Pink ─────────────────────────────────────────────────────────────────
4191
+ pink: "#E91E63",
4192
+ // 500
4193
+ pink_50: "#FCE4EC",
4194
+ pink_100: "#F8BBD0",
4195
+ pink_200: "#F48FB1",
4196
+ pink_300: "#F06292",
4197
+ pink_400: "#EC407A",
4198
+ pink_500: "#E91E63",
4199
+ pink_600: "#D81B60",
4200
+ pink_700: "#C2185B",
4201
+ pink_800: "#AD1457",
4202
+ pink_900: "#880E4F",
4203
+ pink_A100: "#FF80AB",
4204
+ pink_A200: "#FF4081",
4205
+ pink_A400: "#F50057",
4206
+ pink_A700: "#C51162",
4207
+ // ── Purple ───────────────────────────────────────────────────────────────
4208
+ purple: "#9C27B0",
4209
+ // 500
4210
+ purple_50: "#F3E5F5",
4211
+ purple_100: "#E1BEE7",
4212
+ purple_200: "#CE93D8",
4213
+ purple_300: "#BA68C8",
4214
+ purple_400: "#AB47BC",
4215
+ purple_500: "#9C27B0",
4216
+ purple_600: "#8E24AA",
4217
+ purple_700: "#7B1FA2",
4218
+ purple_800: "#6A1B9A",
4219
+ purple_900: "#4A148C",
4220
+ purple_A100: "#EA80FC",
4221
+ purple_A200: "#E040FB",
4222
+ purple_A400: "#D500F9",
4223
+ purple_A700: "#AA00FF",
4224
+ // ── Deep Purple ──────────────────────────────────────────────────────────
4225
+ deep_purple: "#673AB7",
4226
+ // 500
4227
+ deep_purple_50: "#EDE7F6",
4228
+ deep_purple_100: "#D1C4E9",
4229
+ deep_purple_200: "#B39DDB",
4230
+ deep_purple_300: "#9575CD",
4231
+ deep_purple_400: "#7E57C2",
4232
+ deep_purple_500: "#673AB7",
4233
+ deep_purple_600: "#5E35B1",
4234
+ deep_purple_700: "#512DA8",
4235
+ deep_purple_800: "#4527A0",
4236
+ deep_purple_900: "#311B92",
4237
+ deep_purple_A100: "#B388FF",
4238
+ deep_purple_A200: "#7C4DFF",
4239
+ deep_purple_A400: "#651FFF",
4240
+ deep_purple_A700: "#6200EA",
4241
+ // ── Indigo ───────────────────────────────────────────────────────────────
4242
+ indigo: "#3F51B5",
4243
+ // 500
4244
+ indigo_50: "#E8EAF6",
4245
+ indigo_100: "#C5CAE9",
4246
+ indigo_200: "#9FA8DA",
4247
+ indigo_300: "#7986CB",
4248
+ indigo_400: "#5C6BC0",
4249
+ indigo_500: "#3F51B5",
4250
+ indigo_600: "#3949AB",
4251
+ indigo_700: "#303F9F",
4252
+ indigo_800: "#283593",
4253
+ indigo_900: "#1A237E",
4254
+ indigo_A100: "#8C9EFF",
4255
+ indigo_A200: "#536DFE",
4256
+ indigo_A400: "#3D5AFE",
4257
+ indigo_A700: "#304FFE",
4258
+ // ── Blue ─────────────────────────────────────────────────────────────────
4259
+ blue: "#2196F3",
4260
+ // 500
4261
+ blue_50: "#E3F2FD",
4262
+ blue_100: "#BBDEFB",
4263
+ blue_200: "#90CAF9",
4264
+ blue_300: "#64B5F6",
4265
+ blue_400: "#42A5F5",
4266
+ blue_500: "#2196F3",
4267
+ blue_600: "#1E88E5",
4268
+ blue_700: "#1976D2",
4269
+ blue_800: "#1565C0",
4270
+ blue_900: "#0D47A1",
4271
+ blue_A100: "#82B1FF",
4272
+ blue_A200: "#448AFF",
4273
+ blue_A400: "#2979FF",
4274
+ blue_A700: "#2962FF",
4275
+ // ── Light Blue ───────────────────────────────────────────────────────────
4276
+ light_blue: "#03A9F4",
4277
+ // 500
4278
+ light_blue_50: "#E1F5FE",
4279
+ light_blue_100: "#B3E5FC",
4280
+ light_blue_200: "#81D4FA",
4281
+ light_blue_300: "#4FC3F7",
4282
+ light_blue_400: "#29B6F6",
4283
+ light_blue_500: "#03A9F4",
4284
+ light_blue_600: "#039BE5",
4285
+ light_blue_700: "#0288D1",
4286
+ light_blue_800: "#0277BD",
4287
+ light_blue_900: "#01579B",
4288
+ light_blue_A100: "#80D8FF",
4289
+ light_blue_A200: "#40C4FF",
4290
+ light_blue_A400: "#00B0FF",
4291
+ light_blue_A700: "#0091EA",
4292
+ // ── Cyan ─────────────────────────────────────────────────────────────────
4293
+ cyan: "#00BCD4",
4294
+ // 500
4295
+ cyan_50: "#E0F7FA",
4296
+ cyan_100: "#B2EBF2",
4297
+ cyan_200: "#80DEEA",
4298
+ cyan_300: "#4DD0E1",
4299
+ cyan_400: "#26C6DA",
4300
+ cyan_500: "#00BCD4",
4301
+ cyan_600: "#00ACC1",
4302
+ cyan_700: "#0097A7",
4303
+ cyan_800: "#00838F",
4304
+ cyan_900: "#006064",
4305
+ cyan_A100: "#84FFFF",
4306
+ cyan_A200: "#18FFFF",
4307
+ cyan_A400: "#00E5FF",
4308
+ cyan_A700: "#00B8D4",
4309
+ // ── Teal ─────────────────────────────────────────────────────────────────
4310
+ teal: "#009688",
4311
+ // 500
4312
+ teal_50: "#E0F2F1",
4313
+ teal_100: "#B2DFDB",
4314
+ teal_200: "#80CBC4",
4315
+ teal_300: "#4DB6AC",
4316
+ teal_400: "#26A69A",
4317
+ teal_500: "#009688",
4318
+ teal_600: "#00897B",
4319
+ teal_700: "#00796B",
4320
+ teal_800: "#00695C",
4321
+ teal_900: "#004D40",
4322
+ teal_A100: "#A7FFEB",
4323
+ teal_A200: "#64FFDA",
4324
+ teal_A400: "#1DE9B6",
4325
+ teal_A700: "#00BFA5",
4326
+ // ── Green ────────────────────────────────────────────────────────────────
4327
+ green: "#4CAF50",
4328
+ // 500
4329
+ green_50: "#E8F5E9",
4330
+ green_100: "#C8E6C9",
4331
+ green_200: "#A5D6A7",
4332
+ green_300: "#81C784",
4333
+ green_400: "#66BB6A",
4334
+ green_500: "#4CAF50",
4335
+ green_600: "#43A047",
4336
+ green_700: "#388E3C",
4337
+ green_800: "#2E7D32",
4338
+ green_900: "#1B5E20",
4339
+ green_A100: "#B9F6CA",
4340
+ green_A200: "#69F0AE",
4341
+ green_A400: "#00E676",
4342
+ green_A700: "#00C853",
4343
+ // ── Light Green ──────────────────────────────────────────────────────────
4344
+ light_green: "#8BC34A",
4345
+ // 500
4346
+ light_green_50: "#F1F8E9",
4347
+ light_green_100: "#DCEDC8",
4348
+ light_green_200: "#C5E1A5",
4349
+ light_green_300: "#AED581",
4350
+ light_green_400: "#9CCC65",
4351
+ light_green_500: "#8BC34A",
4352
+ light_green_600: "#7CB342",
4353
+ light_green_700: "#689F38",
4354
+ light_green_800: "#558B2F",
4355
+ light_green_900: "#33691E",
4356
+ light_green_A100: "#CCFF90",
4357
+ light_green_A200: "#B2FF59",
4358
+ light_green_A400: "#76FF03",
4359
+ light_green_A700: "#64DD17",
4360
+ // ── Lime ─────────────────────────────────────────────────────────────────
4361
+ lime: "#CDDC39",
4362
+ // 500
4363
+ lime_50: "#F9FBE7",
4364
+ lime_100: "#F0F4C3",
4365
+ lime_200: "#E6EE9C",
4366
+ lime_300: "#DCE775",
4367
+ lime_400: "#D4E157",
4368
+ lime_500: "#CDDC39",
4369
+ lime_600: "#C0CA33",
4370
+ lime_700: "#AFB42B",
4371
+ lime_800: "#9E9D24",
4372
+ lime_900: "#827717",
4373
+ lime_A100: "#F4FF81",
4374
+ lime_A200: "#EEFF41",
4375
+ lime_A400: "#C6FF00",
4376
+ lime_A700: "#AEEA00",
4377
+ // ── Yellow ───────────────────────────────────────────────────────────────
4378
+ yellow: "#FFEB3B",
4379
+ // 500
4380
+ yellow_50: "#FFFDE7",
4381
+ yellow_100: "#FFF9C4",
4382
+ yellow_200: "#FFF59D",
4383
+ yellow_300: "#FFF176",
4384
+ yellow_400: "#FFEE58",
4385
+ yellow_500: "#FFEB3B",
4386
+ yellow_600: "#FDD835",
4387
+ yellow_700: "#F9A825",
4388
+ yellow_800: "#F57F17",
4389
+ yellow_900: "#F57F17",
4390
+ yellow_A100: "#FFFF8D",
4391
+ yellow_A200: "#FFFF00",
4392
+ yellow_A400: "#FFEA00",
4393
+ yellow_A700: "#FFD600",
4394
+ // ── Amber ────────────────────────────────────────────────────────────────
4395
+ amber: "#FFC107",
4396
+ // 500
4397
+ amber_50: "#FFF8E1",
4398
+ amber_100: "#FFECB3",
4399
+ amber_200: "#FFE082",
4400
+ amber_300: "#FFD54F",
4401
+ amber_400: "#FFCA28",
4402
+ amber_500: "#FFC107",
4403
+ amber_600: "#FFB300",
4404
+ amber_700: "#FFA000",
4405
+ amber_800: "#FF8F00",
4406
+ amber_900: "#FF6F00",
4407
+ amber_A100: "#FFE57F",
4408
+ amber_A200: "#FFD740",
4409
+ amber_A400: "#FFC400",
4410
+ amber_A700: "#FFAB00",
4411
+ // ── Orange ───────────────────────────────────────────────────────────────
4412
+ orange: "#FF9800",
4413
+ // 500
4414
+ orange_50: "#FFF3E0",
4415
+ orange_100: "#FFE0B2",
4416
+ orange_200: "#FFCC80",
4417
+ orange_300: "#FFB74D",
4418
+ orange_400: "#FFA726",
4419
+ orange_500: "#FF9800",
4420
+ orange_600: "#FB8C00",
4421
+ orange_700: "#F57C00",
4422
+ orange_800: "#EF6C00",
4423
+ orange_900: "#E65100",
4424
+ orange_A100: "#FFD180",
4425
+ orange_A200: "#FFAB40",
4426
+ orange_A400: "#FF9100",
4427
+ orange_A700: "#FF6D00",
4428
+ // ── Deep Orange ──────────────────────────────────────────────────────────
4429
+ deep_orange: "#FF5722",
4430
+ // 500
4431
+ deep_orange_50: "#FBE9E7",
4432
+ deep_orange_100: "#FFCCBC",
4433
+ deep_orange_200: "#FFAB91",
4434
+ deep_orange_300: "#FF8A65",
4435
+ deep_orange_400: "#FF7043",
4436
+ deep_orange_500: "#FF5722",
4437
+ deep_orange_600: "#F4511E",
4438
+ deep_orange_700: "#E64A19",
4439
+ deep_orange_800: "#D84315",
4440
+ deep_orange_900: "#BF360C",
4441
+ deep_orange_A100: "#FF9E80",
4442
+ deep_orange_A200: "#FF6E40",
4443
+ deep_orange_A400: "#FF3D00",
4444
+ deep_orange_A700: "#DD2C00",
4445
+ // ── Brown ────────────────────────────────────────────────────────────────
4446
+ brown: "#795548",
4447
+ // 500
4448
+ brown_50: "#EFEBE9",
4449
+ brown_100: "#D7CCC8",
4450
+ brown_200: "#BCAAA4",
4451
+ brown_300: "#A1887F",
4452
+ brown_400: "#8D6E63",
4453
+ brown_500: "#795548",
4454
+ brown_600: "#6D4C41",
4455
+ brown_700: "#5D4037",
4456
+ brown_800: "#4E342E",
4457
+ brown_900: "#3E2723",
4458
+ // ── Grey ─────────────────────────────────────────────────────────────────
4459
+ grey_50: "#FAFAFA",
4460
+ grey_100: "#F5F5F5",
4461
+ grey_200: "#EEEEEE",
4462
+ grey_300: "#E0E0E0",
4463
+ grey_400: "#BDBDBD",
4464
+ grey_500: "#9E9E9E",
4465
+ grey_600: "#757575",
4466
+ grey_700: "#616161",
4467
+ grey_800: "#424242",
4468
+ grey_900: "#212121",
4469
+ // aliases
4470
+ gray_50: "#FAFAFA",
4471
+ gray_100: "#F5F5F5",
4472
+ gray_200: "#EEEEEE",
4473
+ gray_300: "#E0E0E0",
4474
+ gray_400: "#BDBDBD",
4475
+ gray_500: "#9E9E9E",
4476
+ gray_600: "#757575",
4477
+ gray_700: "#616161",
4478
+ gray_800: "#424242",
4479
+ gray_900: "#212121",
4480
+ // ── Blue Grey ────────────────────────────────────────────────────────────
4481
+ blue_grey: "#607D8B",
4482
+ // 500
4483
+ blue_grey_50: "#ECEFF1",
4484
+ blue_grey_100: "#CFD8DC",
4485
+ blue_grey_200: "#B0BEC5",
4486
+ blue_grey_300: "#90A4AE",
4487
+ blue_grey_400: "#78909C",
4488
+ blue_grey_500: "#607D8B",
4489
+ blue_grey_600: "#546E7A",
4490
+ blue_grey_700: "#455A64",
4491
+ blue_grey_800: "#37474F",
4492
+ blue_grey_900: "#263238",
4493
+ // aliases
4494
+ blue_gray: "#607D8B",
4495
+ blue_gray_50: "#ECEFF1",
4496
+ blue_gray_100: "#CFD8DC",
4497
+ blue_gray_200: "#B0BEC5",
4498
+ blue_gray_300: "#90A4AE",
4499
+ blue_gray_400: "#78909C",
4500
+ blue_gray_500: "#607D8B",
4501
+ blue_gray_600: "#546E7A",
4502
+ blue_gray_700: "#455A64",
4503
+ blue_gray_800: "#37474F",
4504
+ blue_gray_900: "#263238"
4093
4505
  };
4094
4506
  }
4095
4507
  /**
@@ -4737,7 +5149,8 @@ var SVGRenderer = class {
4737
5149
  if (node.containerType === "split") {
4738
5150
  this.renderSplitDecoration(node, pos, containerGroup);
4739
5151
  }
4740
- if (node.children.length === 0 && this.options.showDiagnostics) {
5152
+ const isCellContainer = node.meta?.source === "cell";
5153
+ if (node.children.length === 0 && this.options.showDiagnostics && !isCellContainer) {
4741
5154
  containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
4742
5155
  } else {
4743
5156
  node.children.forEach((childRef) => {
@@ -4784,7 +5197,6 @@ var SVGRenderer = class {
4784
5197
  case "Table":
4785
5198
  return this.renderTable(node, pos);
4786
5199
  case "Chart":
4787
- case "ChartPlaceholder":
4788
5200
  return this.renderChartPlaceholder(node, pos);
4789
5201
  case "Breadcrumbs":
4790
5202
  return this.renderBreadcrumbs(node, pos);
@@ -4793,6 +5205,8 @@ var SVGRenderer = class {
4793
5205
  // Text/Content components
4794
5206
  case "Text":
4795
5207
  return this.renderText(node, pos);
5208
+ case "Paragraph":
5209
+ return this.renderParagraph(node, pos);
4796
5210
  case "Label":
4797
5211
  return this.renderLabel(node, pos);
4798
5212
  case "Code":
@@ -4872,7 +5286,7 @@ var SVGRenderer = class {
4872
5286
  renderButton(node, pos) {
4873
5287
  const text = String(node.props.text || "Button");
4874
5288
  const variant = String(node.props.variant || "default");
4875
- const size = String(node.props.size || "md");
5289
+ const size = String(node.props.size || "sm");
4876
5290
  const disabled = this.parseBooleanProp(node.props.disabled, false);
4877
5291
  const density = this.ir.project.style.density || "normal";
4878
5292
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -4947,7 +5361,7 @@ var SVGRenderer = class {
4947
5361
  renderLink(node, pos) {
4948
5362
  const text = String(node.props.text || "Link");
4949
5363
  const variant = String(node.props.variant || "primary");
4950
- const size = String(node.props.size || "md");
5364
+ const size = String(node.props.size || "sm");
4951
5365
  const density = this.ir.project.style.density || "normal";
4952
5366
  const fontSize = this.tokens.button.fontSize;
4953
5367
  const fontWeight = this.tokens.button.fontWeight;
@@ -5053,7 +5467,33 @@ var SVGRenderer = class {
5053
5467
  const variant = String(node.props.variant || "default");
5054
5468
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
5055
5469
  const showBorder = this.parseBooleanProp(node.props.border, false);
5056
- const showBackground = this.parseBooleanProp(node.props.background, false);
5470
+ const bgPropStr = String(node.props.background ?? "");
5471
+ let resolvedBg = null;
5472
+ if (bgPropStr === "true") {
5473
+ resolvedBg = this.renderTheme.cardBg;
5474
+ } else if (bgPropStr && bgPropStr !== "false") {
5475
+ if (bgPropStr.startsWith("#") || bgPropStr.startsWith("rgb")) {
5476
+ resolvedBg = bgPropStr;
5477
+ } else if (this.colorResolver.hasColor(bgPropStr)) {
5478
+ resolvedBg = this.colorResolver.resolveColor(bgPropStr, this.renderTheme.cardBg);
5479
+ }
5480
+ }
5481
+ const showBackground = resolvedBg !== null;
5482
+ const colorPropStr = String(node.props.color ?? "");
5483
+ let titleColor = this.renderTheme.text;
5484
+ let subtitleColor = this.renderTheme.textMuted;
5485
+ if (colorPropStr && colorPropStr !== "false") {
5486
+ let resolvedTitleColor = null;
5487
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
5488
+ resolvedTitleColor = colorPropStr;
5489
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
5490
+ resolvedTitleColor = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
5491
+ }
5492
+ if (resolvedTitleColor) {
5493
+ titleColor = resolvedTitleColor;
5494
+ subtitleColor = this.hexToRgba(resolvedTitleColor, 0.65);
5495
+ }
5496
+ }
5057
5497
  const radiusMap = {
5058
5498
  none: 0,
5059
5499
  sm: 4,
@@ -5065,7 +5505,7 @@ var SVGRenderer = class {
5065
5505
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
5066
5506
  let svg = `<g${this.getDataNodeId(node)}>`;
5067
5507
  if (showBorder || showBackground) {
5068
- const bg = showBackground ? this.renderTheme.cardBg : "none";
5508
+ const bg = resolvedBg ?? "none";
5069
5509
  const stroke = showBorder ? this.renderTheme.border : "none";
5070
5510
  svg += `
5071
5511
  <rect x="${pos.x}" y="${pos.y}"
@@ -5081,13 +5521,13 @@ var SVGRenderer = class {
5081
5521
  font-family="Arial, Helvetica, sans-serif"
5082
5522
  font-size="18"
5083
5523
  font-weight="600"
5084
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5524
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5085
5525
  if (topbar.hasSubtitle) {
5086
5526
  svg += `
5087
5527
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
5088
5528
  font-family="Arial, Helvetica, sans-serif"
5089
5529
  font-size="13"
5090
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5530
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5091
5531
  }
5092
5532
  if (topbar.leftIcon) {
5093
5533
  svg += `
@@ -5568,10 +6008,16 @@ var SVGRenderer = class {
5568
6008
  // ============================================================================
5569
6009
  renderText(node, pos) {
5570
6010
  const text = String(node.props.text || "Text content");
5571
- const fontSize = this.tokens.text.fontSize;
6011
+ const sizeProp = String(node.props.size || "");
6012
+ const defaultFontSize = this.tokens.text.fontSize;
6013
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
6014
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
5572
6015
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
6016
+ const bold = this.parseBooleanProp(node.props.bold, false);
6017
+ const italic = this.parseBooleanProp(node.props.italic, false);
5573
6018
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
5574
- const firstLineY = pos.y + fontSize;
6019
+ const totalTextHeight = lines.length * lineHeightPx;
6020
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
5575
6021
  const tspans = lines.map(
5576
6022
  (line, index) => `<tspan x="${pos.x}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
5577
6023
  ).join("");
@@ -5579,13 +6025,54 @@ var SVGRenderer = class {
5579
6025
  <text x="${pos.x}" y="${firstLineY}"
5580
6026
  font-family="Arial, Helvetica, sans-serif"
5581
6027
  font-size="${fontSize}"
6028
+ font-weight="${bold ? "700" : "400"}"
6029
+ font-style="${italic ? "italic" : "normal"}"
5582
6030
  fill="${this.renderTheme.text}">${tspans}</text>
5583
6031
  </g>`;
5584
6032
  }
6033
+ renderParagraph(node, pos) {
6034
+ const text = String(node.props.text || "");
6035
+ const sizeProp = String(node.props.size || "");
6036
+ const defaultFontSize = this.tokens.text.fontSize;
6037
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
6038
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
6039
+ const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
6040
+ const bold = this.parseBooleanProp(node.props.bold, false);
6041
+ const italic = this.parseBooleanProp(node.props.italic, false);
6042
+ const align = String(node.props.align || "left");
6043
+ const lines = this.wrapTextToLines(text, pos.width, fontSize);
6044
+ const totalTextHeight = lines.length * lineHeightPx;
6045
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
6046
+ let textX;
6047
+ let textAnchor;
6048
+ if (align === "center") {
6049
+ textX = pos.x + pos.width / 2;
6050
+ textAnchor = "middle";
6051
+ } else if (align === "right") {
6052
+ textX = pos.x + pos.width;
6053
+ textAnchor = "end";
6054
+ } else {
6055
+ textX = pos.x;
6056
+ textAnchor = "start";
6057
+ }
6058
+ const tspans = lines.map(
6059
+ (line, index) => `<tspan x="${textX}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
6060
+ ).join("");
6061
+ return `<g${this.getDataNodeId(node)}>
6062
+ <text x="${textX}" y="${firstLineY}"
6063
+ font-family="Arial, Helvetica, sans-serif"
6064
+ font-size="${fontSize}"
6065
+ font-weight="${bold ? "700" : "400"}"
6066
+ font-style="${italic ? "italic" : "normal"}"
6067
+ fill="${this.renderTheme.text}"
6068
+ text-anchor="${textAnchor}">${tspans}</text>
6069
+ </g>`;
6070
+ }
5585
6071
  renderLabel(node, pos) {
5586
6072
  const text = String(node.props.text || "Label");
6073
+ const textY = pos.y + Math.round(pos.height / 2) + 4;
5587
6074
  return `<g${this.getDataNodeId(node)}>
5588
- <text x="${pos.x}" y="${pos.y + 12}"
6075
+ <text x="${pos.x}" y="${textY}"
5589
6076
  font-family="Arial, Helvetica, sans-serif"
5590
6077
  font-size="12"
5591
6078
  fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
@@ -5823,34 +6310,145 @@ var SVGRenderer = class {
5823
6310
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
5824
6311
  const activeProp = node.props.active ?? 0;
5825
6312
  const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
5826
- const accentColor = this.resolveAccentColor();
6313
+ const variant = String(node.props.variant || "default");
6314
+ const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
6315
+ const radiusMap = { none: 0, sm: 4, md: 6, lg: 10, full: 20 };
6316
+ const tabRadius = radiusMap[String(node.props.radius || "md")] ?? 6;
6317
+ const sizeMap = { sm: 32, md: 44, lg: 52 };
6318
+ const tabHeight = pos.height > 0 ? pos.height : sizeMap[String(node.props.size || "md")] ?? 44;
6319
+ const fontSize = 13;
6320
+ const textY = pos.y + Math.round(tabHeight / 2) + Math.round(fontSize * 0.4);
6321
+ const iconsStr = String(node.props.icons || "");
6322
+ const iconList = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
6323
+ const isFlat = this.parseBooleanProp(node.props.flat, false);
6324
+ const showBorder = this.parseBooleanProp(node.props.border, true);
5827
6325
  const tabWidth = pos.width / tabs.length;
5828
- let svg = `<g${this.getDataNodeId(node)}>
5829
- <!-- Tab headers -->`;
6326
+ const colorPropStr = String(node.props.color ?? "");
6327
+ let textColorOverride = null;
6328
+ if (colorPropStr && colorPropStr !== "false") {
6329
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
6330
+ textColorOverride = colorPropStr;
6331
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
6332
+ textColorOverride = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
6333
+ }
6334
+ }
6335
+ const activeTextColor = textColorOverride ?? (isFlat ? accentColor : "white");
6336
+ const inactiveTextColor = textColorOverride ? this.hexToRgba(textColorOverride, 0.55) : this.renderTheme.textMuted;
6337
+ const hasRadius = tabRadius > 0;
6338
+ const clipId = hasRadius ? `tc${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 12) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
6339
+ let svg = "";
6340
+ if (hasRadius) {
6341
+ svg += `<defs><clipPath id="${clipId}"><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="${tabRadius}"/></clipPath></defs>`;
6342
+ }
6343
+ svg += `<g${this.getDataNodeId(node)}>`;
6344
+ if (hasRadius) {
6345
+ svg += `<g clip-path="url(#${clipId})">`;
6346
+ }
6347
+ if (!isFlat) {
6348
+ svg += `
6349
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${tabHeight}"
6350
+ fill="${this.renderTheme.bg}"/>`;
6351
+ }
5830
6352
  tabs.forEach((tab, i) => {
5831
6353
  const tabX = pos.x + i * tabWidth;
5832
6354
  const isActive = i === activeIndex;
5833
- svg += `
5834
- <rect x="${tabX}" y="${pos.y}"
5835
- width="${tabWidth}" height="44"
5836
- fill="${isActive ? accentColor : "transparent"}"
5837
- stroke="${isActive ? "none" : this.renderTheme.border}"
5838
- stroke-width="1"/>
5839
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
5840
- font-family="Arial, Helvetica, sans-serif"
5841
- font-size="13"
5842
- font-weight="${isActive ? "600" : "500"}"
5843
- fill="${isActive ? "white" : this.renderTheme.text}"
6355
+ const iconName = iconList[i] || "";
6356
+ const tabIconSvg = iconName ? getIcon(iconName) : null;
6357
+ const iconSize = 14;
6358
+ const iconGap = 4;
6359
+ const charW = fontSize * 0.58;
6360
+ const textEst = tab.length * charW;
6361
+ const totalW = tabIconSvg ? iconSize + iconGap + textEst : textEst;
6362
+ const groupX = tabX + Math.round((tabWidth - totalW) / 2);
6363
+ const iconOffY = pos.y + Math.round((tabHeight - iconSize) / 2);
6364
+ if (isFlat) {
6365
+ if (isActive) {
6366
+ svg += `
6367
+ <rect x="${tabX + 6}" y="${pos.y + tabHeight - 3}"
6368
+ width="${tabWidth - 12}" height="3"
6369
+ rx="1.5"
6370
+ fill="${accentColor}"/>`;
6371
+ }
6372
+ if (tabIconSvg) {
6373
+ svg += `
6374
+ <g transform="translate(${groupX}, ${iconOffY})">
6375
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
6376
+ stroke="${isActive ? accentColor : this.renderTheme.textMuted}"
6377
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6378
+ ${this.extractSvgContent(tabIconSvg)}
6379
+ </svg>
6380
+ </g>
6381
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
6382
+ font-family="Arial, Helvetica, sans-serif"
6383
+ font-size="${fontSize}"
6384
+ font-weight="${isActive ? "600" : "500"}"
6385
+ fill="${isActive ? activeTextColor : inactiveTextColor}">${this.escapeXml(tab)}</text>`;
6386
+ } else {
6387
+ svg += `
6388
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
6389
+ font-family="Arial, Helvetica, sans-serif"
6390
+ font-size="${fontSize}"
6391
+ font-weight="${isActive ? "600" : "500"}"
6392
+ fill="${isActive ? activeTextColor : inactiveTextColor}"
5844
6393
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
6394
+ }
6395
+ } else {
6396
+ svg += `
6397
+ <rect x="${tabX}" y="${pos.y}"
6398
+ width="${tabWidth}" height="${tabHeight}"
6399
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
6400
+ fill="${isActive ? accentColor : "transparent"}"
6401
+ ${!isActive && showBorder ? `stroke="${this.renderTheme.border}" stroke-width="0.5"` : ""}/>`;
6402
+ if (tabIconSvg) {
6403
+ svg += `
6404
+ <g transform="translate(${groupX}, ${iconOffY})">
6405
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
6406
+ stroke="${isActive ? activeTextColor : this.renderTheme.textMuted}"
6407
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6408
+ ${this.extractSvgContent(tabIconSvg)}
6409
+ </svg>
6410
+ </g>
6411
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
6412
+ font-family="Arial, Helvetica, sans-serif"
6413
+ font-size="${fontSize}"
6414
+ font-weight="${isActive ? "600" : "500"}"
6415
+ fill="${isActive ? activeTextColor : this.renderTheme.text}">${this.escapeXml(tab)}</text>`;
6416
+ } else {
6417
+ svg += `
6418
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
6419
+ font-family="Arial, Helvetica, sans-serif"
6420
+ font-size="${fontSize}"
6421
+ font-weight="${isActive ? "600" : "500"}"
6422
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
6423
+ text-anchor="middle">${this.escapeXml(tab)}</text>`;
6424
+ }
6425
+ }
5845
6426
  });
5846
- svg += `
6427
+ const contentY = pos.y + tabHeight;
6428
+ const contentH = pos.height - tabHeight;
6429
+ if (contentH >= 20 && !isFlat) {
6430
+ svg += `
5847
6431
  <!-- Tab content area -->
5848
- <rect x="${pos.x}" y="${pos.y + 44}"
5849
- width="${pos.width}" height="${pos.height - 44}"
5850
- fill="${this.renderTheme.cardBg}"
5851
- stroke="${this.renderTheme.border}"
5852
- stroke-width="1"/>
6432
+ <rect x="${pos.x}" y="${contentY}"
6433
+ width="${pos.width}" height="${contentH}"
6434
+ fill="${this.renderTheme.cardBg}"
6435
+ ${showBorder ? `stroke="${this.renderTheme.border}" stroke-width="1"` : ""}/>`;
6436
+ } else if (isFlat && showBorder) {
6437
+ svg += `
6438
+ <line x1="${pos.x}" y1="${contentY}" x2="${pos.x + pos.width}" y2="${contentY}"
6439
+ stroke="${this.renderTheme.border}" stroke-width="1"/>`;
6440
+ }
6441
+ if (hasRadius) {
6442
+ svg += `
5853
6443
  </g>`;
6444
+ if (!isFlat && showBorder) {
6445
+ svg += `
6446
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6447
+ rx="${tabRadius}" fill="none"
6448
+ stroke="${this.renderTheme.border}" stroke-width="1"/>`;
6449
+ }
6450
+ }
6451
+ svg += "\n </g>";
5854
6452
  return svg;
5855
6453
  }
5856
6454
  renderDivider(node, pos) {
@@ -5917,15 +6515,20 @@ var SVGRenderer = class {
5917
6515
  const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
5918
6516
  const bgColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.renderTheme.border;
5919
6517
  const textColor = hasExplicitVariantColor ? "white" : this.renderTheme.text;
6518
+ const size = String(node.props.size || "md");
6519
+ const BADGE_FONT_SIZES = { xs: 9, sm: 11, md: 12, lg: 13, xl: 15 };
6520
+ const fontSize = BADGE_FONT_SIZES[size] ?? this.tokens.badge.fontSize;
6521
+ const customPadding = node.props.padding !== void 0 ? Number(node.props.padding) : void 0;
6522
+ const BADGE_PAD_X = { xs: 5, sm: 6, md: 8, lg: 10, xl: 14 };
6523
+ const paddingX = customPadding !== void 0 && !isNaN(customPadding) ? customPadding : BADGE_PAD_X[size] ?? this.tokens.badge.paddingX;
5920
6524
  const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
5921
- const fontSize = this.tokens.badge.fontSize;
5922
6525
  return `<g${this.getDataNodeId(node)}>
5923
6526
  <rect x="${pos.x}" y="${pos.y}"
5924
6527
  width="${pos.width}" height="${pos.height}"
5925
6528
  rx="${badgeRadius}"
5926
6529
  fill="${bgColor}"
5927
6530
  stroke="none"/>
5928
- <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
6531
+ <text x="${pos.x + paddingX + (pos.width - paddingX * 2) / 2}" y="${pos.y + pos.height / 2 + fontSize * 0.35}"
5929
6532
  font-family="Arial, Helvetica, sans-serif"
5930
6533
  font-size="${fontSize}"
5931
6534
  font-weight="600"
@@ -6134,11 +6737,20 @@ var SVGRenderer = class {
6134
6737
  return svg;
6135
6738
  }
6136
6739
  renderImage(node, pos) {
6137
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
6740
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
6138
6741
  const placeholderIcon = String(node.props.icon || "").trim();
6139
6742
  const variant = String(node.props.variant || "").trim();
6140
6743
  const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
6141
6744
  const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
6745
+ const hasExplicitHeight = node.props.height !== void 0 && !isNaN(Number(node.props.height)) && Number(node.props.height) > 0;
6746
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
6747
+ const useClip = isCircle || hasExplicitHeight;
6748
+ const clipId = useClip ? `iclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
6749
+ const imgCx = pos.x + pos.width / 2;
6750
+ const imgCy = pos.y + pos.height / 2;
6751
+ const imgR = Math.min(pos.width, pos.height) / 2;
6752
+ const defsHtml = useClip ? `<defs><clipPath id="${clipId}">${isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="4"/>`}</clipPath></defs>` : "";
6753
+ const clipAttr = useClip ? ` clip-path="url(#${clipId})"` : "";
6142
6754
  const aspectRatios = {
6143
6755
  landscape: 16 / 9,
6144
6756
  portrait: 2 / 3,
@@ -6147,12 +6759,24 @@ var SVGRenderer = class {
6147
6759
  avatar: 1
6148
6760
  };
6149
6761
  const ratio = aspectRatios[placeholder] || 16 / 9;
6150
- const maxSize = Math.min(pos.width, pos.height) * 0.8;
6151
- let iconWidth = maxSize;
6152
- let iconHeight = maxSize / ratio;
6153
- if (iconHeight > pos.height * 0.8) {
6154
- iconHeight = pos.height * 0.8;
6155
- iconWidth = iconHeight * ratio;
6762
+ let iconWidth;
6763
+ let iconHeight;
6764
+ if (isCircle) {
6765
+ const diameter = 2 * imgR;
6766
+ iconWidth = diameter;
6767
+ iconHeight = diameter / ratio;
6768
+ if (iconHeight < diameter) {
6769
+ iconHeight = diameter;
6770
+ iconWidth = diameter * ratio;
6771
+ }
6772
+ } else {
6773
+ const maxSize = Math.min(pos.width, pos.height) * 0.8;
6774
+ iconWidth = maxSize;
6775
+ iconHeight = maxSize / ratio;
6776
+ if (iconHeight > pos.height * 0.8) {
6777
+ iconHeight = pos.height * 0.8;
6778
+ iconWidth = iconHeight * ratio;
6779
+ }
6156
6780
  }
6157
6781
  const offsetX = pos.x + (pos.width - iconWidth) / 2;
6158
6782
  const offsetY = pos.y + (pos.height - iconHeight) / 2;
@@ -6165,17 +6789,18 @@ var SVGRenderer = class {
6165
6789
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
6166
6790
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
6167
6791
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
6168
- return `<g${this.getDataNodeId(node)}>
6792
+ const iconBorderShape = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>`;
6793
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6169
6794
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
6170
6795
  <g transform="translate(${iconOffsetX}, ${iconOffsetY})">
6171
6796
  <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6172
6797
  ${this.extractSvgContent(placeholderIconSvg)}
6173
6798
  </svg>
6174
6799
  </g>
6175
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6800
+ ${iconBorderShape}
6176
6801
  </g>`;
6177
6802
  }
6178
- let svg = `<g${this.getDataNodeId(node)}>
6803
+ let svg = `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6179
6804
  <!-- Image Background -->
6180
6805
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
6181
6806
  if (["landscape", "portrait", "square"].includes(placeholder)) {
@@ -6233,10 +6858,10 @@ var SVGRenderer = class {
6233
6858
  width="${personWidth * 0.6}" height="${personHeight * 0.5}"
6234
6859
  fill="#666" rx="3"/>`;
6235
6860
  }
6861
+ const borderShape = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>`;
6236
6862
  svg += `
6237
6863
  <!-- Border -->
6238
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6239
- fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6864
+ ${borderShape}
6240
6865
  </g>`;
6241
6866
  return svg;
6242
6867
  }
@@ -6340,10 +6965,29 @@ var SVGRenderer = class {
6340
6965
  </g>`;
6341
6966
  }
6342
6967
  const iconSize = this.getIconSize(size);
6343
- const offsetX = pos.x + (pos.width - iconSize) / 2;
6344
- const offsetY = pos.y + (pos.height - iconSize) / 2;
6968
+ const paddingPx = Math.max(0, Number(node.props.padding || 0));
6969
+ const renderedIconSize = Math.max(4, iconSize - paddingPx * 2);
6970
+ const offsetX = pos.x + (pos.width - renderedIconSize) / 2;
6971
+ const offsetY = pos.y + (pos.height - renderedIconSize) / 2;
6972
+ const isIconCircle = this.parseBooleanProp(node.props.circle, false);
6973
+ if (isIconCircle) {
6974
+ const cx = pos.x + pos.width / 2;
6975
+ const cy = pos.y + pos.height / 2;
6976
+ const r = Math.min(pos.width, pos.height) / 2;
6977
+ const iconClipId = `iconclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}`;
6978
+ const bgColor = this.hexToRgba(iconColor, 0.12);
6979
+ return `<defs><clipPath id="${iconClipId}"><circle cx="${cx}" cy="${cy}" r="${r}"/></clipPath></defs><g${this.getDataNodeId(node)} clip-path="url(#${iconClipId})">
6980
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${bgColor}"/>
6981
+ <g transform="translate(${offsetX}, ${offsetY})">
6982
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6983
+ ${this.extractSvgContent(iconSvg)}
6984
+ </svg>
6985
+ </g>
6986
+ </g>
6987
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${this.hexToRgba(iconColor, 0.35)}" stroke-width="1"/>`;
6988
+ }
6345
6989
  const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
6346
- <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6990
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6347
6991
  ${this.extractSvgContent(iconSvg)}
6348
6992
  </svg>
6349
6993
  </g>`;
@@ -6352,7 +6996,7 @@ var SVGRenderer = class {
6352
6996
  renderIconButton(node, pos) {
6353
6997
  const iconName = String(node.props.icon || "help-circle");
6354
6998
  const variant = String(node.props.variant || "default");
6355
- const size = String(node.props.size || "md");
6999
+ const size = String(node.props.size || "sm");
6356
7000
  const disabled = String(node.props.disabled || "false") === "true";
6357
7001
  const density = this.ir.project.style.density || "normal";
6358
7002
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -6480,7 +7124,7 @@ var SVGRenderer = class {
6480
7124
  wrapTextToLines(text, maxWidth, fontSize) {
6481
7125
  const normalized = text.replace(/\r\n/g, "\n");
6482
7126
  const paragraphs = normalized.split("\n");
6483
- const charWidth = fontSize * 0.6;
7127
+ const charWidth = fontSize * 0.5;
6484
7128
  const safeWidth = Math.max(maxWidth || 0, charWidth);
6485
7129
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
6486
7130
  const lines = [];
@@ -6722,8 +7366,8 @@ var SVGRenderer = class {
6722
7366
  return false;
6723
7367
  }
6724
7368
  const direction = String(parent.params.direction || "vertical");
6725
- const align = parent.style.align || "justify";
6726
- return direction === "horizontal" && align === "justify";
7369
+ const justify = parent.style.justify || "stretch";
7370
+ return direction === "horizontal" && justify === "stretch";
6727
7371
  }
6728
7372
  buildParentContainerIndex() {
6729
7373
  this.parentContainerByChildId.clear();
@@ -7595,7 +8239,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7595
8239
  renderButton(node, pos) {
7596
8240
  const text = String(node.props.text || "Button");
7597
8241
  const variant = String(node.props.variant || "default");
7598
- const size = String(node.props.size || "md");
8242
+ const size = String(node.props.size || "sm");
7599
8243
  const density = this.ir.project.style.density || "normal";
7600
8244
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
7601
8245
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -7702,7 +8346,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7702
8346
  renderIconButton(node, pos) {
7703
8347
  const iconName = String(node.props.icon || "help-circle");
7704
8348
  const variant = String(node.props.variant || "default");
7705
- const size = String(node.props.size || "md");
8349
+ const size = String(node.props.size || "sm");
7706
8350
  const density = this.ir.project.style.density || "normal";
7707
8351
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
7708
8352
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -7955,26 +8599,58 @@ var SketchSVGRenderer = class extends SVGRenderer {
7955
8599
  const variant = String(node.props.variant || "default");
7956
8600
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
7957
8601
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
7958
- let svg = `<g${this.getDataNodeId(node)}>
8602
+ const bgPropStr = String(node.props.background ?? "");
8603
+ let sketchTopbarBg = this.renderTheme.cardBg;
8604
+ if (bgPropStr && bgPropStr !== "false" && bgPropStr !== "true") {
8605
+ if (bgPropStr.startsWith("#") || bgPropStr.startsWith("rgb")) {
8606
+ sketchTopbarBg = bgPropStr;
8607
+ } else if (this.colorResolver.hasColor(bgPropStr)) {
8608
+ sketchTopbarBg = this.colorResolver.resolveColor(bgPropStr, this.renderTheme.cardBg);
8609
+ }
8610
+ }
8611
+ const colorPropStr = String(node.props.color ?? "");
8612
+ let titleColor = this.renderTheme.text;
8613
+ let subtitleColor = this.renderTheme.textMuted;
8614
+ if (colorPropStr && colorPropStr !== "false") {
8615
+ let resolvedTitleColor = null;
8616
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
8617
+ resolvedTitleColor = colorPropStr;
8618
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
8619
+ resolvedTitleColor = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
8620
+ }
8621
+ if (resolvedTitleColor) {
8622
+ titleColor = resolvedTitleColor;
8623
+ subtitleColor = this.hexToRgba(resolvedTitleColor, 0.65);
8624
+ }
8625
+ }
8626
+ const showBorder = this.parseBooleanProp(node.props.border, false);
8627
+ const hasBgProp = bgPropStr === "true" || bgPropStr && bgPropStr !== "false" && bgPropStr !== "";
8628
+ const showBackground = hasBgProp;
8629
+ let svg = `<g${this.getDataNodeId(node)}>`;
8630
+ if (showBorder || showBackground) {
8631
+ const stroke = showBorder ? "#2D3748" : "none";
8632
+ svg += `
7959
8633
  <rect x="${pos.x}" y="${pos.y}"
7960
8634
  width="${pos.width}" height="${pos.height}"
7961
- fill="${this.renderTheme.cardBg}"
7962
- stroke="#2D3748"
8635
+ fill="${sketchTopbarBg}"
8636
+ stroke="${stroke}"
7963
8637
  stroke-width="0.5"
7964
- filter="url(#sketch-rough)"/>
7965
-
7966
- <!-- Title -->
8638
+ filter="url(#sketch-rough)"/>`;
8639
+ }
8640
+ svg += `
8641
+ <!-- Title -->`;
8642
+ svg += `
7967
8643
  <text x="${topbar.textX}" y="${topbar.titleY}"
7968
8644
  font-family="${this.fontFamily}"
7969
8645
  font-size="18"
7970
8646
  font-weight="600"
7971
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8647
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
7972
8648
  if (topbar.hasSubtitle) {
7973
8649
  svg += `
7974
8650
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
7975
8651
  font-family="${this.fontFamily}"
7976
8652
  font-size="13"
7977
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8653
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
7978
8654
  }
7979
8655
  if (topbar.leftIcon) {
7980
8656
  svg += `
@@ -8054,8 +8730,13 @@ var SketchSVGRenderer = class extends SVGRenderer {
8054
8730
  */
8055
8731
  renderText(node, pos) {
8056
8732
  const text = String(node.props.text || "Text content");
8057
- const fontSize = this.tokens.text.fontSize;
8733
+ const sizeProp = String(node.props.size || "");
8734
+ const defaultFontSize = this.tokens.text.fontSize;
8735
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
8736
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
8058
8737
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
8738
+ const bold = this.parseBooleanProp(node.props.bold, false);
8739
+ const italic = this.parseBooleanProp(node.props.italic, false);
8059
8740
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
8060
8741
  const firstLineY = pos.y + fontSize;
8061
8742
  const tspans = lines.map(
@@ -8065,9 +8746,48 @@ var SketchSVGRenderer = class extends SVGRenderer {
8065
8746
  <text x="${pos.x}" y="${firstLineY}"
8066
8747
  font-family="${this.fontFamily}"
8067
8748
  font-size="${fontSize}"
8749
+ font-weight="${bold ? "700" : "400"}"
8750
+ font-style="${italic ? "italic" : "normal"}"
8068
8751
  fill="${this.renderTheme.text}">${tspans}</text>
8069
8752
  </g>`;
8070
8753
  }
8754
+ renderParagraph(node, pos) {
8755
+ const text = String(node.props.text || "");
8756
+ const sizeProp = String(node.props.size || "");
8757
+ const defaultFontSize = this.tokens.text.fontSize;
8758
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
8759
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
8760
+ const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
8761
+ const bold = this.parseBooleanProp(node.props.bold, false);
8762
+ const italic = this.parseBooleanProp(node.props.italic, false);
8763
+ const align = String(node.props.align || "left");
8764
+ const lines = this.wrapTextToLines(text, pos.width, fontSize);
8765
+ const firstLineY = pos.y + fontSize;
8766
+ let textX;
8767
+ let textAnchor;
8768
+ if (align === "center") {
8769
+ textX = pos.x + pos.width / 2;
8770
+ textAnchor = "middle";
8771
+ } else if (align === "right") {
8772
+ textX = pos.x + pos.width;
8773
+ textAnchor = "end";
8774
+ } else {
8775
+ textX = pos.x;
8776
+ textAnchor = "start";
8777
+ }
8778
+ const tspans = lines.map(
8779
+ (line, index) => `<tspan x="${textX}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
8780
+ ).join("");
8781
+ return `<g${this.getDataNodeId(node)}>
8782
+ <text x="${textX}" y="${firstLineY}"
8783
+ font-family="${this.fontFamily}"
8784
+ font-size="${fontSize}"
8785
+ font-weight="${bold ? "700" : "400"}"
8786
+ font-style="${italic ? "italic" : "normal"}"
8787
+ fill="${this.renderTheme.text}"
8788
+ text-anchor="${textAnchor}">${tspans}</text>
8789
+ </g>`;
8790
+ }
8071
8791
  /**
8072
8792
  * Render label with Comic Sans
8073
8793
  */
@@ -8298,36 +9018,150 @@ var SketchSVGRenderer = class extends SVGRenderer {
8298
9018
  renderTabs(node, pos) {
8299
9019
  const itemsStr = String(node.props.items || "");
8300
9020
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
8301
- const accentColor = this.resolveAccentColor();
9021
+ const activeProp = node.props.active ?? 0;
9022
+ const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
9023
+ const variant = String(node.props.variant || "default");
9024
+ const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
9025
+ const radiusMap = { none: 0, sm: 4, md: 6, lg: 10, full: 20 };
9026
+ const tabRadius = radiusMap[String(node.props.radius || "md")] ?? 6;
9027
+ const sizeMap = { sm: 32, md: 44, lg: 52 };
9028
+ const tabHeight = pos.height > 0 ? pos.height : sizeMap[String(node.props.size || "md")] ?? 44;
9029
+ const fontSize = 13;
9030
+ const textY = pos.y + Math.round(tabHeight / 2) + Math.round(fontSize * 0.4);
9031
+ const iconsStr = String(node.props.icons || "");
9032
+ const iconList = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
9033
+ const isFlat = this.parseBooleanProp(node.props.flat, false);
9034
+ const showBorder = this.parseBooleanProp(node.props.border, true);
8302
9035
  const tabWidth = pos.width / tabs.length;
8303
- let svg = `<g${this.getDataNodeId(node)}>
8304
- <!-- Tab headers -->`;
9036
+ const colorPropStr = String(node.props.color ?? "");
9037
+ let textColorOverride = null;
9038
+ if (colorPropStr && colorPropStr !== "false") {
9039
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
9040
+ textColorOverride = colorPropStr;
9041
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
9042
+ textColorOverride = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
9043
+ }
9044
+ }
9045
+ const activeTextColor = textColorOverride ?? (isFlat ? accentColor : "white");
9046
+ const inactiveTextColor = textColorOverride ? this.hexToRgba(textColorOverride, 0.55) : this.renderTheme.textMuted;
9047
+ const hasRadius = tabRadius > 0;
9048
+ const clipId = hasRadius ? `stc${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 12) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
9049
+ let svg = "";
9050
+ if (hasRadius) {
9051
+ svg += `<defs><clipPath id="${clipId}"><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="${tabRadius}"/></clipPath></defs>`;
9052
+ }
9053
+ svg += `<g${this.getDataNodeId(node)}>`;
9054
+ if (hasRadius) {
9055
+ svg += `<g clip-path="url(#${clipId})">`;
9056
+ }
9057
+ if (!isFlat) {
9058
+ svg += `
9059
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${tabHeight}"
9060
+ fill="${this.renderTheme.bg}"/>`;
9061
+ }
8305
9062
  tabs.forEach((tab, i) => {
8306
9063
  const tabX = pos.x + i * tabWidth;
8307
- const isActive = i === 0;
8308
- svg += `
9064
+ const isActive = i === activeIndex;
9065
+ const iconName = iconList[i] || "";
9066
+ const tabIconSvg = iconName ? getIcon(iconName) : null;
9067
+ const iconSize = 14;
9068
+ const iconGap = 4;
9069
+ const charW = fontSize * 0.58;
9070
+ const textEst = tab.length * charW;
9071
+ const totalW = tabIconSvg ? iconSize + iconGap + textEst : textEst;
9072
+ const groupX = tabX + Math.round((tabWidth - totalW) / 2);
9073
+ const iconOffY = pos.y + Math.round((tabHeight - iconSize) / 2);
9074
+ if (isFlat) {
9075
+ if (isActive) {
9076
+ svg += `
9077
+ <rect x="${tabX + 6}" y="${pos.y + tabHeight - 3}"
9078
+ width="${tabWidth - 12}" height="3"
9079
+ rx="1.5"
9080
+ fill="${accentColor}"
9081
+ filter="url(#sketch-rough)"/>`;
9082
+ }
9083
+ if (tabIconSvg) {
9084
+ svg += `
9085
+ <g transform="translate(${groupX}, ${iconOffY})">
9086
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
9087
+ stroke="${isActive ? accentColor : this.renderTheme.textMuted}"
9088
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
9089
+ ${this.extractSvgContent(tabIconSvg)}
9090
+ </svg>
9091
+ </g>
9092
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
9093
+ font-family="${this.fontFamily}"
9094
+ font-size="${fontSize}"
9095
+ font-weight="${isActive ? "600" : "500"}"
9096
+ fill="${isActive ? activeTextColor : inactiveTextColor}">${this.escapeXml(tab)}</text>`;
9097
+ } else {
9098
+ svg += `
9099
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
9100
+ font-family="${this.fontFamily}"
9101
+ font-size="${fontSize}"
9102
+ font-weight="${isActive ? "600" : "500"}"
9103
+ fill="${isActive ? activeTextColor : inactiveTextColor}"
9104
+ text-anchor="middle">${this.escapeXml(tab)}</text>`;
9105
+ }
9106
+ } else {
9107
+ svg += `
8309
9108
  <rect x="${tabX}" y="${pos.y}"
8310
- width="${tabWidth}" height="44"
9109
+ width="${tabWidth}" height="${tabHeight}"
9110
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
8311
9111
  fill="${isActive ? accentColor : "transparent"}"
8312
- stroke="${isActive ? accentColor : "#2D3748"}"
9112
+ stroke="${isActive ? accentColor : showBorder ? "#2D3748" : "none"}"
8313
9113
  stroke-width="0.5"
8314
- filter="url(#sketch-rough)"/>
8315
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
9114
+ filter="url(#sketch-rough)"/>`;
9115
+ if (tabIconSvg) {
9116
+ svg += `
9117
+ <g transform="translate(${groupX}, ${iconOffY})">
9118
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
9119
+ stroke="${isActive ? activeTextColor : this.renderTheme.textMuted}"
9120
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
9121
+ ${this.extractSvgContent(tabIconSvg)}
9122
+ </svg>
9123
+ </g>
9124
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
8316
9125
  font-family="${this.fontFamily}"
8317
- font-size="13"
9126
+ font-size="${fontSize}"
9127
+ font-weight="${isActive ? "600" : "500"}"
9128
+ fill="${isActive ? activeTextColor : this.renderTheme.text}">${this.escapeXml(tab)}</text>`;
9129
+ } else {
9130
+ svg += `
9131
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
9132
+ font-family="${this.fontFamily}"
9133
+ font-size="${fontSize}"
8318
9134
  font-weight="${isActive ? "600" : "500"}"
8319
- fill="${isActive ? "white" : this.renderTheme.text}"
9135
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
8320
9136
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
9137
+ }
9138
+ }
8321
9139
  });
8322
- svg += `
9140
+ const contentY = pos.y + tabHeight;
9141
+ const contentH = pos.height - tabHeight;
9142
+ if (contentH >= 20 && !isFlat) {
9143
+ svg += `
8323
9144
  <!-- Tab content area -->
8324
- <rect x="${pos.x}" y="${pos.y + 44}"
8325
- width="${pos.width}" height="${pos.height - 44}"
9145
+ <rect x="${pos.x}" y="${contentY}"
9146
+ width="${pos.width}" height="${contentH}"
8326
9147
  fill="${this.renderTheme.cardBg}"
8327
- stroke="#2D3748"
8328
- stroke-width="0.5"
8329
- filter="url(#sketch-rough)"/>
9148
+ ${showBorder ? 'stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"' : ""}/>`;
9149
+ } else if (isFlat && showBorder) {
9150
+ svg += `
9151
+ <line x1="${pos.x}" y1="${contentY}" x2="${pos.x + pos.width}" y2="${contentY}"
9152
+ stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
9153
+ }
9154
+ if (hasRadius) {
9155
+ svg += `
8330
9156
  </g>`;
9157
+ if (!isFlat && showBorder) {
9158
+ svg += `
9159
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
9160
+ rx="${tabRadius}" fill="none" stroke="#2D3748" stroke-width="0.5"
9161
+ filter="url(#sketch-rough)"/>`;
9162
+ }
9163
+ }
9164
+ svg += "\n </g>";
8331
9165
  return svg;
8332
9166
  }
8333
9167
  /**
@@ -8564,11 +9398,20 @@ var SketchSVGRenderer = class extends SVGRenderer {
8564
9398
  * Render image with sketch filter
8565
9399
  */
8566
9400
  renderImage(node, pos) {
8567
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
9401
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
8568
9402
  const iconType = String(node.props.icon || "").trim();
8569
9403
  const variant = String(node.props.variant || "").trim();
8570
9404
  const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
8571
9405
  const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
9406
+ const hasExplicitHeight = node.props.height !== void 0 && !isNaN(Number(node.props.height)) && Number(node.props.height) > 0;
9407
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
9408
+ const useClip = isCircle || hasExplicitHeight;
9409
+ const clipId = useClip ? `sclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}` : "";
9410
+ const imgCx = pos.x + pos.width / 2;
9411
+ const imgCy = pos.y + pos.height / 2;
9412
+ const imgR = Math.min(pos.width, pos.height) / 2;
9413
+ const defsHtml = useClip ? `<defs><clipPath id="${clipId}">${isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="4"/>`}</clipPath></defs>` : "";
9414
+ const clipAttr = useClip ? ` clip-path="url(#${clipId})"` : "";
8572
9415
  if (iconSvg) {
8573
9416
  const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
8574
9417
  const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
@@ -8578,7 +9421,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
8578
9421
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
8579
9422
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
8580
9423
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
8581
- return `<g${this.getDataNodeId(node)}>
9424
+ const sketchIconBorder = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>` : `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="#2D3748" stroke-width="0.5" rx="4" filter="url(#sketch-rough)"/>`;
9425
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
8582
9426
  <rect x="${pos.x}" y="${pos.y}"
8583
9427
  width="${pos.width}" height="${pos.height}"
8584
9428
  fill="${bgColor}"
@@ -8589,17 +9433,11 @@ var SketchSVGRenderer = class extends SVGRenderer {
8589
9433
  ${this.extractSvgContent(iconSvg)}
8590
9434
  </svg>
8591
9435
  </g>
8592
- <rect x="${pos.x}" y="${pos.y}"
8593
- width="${pos.width}" height="${pos.height}"
8594
- fill="none"
8595
- stroke="#2D3748"
8596
- stroke-width="0.5"
8597
- rx="4"
8598
- filter="url(#sketch-rough)"/>
9436
+ ${sketchIconBorder}
8599
9437
  </g>`;
8600
9438
  }
8601
- return `<g${this.getDataNodeId(node)}>
8602
- <!-- Image Background -->
9439
+ const sketchCircleBorder = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="#2D3748" stroke-width="0.5"/>` : "";
9440
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
8603
9441
  <rect x="${pos.x}" y="${pos.y}"
8604
9442
  width="${pos.width}" height="${pos.height}"
8605
9443
  fill="${imageBg}"
@@ -8607,14 +9445,12 @@ var SketchSVGRenderer = class extends SVGRenderer {
8607
9445
  stroke-width="0.5"
8608
9446
  rx="4"
8609
9447
  filter="url(#sketch-rough)"/>
8610
-
8611
- <!-- Placeholder icon -->
8612
9448
  <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
8613
9449
  font-family="${this.fontFamily}"
8614
9450
  font-size="24"
8615
9451
  fill="#666"
8616
9452
  text-anchor="middle">\u{1F5BC}</text>
8617
- </g>`;
9453
+ </g>${sketchCircleBorder}`;
8618
9454
  }
8619
9455
  /**
8620
9456
  * Render breadcrumbs with Comic Sans
@@ -8730,20 +9566,33 @@ var SketchSVGRenderer = class extends SVGRenderer {
8730
9566
  }
8731
9567
  const iconSize = this.getIconSize(size);
8732
9568
  const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
8733
- const offsetX = pos.x + (pos.width - iconSize) / 2;
8734
- const offsetY = pos.y + (pos.height - iconSize) / 2;
9569
+ const paddingPx = Math.max(0, Number(node.props.padding || 0));
9570
+ const renderedIconSize = Math.max(4, iconSize - paddingPx * 2);
9571
+ const offsetX = pos.x + (pos.width - renderedIconSize) / 2;
9572
+ const offsetY = pos.y + (pos.height - renderedIconSize) / 2;
9573
+ const isIconCircle = this.parseBooleanProp(node.props.circle, false);
9574
+ if (isIconCircle) {
9575
+ const cx = pos.x + pos.width / 2;
9576
+ const cy = pos.y + pos.height / 2;
9577
+ const r = Math.min(pos.width, pos.height) / 2;
9578
+ const iconClipId = `sconclip${String(node.meta?.nodeId ?? "").replace(/\W/g, "").slice(0, 16) || `${Math.round(Math.abs(pos.x))}x${Math.round(Math.abs(pos.y))}`}`;
9579
+ const bgColor = this.hexToRgba(iconColor, 0.1);
9580
+ return `<defs><clipPath id="${iconClipId}"><circle cx="${cx}" cy="${cy}" r="${r}"/></clipPath></defs><g${this.getDataNodeId(node)} clip-path="url(#${iconClipId})">
9581
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${bgColor}"/>
9582
+ <g transform="translate(${offsetX}, ${offsetY})">
9583
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
9584
+ ${this.extractSvgContent(iconSvg)}
9585
+ </svg>
9586
+ </g>
9587
+ </g>
9588
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${this.hexToRgba(iconColor, 0.35)}" stroke-width="0.5"/>`;
9589
+ }
8735
9590
  return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
8736
- <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
9591
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
8737
9592
  ${this.extractSvgContent(iconSvg)}
8738
9593
  </svg>
8739
9594
  </g>`;
8740
9595
  }
8741
- /**
8742
- * Render chart placeholder with sketch filter and Comic Sans
8743
- */
8744
- renderChartPlaceholder(node, pos) {
8745
- return super.renderChartPlaceholder(node, pos);
8746
- }
8747
9596
  /**
8748
9597
  * Helper method to get icon SVG
8749
9598
  */