@wire-dsl/engine 0.6.0 → 0.8.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
@@ -2543,13 +2543,13 @@ ${messages}`);
2543
2543
  "Heading",
2544
2544
  "Text",
2545
2545
  "Label",
2546
+ "Paragraph",
2546
2547
  "Image",
2547
2548
  "Card",
2548
2549
  "Stat",
2549
2550
  "Topbar",
2550
2551
  "Table",
2551
2552
  "Chart",
2552
- "ChartPlaceholder",
2553
2553
  "Textarea",
2554
2554
  "Select",
2555
2555
  "Checkbox",
@@ -2884,19 +2884,19 @@ var ICON_SIZES_BY_DENSITY = {
2884
2884
  comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
2885
2885
  };
2886
2886
  var ICON_BUTTON_SIZES_BY_DENSITY = {
2887
- compact: { sm: 20, md: 24, lg: 32 },
2888
- normal: { sm: 24, md: 32, lg: 40 },
2889
- 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 }
2890
2890
  };
2891
2891
  var CONTROL_HEIGHTS_BY_DENSITY = {
2892
- compact: { sm: 28, md: 32, lg: 36 },
2893
- normal: { sm: 36, md: 40, lg: 48 },
2894
- 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 }
2895
2895
  };
2896
2896
  var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
2897
- compact: { sm: 20, md: 24, lg: 32 },
2898
- normal: { sm: 24, md: 32, lg: 40 },
2899
- 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 }
2900
2900
  };
2901
2901
  var CONTROL_PADDING_BY_DENSITY = {
2902
2902
  compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
@@ -3569,7 +3569,7 @@ var LayoutEngine = class {
3569
3569
  wrapTextToLines(text, maxWidth, fontSize) {
3570
3570
  const normalized = text.replace(/\r\n/g, "\n");
3571
3571
  const paragraphs = normalized.split("\n");
3572
- const charWidth = fontSize * 0.6;
3572
+ const charWidth = fontSize * 0.5;
3573
3573
  const safeWidth = Math.max(maxWidth, charWidth);
3574
3574
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
3575
3575
  const lines = [];
@@ -3607,12 +3607,14 @@ var LayoutEngine = class {
3607
3607
  getIntrinsicComponentHeight(node, availableWidth) {
3608
3608
  if (node.kind !== "component") return this.getComponentHeight();
3609
3609
  const controlSize = String(node.props.size || "md");
3610
+ const actionControlSize = String(node.props.size || "sm");
3610
3611
  const density = this.style.density || "normal";
3611
3612
  const inputControlHeight = resolveControlHeight(controlSize, density);
3612
- const actionControlHeight = resolveActionControlHeight(controlSize, density);
3613
+ const actionControlHeight = resolveActionControlHeight(actionControlSize, density);
3613
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;
3614
3615
  if (node.componentType === "Image") {
3615
3616
  const placeholder = String(node.props.placeholder || "landscape");
3617
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
3616
3618
  const aspectRatios = {
3617
3619
  landscape: 16 / 9,
3618
3620
  portrait: 2 / 3,
@@ -3620,7 +3622,7 @@ var LayoutEngine = class {
3620
3622
  icon: 1,
3621
3623
  avatar: 1
3622
3624
  };
3623
- const ratio = aspectRatios[placeholder] || 16 / 9;
3625
+ const ratio = isCircle ? 1 : aspectRatios[placeholder] || 16 / 9;
3624
3626
  const explicitHeight = Number(node.props.height);
3625
3627
  if (!isNaN(explicitHeight) && explicitHeight > 0) {
3626
3628
  return explicitHeight;
@@ -3672,13 +3674,19 @@ var LayoutEngine = class {
3672
3674
  }
3673
3675
  return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
3674
3676
  }
3675
- if (node.componentType === "Text") {
3677
+ if (node.componentType === "Text" || node.componentType === "Paragraph") {
3676
3678
  const content = String(node.props.text || "");
3677
- 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;
3678
3683
  const lineHeightPx = Math.ceil(fontSize * lineHeight);
3679
3684
  const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
3680
3685
  const lines = this.wrapTextToLines(content, maxWidth, fontSize);
3681
3686
  const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
3687
+ if (sizeProp) {
3688
+ return wrappedHeight;
3689
+ }
3682
3690
  return Math.max(this.getComponentHeight(), wrappedHeight);
3683
3691
  }
3684
3692
  if (node.componentType === "Alert") {
@@ -3707,7 +3715,7 @@ var LayoutEngine = class {
3707
3715
  if (node.componentType === "Modal") return 300;
3708
3716
  if (node.componentType === "Card") return 120;
3709
3717
  if (node.componentType === "Stat") return 120;
3710
- if (node.componentType === "Chart" || node.componentType === "ChartPlaceholder") return 250;
3718
+ if (node.componentType === "Chart") return 250;
3711
3719
  if (node.componentType === "List") {
3712
3720
  const itemsFromProps = String(node.props.items || "").split(",").map((item) => item.trim()).filter(Boolean);
3713
3721
  const parsedItemsMock = Number(node.props.itemsMock ?? 4);
@@ -3718,7 +3726,14 @@ var LayoutEngine = class {
3718
3726
  const contentHeight = titleHeight + itemCount * itemHeight;
3719
3727
  return Math.max(this.getComponentHeight(), contentHeight);
3720
3728
  }
3721
- 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
+ }
3722
3737
  if (node.componentType === "Divider") return 1;
3723
3738
  if (node.componentType === "Separate") return this.getSeparateSize(node);
3724
3739
  if (node.componentType === "Input" || node.componentType === "Select") {
@@ -3727,6 +3742,11 @@ var LayoutEngine = class {
3727
3742
  if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
3728
3743
  return actionControlHeight + controlLabelOffset;
3729
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
+ }
3730
3750
  return this.getComponentHeight();
3731
3751
  }
3732
3752
  getControlLabelOffset(label) {
@@ -3782,7 +3802,7 @@ var LayoutEngine = class {
3782
3802
  return resolveIconSize(size, this.style.density || "normal");
3783
3803
  }
3784
3804
  if (node.componentType === "IconButton") {
3785
- const size = String(node.props.size || "md");
3805
+ const size = String(node.props.size || "sm");
3786
3806
  const density = this.style.density || "normal";
3787
3807
  const baseSize = resolveIconButtonSize(size, density);
3788
3808
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -3840,7 +3860,13 @@ var LayoutEngine = class {
3840
3860
  }
3841
3861
  if (node.componentType === "Badge" || node.componentType === "Chip") {
3842
3862
  const text = String(node.props.text || "");
3843
- 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);
3844
3870
  }
3845
3871
  return 120;
3846
3872
  }
@@ -4124,34 +4150,358 @@ MockDataGenerator.HEADER_TO_MOCK = {
4124
4150
  var ColorResolver = class {
4125
4151
  constructor() {
4126
4152
  this.customColors = {};
4127
- // Named colors palette
4153
+ // Named colors palette — Full Material Design + utility aliases
4128
4154
  this.namedColors = {
4129
- // Grayscale
4155
+ // ── Utility ──────────────────────────────────────────────────────────────
4130
4156
  white: "#FFFFFF",
4131
4157
  black: "#000000",
4132
- gray: "#6B7280",
4133
- slate: "#64748B",
4158
+ gray: "#9E9E9E",
4159
+ // alias for grey 500
4160
+ grey: "#9E9E9E",
4161
+ slate: "#607D8B",
4162
+ // alias for blue_grey 500
4134
4163
  zinc: "#71717A",
4135
- // Colors
4136
- red: "#EF4444",
4137
- orange: "#F97316",
4138
- yellow: "#EAB308",
4139
- lime: "#84CC16",
4140
- green: "#22C55E",
4164
+ // Extra well-known non-Material names (kept for backward compat)
4141
4165
  emerald: "#10B981",
4142
- teal: "#14B8A6",
4143
- cyan: "#06B6D4",
4144
- blue: "#3B82F6",
4145
- indigo: "#6366F1",
4146
4166
  violet: "#8B5CF6",
4147
- purple: "#A855F7",
4148
4167
  fuchsia: "#D946EF",
4149
- pink: "#EC4899",
4150
4168
  rose: "#F43F5E",
4151
- // Light variants
4152
- "red-light": "#FEE2E2",
4153
- "blue-light": "#DBEAFE",
4154
- "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"
4155
4505
  };
4156
4506
  }
4157
4507
  /**
@@ -4799,7 +5149,8 @@ var SVGRenderer = class {
4799
5149
  if (node.containerType === "split") {
4800
5150
  this.renderSplitDecoration(node, pos, containerGroup);
4801
5151
  }
4802
- 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) {
4803
5154
  containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
4804
5155
  } else {
4805
5156
  node.children.forEach((childRef) => {
@@ -4846,7 +5197,6 @@ var SVGRenderer = class {
4846
5197
  case "Table":
4847
5198
  return this.renderTable(node, pos);
4848
5199
  case "Chart":
4849
- case "ChartPlaceholder":
4850
5200
  return this.renderChartPlaceholder(node, pos);
4851
5201
  case "Breadcrumbs":
4852
5202
  return this.renderBreadcrumbs(node, pos);
@@ -4855,6 +5205,8 @@ var SVGRenderer = class {
4855
5205
  // Text/Content components
4856
5206
  case "Text":
4857
5207
  return this.renderText(node, pos);
5208
+ case "Paragraph":
5209
+ return this.renderParagraph(node, pos);
4858
5210
  case "Label":
4859
5211
  return this.renderLabel(node, pos);
4860
5212
  case "Code":
@@ -4934,7 +5286,7 @@ var SVGRenderer = class {
4934
5286
  renderButton(node, pos) {
4935
5287
  const text = String(node.props.text || "Button");
4936
5288
  const variant = String(node.props.variant || "default");
4937
- const size = String(node.props.size || "md");
5289
+ const size = String(node.props.size || "sm");
4938
5290
  const disabled = this.parseBooleanProp(node.props.disabled, false);
4939
5291
  const density = this.ir.project.style.density || "normal";
4940
5292
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -5009,7 +5361,7 @@ var SVGRenderer = class {
5009
5361
  renderLink(node, pos) {
5010
5362
  const text = String(node.props.text || "Link");
5011
5363
  const variant = String(node.props.variant || "primary");
5012
- const size = String(node.props.size || "md");
5364
+ const size = String(node.props.size || "sm");
5013
5365
  const density = this.ir.project.style.density || "normal";
5014
5366
  const fontSize = this.tokens.button.fontSize;
5015
5367
  const fontWeight = this.tokens.button.fontWeight;
@@ -5115,7 +5467,33 @@ var SVGRenderer = class {
5115
5467
  const variant = String(node.props.variant || "default");
5116
5468
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
5117
5469
  const showBorder = this.parseBooleanProp(node.props.border, false);
5118
- 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
+ }
5119
5497
  const radiusMap = {
5120
5498
  none: 0,
5121
5499
  sm: 4,
@@ -5127,7 +5505,7 @@ var SVGRenderer = class {
5127
5505
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
5128
5506
  let svg = `<g${this.getDataNodeId(node)}>`;
5129
5507
  if (showBorder || showBackground) {
5130
- const bg = showBackground ? this.renderTheme.cardBg : "none";
5508
+ const bg = resolvedBg ?? "none";
5131
5509
  const stroke = showBorder ? this.renderTheme.border : "none";
5132
5510
  svg += `
