@wire-dsl/engine 0.6.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.cjs CHANGED
@@ -2589,13 +2589,13 @@ ${messages}`);
2589
2589
  "Heading",
2590
2590
  "Text",
2591
2591
  "Label",
2592
+ "Paragraph",
2592
2593
  "Image",
2593
2594
  "Card",
2594
2595
  "Stat",
2595
2596
  "Topbar",
2596
2597
  "Table",
2597
2598
  "Chart",
2598
- "ChartPlaceholder",
2599
2599
  "Textarea",
2600
2600
  "Select",
2601
2601
  "Checkbox",
@@ -2930,19 +2930,19 @@ var ICON_SIZES_BY_DENSITY = {
2930
2930
  comfortable: { xs: 14, sm: 16, md: 20, lg: 28, xl: 36 }
2931
2931
  };
2932
2932
  var ICON_BUTTON_SIZES_BY_DENSITY = {
2933
- compact: { sm: 20, md: 24, lg: 32 },
2934
- normal: { sm: 24, md: 32, lg: 40 },
2935
- comfortable: { sm: 28, md: 40, lg: 48 }
2933
+ compact: { xs: 16, sm: 20, md: 24, lg: 32, xl: 40 },
2934
+ normal: { xs: 20, sm: 24, md: 32, lg: 40, xl: 48 },
2935
+ comfortable: { xs: 24, sm: 28, md: 40, lg: 48, xl: 56 }
2936
2936
  };
2937
2937
  var CONTROL_HEIGHTS_BY_DENSITY = {
2938
- compact: { sm: 28, md: 32, lg: 36 },
2939
- normal: { sm: 36, md: 40, lg: 48 },
2940
- comfortable: { sm: 40, md: 48, lg: 56 }
2938
+ compact: { xs: 24, sm: 28, md: 32, lg: 36, xl: 44 },
2939
+ normal: { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 },
2940
+ comfortable: { xs: 32, sm: 40, md: 48, lg: 56, xl: 64 }
2941
2941
  };
2942
2942
  var ACTION_CONTROL_HEIGHTS_BY_DENSITY = {
2943
- compact: { sm: 20, md: 24, lg: 32 },
2944
- normal: { sm: 24, md: 32, lg: 40 },
2945
- comfortable: { sm: 28, md: 40, lg: 48 }
2943
+ compact: { xs: 24, sm: 28, md: 32, lg: 36, xl: 44 },
2944
+ normal: { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 },
2945
+ comfortable: { xs: 32, sm: 40, md: 48, lg: 56, xl: 64 }
2946
2946
  };
2947
2947
  var CONTROL_PADDING_BY_DENSITY = {
2948
2948
  compact: { none: 0, xs: 4, sm: 8, md: 10, lg: 14, xl: 18 },
@@ -3615,7 +3615,7 @@ var LayoutEngine = class {
3615
3615
  wrapTextToLines(text, maxWidth, fontSize) {
3616
3616
  const normalized = text.replace(/\r\n/g, "\n");
3617
3617
  const paragraphs = normalized.split("\n");
3618
- const charWidth = fontSize * 0.6;
3618
+ const charWidth = fontSize * 0.5;
3619
3619
  const safeWidth = Math.max(maxWidth, charWidth);
3620
3620
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
3621
3621
  const lines = [];
@@ -3653,12 +3653,14 @@ var LayoutEngine = class {
3653
3653
  getIntrinsicComponentHeight(node, availableWidth) {
3654
3654
  if (node.kind !== "component") return this.getComponentHeight();
3655
3655
  const controlSize = String(node.props.size || "md");
3656
+ const actionControlSize = String(node.props.size || "sm");
3656
3657
  const density = this.style.density || "normal";
3657
3658
  const inputControlHeight = resolveControlHeight(controlSize, density);
3658
- const actionControlHeight = resolveActionControlHeight(controlSize, density);
3659
+ const actionControlHeight = resolveActionControlHeight(actionControlSize, density);
3659
3660
  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;
3660
3661
  if (node.componentType === "Image") {
3661
3662
  const placeholder = String(node.props.placeholder || "landscape");
3663
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
3662
3664
  const aspectRatios = {
3663
3665
  landscape: 16 / 9,
3664
3666
  portrait: 2 / 3,
@@ -3666,7 +3668,7 @@ var LayoutEngine = class {
3666
3668
  icon: 1,
3667
3669
  avatar: 1
3668
3670
  };
3669
- const ratio = aspectRatios[placeholder] || 16 / 9;
3671
+ const ratio = isCircle ? 1 : aspectRatios[placeholder] || 16 / 9;
3670
3672
  const explicitHeight = Number(node.props.height);
3671
3673
  if (!isNaN(explicitHeight) && explicitHeight > 0) {
3672
3674
  return explicitHeight;
@@ -3718,13 +3720,19 @@ var LayoutEngine = class {
3718
3720
  }
3719
3721
  return Math.max(1, Math.ceil(wrappedHeight + verticalPadding * 2));
3720
3722
  }
3721
- if (node.componentType === "Text") {
3723
+ if (node.componentType === "Text" || node.componentType === "Paragraph") {
3722
3724
  const content = String(node.props.text || "");
3723
- const { fontSize, lineHeight } = this.getTextMetricsForDensity();
3725
+ const { fontSize: defaultFontSize, lineHeight } = this.getTextMetricsForDensity();
3726
+ const sizeProp = String(node.props.size || "");
3727
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
3728
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
3724
3729
  const lineHeightPx = Math.ceil(fontSize * lineHeight);
3725
3730
  const maxWidth = availableWidth && availableWidth > 0 ? availableWidth : 200;
3726
3731
  const lines = this.wrapTextToLines(content, maxWidth, fontSize);
3727
3732
  const wrappedHeight = Math.max(1, lines.length) * lineHeightPx;
3733
+ if (sizeProp) {
3734
+ return wrappedHeight;
3735
+ }
3728
3736
  return Math.max(this.getComponentHeight(), wrappedHeight);
3729
3737
  }
3730
3738
  if (node.componentType === "Alert") {
@@ -3753,7 +3761,7 @@ var LayoutEngine = class {
3753
3761
  if (node.componentType === "Modal") return 300;
3754
3762
  if (node.componentType === "Card") return 120;
3755
3763
  if (node.componentType === "Stat") return 120;
3756
- if (node.componentType === "Chart" || node.componentType === "ChartPlaceholder") return 250;
3764
+ if (node.componentType === "Chart") return 250;
3757
3765
  if (node.componentType === "List") {
3758
3766
  const itemsFromProps = String(node.props.items || "").split(",").map((item) => item.trim()).filter(Boolean);
3759
3767
  const parsedItemsMock = Number(node.props.itemsMock ?? 4);
@@ -3764,7 +3772,14 @@ var LayoutEngine = class {
3764
3772
  const contentHeight = titleHeight + itemCount * itemHeight;
3765
3773
  return Math.max(this.getComponentHeight(), contentHeight);
3766
3774
  }
3767
- if (node.componentType === "Topbar") return 56;
3775
+ if (node.componentType === "Topbar") {
3776
+ const TOPBAR_HEIGHTS = { sm: 44, md: 56, lg: 72 };
3777
+ return TOPBAR_HEIGHTS[String(node.props.size || "md")] ?? 56;
3778
+ }
3779
+ if (node.componentType === "Tabs") {
3780
+ const TABS_HEIGHTS = { sm: 32, md: 44, lg: 52 };
3781
+ return TABS_HEIGHTS[String(node.props.size || "md")] ?? 44;
3782
+ }
3768
3783
  if (node.componentType === "Divider") return 1;
3769
3784
  if (node.componentType === "Separate") return this.getSeparateSize(node);
3770
3785
  if (node.componentType === "Input" || node.componentType === "Select") {
@@ -3773,6 +3788,11 @@ var LayoutEngine = class {
3773
3788
  if (node.componentType === "Button" || node.componentType === "IconButton" || node.componentType === "Link") {
3774
3789
  return actionControlHeight + controlLabelOffset;
3775
3790
  }
3791
+ if (node.componentType === "Badge" || node.componentType === "Chip") {
3792
+ const BADGE_HEIGHTS = { xs: 28, sm: 36, md: 40, lg: 48, xl: 56 };
3793
+ const badgeSize = String(node.props.size || "md");
3794
+ return BADGE_HEIGHTS[badgeSize] ?? 40;
3795
+ }
3776
3796
  return this.getComponentHeight();
3777
3797
  }
3778
3798
  getControlLabelOffset(label) {
@@ -3828,7 +3848,7 @@ var LayoutEngine = class {
3828
3848
  return resolveIconSize(size, this.style.density || "normal");
3829
3849
  }
3830
3850
  if (node.componentType === "IconButton") {
3831
- const size = String(node.props.size || "md");
3851
+ const size = String(node.props.size || "sm");
3832
3852
  const density = this.style.density || "normal";
3833
3853
  const baseSize = resolveIconButtonSize(size, density);
3834
3854
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -3886,7 +3906,13 @@ var LayoutEngine = class {
3886
3906
  }
3887
3907
  if (node.componentType === "Badge" || node.componentType === "Chip") {
3888
3908
  const text = String(node.props.text || "");
3889
- return Math.max(50, text.length * 7 + 16);
3909
+ const size = String(node.props.size || "md");
3910
+ const BADGE_CHAR_W = { xs: 6, sm: 6.5, md: 7, lg: 7.5, xl: 8.5 };
3911
+ const BADGE_PAD_X = { xs: 5, sm: 6, md: 8, lg: 10, xl: 14 };
3912
+ const customPadding = node.props.padding !== void 0 ? Number(node.props.padding) : void 0;
3913
+ const padX = customPadding !== void 0 && !isNaN(customPadding) ? customPadding : BADGE_PAD_X[size] ?? 8;
3914
+ const charW = BADGE_CHAR_W[size] ?? 7;
3915
+ return Math.max(padX * 4, text.length * charW + padX * 2);
3890
3916
  }
3891
3917
  return 120;
3892
3918
  }
@@ -4170,34 +4196,358 @@ MockDataGenerator.HEADER_TO_MOCK = {
4170
4196
  var ColorResolver = class {
4171
4197
  constructor() {
4172
4198
  this.customColors = {};
4173
- // Named colors palette
4199
+ // Named colors palette — Full Material Design + utility aliases
4174
4200
  this.namedColors = {
4175
- // Grayscale
4201
+ // ── Utility ──────────────────────────────────────────────────────────────
4176
4202
  white: "#FFFFFF",
4177
4203
  black: "#000000",
4178
- gray: "#6B7280",
4179
- slate: "#64748B",
4204
+ gray: "#9E9E9E",
4205
+ // alias for grey 500
4206
+ grey: "#9E9E9E",
4207
+ slate: "#607D8B",
4208
+ // alias for blue_grey 500
4180
4209
  zinc: "#71717A",
4181
- // Colors
4182
- red: "#EF4444",
4183
- orange: "#F97316",
4184
- yellow: "#EAB308",
4185
- lime: "#84CC16",
4186
- green: "#22C55E",
4210
+ // Extra well-known non-Material names (kept for backward compat)
4187
4211
  emerald: "#10B981",
4188
- teal: "#14B8A6",
4189
- cyan: "#06B6D4",
4190
- blue: "#3B82F6",
4191
- indigo: "#6366F1",
4192
4212
  violet: "#8B5CF6",
4193
- purple: "#A855F7",
4194
4213
  fuchsia: "#D946EF",
4195
- pink: "#EC4899",
4196
4214
  rose: "#F43F5E",
4197
- // Light variants
4198
- "red-light": "#FEE2E2",
4199
- "blue-light": "#DBEAFE",
4200
- "green-light": "#DCFCE7"
4215
+ // Light utility variants
4216
+ "red-light": "#FFCDD2",
4217
+ "blue-light": "#BBDEFB",
4218
+ "green-light": "#C8E6C9",
4219
+ // ── Red ──────────────────────────────────────────────────────────────────
4220
+ red: "#F44336",
4221
+ // 500
4222
+ red_50: "#FFEBEE",
4223
+ red_100: "#FFCDD2",
4224
+ red_200: "#EF9A9A",
4225
+ red_300: "#E57373",
4226
+ red_400: "#EF5350",
4227
+ red_500: "#F44336",
4228
+ red_600: "#E53935",
4229
+ red_700: "#D32F2F",
4230
+ red_800: "#C62828",
4231
+ red_900: "#B71C1C",
4232
+ red_A100: "#FF8A80",
4233
+ red_A200: "#FF5252",
4234
+ red_A400: "#FF1744",
4235
+ red_A700: "#D50000",
4236
+ // ── Pink ─────────────────────────────────────────────────────────────────
4237
+ pink: "#E91E63",
4238
+ // 500
4239
+ pink_50: "#FCE4EC",
4240
+ pink_100: "#F8BBD0",
4241
+ pink_200: "#F48FB1",
4242
+ pink_300: "#F06292",
4243
+ pink_400: "#EC407A",
4244
+ pink_500: "#E91E63",
4245
+ pink_600: "#D81B60",
4246
+ pink_700: "#C2185B",
4247
+ pink_800: "#AD1457",
4248
+ pink_900: "#880E4F",
4249
+ pink_A100: "#FF80AB",
4250
+ pink_A200: "#FF4081",
4251
+ pink_A400: "#F50057",
4252
+ pink_A700: "#C51162",
4253
+ // ── Purple ───────────────────────────────────────────────────────────────
4254
+ purple: "#9C27B0",
4255
+ // 500
4256
+ purple_50: "#F3E5F5",
4257
+ purple_100: "#E1BEE7",
4258
+ purple_200: "#CE93D8",
4259
+ purple_300: "#BA68C8",
4260
+ purple_400: "#AB47BC",
4261
+ purple_500: "#9C27B0",
4262
+ purple_600: "#8E24AA",
4263
+ purple_700: "#7B1FA2",
4264
+ purple_800: "#6A1B9A",
4265
+ purple_900: "#4A148C",
4266
+ purple_A100: "#EA80FC",
4267
+ purple_A200: "#E040FB",
4268
+ purple_A400: "#D500F9",
4269
+ purple_A700: "#AA00FF",
4270
+ // ── Deep Purple ──────────────────────────────────────────────────────────
4271
+ deep_purple: "#673AB7",
4272
+ // 500
4273
+ deep_purple_50: "#EDE7F6",
4274
+ deep_purple_100: "#D1C4E9",
4275
+ deep_purple_200: "#B39DDB",
4276
+ deep_purple_300: "#9575CD",
4277
+ deep_purple_400: "#7E57C2",
4278
+ deep_purple_500: "#673AB7",
4279
+ deep_purple_600: "#5E35B1",
4280
+ deep_purple_700: "#512DA8",
4281
+ deep_purple_800: "#4527A0",
4282
+ deep_purple_900: "#311B92",
4283
+ deep_purple_A100: "#B388FF",
4284
+ deep_purple_A200: "#7C4DFF",
4285
+ deep_purple_A400: "#651FFF",
4286
+ deep_purple_A700: "#6200EA",
4287
+ // ── Indigo ───────────────────────────────────────────────────────────────
4288
+ indigo: "#3F51B5",
4289
+ // 500
4290
+ indigo_50: "#E8EAF6",
4291
+ indigo_100: "#C5CAE9",
4292
+ indigo_200: "#9FA8DA",
4293
+ indigo_300: "#7986CB",
4294
+ indigo_400: "#5C6BC0",
4295
+ indigo_500: "#3F51B5",
4296
+ indigo_600: "#3949AB",
4297
+ indigo_700: "#303F9F",
4298
+ indigo_800: "#283593",
4299
+ indigo_900: "#1A237E",
4300
+ indigo_A100: "#8C9EFF",
4301
+ indigo_A200: "#536DFE",
4302
+ indigo_A400: "#3D5AFE",
4303
+ indigo_A700: "#304FFE",
4304
+ // ── Blue ─────────────────────────────────────────────────────────────────
4305
+ blue: "#2196F3",
4306
+ // 500
4307
+ blue_50: "#E3F2FD",
4308
+ blue_100: "#BBDEFB",
4309
+ blue_200: "#90CAF9",
4310
+ blue_300: "#64B5F6",
4311
+ blue_400: "#42A5F5",
4312
+ blue_500: "#2196F3",
4313
+ blue_600: "#1E88E5",
4314
+ blue_700: "#1976D2",
4315
+ blue_800: "#1565C0",
4316
+ blue_900: "#0D47A1",
4317
+ blue_A100: "#82B1FF",
4318
+ blue_A200: "#448AFF",
4319
+ blue_A400: "#2979FF",
4320
+ blue_A700: "#2962FF",
4321
+ // ── Light Blue ───────────────────────────────────────────────────────────
4322
+ light_blue: "#03A9F4",
4323
+ // 500
4324
+ light_blue_50: "#E1F5FE",
4325
+ light_blue_100: "#B3E5FC",
4326
+ light_blue_200: "#81D4FA",
4327
+ light_blue_300: "#4FC3F7",
4328
+ light_blue_400: "#29B6F6",
4329
+ light_blue_500: "#03A9F4",
4330
+ light_blue_600: "#039BE5",
4331
+ light_blue_700: "#0288D1",
4332
+ light_blue_800: "#0277BD",
4333
+ light_blue_900: "#01579B",
4334
+ light_blue_A100: "#80D8FF",
4335
+ light_blue_A200: "#40C4FF",
4336
+ light_blue_A400: "#00B0FF",
4337
+ light_blue_A700: "#0091EA",
4338
+ // ── Cyan ─────────────────────────────────────────────────────────────────
4339
+ cyan: "#00BCD4",
4340
+ // 500
4341
+ cyan_50: "#E0F7FA",
4342
+ cyan_100: "#B2EBF2",
4343
+ cyan_200: "#80DEEA",
4344
+ cyan_300: "#4DD0E1",
4345
+ cyan_400: "#26C6DA",
4346
+ cyan_500: "#00BCD4",
4347
+ cyan_600: "#00ACC1",
4348
+ cyan_700: "#0097A7",
4349
+ cyan_800: "#00838F",
4350
+ cyan_900: "#006064",
4351
+ cyan_A100: "#84FFFF",
4352
+ cyan_A200: "#18FFFF",
4353
+ cyan_A400: "#00E5FF",
4354
+ cyan_A700: "#00B8D4",
4355
+ // ── Teal ─────────────────────────────────────────────────────────────────
4356
+ teal: "#009688",
4357
+ // 500
4358
+ teal_50: "#E0F2F1",
4359
+ teal_100: "#B2DFDB",
4360
+ teal_200: "#80CBC4",
4361
+ teal_300: "#4DB6AC",
4362
+ teal_400: "#26A69A",
4363
+ teal_500: "#009688",
4364
+ teal_600: "#00897B",
4365
+ teal_700: "#00796B",
4366
+ teal_800: "#00695C",
4367
+ teal_900: "#004D40",
4368
+ teal_A100: "#A7FFEB",
4369
+ teal_A200: "#64FFDA",
4370
+ teal_A400: "#1DE9B6",
4371
+ teal_A700: "#00BFA5",
4372
+ // ── Green ────────────────────────────────────────────────────────────────
4373
+ green: "#4CAF50",
4374
+ // 500
4375
+ green_50: "#E8F5E9",
4376
+ green_100: "#C8E6C9",
4377
+ green_200: "#A5D6A7",
4378
+ green_300: "#81C784",
4379
+ green_400: "#66BB6A",
4380
+ green_500: "#4CAF50",
4381
+ green_600: "#43A047",
4382
+ green_700: "#388E3C",
4383
+ green_800: "#2E7D32",
4384
+ green_900: "#1B5E20",
4385
+ green_A100: "#B9F6CA",
4386
+ green_A200: "#69F0AE",
4387
+ green_A400: "#00E676",
4388
+ green_A700: "#00C853",
4389
+ // ── Light Green ──────────────────────────────────────────────────────────
4390
+ light_green: "#8BC34A",
4391
+ // 500
4392
+ light_green_50: "#F1F8E9",
4393
+ light_green_100: "#DCEDC8",
4394
+ light_green_200: "#C5E1A5",
4395
+ light_green_300: "#AED581",
4396
+ light_green_400: "#9CCC65",
4397
+ light_green_500: "#8BC34A",
4398
+ light_green_600: "#7CB342",
4399
+ light_green_700: "#689F38",
4400
+ light_green_800: "#558B2F",
4401
+ light_green_900: "#33691E",
4402
+ light_green_A100: "#CCFF90",
4403
+ light_green_A200: "#B2FF59",
4404
+ light_green_A400: "#76FF03",
4405
+ light_green_A700: "#64DD17",
4406
+ // ── Lime ─────────────────────────────────────────────────────────────────
4407
+ lime: "#CDDC39",
4408
+ // 500
4409
+ lime_50: "#F9FBE7",
4410
+ lime_100: "#F0F4C3",
4411
+ lime_200: "#E6EE9C",
4412
+ lime_300: "#DCE775",
4413
+ lime_400: "#D4E157",
4414
+ lime_500: "#CDDC39",
4415
+ lime_600: "#C0CA33",
4416
+ lime_700: "#AFB42B",
4417
+ lime_800: "#9E9D24",
4418
+ lime_900: "#827717",
4419
+ lime_A100: "#F4FF81",
4420
+ lime_A200: "#EEFF41",
4421
+ lime_A400: "#C6FF00",
4422
+ lime_A700: "#AEEA00",
4423
+ // ── Yellow ───────────────────────────────────────────────────────────────
4424
+ yellow: "#FFEB3B",
4425
+ // 500
4426
+ yellow_50: "#FFFDE7",
4427
+ yellow_100: "#FFF9C4",
4428
+ yellow_200: "#FFF59D",
4429
+ yellow_300: "#FFF176",
4430
+ yellow_400: "#FFEE58",
4431
+ yellow_500: "#FFEB3B",
4432
+ yellow_600: "#FDD835",
4433
+ yellow_700: "#F9A825",
4434
+ yellow_800: "#F57F17",
4435
+ yellow_900: "#F57F17",
4436
+ yellow_A100: "#FFFF8D",
4437
+ yellow_A200: "#FFFF00",
4438
+ yellow_A400: "#FFEA00",
4439
+ yellow_A700: "#FFD600",
4440
+ // ── Amber ────────────────────────────────────────────────────────────────
4441
+ amber: "#FFC107",
4442
+ // 500
4443
+ amber_50: "#FFF8E1",
4444
+ amber_100: "#FFECB3",
4445
+ amber_200: "#FFE082",
4446
+ amber_300: "#FFD54F",
4447
+ amber_400: "#FFCA28",
4448
+ amber_500: "#FFC107",
4449
+ amber_600: "#FFB300",
4450
+ amber_700: "#FFA000",
4451
+ amber_800: "#FF8F00",
4452
+ amber_900: "#FF6F00",
4453
+ amber_A100: "#FFE57F",
4454
+ amber_A200: "#FFD740",
4455
+ amber_A400: "#FFC400",
4456
+ amber_A700: "#FFAB00",
4457
+ // ── Orange ───────────────────────────────────────────────────────────────
4458
+ orange: "#FF9800",
4459
+ // 500
4460
+ orange_50: "#FFF3E0",
4461
+ orange_100: "#FFE0B2",
4462
+ orange_200: "#FFCC80",
4463
+ orange_300: "#FFB74D",
4464
+ orange_400: "#FFA726",
4465
+ orange_500: "#FF9800",
4466
+ orange_600: "#FB8C00",
4467
+ orange_700: "#F57C00",
4468
+ orange_800: "#EF6C00",
4469
+ orange_900: "#E65100",
4470
+ orange_A100: "#FFD180",
4471
+ orange_A200: "#FFAB40",
4472
+ orange_A400: "#FF9100",
4473
+ orange_A700: "#FF6D00",
4474
+ // ── Deep Orange ──────────────────────────────────────────────────────────
4475
+ deep_orange: "#FF5722",
4476
+ // 500
4477
+ deep_orange_50: "#FBE9E7",
4478
+ deep_orange_100: "#FFCCBC",
4479
+ deep_orange_200: "#FFAB91",
4480
+ deep_orange_300: "#FF8A65",
4481
+ deep_orange_400: "#FF7043",
4482
+ deep_orange_500: "#FF5722",
4483
+ deep_orange_600: "#F4511E",
4484
+ deep_orange_700: "#E64A19",
4485
+ deep_orange_800: "#D84315",
4486
+ deep_orange_900: "#BF360C",
4487
+ deep_orange_A100: "#FF9E80",
4488
+ deep_orange_A200: "#FF6E40",
4489
+ deep_orange_A400: "#FF3D00",
4490
+ deep_orange_A700: "#DD2C00",
4491
+ // ── Brown ────────────────────────────────────────────────────────────────
4492
+ brown: "#795548",
4493
+ // 500
4494
+ brown_50: "#EFEBE9",
4495
+ brown_100: "#D7CCC8",
4496
+ brown_200: "#BCAAA4",
4497
+ brown_300: "#A1887F",
4498
+ brown_400: "#8D6E63",
4499
+ brown_500: "#795548",
4500
+ brown_600: "#6D4C41",
4501
+ brown_700: "#5D4037",
4502
+ brown_800: "#4E342E",
4503
+ brown_900: "#3E2723",
4504
+ // ── Grey ─────────────────────────────────────────────────────────────────
4505
+ grey_50: "#FAFAFA",
4506
+ grey_100: "#F5F5F5",
4507
+ grey_200: "#EEEEEE",
4508
+ grey_300: "#E0E0E0",
4509
+ grey_400: "#BDBDBD",
4510
+ grey_500: "#9E9E9E",
4511
+ grey_600: "#757575",
4512
+ grey_700: "#616161",
4513
+ grey_800: "#424242",
4514
+ grey_900: "#212121",
4515
+ // aliases
4516
+ gray_50: "#FAFAFA",
4517
+ gray_100: "#F5F5F5",
4518
+ gray_200: "#EEEEEE",
4519
+ gray_300: "#E0E0E0",
4520
+ gray_400: "#BDBDBD",
4521
+ gray_500: "#9E9E9E",
4522
+ gray_600: "#757575",
4523
+ gray_700: "#616161",
4524
+ gray_800: "#424242",
4525
+ gray_900: "#212121",
4526
+ // ── Blue Grey ────────────────────────────────────────────────────────────
4527
+ blue_grey: "#607D8B",
4528
+ // 500
4529
+ blue_grey_50: "#ECEFF1",
4530
+ blue_grey_100: "#CFD8DC",
4531
+ blue_grey_200: "#B0BEC5",
4532
+ blue_grey_300: "#90A4AE",
4533
+ blue_grey_400: "#78909C",
4534
+ blue_grey_500: "#607D8B",
4535
+ blue_grey_600: "#546E7A",
4536
+ blue_grey_700: "#455A64",
4537
+ blue_grey_800: "#37474F",
4538
+ blue_grey_900: "#263238",
4539
+ // aliases
4540
+ blue_gray: "#607D8B",
4541
+ blue_gray_50: "#ECEFF1",
4542
+ blue_gray_100: "#CFD8DC",
4543
+ blue_gray_200: "#B0BEC5",
4544
+ blue_gray_300: "#90A4AE",
4545
+ blue_gray_400: "#78909C",
4546
+ blue_gray_500: "#607D8B",
4547
+ blue_gray_600: "#546E7A",
4548
+ blue_gray_700: "#455A64",
4549
+ blue_gray_800: "#37474F",
4550
+ blue_gray_900: "#263238"
4201
4551
  };
4202
4552
  }
4203
4553
  /**
@@ -4845,7 +5195,8 @@ var SVGRenderer = class {
4845
5195
  if (node.containerType === "split") {
4846
5196
  this.renderSplitDecoration(node, pos, containerGroup);
4847
5197
  }
4848
- if (node.children.length === 0 && this.options.showDiagnostics) {
5198
+ const isCellContainer = node.meta?.source === "cell";
5199
+ if (node.children.length === 0 && this.options.showDiagnostics && !isCellContainer) {
4849
5200
  containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
4850
5201
  } else {
4851
5202
  node.children.forEach((childRef) => {
@@ -4892,7 +5243,6 @@ var SVGRenderer = class {
4892
5243
  case "Table":
4893
5244
  return this.renderTable(node, pos);
4894
5245
  case "Chart":
4895
- case "ChartPlaceholder":
4896
5246
  return this.renderChartPlaceholder(node, pos);
4897
5247
  case "Breadcrumbs":
4898
5248
  return this.renderBreadcrumbs(node, pos);
@@ -4901,6 +5251,8 @@ var SVGRenderer = class {
4901
5251
  // Text/Content components
4902
5252
  case "Text":
4903
5253
  return this.renderText(node, pos);
5254
+ case "Paragraph":
5255
+ return this.renderParagraph(node, pos);
4904
5256
  case "Label":
4905
5257
  return this.renderLabel(node, pos);
4906
5258
  case "Code":
@@ -4980,7 +5332,7 @@ var SVGRenderer = class {
4980
5332
  renderButton(node, pos) {
4981
5333
  const text = String(node.props.text || "Button");
4982
5334
  const variant = String(node.props.variant || "default");
4983
- const size = String(node.props.size || "md");
5335
+ const size = String(node.props.size || "sm");
4984
5336
  const disabled = this.parseBooleanProp(node.props.disabled, false);
4985
5337
  const density = this.ir.project.style.density || "normal";
4986
5338
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -5055,7 +5407,7 @@ var SVGRenderer = class {
5055
5407
  renderLink(node, pos) {
5056
5408
  const text = String(node.props.text || "Link");
5057
5409
  const variant = String(node.props.variant || "primary");
5058
- const size = String(node.props.size || "md");
5410
+ const size = String(node.props.size || "sm");
5059
5411
  const density = this.ir.project.style.density || "normal";
5060
5412
  const fontSize = this.tokens.button.fontSize;
5061
5413
  const fontWeight = this.tokens.button.fontWeight;
@@ -5161,7 +5513,33 @@ var SVGRenderer = class {
5161
5513
  const variant = String(node.props.variant || "default");
5162
5514
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
5163
5515
  const showBorder = this.parseBooleanProp(node.props.border, false);
5164
- const showBackground = this.parseBooleanProp(node.props.background, false);
5516
+ const bgPropStr = String(node.props.background ?? "");
5517
+ let resolvedBg = null;
5518
+ if (bgPropStr === "true") {
5519
+ resolvedBg = this.renderTheme.cardBg;
5520
+ } else if (bgPropStr && bgPropStr !== "false") {
5521
+ if (bgPropStr.startsWith("#") || bgPropStr.startsWith("rgb")) {
5522
+ resolvedBg = bgPropStr;
5523
+ } else if (this.colorResolver.hasColor(bgPropStr)) {
5524
+ resolvedBg = this.colorResolver.resolveColor(bgPropStr, this.renderTheme.cardBg);
5525
+ }
5526
+ }
5527
+ const showBackground = resolvedBg !== null;
5528
+ const colorPropStr = String(node.props.color ?? "");
5529
+ let titleColor = this.renderTheme.text;
5530
+ let subtitleColor = this.renderTheme.textMuted;
5531
+ if (colorPropStr && colorPropStr !== "false") {
5532
+ let resolvedTitleColor = null;
5533
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
5534
+ resolvedTitleColor = colorPropStr;
5535
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
5536
+ resolvedTitleColor = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
5537
+ }
5538
+ if (resolvedTitleColor) {
5539
+ titleColor = resolvedTitleColor;
5540
+ subtitleColor = this.hexToRgba(resolvedTitleColor, 0.65);
5541
+ }
5542
+ }
5165
5543
  const radiusMap = {
5166
5544
  none: 0,
5167
5545
  sm: 4,
@@ -5173,7 +5551,7 @@ var SVGRenderer = class {
5173
5551
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
5174
5552
  let svg = `<g${this.getDataNodeId(node)}>`;
5175
5553
  if (showBorder || showBackground) {
5176
- const bg = showBackground ? this.renderTheme.cardBg : "none";
5554
+ const bg = resolvedBg ?? "none";
5177
5555
  const stroke = showBorder ? this.renderTheme.border : "none";
5178
5556
  svg += `
5179
5557
  <rect x="${pos.x}" y="${pos.y}"
@@ -5189,13 +5567,13 @@ var SVGRenderer = class {
5189
5567
  font-family="Arial, Helvetica, sans-serif"
5190
5568
  font-size="18"
5191
5569
  font-weight="600"
5192
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5570
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
5193
5571
  if (topbar.hasSubtitle) {
5194
5572
  svg += `
5195
5573
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
5196
5574
  font-family="Arial, Helvetica, sans-serif"
5197
5575
  font-size="13"
5198
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5576
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
5199
5577
  }
5200
5578
  if (topbar.leftIcon) {
5201
5579
  svg += `
@@ -5676,8 +6054,13 @@ var SVGRenderer = class {
5676
6054
  // ============================================================================
5677
6055
  renderText(node, pos) {
5678
6056
  const text = String(node.props.text || "Text content");
5679
- const fontSize = this.tokens.text.fontSize;
6057
+ const sizeProp = String(node.props.size || "");
6058
+ const defaultFontSize = this.tokens.text.fontSize;
6059
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
6060
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
5680
6061
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
6062
+ const bold = this.parseBooleanProp(node.props.bold, false);
6063
+ const italic = this.parseBooleanProp(node.props.italic, false);
5681
6064
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
5682
6065
  const totalTextHeight = lines.length * lineHeightPx;
5683
6066
  const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
@@ -5688,9 +6071,49 @@ var SVGRenderer = class {
5688
6071
  <text x="${pos.x}" y="${firstLineY}"
5689
6072
  font-family="Arial, Helvetica, sans-serif"
5690
6073
  font-size="${fontSize}"
6074
+ font-weight="${bold ? "700" : "400"}"
6075
+ font-style="${italic ? "italic" : "normal"}"
5691
6076
  fill="${this.renderTheme.text}">${tspans}</text>
5692
6077
  </g>`;
5693
6078
  }
6079
+ renderParagraph(node, pos) {
6080
+ const text = String(node.props.text || "");
6081
+ const sizeProp = String(node.props.size || "");
6082
+ const defaultFontSize = this.tokens.text.fontSize;
6083
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
6084
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
6085
+ const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
6086
+ const bold = this.parseBooleanProp(node.props.bold, false);
6087
+ const italic = this.parseBooleanProp(node.props.italic, false);
6088
+ const align = String(node.props.align || "left");
6089
+ const lines = this.wrapTextToLines(text, pos.width, fontSize);
6090
+ const totalTextHeight = lines.length * lineHeightPx;
6091
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
6092
+ let textX;
6093
+ let textAnchor;
6094
+ if (align === "center") {
6095
+ textX = pos.x + pos.width / 2;
6096
+ textAnchor = "middle";
6097
+ } else if (align === "right") {
6098
+ textX = pos.x + pos.width;
6099
+ textAnchor = "end";
6100
+ } else {
6101
+ textX = pos.x;
6102
+ textAnchor = "start";
6103
+ }
6104
+ const tspans = lines.map(
6105
+ (line, index) => `<tspan x="${textX}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
6106
+ ).join("");
6107
+ return `<g${this.getDataNodeId(node)}>
6108
+ <text x="${textX}" y="${firstLineY}"
6109
+ font-family="Arial, Helvetica, sans-serif"
6110
+ font-size="${fontSize}"
6111
+ font-weight="${bold ? "700" : "400"}"
6112
+ font-style="${italic ? "italic" : "normal"}"
6113
+ fill="${this.renderTheme.text}"
6114
+ text-anchor="${textAnchor}">${tspans}</text>
6115
+ </g>`;
6116
+ }
5694
6117
  renderLabel(node, pos) {
5695
6118
  const text = String(node.props.text || "Label");
5696
6119
  const textY = pos.y + Math.round(pos.height / 2) + 4;
@@ -5933,34 +6356,145 @@ var SVGRenderer = class {
5933
6356
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
5934
6357
  const activeProp = node.props.active ?? 0;
5935
6358
  const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
5936
- const accentColor = this.resolveAccentColor();
6359
+ const variant = String(node.props.variant || "default");
6360
+ const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
6361
+ const radiusMap = { none: 0, sm: 4, md: 6, lg: 10, full: 20 };
6362
+ const tabRadius = radiusMap[String(node.props.radius || "md")] ?? 6;
6363
+ const sizeMap = { sm: 32, md: 44, lg: 52 };
6364
+ const tabHeight = pos.height > 0 ? pos.height : sizeMap[String(node.props.size || "md")] ?? 44;
6365
+ const fontSize = 13;
6366
+ const textY = pos.y + Math.round(tabHeight / 2) + Math.round(fontSize * 0.4);
6367
+ const iconsStr = String(node.props.icons || "");
6368
+ const iconList = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
6369
+ const isFlat = this.parseBooleanProp(node.props.flat, false);
6370
+ const showBorder = this.parseBooleanProp(node.props.border, true);
5937
6371
  const tabWidth = pos.width / tabs.length;
5938
- let svg = `<g${this.getDataNodeId(node)}>
5939
- <!-- Tab headers -->`;
6372
+ const colorPropStr = String(node.props.color ?? "");
6373
+ let textColorOverride = null;
6374
+ if (colorPropStr && colorPropStr !== "false") {
6375
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
6376
+ textColorOverride = colorPropStr;
6377
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
6378
+ textColorOverride = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
6379
+ }
6380
+ }
6381
+ const activeTextColor = textColorOverride ?? (isFlat ? accentColor : "white");
6382
+ const inactiveTextColor = textColorOverride ? this.hexToRgba(textColorOverride, 0.55) : this.renderTheme.textMuted;
6383
+ const hasRadius = tabRadius > 0;
6384
+ 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))}`}` : "";
6385
+ let svg = "";
6386
+ if (hasRadius) {
6387
+ svg += `<defs><clipPath id="${clipId}"><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="${tabRadius}"/></clipPath></defs>`;
6388
+ }
6389
+ svg += `<g${this.getDataNodeId(node)}>`;
6390
+ if (hasRadius) {
6391
+ svg += `<g clip-path="url(#${clipId})">`;
6392
+ }
6393
+ if (!isFlat) {
6394
+ svg += `
6395
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${tabHeight}"
6396
+ fill="${this.renderTheme.bg}"/>`;
6397
+ }
5940
6398
  tabs.forEach((tab, i) => {
5941
6399
  const tabX = pos.x + i * tabWidth;
5942
6400
  const isActive = i === activeIndex;
5943
- svg += `
5944
- <rect x="${tabX}" y="${pos.y}"
5945
- width="${tabWidth}" height="44"
5946
- fill="${isActive ? accentColor : "transparent"}"
5947
- stroke="${isActive ? "none" : this.renderTheme.border}"
5948
- stroke-width="1"/>
5949
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
5950
- font-family="Arial, Helvetica, sans-serif"
5951
- font-size="13"
5952
- font-weight="${isActive ? "600" : "500"}"
5953
- fill="${isActive ? "white" : this.renderTheme.text}"
6401
+ const iconName = iconList[i] || "";
6402
+ const tabIconSvg = iconName ? getIcon(iconName) : null;
6403
+ const iconSize = 14;
6404
+ const iconGap = 4;
6405
+ const charW = fontSize * 0.58;
6406
+ const textEst = tab.length * charW;
6407
+ const totalW = tabIconSvg ? iconSize + iconGap + textEst : textEst;
6408
+ const groupX = tabX + Math.round((tabWidth - totalW) / 2);
6409
+ const iconOffY = pos.y + Math.round((tabHeight - iconSize) / 2);
6410
+ if (isFlat) {
6411
+ if (isActive) {
6412
+ svg += `
6413
+ <rect x="${tabX + 6}" y="${pos.y + tabHeight - 3}"
6414
+ width="${tabWidth - 12}" height="3"
6415
+ rx="1.5"
6416
+ fill="${accentColor}"/>`;
6417
+ }
6418
+ if (tabIconSvg) {
6419
+ svg += `
6420
+ <g transform="translate(${groupX}, ${iconOffY})">
6421
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
6422
+ stroke="${isActive ? accentColor : this.renderTheme.textMuted}"
6423
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6424
+ ${this.extractSvgContent(tabIconSvg)}
6425
+ </svg>
6426
+ </g>
6427
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
6428
+ font-family="Arial, Helvetica, sans-serif"
6429
+ font-size="${fontSize}"
6430
+ font-weight="${isActive ? "600" : "500"}"
6431
+ fill="${isActive ? activeTextColor : inactiveTextColor}">${this.escapeXml(tab)}</text>`;
6432
+ } else {
6433
+ svg += `
6434
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
6435
+ font-family="Arial, Helvetica, sans-serif"
6436
+ font-size="${fontSize}"
6437
+ font-weight="${isActive ? "600" : "500"}"
6438
+ fill="${isActive ? activeTextColor : inactiveTextColor}"
6439
+ text-anchor="middle">${this.escapeXml(tab)}</text>`;
6440
+ }
6441
+ } else {
6442
+ svg += `
6443
+ <rect x="${tabX}" y="${pos.y}"
6444
+ width="${tabWidth}" height="${tabHeight}"
6445
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
6446
+ fill="${isActive ? accentColor : "transparent"}"
6447
+ ${!isActive && showBorder ? `stroke="${this.renderTheme.border}" stroke-width="0.5"` : ""}/>`;
6448
+ if (tabIconSvg) {
6449
+ svg += `
6450
+ <g transform="translate(${groupX}, ${iconOffY})">
6451
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
6452
+ stroke="${isActive ? activeTextColor : this.renderTheme.textMuted}"
6453
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6454
+ ${this.extractSvgContent(tabIconSvg)}
6455
+ </svg>
6456
+ </g>
6457
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
6458
+ font-family="Arial, Helvetica, sans-serif"
6459
+ font-size="${fontSize}"
6460
+ font-weight="${isActive ? "600" : "500"}"
6461
+ fill="${isActive ? activeTextColor : this.renderTheme.text}">${this.escapeXml(tab)}</text>`;
6462
+ } else {
6463
+ svg += `
6464
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
6465
+ font-family="Arial, Helvetica, sans-serif"
6466
+ font-size="${fontSize}"
6467
+ font-weight="${isActive ? "600" : "500"}"
6468
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
5954
6469
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
6470
+ }
6471
+ }
5955
6472
  });