5133
5511
  <rect x="${pos.x}" y="${pos.y}"
@@ -5143,13 +5521,13 @@ var SVGRenderer = class {
5143
5521
  font-family="Arial, Helvetica, sans-serif"
5144
5522
  font-size="18"
5145
5523
  font-weight="600"
5146
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5524
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5147
5525
  if (topbar.hasSubtitle) {
5148
5526
  svg += `
5149
5527
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
5150
5528
  font-family="Arial, Helvetica, sans-serif"
5151
5529
  font-size="13"
5152
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5530
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5153
5531
  }
5154
5532
  if (topbar.leftIcon) {
5155
5533
  svg += `
@@ -5630,8 +6008,13 @@ var SVGRenderer = class {
5630
6008
  // ============================================================================
5631
6009
  renderText(node, pos) {
5632
6010
  const text = String(node.props.text || "Text content");
5633
- 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;
5634
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);
5635
6018
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
5636
6019
  const totalTextHeight = lines.length * lineHeightPx;
5637
6020
  const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
@@ -5642,9 +6025,49 @@ var SVGRenderer = class {
5642
6025
  <text x="${pos.x}" y="${firstLineY}"
5643
6026
  font-family="Arial, Helvetica, sans-serif"
5644
6027
  font-size="${fontSize}"
6028
+ font-weight="${bold ? "700" : "400"}"
6029
+ font-style="${italic ? "italic" : "normal"}"
5645
6030
  fill="${this.renderTheme.text}">${tspans}</text>
5646
6031
  </g>`;
5647
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
+ }
5648
6071
  renderLabel(node, pos) {
5649
6072
  const text = String(node.props.text || "Label");
5650
6073
  const textY = pos.y + Math.round(pos.height / 2) + 4;
@@ -5887,34 +6310,145 @@ var SVGRenderer = class {
5887
6310
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
5888
6311
  const activeProp = node.props.active ?? 0;
5889
6312
  const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
5890
- 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);
5891
6325
  const tabWidth = pos.width / tabs.length;
5892
- let svg = `<g${this.getDataNodeId(node)}>
5893
- <!-- 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
+ }
5894
6352
  tabs.forEach((tab, i) => {
5895
6353
  const tabX = pos.x + i * tabWidth;
5896
6354
  const isActive = i === activeIndex;
5897
- svg += `
5898
- <rect x="${tabX}" y="${pos.y}"
5899
- width="${tabWidth}" height="44"
5900
- fill="${isActive ? accentColor : "transparent"}"
5901
- stroke="${isActive ? "none" : this.renderTheme.border}"
5902
- stroke-width="1"/>
5903
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
5904
- font-family="Arial, Helvetica, sans-serif"
5905
- font-size="13"
5906
- font-weight="${isActive ? "600" : "500"}"
5907
- 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}"
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}"
5908
6423
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
6424
+ }
6425
+ }
5909
6426
  });
5910
- svg += `
6427
+ const contentY = pos.y + tabHeight;
6428
+ const contentH = pos.height - tabHeight;
6429
+ if (contentH >= 20 && !isFlat) {
6430
+ svg += `
5911
6431
  <!-- Tab content area -->
5912
- <rect x="${pos.x}" y="${pos.y + 44}"
5913
- width="${pos.width}" height="${pos.height - 44}"
5914
- fill="${this.renderTheme.cardBg}"
5915
- stroke="${this.renderTheme.border}"
5916
- 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 += `
5917
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>";
5918
6452
  return svg;
5919
6453
  }
5920
6454
  renderDivider(node, pos) {
@@ -5981,15 +6515,20 @@ var SVGRenderer = class {
5981
6515
  const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
5982
6516
  const bgColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.renderTheme.border;
5983
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;
5984
6524
  const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
5985
- const fontSize = this.tokens.badge.fontSize;
5986
6525
  return `<g${this.getDataNodeId(node)}>
5987
6526
  <rect x="${pos.x}" y="${pos.y}"
5988
6527
  width="${pos.width}" height="${pos.height}"
5989
6528
  rx="${badgeRadius}"
5990
6529
  fill="${bgColor}"
5991
6530
  stroke="none"/>
5992
- <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}"
5993
6532
  font-family="Arial, Helvetica, sans-serif"
5994
6533
  font-size="${fontSize}"
5995
6534
  font-weight="600"
@@ -6198,11 +6737,20 @@ var SVGRenderer = class {
6198
6737
  return svg;
6199
6738
  }
6200
6739
  renderImage(node, pos) {
6201
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
6740
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
6202
6741
  const placeholderIcon = String(node.props.icon || "").trim();
6203
6742
  const variant = String(node.props.variant || "").trim();
6204
6743
  const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
6205
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})"` : "";
6206
6754
  const aspectRatios = {
6207
6755
  landscape: 16 / 9,
6208
6756
  portrait: 2 / 3,
@@ -6211,12 +6759,24 @@ var SVGRenderer = class {
6211
6759
  avatar: 1
6212
6760
  };
6213
6761
  const ratio = aspectRatios[placeholder] || 16 / 9;
6214
- const maxSize = Math.min(pos.width, pos.height) * 0.8;
6215
- let iconWidth = maxSize;
6216
- let iconHeight = maxSize / ratio;
6217
- if (iconHeight > pos.height * 0.8) {
6218
- iconHeight = pos.height * 0.8;
6219
- 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
+ }
6220
6780
  }
6221
6781
  const offsetX = pos.x + (pos.width - iconWidth) / 2;
6222
6782
  const offsetY = pos.y + (pos.height - iconHeight) / 2;
@@ -6229,17 +6789,18 @@ var SVGRenderer = class {
6229
6789
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
6230
6790
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
6231
6791
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
6232
- 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}>
6233
6794
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
6234
6795
  <g transform="translate(${iconOffsetX}, ${iconOffsetY})">
6235
6796
  <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6236
6797
  ${this.extractSvgContent(placeholderIconSvg)}
6237
6798
  </svg>
6238
6799
  </g>
6239
- <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}
6240
6801
  </g>`;
6241
6802
  }
6242
- let svg = `<g${this.getDataNodeId(node)}>
6803
+ let svg = `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6243
6804
  <!-- Image Background -->
6244
6805
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
6245
6806
  if (["landscape", "portrait", "square"].includes(placeholder)) {
@@ -6297,10 +6858,10 @@ var SVGRenderer = class {
6297
6858
  width="${personWidth * 0.6}" height="${personHeight * 0.5}"
6298
6859
  fill="#666" rx="3"/>`;
6299
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"/>`;
6300
6862
  svg += `
6301
6863
  <!-- Border -->
6302
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6303
- fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6864
+ ${borderShape}
6304
6865
  </g>`;
6305
6866
  return svg;
6306
6867
  }
@@ -6404,10 +6965,29 @@ var SVGRenderer = class {
6404
6965
  </g>`;
6405
6966
  }
6406
6967
  const iconSize = this.getIconSize(size);
6407
- const offsetX = pos.x + (pos.width - iconSize) / 2;
6408
- 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
+ }
6409
6989
  const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
6410
- <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">
6411
6991
  ${this.extractSvgContent(iconSvg)}
6412
6992
  </svg>
6413
6993
  </g>`;
@@ -6416,7 +6996,7 @@ var SVGRenderer = class {
6416
6996
  renderIconButton(node, pos) {
6417
6997
  const iconName = String(node.props.icon || "help-circle");
6418
6998
  const variant = String(node.props.variant || "default");
6419
- const size = String(node.props.size || "md");
6999
+ const size = String(node.props.size || "sm");
6420
7000
  const disabled = String(node.props.disabled || "false") === "true";
6421
7001
  const density = this.ir.project.style.density || "normal";
6422
7002
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -6544,7 +7124,7 @@ var SVGRenderer = class {
6544
7124
  wrapTextToLines(text, maxWidth, fontSize) {
6545
7125
  const normalized = text.replace(/\r\n/g, "\n");
6546
7126
  const paragraphs = normalized.split("\n");
6547
- const charWidth = fontSize * 0.6;
7127
+ const charWidth = fontSize * 0.5;
6548
7128
  const safeWidth = Math.max(maxWidth || 0, charWidth);
6549
7129
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
6550
7130
  const lines = [];
@@ -7659,7 +8239,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7659
8239
  renderButton(node, pos) {
7660
8240
  const text = String(node.props.text || "Button");
7661
8241
  const variant = String(node.props.variant || "default");
7662
- const size = String(node.props.size || "md");
8242
+ const size = String(node.props.size || "sm");
7663
8243
  const density = this.ir.project.style.density || "normal";
7664
8244
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
7665
8245
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -7766,7 +8346,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7766
8346
  renderIconButton(node, pos) {
7767
8347
  const iconName = String(node.props.icon || "help-circle");
7768
8348
  const variant = String(node.props.variant || "default");
7769
- const size = String(node.props.size || "md");
8349
+ const size = String(node.props.size || "sm");
7770
8350
  const density = this.ir.project.style.density || "normal";
7771
8351
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
7772
8352
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -8019,26 +8599,58 @@ var SketchSVGRenderer = class extends SVGRenderer {
8019
8599
  const variant = String(node.props.variant || "default");
8020
8600
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
8021
8601
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
8022
- 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 += `
8023
8633
  <rect x="${pos.x}" y="${pos.y}"
8024
8634
  width="${pos.width}" height="${pos.height}"
8025
- fill="${this.renderTheme.cardBg}"
8026
- stroke="#2D3748"
8635
+ fill="${sketchTopbarBg}"
8636
+ stroke="${stroke}"
8027
8637
  stroke-width="0.5"
8028
- filter="url(#sketch-rough)"/>
8029
-
8030
- <!-- Title -->
8638
+ filter="url(#sketch-rough)"/>`;
8639
+ }
8640
+ svg += `
8641
+ <!-- Title -->`;
8642
+ svg += `
8031
8643
  <text x="${topbar.textX}" y="${topbar.titleY}"
8032
8644
  font-family="${this.fontFamily}"
8033
8645
  font-size="18"
8034
8646
  font-weight="600"
8035
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8647
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8036
8648
  if (topbar.hasSubtitle) {
8037
8649
  svg += `
8038
8650
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
8039
8651
  font-family="${this.fontFamily}"
8040
8652
  font-size="13"
8041
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8653
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8042
8654
  }
8043
8655
  if (topbar.leftIcon) {
8044
8656
  svg += `
@@ -8118,8 +8730,13 @@ var SketchSVGRenderer = class extends SVGRenderer {
8118
8730
  */
8119
8731
  renderText(node, pos) {
8120
8732
  const text = String(node.props.text || "Text content");
8121
- 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;
8122
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);
8123
8740
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
8124
8741
  const firstLineY = pos.y + fontSize;
8125
8742
  const tspans = lines.map(
@@ -8129,9 +8746,48 @@ var SketchSVGRenderer = class extends SVGRenderer {
8129
8746
  <text x="${pos.x}" y="${firstLineY}"
8130
8747
  font-family="${this.fontFamily}"
8131
8748
  font-size="${fontSize}"
8749
+ font-weight="${bold ? "700" : "400"}"
8750
+ font-style="${italic ? "italic" : "normal"}"
8132
8751
  fill="${this.renderTheme.text}">${tspans}</text>
8133
8752
  </g>`;
8134
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
+ }
8135
8791
  /**
8136
8792
  * Render label with Comic Sans
8137
8793
  */
@@ -8362,36 +9018,150 @@ var SketchSVGRenderer = class extends SVGRenderer {
8362
9018
  renderTabs(node, pos) {
8363
9019
  const itemsStr = String(node.props.items || "");
8364
9020
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
8365
- 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);
8366
9035
  const tabWidth = pos.width / tabs.length;
8367
- let svg = `<g${this.getDataNodeId(node)}>
8368
- <!-- 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
+ }
8369
9062
  tabs.forEach((tab, i) => {
8370
9063
  const tabX = pos.x + i * tabWidth;
8371
- const isActive = i === 0;
8372
- 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 += `
8373
9108
  <rect x="${tabX}" y="${pos.y}"
8374
- width="${tabWidth}" height="44"
9109
+ width="${tabWidth}" height="${tabHeight}"
9110
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
8375
9111
  fill="${isActive ? accentColor : "transparent"}"
8376
- stroke="${isActive ? accentColor : "#2D3748"}"
9112
+ stroke="${isActive ? accentColor : showBorder ? "#2D3748" : "none"}"
8377
9113
  stroke-width="0.5"
8378
- filter="url(#sketch-rough)"/>
8379
- <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}"
8380
9125
  font-family="${this.fontFamily}"
8381
- 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}"
8382
9134
  font-weight="${isActive ? "600" : "500"}"
8383
- fill="${isActive ? "white" : this.renderTheme.text}"
9135
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
8384
9136
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
9137
+ }
9138
+ }
8385
9139
  });
8386
- svg += `
9140
+ const contentY = pos.y + tabHeight;
9141
+ const contentH = pos.height - tabHeight;
9142
+ if (contentH >= 20 && !isFlat) {
9143
+ svg += `
8387
9144
  <!-- Tab content area -->
8388
- <rect x="${pos.x}" y="${pos.y + 44}"
8389
- width="${pos.width}" height="${pos.height - 44}"
9145
+ <rect x="${pos.x}" y="${contentY}"
9146
+ width="${pos.width}" height="${contentH}"
8390
9147
  fill="${this.renderTheme.cardBg}"
8391
- stroke="#2D3748"
8392
- stroke-width="0.5"
8393
- 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 += `
8394
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>";
8395
9165
  return svg;
8396
9166
  }
8397
9167
  /**
@@ -8628,11 +9398,20 @@ var SketchSVGRenderer = class extends SVGRenderer {
8628
9398
  * Render image with sketch filter
8629
9399
  */
8630
9400
  renderImage(node, pos) {
8631
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
9401
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
8632
9402
  const iconType = String(node.props.icon || "").trim();
8633
9403
  const variant = String(node.props.variant || "").trim();
8634
9404
  const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
8635
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})"` : "";
8636
9415
  if (iconSvg) {
8637
9416
  const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
8638
9417
  const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
@@ -8642,7 +9421,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
8642
9421
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
8643
9422
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
8644
9423
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
8645
- 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}>
8646
9426
  <rect x="${pos.x}" y="${pos.y}"
8647
9427
  width="${pos.width}" height="${pos.height}"
8648
9428
  fill="${bgColor}"
@@ -8653,17 +9433,11 @@ var SketchSVGRenderer = class extends SVGRenderer {
8653
9433
  ${this.extractSvgContent(iconSvg)}
8654
9434
  </svg>
8655
9435
  </g>
8656
- <rect x="${pos.x}" y="${pos.y}"
8657
- width="${pos.width}" height="${pos.height}"
8658
- fill="none"
8659
- stroke="#2D3748"
8660
- stroke-width="0.5"
8661
- rx="4"
8662
- filter="url(#sketch-rough)"/>
9436
+ ${sketchIconBorder}
8663
9437
  </g>`;
8664
9438
  }
8665
- return `<g${this.getDataNodeId(node)}>
8666
- <!-- 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}>
8667
9441
  <rect x="${pos.x}" y="${pos.y}"
8668
9442
  width="${pos.width}" height="${pos.height}"
8669
9443
  fill="${imageBg}"
@@ -8671,14 +9445,12 @@ var SketchSVGRenderer = class extends SVGRenderer {
8671
9445
  stroke-width="0.5"
8672
9446
  rx="4"
8673
9447
  filter="url(#sketch-rough)"/>
8674
-
8675
- <!-- Placeholder icon -->
8676
9448
  <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
8677
9449
  font-family="${this.fontFamily}"
8678
9450
  font-size="24"
8679
9451
  fill="#666"
8680
9452
  text-anchor="middle">\u{1F5BC}</text>
8681
- </g>`;
9453
+ </g>${sketchCircleBorder}`;
8682
9454
  }
8683
9455
  /**
8684
9456
  * Render breadcrumbs with Comic Sans
@@ -8794,20 +9566,33 @@ var SketchSVGRenderer = class extends SVGRenderer {
8794
9566
  }
8795
9567
  const iconSize = this.getIconSize(size);
8796
9568
  const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
8797
- const offsetX = pos.x + (pos.width - iconSize) / 2;
8798
- 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
+ }
8799
9590
  return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
8800
- <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">
8801
9592
  ${this.extractSvgContent(iconSvg)}
8802
9593
  </svg>
8803
9594
  </g>`;
8804
9595
  }
8805
- /**
8806
- * Render chart placeholder with sketch filter and Comic Sans
8807
- */
8808
- renderChartPlaceholder(node, pos) {
8809
- return super.renderChartPlaceholder(node, pos);
8810
- }
8811
9596
  /**
8812
9597
  * Helper method to get icon SVG
8813
9598
  */