5956
- svg += `
6473
+ const contentY = pos.y + tabHeight;
6474
+ const contentH = pos.height - tabHeight;
6475
+ if (contentH >= 20 && !isFlat) {
6476
+ svg += `
5957
6477
  <!-- Tab content area -->
5958
- <rect x="${pos.x}" y="${pos.y + 44}"
5959
- width="${pos.width}" height="${pos.height - 44}"
5960
- fill="${this.renderTheme.cardBg}"
5961
- stroke="${this.renderTheme.border}"
5962
- stroke-width="1"/>
6478
+ <rect x="${pos.x}" y="${contentY}"
6479
+ width="${pos.width}" height="${contentH}"
6480
+ fill="${this.renderTheme.cardBg}"
6481
+ ${showBorder ? `stroke="${this.renderTheme.border}" stroke-width="1"` : ""}/>`;
6482
+ } else if (isFlat && showBorder) {
6483
+ svg += `
6484
+ <line x1="${pos.x}" y1="${contentY}" x2="${pos.x + pos.width}" y2="${contentY}"
6485
+ stroke="${this.renderTheme.border}" stroke-width="1"/>`;
6486
+ }
6487
+ if (hasRadius) {
6488
+ svg += `
5963
6489
  </g>`;
6490
+ if (!isFlat && showBorder) {
6491
+ svg += `
6492
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6493
+ rx="${tabRadius}" fill="none"
6494
+ stroke="${this.renderTheme.border}" stroke-width="1"/>`;
6495
+ }
6496
+ }
6497
+ svg += "\n </g>";
5964
6498
  return svg;
5965
6499
  }
5966
6500
  renderDivider(node, pos) {
@@ -6027,15 +6561,20 @@ var SVGRenderer = class {
6027
6561
  const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
6028
6562
  const bgColor = hasExplicitVariantColor ? this.resolveVariantColor(variant, this.renderTheme.primary) : this.renderTheme.border;
6029
6563
  const textColor = hasExplicitVariantColor ? "white" : this.renderTheme.text;
6564
+ const size = String(node.props.size || "md");
6565
+ const BADGE_FONT_SIZES = { xs: 9, sm: 11, md: 12, lg: 13, xl: 15 };
6566
+ const fontSize = BADGE_FONT_SIZES[size] ?? this.tokens.badge.fontSize;
6567
+ const customPadding = node.props.padding !== void 0 ? Number(node.props.padding) : void 0;
6568
+ const BADGE_PAD_X = { xs: 5, sm: 6, md: 8, lg: 10, xl: 14 };
6569
+ const paddingX = customPadding !== void 0 && !isNaN(customPadding) ? customPadding : BADGE_PAD_X[size] ?? this.tokens.badge.paddingX;
6030
6570
  const badgeRadius = this.tokens.badge.radius === "pill" ? pos.height / 2 : this.tokens.badge.radius;
6031
- const fontSize = this.tokens.badge.fontSize;
6032
6571
  return `<g${this.getDataNodeId(node)}>
6033
6572
  <rect x="${pos.x}" y="${pos.y}"
6034
6573
  width="${pos.width}" height="${pos.height}"
6035
6574
  rx="${badgeRadius}"
6036
6575
  fill="${bgColor}"
6037
6576
  stroke="none"/>
6038
- <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2 + 4}"
6577
+ <text x="${pos.x + paddingX + (pos.width - paddingX * 2) / 2}" y="${pos.y + pos.height / 2 + fontSize * 0.35}"
6039
6578
  font-family="Arial, Helvetica, sans-serif"
6040
6579
  font-size="${fontSize}"
6041
6580
  font-weight="600"
@@ -6244,11 +6783,20 @@ var SVGRenderer = class {
6244
6783
  return svg;
6245
6784
  }
6246
6785
  renderImage(node, pos) {
6247
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
6786
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
6248
6787
  const placeholderIcon = String(node.props.icon || "").trim();
6249
6788
  const variant = String(node.props.variant || "").trim();
6250
6789
  const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
6251
6790
  const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
6791
+ const hasExplicitHeight = node.props.height !== void 0 && !isNaN(Number(node.props.height)) && Number(node.props.height) > 0;
6792
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
6793
+ const useClip = isCircle || hasExplicitHeight;
6794
+ 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))}`}` : "";
6795
+ const imgCx = pos.x + pos.width / 2;
6796
+ const imgCy = pos.y + pos.height / 2;
6797
+ const imgR = Math.min(pos.width, pos.height) / 2;
6798
+ 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>` : "";
6799
+ const clipAttr = useClip ? ` clip-path="url(#${clipId})"` : "";
6252
6800
  const aspectRatios = {
6253
6801
  landscape: 16 / 9,
6254
6802
  portrait: 2 / 3,
@@ -6257,12 +6805,24 @@ var SVGRenderer = class {
6257
6805
  avatar: 1
6258
6806
  };
6259
6807
  const ratio = aspectRatios[placeholder] || 16 / 9;
6260
- const maxSize = Math.min(pos.width, pos.height) * 0.8;
6261
- let iconWidth = maxSize;
6262
- let iconHeight = maxSize / ratio;
6263
- if (iconHeight > pos.height * 0.8) {
6264
- iconHeight = pos.height * 0.8;
6265
- iconWidth = iconHeight * ratio;
6808
+ let iconWidth;
6809
+ let iconHeight;
6810
+ if (isCircle) {
6811
+ const diameter = 2 * imgR;
6812
+ iconWidth = diameter;
6813
+ iconHeight = diameter / ratio;
6814
+ if (iconHeight < diameter) {
6815
+ iconHeight = diameter;
6816
+ iconWidth = diameter * ratio;
6817
+ }
6818
+ } else {
6819
+ const maxSize = Math.min(pos.width, pos.height) * 0.8;
6820
+ iconWidth = maxSize;
6821
+ iconHeight = maxSize / ratio;
6822
+ if (iconHeight > pos.height * 0.8) {
6823
+ iconHeight = pos.height * 0.8;
6824
+ iconWidth = iconHeight * ratio;
6825
+ }
6266
6826
  }
6267
6827
  const offsetX = pos.x + (pos.width - iconWidth) / 2;
6268
6828
  const offsetY = pos.y + (pos.height - iconHeight) / 2;
@@ -6275,17 +6835,18 @@ var SVGRenderer = class {
6275
6835
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
6276
6836
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
6277
6837
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
6278
- return `<g${this.getDataNodeId(node)}>
6838
+ 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"/>`;
6839
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6279
6840
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
6280
6841
  <g transform="translate(${iconOffsetX}, ${iconOffsetY})">
6281
6842
  <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6282
6843
  ${this.extractSvgContent(placeholderIconSvg)}
6283
6844
  </svg>
6284
6845
  </g>
6285
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6846
+ ${iconBorderShape}
6286
6847
  </g>`;
6287
6848
  }
6288
- let svg = `<g${this.getDataNodeId(node)}>
6849
+ let svg = `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
6289
6850
  <!-- Image Background -->
6290
6851
  <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
6291
6852
  if (["landscape", "portrait", "square"].includes(placeholder)) {
@@ -6343,10 +6904,10 @@ var SVGRenderer = class {
6343
6904
  width="${personWidth * 0.6}" height="${personHeight * 0.5}"
6344
6905
  fill="#666" rx="3"/>`;
6345
6906
  }
6907
+ 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"/>`;
6346
6908
  svg += `
6347
6909
  <!-- Border -->
6348
- <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
6349
- fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
6910
+ ${borderShape}
6350
6911
  </g>`;
6351
6912
  return svg;
6352
6913
  }
@@ -6450,10 +7011,29 @@ var SVGRenderer = class {
6450
7011
  </g>`;
6451
7012
  }
6452
7013
  const iconSize = this.getIconSize(size);
6453
- const offsetX = pos.x + (pos.width - iconSize) / 2;
6454
- const offsetY = pos.y + (pos.height - iconSize) / 2;
7014
+ const paddingPx = Math.max(0, Number(node.props.padding || 0));
7015
+ const renderedIconSize = Math.max(4, iconSize - paddingPx * 2);
7016
+ const offsetX = pos.x + (pos.width - renderedIconSize) / 2;
7017
+ const offsetY = pos.y + (pos.height - renderedIconSize) / 2;
7018
+ const isIconCircle = this.parseBooleanProp(node.props.circle, false);
7019
+ if (isIconCircle) {
7020
+ const cx = pos.x + pos.width / 2;
7021
+ const cy = pos.y + pos.height / 2;
7022
+ const r = Math.min(pos.width, pos.height) / 2;
7023
+ 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))}`}`;
7024
+ const bgColor = this.hexToRgba(iconColor, 0.12);
7025
+ return `<defs><clipPath id="${iconClipId}"><circle cx="${cx}" cy="${cy}" r="${r}"/></clipPath></defs><g${this.getDataNodeId(node)} clip-path="url(#${iconClipId})">
7026
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${bgColor}"/>
7027
+ <g transform="translate(${offsetX}, ${offsetY})">
7028
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7029
+ ${this.extractSvgContent(iconSvg)}
7030
+ </svg>
7031
+ </g>
7032
+ </g>
7033
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${this.hexToRgba(iconColor, 0.35)}" stroke-width="1"/>`;
7034
+ }
6455
7035
  const wrappedSvg = `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
6456
- <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7036
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6457
7037
  ${this.extractSvgContent(iconSvg)}
6458
7038
  </svg>
6459
7039
  </g>`;
@@ -6462,7 +7042,7 @@ var SVGRenderer = class {
6462
7042
  renderIconButton(node, pos) {
6463
7043
  const iconName = String(node.props.icon || "help-circle");
6464
7044
  const variant = String(node.props.variant || "default");
6465
- const size = String(node.props.size || "md");
7045
+ const size = String(node.props.size || "sm");
6466
7046
  const disabled = String(node.props.disabled || "false") === "true";
6467
7047
  const density = this.ir.project.style.density || "normal";
6468
7048
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -6590,7 +7170,7 @@ var SVGRenderer = class {
6590
7170
  wrapTextToLines(text, maxWidth, fontSize) {
6591
7171
  const normalized = text.replace(/\r\n/g, "\n");
6592
7172
  const paragraphs = normalized.split("\n");
6593
- const charWidth = fontSize * 0.6;
7173
+ const charWidth = fontSize * 0.5;
6594
7174
  const safeWidth = Math.max(maxWidth || 0, charWidth);
6595
7175
  const maxCharsPerLine = Math.max(1, Math.floor(safeWidth / charWidth));
6596
7176
  const lines = [];
@@ -7705,7 +8285,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7705
8285
  renderButton(node, pos) {
7706
8286
  const text = String(node.props.text || "Button");
7707
8287
  const variant = String(node.props.variant || "default");
7708
- const size = String(node.props.size || "md");
8288
+ const size = String(node.props.size || "sm");
7709
8289
  const density = this.ir.project.style.density || "normal";
7710
8290
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
7711
8291
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
@@ -7812,7 +8392,7 @@ var SketchSVGRenderer = class extends SVGRenderer {
7812
8392
  renderIconButton(node, pos) {
7813
8393
  const iconName = String(node.props.icon || "help-circle");
7814
8394
  const variant = String(node.props.variant || "default");
7815
- const size = String(node.props.size || "md");
8395
+ const size = String(node.props.size || "sm");
7816
8396
  const density = this.ir.project.style.density || "normal";
7817
8397
  const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
7818
8398
  const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
@@ -8065,26 +8645,58 @@ var SketchSVGRenderer = class extends SVGRenderer {
8065
8645
  const variant = String(node.props.variant || "default");
8066
8646
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
8067
8647
  const topbar = this.calculateTopbarLayout(node, pos, title, subtitle, actions, user);
8068
- let svg = `<g${this.getDataNodeId(node)}>
8648
+ const bgPropStr = String(node.props.background ?? "");
8649
+ let sketchTopbarBg = this.renderTheme.cardBg;
8650
+ if (bgPropStr && bgPropStr !== "false" && bgPropStr !== "true") {
8651
+ if (bgPropStr.startsWith("#") || bgPropStr.startsWith("rgb")) {
8652
+ sketchTopbarBg = bgPropStr;
8653
+ } else if (this.colorResolver.hasColor(bgPropStr)) {
8654
+ sketchTopbarBg = this.colorResolver.resolveColor(bgPropStr, this.renderTheme.cardBg);
8655
+ }
8656
+ }
8657
+ const colorPropStr = String(node.props.color ?? "");
8658
+ let titleColor = this.renderTheme.text;
8659
+ let subtitleColor = this.renderTheme.textMuted;
8660
+ if (colorPropStr && colorPropStr !== "false") {
8661
+ let resolvedTitleColor = null;
8662
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
8663
+ resolvedTitleColor = colorPropStr;
8664
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
8665
+ resolvedTitleColor = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
8666
+ }
8667
+ if (resolvedTitleColor) {
8668
+ titleColor = resolvedTitleColor;
8669
+ subtitleColor = this.hexToRgba(resolvedTitleColor, 0.65);
8670
+ }
8671
+ }
8672
+ const showBorder = this.parseBooleanProp(node.props.border, false);
8673
+ const hasBgProp = bgPropStr === "true" || bgPropStr && bgPropStr !== "false" && bgPropStr !== "";
8674
+ const showBackground = hasBgProp;
8675
+ let svg = `<g${this.getDataNodeId(node)}>`;
8676
+ if (showBorder || showBackground) {
8677
+ const stroke = showBorder ? "#2D3748" : "none";
8678
+ svg += `
8069
8679
  <rect x="${pos.x}" y="${pos.y}"
8070
8680
  width="${pos.width}" height="${pos.height}"
8071
- fill="${this.renderTheme.cardBg}"
8072
- stroke="#2D3748"
8681
+ fill="${sketchTopbarBg}"
8682
+ stroke="${stroke}"
8073
8683
  stroke-width="0.5"
8074
- filter="url(#sketch-rough)"/>
8075
-
8076
- <!-- Title -->
8684
+ filter="url(#sketch-rough)"/>`;
8685
+ }
8686
+ svg += `
8687
+ <!-- Title -->`;
8688
+ svg += `
8077
8689
  <text x="${topbar.textX}" y="${topbar.titleY}"
8078
8690
  font-family="${this.fontFamily}"
8079
8691
  font-size="18"
8080
8692
  font-weight="600"
8081
- fill="${this.renderTheme.text}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8693
+ fill="${titleColor}">${this.escapeXml(topbar.visibleTitle)}</text>`;
8082
8694
  if (topbar.hasSubtitle) {
8083
8695
  svg += `
8084
8696
  <text x="${topbar.textX}" y="${topbar.subtitleY}"
8085
8697
  font-family="${this.fontFamily}"
8086
8698
  font-size="13"
8087
- fill="${this.renderTheme.textMuted}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8699
+ fill="${subtitleColor}">${this.escapeXml(topbar.visibleSubtitle)}</text>`;
8088
8700
  }
8089
8701
  if (topbar.leftIcon) {
8090
8702
  svg += `
@@ -8164,8 +8776,13 @@ var SketchSVGRenderer = class extends SVGRenderer {
8164
8776
  */
8165
8777
  renderText(node, pos) {
8166
8778
  const text = String(node.props.text || "Text content");
8167
- const fontSize = this.tokens.text.fontSize;
8779
+ const sizeProp = String(node.props.size || "");
8780
+ const defaultFontSize = this.tokens.text.fontSize;
8781
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
8782
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
8168
8783
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
8784
+ const bold = this.parseBooleanProp(node.props.bold, false);
8785
+ const italic = this.parseBooleanProp(node.props.italic, false);
8169
8786
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
8170
8787
  const firstLineY = pos.y + fontSize;
8171
8788
  const tspans = lines.map(
@@ -8175,9 +8792,48 @@ var SketchSVGRenderer = class extends SVGRenderer {
8175
8792
  <text x="${pos.x}" y="${firstLineY}"
8176
8793
  font-family="${this.fontFamily}"
8177
8794
  font-size="${fontSize}"
8795
+ font-weight="${bold ? "700" : "400"}"
8796
+ font-style="${italic ? "italic" : "normal"}"
8178
8797
  fill="${this.renderTheme.text}">${tspans}</text>
8179
8798
  </g>`;
8180
8799
  }
8800
+ renderParagraph(node, pos) {
8801
+ const text = String(node.props.text || "");
8802
+ const sizeProp = String(node.props.size || "");
8803
+ const defaultFontSize = this.tokens.text.fontSize;
8804
+ const textFontSizeMap = { xs: 10, sm: 12, lg: 16, xl: 20 };
8805
+ const fontSize = textFontSizeMap[sizeProp] ?? defaultFontSize;
8806
+ const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
8807
+ const bold = this.parseBooleanProp(node.props.bold, false);
8808
+ const italic = this.parseBooleanProp(node.props.italic, false);
8809
+ const align = String(node.props.align || "left");
8810
+ const lines = this.wrapTextToLines(text, pos.width, fontSize);
8811
+ const firstLineY = pos.y + fontSize;
8812
+ let textX;
8813
+ let textAnchor;
8814
+ if (align === "center") {
8815
+ textX = pos.x + pos.width / 2;
8816
+ textAnchor = "middle";
8817
+ } else if (align === "right") {
8818
+ textX = pos.x + pos.width;
8819
+ textAnchor = "end";
8820
+ } else {
8821
+ textX = pos.x;
8822
+ textAnchor = "start";
8823
+ }
8824
+ const tspans = lines.map(
8825
+ (line, index) => `<tspan x="${textX}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
8826
+ ).join("");
8827
+ return `<g${this.getDataNodeId(node)}>
8828
+ <text x="${textX}" y="${firstLineY}"
8829
+ font-family="${this.fontFamily}"
8830
+ font-size="${fontSize}"
8831
+ font-weight="${bold ? "700" : "400"}"
8832
+ font-style="${italic ? "italic" : "normal"}"
8833
+ fill="${this.renderTheme.text}"
8834
+ text-anchor="${textAnchor}">${tspans}</text>
8835
+ </g>`;
8836
+ }
8181
8837
  /**
8182
8838
  * Render label with Comic Sans
8183
8839
  */
@@ -8408,36 +9064,150 @@ var SketchSVGRenderer = class extends SVGRenderer {
8408
9064
  renderTabs(node, pos) {
8409
9065
  const itemsStr = String(node.props.items || "");
8410
9066
  const tabs = itemsStr ? itemsStr.split(",").map((t) => t.trim()) : ["Tab 1", "Tab 2", "Tab 3"];
8411
- const accentColor = this.resolveAccentColor();
9067
+ const activeProp = node.props.active ?? 0;
9068
+ const activeIndex = Number.isFinite(Number(activeProp)) ? Math.max(0, Math.floor(Number(activeProp))) : 0;
9069
+ const variant = String(node.props.variant || "default");
9070
+ const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
9071
+ const radiusMap = { none: 0, sm: 4, md: 6, lg: 10, full: 20 };
9072
+ const tabRadius = radiusMap[String(node.props.radius || "md")] ?? 6;
9073
+ const sizeMap = { sm: 32, md: 44, lg: 52 };
9074
+ const tabHeight = pos.height > 0 ? pos.height : sizeMap[String(node.props.size || "md")] ?? 44;
9075
+ const fontSize = 13;
9076
+ const textY = pos.y + Math.round(tabHeight / 2) + Math.round(fontSize * 0.4);
9077
+ const iconsStr = String(node.props.icons || "");
9078
+ const iconList = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
9079
+ const isFlat = this.parseBooleanProp(node.props.flat, false);
9080
+ const showBorder = this.parseBooleanProp(node.props.border, true);
8412
9081
  const tabWidth = pos.width / tabs.length;
8413
- let svg = `<g${this.getDataNodeId(node)}>
8414
- <!-- Tab headers -->`;
9082
+ const colorPropStr = String(node.props.color ?? "");
9083
+ let textColorOverride = null;
9084
+ if (colorPropStr && colorPropStr !== "false") {
9085
+ if (colorPropStr.startsWith("#") || colorPropStr.startsWith("rgb")) {
9086
+ textColorOverride = colorPropStr;
9087
+ } else if (this.colorResolver.hasColor(colorPropStr)) {
9088
+ textColorOverride = this.colorResolver.resolveColor(colorPropStr, this.renderTheme.text);
9089
+ }
9090
+ }
9091
+ const activeTextColor = textColorOverride ?? (isFlat ? accentColor : "white");
9092
+ const inactiveTextColor = textColorOverride ? this.hexToRgba(textColorOverride, 0.55) : this.renderTheme.textMuted;
9093
+ const hasRadius = tabRadius > 0;
9094
+ 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))}`}` : "";
9095
+ let svg = "";
9096
+ if (hasRadius) {
9097
+ svg += `<defs><clipPath id="${clipId}"><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" rx="${tabRadius}"/></clipPath></defs>`;
9098
+ }
9099
+ svg += `<g${this.getDataNodeId(node)}>`;
9100
+ if (hasRadius) {
9101
+ svg += `<g clip-path="url(#${clipId})">`;
9102
+ }
9103
+ if (!isFlat) {
9104
+ svg += `
9105
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${tabHeight}"
9106
+ fill="${this.renderTheme.bg}"/>`;
9107
+ }
8415
9108
  tabs.forEach((tab, i) => {
8416
9109
  const tabX = pos.x + i * tabWidth;
8417
- const isActive = i === 0;
8418
- svg += `
9110
+ const isActive = i === activeIndex;
9111
+ const iconName = iconList[i] || "";
9112
+ const tabIconSvg = iconName ? getIcon(iconName) : null;
9113
+ const iconSize = 14;
9114
+ const iconGap = 4;
9115
+ const charW = fontSize * 0.58;
9116
+ const textEst = tab.length * charW;
9117
+ const totalW = tabIconSvg ? iconSize + iconGap + textEst : textEst;
9118
+ const groupX = tabX + Math.round((tabWidth - totalW) / 2);
9119
+ const iconOffY = pos.y + Math.round((tabHeight - iconSize) / 2);
9120
+ if (isFlat) {
9121
+ if (isActive) {
9122
+ svg += `
9123
+ <rect x="${tabX + 6}" y="${pos.y + tabHeight - 3}"
9124
+ width="${tabWidth - 12}" height="3"
9125
+ rx="1.5"
9126
+ fill="${accentColor}"
9127
+ filter="url(#sketch-rough)"/>`;
9128
+ }
9129
+ if (tabIconSvg) {
9130
+ svg += `
9131
+ <g transform="translate(${groupX}, ${iconOffY})">
9132
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
9133
+ stroke="${isActive ? accentColor : this.renderTheme.textMuted}"
9134
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
9135
+ ${this.extractSvgContent(tabIconSvg)}
9136
+ </svg>
9137
+ </g>
9138
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
9139
+ font-family="${this.fontFamily}"
9140
+ font-size="${fontSize}"
9141
+ font-weight="${isActive ? "600" : "500"}"
9142
+ fill="${isActive ? activeTextColor : inactiveTextColor}">${this.escapeXml(tab)}</text>`;
9143
+ } else {
9144
+ svg += `
9145
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
9146
+ font-family="${this.fontFamily}"
9147
+ font-size="${fontSize}"
9148
+ font-weight="${isActive ? "600" : "500"}"
9149
+ fill="${isActive ? activeTextColor : inactiveTextColor}"
9150
+ text-anchor="middle">${this.escapeXml(tab)}</text>`;
9151
+ }
9152
+ } else {
9153
+ svg += `
8419
9154
  <rect x="${tabX}" y="${pos.y}"
8420
- width="${tabWidth}" height="44"
9155
+ width="${tabWidth}" height="${tabHeight}"
9156
+ rx="${!showBorder && hasRadius && isActive ? tabRadius : 0}"
8421
9157
  fill="${isActive ? accentColor : "transparent"}"
8422
- stroke="${isActive ? accentColor : "#2D3748"}"
9158
+ stroke="${isActive ? accentColor : showBorder ? "#2D3748" : "none"}"
8423
9159
  stroke-width="0.5"
8424
- filter="url(#sketch-rough)"/>
8425
- <text x="${tabX + tabWidth / 2}" y="${pos.y + 28}"
9160
+ filter="url(#sketch-rough)"/>`;
9161
+ if (tabIconSvg) {
9162
+ svg += `
9163
+ <g transform="translate(${groupX}, ${iconOffY})">
9164
+ <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
9165
+ stroke="${isActive ? activeTextColor : this.renderTheme.textMuted}"
9166
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
9167
+ ${this.extractSvgContent(tabIconSvg)}
9168
+ </svg>
9169
+ </g>
9170
+ <text x="${groupX + iconSize + iconGap}" y="${textY}"
8426
9171
  font-family="${this.fontFamily}"
8427
- font-size="13"
9172
+ font-size="${fontSize}"
9173
+ font-weight="${isActive ? "600" : "500"}"
9174
+ fill="${isActive ? activeTextColor : this.renderTheme.text}">${this.escapeXml(tab)}</text>`;
9175
+ } else {
9176
+ svg += `
9177
+ <text x="${tabX + tabWidth / 2}" y="${textY}"
9178
+ font-family="${this.fontFamily}"
9179
+ font-size="${fontSize}"
8428
9180
  font-weight="${isActive ? "600" : "500"}"
8429
- fill="${isActive ? "white" : this.renderTheme.text}"
9181
+ fill="${isActive ? activeTextColor : this.renderTheme.text}"
8430
9182
  text-anchor="middle">${this.escapeXml(tab)}</text>`;
9183
+ }
9184
+ }
8431
9185
  });
8432
- svg += `
9186
+ const contentY = pos.y + tabHeight;
9187
+ const contentH = pos.height - tabHeight;
9188
+ if (contentH >= 20 && !isFlat) {
9189
+ svg += `
8433
9190
  <!-- Tab content area -->
8434
- <rect x="${pos.x}" y="${pos.y + 44}"
8435
- width="${pos.width}" height="${pos.height - 44}"
9191
+ <rect x="${pos.x}" y="${contentY}"
9192
+ width="${pos.width}" height="${contentH}"
8436
9193
  fill="${this.renderTheme.cardBg}"
8437
- stroke="#2D3748"
8438
- stroke-width="0.5"
8439
- filter="url(#sketch-rough)"/>
9194
+ ${showBorder ? 'stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"' : ""}/>`;
9195
+ } else if (isFlat && showBorder) {
9196
+ svg += `
9197
+ <line x1="${pos.x}" y1="${contentY}" x2="${pos.x + pos.width}" y2="${contentY}"
9198
+ stroke="#2D3748" stroke-width="0.5" filter="url(#sketch-rough)"/>`;
9199
+ }
9200
+ if (hasRadius) {
9201
+ svg += `
8440
9202
  </g>`;
9203
+ if (!isFlat && showBorder) {
9204
+ svg += `
9205
+ <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
9206
+ rx="${tabRadius}" fill="none" stroke="#2D3748" stroke-width="0.5"
9207
+ filter="url(#sketch-rough)"/>`;
9208
+ }
9209
+ }
9210
+ svg += "\n </g>";
8441
9211
  return svg;
8442
9212
  }
8443
9213
  /**
@@ -8674,11 +9444,20 @@ var SketchSVGRenderer = class extends SVGRenderer {
8674
9444
  * Render image with sketch filter
8675
9445
  */
8676
9446
  renderImage(node, pos) {
8677
- const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
9447
+ const placeholder = String(node.props.type || "landscape").toLowerCase();
8678
9448
  const iconType = String(node.props.icon || "").trim();
8679
9449
  const variant = String(node.props.variant || "").trim();
8680
9450
  const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
8681
9451
  const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
9452
+ const hasExplicitHeight = node.props.height !== void 0 && !isNaN(Number(node.props.height)) && Number(node.props.height) > 0;
9453
+ const isCircle = this.parseBooleanProp(node.props.circle, false);
9454
+ const useClip = isCircle || hasExplicitHeight;
9455
+ 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))}`}` : "";
9456
+ const imgCx = pos.x + pos.width / 2;
9457
+ const imgCy = pos.y + pos.height / 2;
9458
+ const imgR = Math.min(pos.width, pos.height) / 2;
9459
+ 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>` : "";
9460
+ const clipAttr = useClip ? ` clip-path="url(#${clipId})"` : "";
8682
9461
  if (iconSvg) {
8683
9462
  const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
8684
9463
  const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
@@ -8688,7 +9467,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
8688
9467
  const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
8689
9468
  const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
8690
9469
  const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
8691
- return `<g${this.getDataNodeId(node)}>
9470
+ 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)"/>`;
9471
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
8692
9472
  <rect x="${pos.x}" y="${pos.y}"
8693
9473
  width="${pos.width}" height="${pos.height}"
8694
9474
  fill="${bgColor}"
@@ -8699,17 +9479,11 @@ var SketchSVGRenderer = class extends SVGRenderer {
8699
9479
  ${this.extractSvgContent(iconSvg)}
8700
9480
  </svg>
8701
9481
  </g>
8702
- <rect x="${pos.x}" y="${pos.y}"
8703
- width="${pos.width}" height="${pos.height}"
8704
- fill="none"
8705
- stroke="#2D3748"
8706
- stroke-width="0.5"
8707
- rx="4"
8708
- filter="url(#sketch-rough)"/>
9482
+ ${sketchIconBorder}
8709
9483
  </g>`;
8710
9484
  }
8711
- return `<g${this.getDataNodeId(node)}>
8712
- <!-- Image Background -->
9485
+ const sketchCircleBorder = isCircle ? `<circle cx="${imgCx}" cy="${imgCy}" r="${imgR}" fill="none" stroke="#2D3748" stroke-width="0.5"/>` : "";
9486
+ return `${defsHtml}<g${this.getDataNodeId(node)}${clipAttr}>
8713
9487
  <rect x="${pos.x}" y="${pos.y}"
8714
9488
  width="${pos.width}" height="${pos.height}"
8715
9489
  fill="${imageBg}"
@@ -8717,14 +9491,12 @@ var SketchSVGRenderer = class extends SVGRenderer {
8717
9491
  stroke-width="0.5"
8718
9492
  rx="4"
8719
9493
  filter="url(#sketch-rough)"/>
8720
-
8721
- <!-- Placeholder icon -->
8722
9494
  <text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
8723
9495
  font-family="${this.fontFamily}"
8724
9496
  font-size="24"
8725
9497
  fill="#666"
8726
9498
  text-anchor="middle">\u{1F5BC}</text>
8727
- </g>`;
9499
+ </g>${sketchCircleBorder}`;
8728
9500
  }
8729
9501
  /**
8730
9502
  * Render breadcrumbs with Comic Sans
@@ -8840,20 +9612,33 @@ var SketchSVGRenderer = class extends SVGRenderer {
8840
9612
  }
8841
9613
  const iconSize = this.getIconSize(size);
8842
9614
  const iconColor = variant === "default" ? this.resolveTextColor() : this.resolveVariantColor(variant, this.resolveTextColor());
8843
- const offsetX = pos.x + (pos.width - iconSize) / 2;
8844
- const offsetY = pos.y + (pos.height - iconSize) / 2;
9615
+ const paddingPx = Math.max(0, Number(node.props.padding || 0));
9616
+ const renderedIconSize = Math.max(4, iconSize - paddingPx * 2);
9617
+ const offsetX = pos.x + (pos.width - renderedIconSize) / 2;
9618
+ const offsetY = pos.y + (pos.height - renderedIconSize) / 2;
9619
+ const isIconCircle = this.parseBooleanProp(node.props.circle, false);
9620
+ if (isIconCircle) {
9621
+ const cx = pos.x + pos.width / 2;
9622
+ const cy = pos.y + pos.height / 2;
9623
+ const r = Math.min(pos.width, pos.height) / 2;
9624
+ 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))}`}`;
9625
+ const bgColor = this.hexToRgba(iconColor, 0.1);
9626
+ return `<defs><clipPath id="${iconClipId}"><circle cx="${cx}" cy="${cy}" r="${r}"/></clipPath></defs><g${this.getDataNodeId(node)} clip-path="url(#${iconClipId})">
9627
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${bgColor}"/>
9628
+ <g transform="translate(${offsetX}, ${offsetY})">
9629
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
9630
+ ${this.extractSvgContent(iconSvg)}
9631
+ </svg>
9632
+ </g>
9633
+ </g>
9634
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${this.hexToRgba(iconColor, 0.35)}" stroke-width="0.5"/>`;
9635
+ }
8845
9636
  return `<g${this.getDataNodeId(node)} transform="translate(${offsetX}, ${offsetY})">
8846
- <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
9637
+ <svg width="${renderedIconSize}" height="${renderedIconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
8847
9638
  ${this.extractSvgContent(iconSvg)}
8848
9639
  </svg>
8849
9640
  </g>`;
8850
9641
  }
8851
- /**
8852
- * Render chart placeholder with sketch filter and Comic Sans
8853
- */
8854
- renderChartPlaceholder(node, pos) {
8855
- return super.renderChartPlaceholder(node, pos);
8856
- }
8857
9642
  /**
8858
9643
  * Helper method to get icon SVG
8859
9644
  */