dispersa 0.1.2 → 0.2.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/builders.cjs CHANGED
@@ -460,7 +460,7 @@ function colorObjectToHex(color) {
460
460
  return culori.formatHex(culoriColor);
461
461
  }
462
462
 
463
- // src/lib/processing/processors/transforms/built-in/dimension-converter.ts
463
+ // src/processing/processors/transforms/built-in/dimension-converter.ts
464
464
  function isDimensionObject(value) {
465
465
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
466
466
  }
@@ -468,6 +468,757 @@ function dimensionObjectToString(dimension) {
468
468
  return `${dimension.value}${dimension.unit}`;
469
469
  }
470
470
 
471
+ // src/renderers/android.ts
472
+ init_errors();
473
+ init_token_utils();
474
+ init_utils();
475
+
476
+ // src/renderers/output-tree.ts
477
+ var outputTree = (files) => {
478
+ return {
479
+ kind: "outputTree",
480
+ files
481
+ };
482
+ };
483
+
484
+ // src/renderers/android.ts
485
+ var toSRGB = culori.converter("rgb");
486
+ var toP3 = culori.converter("p3");
487
+ var KOTLIN_KEYWORDS = /* @__PURE__ */ new Set([
488
+ "val",
489
+ "var",
490
+ "fun",
491
+ "class",
492
+ "object",
493
+ "when",
494
+ "is",
495
+ "in",
496
+ "return",
497
+ "break",
498
+ "continue",
499
+ "do",
500
+ "while",
501
+ "for",
502
+ "if",
503
+ "else",
504
+ "try",
505
+ "catch",
506
+ "throw",
507
+ "as",
508
+ "this",
509
+ "super",
510
+ "null",
511
+ "true",
512
+ "false"
513
+ ]);
514
+ var KOTLIN_TYPE_GROUP_MAP = {
515
+ color: "Colors",
516
+ dimension: "Spacing",
517
+ fontFamily: "Fonts",
518
+ fontWeight: "FontWeights",
519
+ duration: "Durations",
520
+ shadow: "Shadows",
521
+ typography: "Typography",
522
+ number: "Numbers",
523
+ cubicBezier: "Animations",
524
+ border: "Borders"
525
+ };
526
+ function resolveColorFormat(format) {
527
+ if (format === "argb_floats" || format === "argb_float") {
528
+ return "argb_float";
529
+ }
530
+ return "argb_hex";
531
+ }
532
+ function indent(width, level) {
533
+ return " ".repeat(width * level);
534
+ }
535
+ function escapeKotlinString(str) {
536
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
537
+ }
538
+ function escapeKDoc(str) {
539
+ return str.replace(/\*\//g, "* /").replace(/\r?\n/g, " ").trim();
540
+ }
541
+ function formatKotlinNumber(value) {
542
+ return Number.isInteger(value) ? `${value}.0` : String(value);
543
+ }
544
+ function roundComponent(value) {
545
+ return Math.round(value * 1e3) / 1e3;
546
+ }
547
+ function toResourceName(family) {
548
+ return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
549
+ }
550
+ function toPascalCase(name) {
551
+ const pascal = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
552
+ const result = pascal.charAt(0).toUpperCase() + pascal.slice(1);
553
+ if (/^\d/.test(result)) {
554
+ return `_${result}`;
555
+ }
556
+ return KOTLIN_KEYWORDS.has(result.charAt(0).toLowerCase() + result.slice(1)) ? `\`${result}\`` : result;
557
+ }
558
+ function toKotlinIdentifier(name) {
559
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
560
+ const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
561
+ if (/^\d/.test(identifier)) {
562
+ return `_${identifier}`;
563
+ }
564
+ return KOTLIN_KEYWORDS.has(identifier) ? `\`${identifier}\`` : identifier;
565
+ }
566
+ var AndroidRenderer = class {
567
+ async format(context, options) {
568
+ if (!options?.packageName) {
569
+ throw new ConfigurationError(
570
+ `Output "${context.output.name}": packageName is required for Android output`
571
+ );
572
+ }
573
+ const opts = {
574
+ preset: options?.preset ?? "standalone",
575
+ packageName: options.packageName,
576
+ objectName: options?.objectName ?? "DesignTokens",
577
+ colorFormat: resolveColorFormat(options?.colorFormat),
578
+ colorSpace: options?.colorSpace ?? "sRGB",
579
+ structure: options?.structure ?? "nested",
580
+ visibility: options?.visibility,
581
+ indent: options?.indent ?? 4
582
+ };
583
+ if (opts.preset === "bundle") {
584
+ return await this.formatBundle(context, opts);
585
+ }
586
+ return await this.formatStandalone(context, opts);
587
+ }
588
+ // -----------------------------------------------------------------------
589
+ // Token tree (nested mode)
590
+ // -----------------------------------------------------------------------
591
+ buildTokenTree(tokens) {
592
+ const root = { children: /* @__PURE__ */ new Map() };
593
+ for (const [, token] of getSortedTokenEntries(tokens)) {
594
+ let current = root;
595
+ const segments = token.path;
596
+ for (let i = 0; i < segments.length - 1; i++) {
597
+ const seg = segments[i];
598
+ if (!current.children.has(seg)) {
599
+ current.children.set(seg, { children: /* @__PURE__ */ new Map() });
600
+ }
601
+ current = current.children.get(seg);
602
+ }
603
+ const leafName = segments[segments.length - 1] ?? token.name;
604
+ const leaf = current.children.get(leafName) ?? { children: /* @__PURE__ */ new Map() };
605
+ leaf.token = token;
606
+ current.children.set(leafName, leaf);
607
+ }
608
+ return root;
609
+ }
610
+ // -----------------------------------------------------------------------
611
+ // Flat structure grouping
612
+ // -----------------------------------------------------------------------
613
+ groupTokensByType(tokens) {
614
+ const groupMap = /* @__PURE__ */ new Map();
615
+ for (const [, token] of getSortedTokenEntries(tokens)) {
616
+ const groupName = KOTLIN_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
617
+ const existing = groupMap.get(groupName) ?? [];
618
+ existing.push(token);
619
+ groupMap.set(groupName, existing);
620
+ }
621
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
622
+ name,
623
+ tokens: groupTokens
624
+ }));
625
+ }
626
+ /**
627
+ * Builds a flattened camelCase name from a token's path, stripping the
628
+ * type prefix segment (which is already represented by the group object).
629
+ */
630
+ buildFlatKotlinName(token) {
631
+ const path = token.path;
632
+ const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
633
+ const joined = withoutTypePrefix.join("_");
634
+ return toKotlinIdentifier(joined);
635
+ }
636
+ // -----------------------------------------------------------------------
637
+ // Rendering
638
+ // -----------------------------------------------------------------------
639
+ formatTokens(tokens, options) {
640
+ if (options.structure === "flat") {
641
+ return this.formatAsFlat(tokens, options);
642
+ }
643
+ return this.formatAsNested(tokens, options);
644
+ }
645
+ formatAsNested(tokens, options) {
646
+ const tree = this.buildTokenTree(tokens);
647
+ const tokenTypes = /* @__PURE__ */ new Set();
648
+ this.collectTokenTypes(tree, tokenTypes);
649
+ return this.buildFile(tokenTypes, options, (lines, vis) => {
650
+ lines.push(`@Suppress("unused")`);
651
+ lines.push(`${vis}object ${options.objectName} {`);
652
+ this.renderTreeChildren(lines, tree, 1, options);
653
+ lines.push("}");
654
+ });
655
+ }
656
+ formatAsFlat(tokens, options) {
657
+ const groups = this.groupTokensByType(tokens);
658
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
659
+ return this.buildFile(tokenTypes, options, (lines, vis) => {
660
+ lines.push(`@Suppress("unused")`);
661
+ lines.push(`${vis}object ${options.objectName} {`);
662
+ this.renderFlatGroups(lines, groups, 1, options);
663
+ lines.push("}");
664
+ });
665
+ }
666
+ /**
667
+ * Shared file preamble: header, package, imports, optional ShadowToken class.
668
+ * The `renderBody` callback appends the main object(s) to `lines`.
669
+ */
670
+ buildFile(tokenTypes, options, renderBody) {
671
+ const imports = this.collectImports(tokenTypes, options);
672
+ const vis = options.visibility ? `${options.visibility} ` : "";
673
+ const lines = [];
674
+ lines.push(this.buildFileHeader());
675
+ lines.push("");
676
+ lines.push(`package ${options.packageName}`);
677
+ lines.push("");
678
+ for (const imp of imports) {
679
+ lines.push(`import ${imp}`);
680
+ }
681
+ if (imports.length > 0) {
682
+ lines.push("");
683
+ }
684
+ if (tokenTypes.has("shadow")) {
685
+ lines.push(...this.buildShadowTokenClass(vis, options));
686
+ lines.push("");
687
+ }
688
+ renderBody(lines, vis);
689
+ lines.push("");
690
+ return lines.join("\n");
691
+ }
692
+ renderFlatGroups(lines, groups, baseDepth, options) {
693
+ const vis = options.visibility ? `${options.visibility} ` : "";
694
+ const groupIndent = indent(options.indent, baseDepth);
695
+ const valIndent = indent(options.indent, baseDepth + 1);
696
+ for (const group of groups) {
697
+ lines.push(`${groupIndent}${vis}object ${group.name} {`);
698
+ for (const token of group.tokens) {
699
+ const kotlinName = this.buildFlatKotlinName(token);
700
+ const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
701
+ const annotation = this.typeAnnotationSuffix(token);
702
+ if (token.$description) {
703
+ lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
704
+ }
705
+ lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
706
+ }
707
+ lines.push(`${groupIndent}}`);
708
+ lines.push("");
709
+ }
710
+ }
711
+ renderTreeChildren(lines, node, depth, options) {
712
+ const vis = options.visibility ? `${options.visibility} ` : "";
713
+ const pad = indent(options.indent, depth);
714
+ const entries = Array.from(node.children.entries());
715
+ for (let idx = 0; idx < entries.length; idx++) {
716
+ const [key, child] = entries[idx];
717
+ if (child.token && child.children.size === 0) {
718
+ this.renderLeaf(lines, key, child.token, depth, options);
719
+ } else if (child.children.size > 0 && !child.token) {
720
+ const objectName = toPascalCase(key);
721
+ lines.push(`${pad}${vis}object ${objectName} {`);
722
+ this.renderTreeChildren(lines, child, depth + 1, options);
723
+ lines.push(`${pad}}`);
724
+ if (idx < entries.length - 1) {
725
+ lines.push("");
726
+ }
727
+ } else {
728
+ this.renderLeaf(lines, key, child.token, depth, options);
729
+ this.renderTreeChildren(lines, child, depth, options);
730
+ }
731
+ }
732
+ }
733
+ renderLeaf(lines, key, token, depth, options) {
734
+ const vis = options.visibility ? `${options.visibility} ` : "";
735
+ const pad = indent(options.indent, depth);
736
+ const kotlinName = toKotlinIdentifier(key);
737
+ const kotlinValue = this.formatKotlinValue(token, options, depth);
738
+ const annotation = this.typeAnnotationSuffix(token);
739
+ if (token.$description) {
740
+ lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
741
+ }
742
+ lines.push(`${pad}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
743
+ }
744
+ buildFileHeader() {
745
+ return [
746
+ "// Generated by Dispersa - do not edit manually",
747
+ "// https://github.com/timges/dispersa"
748
+ ].join("\n");
749
+ }
750
+ // -----------------------------------------------------------------------
751
+ // Shadow data class
752
+ // -----------------------------------------------------------------------
753
+ buildShadowTokenClass(vis, options) {
754
+ const i1 = indent(options.indent, 1);
755
+ return [
756
+ "@Immutable",
757
+ `${vis}data class ShadowToken(`,
758
+ `${i1}val color: Color,`,
759
+ `${i1}val elevation: Dp,`,
760
+ `${i1}val offsetX: Dp,`,
761
+ `${i1}val offsetY: Dp,`,
762
+ ")"
763
+ ];
764
+ }
765
+ // -----------------------------------------------------------------------
766
+ // Imports (tree-shaken)
767
+ // -----------------------------------------------------------------------
768
+ collectImports(tokenTypes, options) {
769
+ const imports = /* @__PURE__ */ new Set();
770
+ const ns = "androidx.compose";
771
+ const hasColors = tokenTypes.has("color") || tokenTypes.has("shadow") || tokenTypes.has("border");
772
+ if (hasColors) {
773
+ imports.add(`${ns}.ui.graphics.Color`);
774
+ }
775
+ if (tokenTypes.has("dimension") || tokenTypes.has("shadow") || tokenTypes.has("border")) {
776
+ imports.add(`${ns}.ui.unit.Dp`);
777
+ imports.add(`${ns}.ui.unit.dp`);
778
+ }
779
+ if (tokenTypes.has("typography") || tokenTypes.has("fontFamily")) {
780
+ imports.add(`${ns}.ui.text.TextStyle`);
781
+ imports.add(`${ns}.ui.unit.sp`);
782
+ }
783
+ if (tokenTypes.has("typography") || tokenTypes.has("fontWeight")) {
784
+ imports.add(`${ns}.ui.text.font.FontWeight`);
785
+ }
786
+ if (tokenTypes.has("fontFamily")) {
787
+ imports.add(`${ns}.ui.text.font.FontFamily`);
788
+ }
789
+ if (tokenTypes.has("duration")) {
790
+ imports.add("kotlin.time.Duration");
791
+ imports.add("kotlin.time.Duration.Companion.milliseconds");
792
+ imports.add("kotlin.time.Duration.Companion.seconds");
793
+ }
794
+ if (tokenTypes.has("cubicBezier")) {
795
+ imports.add(`${ns}.animation.core.CubicBezierEasing`);
796
+ }
797
+ if (tokenTypes.has("shadow")) {
798
+ imports.add(`${ns}.runtime.Immutable`);
799
+ }
800
+ if (tokenTypes.has("border")) {
801
+ imports.add(`${ns}.foundation.BorderStroke`);
802
+ }
803
+ if (options.colorSpace === "displayP3" && hasColors) {
804
+ imports.add(`${ns}.ui.graphics.colorspace.ColorSpaces`);
805
+ }
806
+ return Array.from(imports).sort();
807
+ }
808
+ collectTokenTypes(node, types) {
809
+ if (node.token?.$type) {
810
+ types.add(node.token.$type);
811
+ }
812
+ for (const child of node.children.values()) {
813
+ this.collectTokenTypes(child, types);
814
+ }
815
+ }
816
+ collectTokenTypesFromEntries(tokens) {
817
+ const types = /* @__PURE__ */ new Set();
818
+ for (const [, token] of Object.entries(tokens)) {
819
+ if (token.$type) {
820
+ types.add(token.$type);
821
+ }
822
+ }
823
+ return types;
824
+ }
825
+ // -----------------------------------------------------------------------
826
+ // Type annotations
827
+ // -----------------------------------------------------------------------
828
+ getTypeAnnotation(token) {
829
+ switch (token.$type) {
830
+ case "color":
831
+ return "Color";
832
+ case "dimension":
833
+ return "Dp";
834
+ case "fontFamily":
835
+ return "FontFamily";
836
+ case "fontWeight":
837
+ return "FontWeight";
838
+ case "duration":
839
+ return "Duration";
840
+ case "shadow":
841
+ return "ShadowToken";
842
+ case "cubicBezier":
843
+ return "CubicBezierEasing";
844
+ case "number":
845
+ return "Double";
846
+ case "typography":
847
+ return "TextStyle";
848
+ case "border":
849
+ return "BorderStroke";
850
+ default: {
851
+ const value = token.$value;
852
+ if (typeof value === "string") {
853
+ return "String";
854
+ }
855
+ if (typeof value === "boolean") {
856
+ return "Boolean";
857
+ }
858
+ if (typeof value === "number") {
859
+ return "Double";
860
+ }
861
+ return void 0;
862
+ }
863
+ }
864
+ }
865
+ typeAnnotationSuffix(token) {
866
+ const type = this.getTypeAnnotation(token);
867
+ return type ? `: ${type}` : "";
868
+ }
869
+ // -----------------------------------------------------------------------
870
+ // Value formatting
871
+ // -----------------------------------------------------------------------
872
+ formatKotlinValue(token, options, depth) {
873
+ const value = token.$value;
874
+ if (token.$type === "color") {
875
+ return this.formatColorValue(value, options);
876
+ }
877
+ if (token.$type === "dimension") {
878
+ return this.formatDimensionValue(value);
879
+ }
880
+ if (token.$type === "fontFamily") {
881
+ return this.formatFontFamilyValue(value);
882
+ }
883
+ if (token.$type === "fontWeight") {
884
+ return this.formatFontWeightValue(value);
885
+ }
886
+ if (token.$type === "duration") {
887
+ return this.formatDurationValue(value);
888
+ }
889
+ if (token.$type === "shadow") {
890
+ return this.formatShadowValue(value, options, depth);
891
+ }
892
+ if (token.$type === "typography") {
893
+ return this.formatTypographyValue(value, options, depth);
894
+ }
895
+ if (token.$type === "border") {
896
+ return this.formatBorderValue(value, options);
897
+ }
898
+ if (token.$type === "number") {
899
+ return typeof value === "number" ? formatKotlinNumber(value) : String(value);
900
+ }
901
+ if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
902
+ return `CubicBezierEasing(${value[0]}f, ${value[1]}f, ${value[2]}f, ${value[3]}f)`;
903
+ }
904
+ if (typeof value === "string") {
905
+ return `"${escapeKotlinString(value)}"`;
906
+ }
907
+ if (typeof value === "number") {
908
+ return formatKotlinNumber(value);
909
+ }
910
+ if (typeof value === "boolean") {
911
+ return value ? "true" : "false";
912
+ }
913
+ return `"${escapeKotlinString(String(value))}"`;
914
+ }
915
+ formatColorValue(value, options) {
916
+ if (!isColorObject(value)) {
917
+ if (typeof value === "string") {
918
+ const hex = value.replace("#", "");
919
+ if (/^[0-9a-fA-F]{6,8}$/.test(hex)) {
920
+ const argb = hex.length === 8 ? hex : `FF${hex}`;
921
+ return `Color(0x${argb.toUpperCase()})`;
922
+ }
923
+ }
924
+ return "Color.Unspecified";
925
+ }
926
+ const colorObj = value;
927
+ const alpha = colorObj.alpha ?? 1;
928
+ if (options.colorFormat === "argb_float" || options.colorSpace === "displayP3") {
929
+ return this.formatFloatColor(colorObj, alpha, options);
930
+ }
931
+ return this.formatHexColor(colorObj, alpha);
932
+ }
933
+ formatFloatColor(colorObj, alpha, options) {
934
+ if (options.colorSpace === "displayP3") {
935
+ const p3 = toP3(dtcgObjectToCulori(colorObj));
936
+ const r2 = roundComponent(p3?.r ?? 0);
937
+ const g2 = roundComponent(p3?.g ?? 0);
938
+ const b2 = roundComponent(p3?.b ?? 0);
939
+ return `Color(${r2}f, ${g2}f, ${b2}f, ${roundComponent(alpha)}f, ColorSpaces.DisplayP3)`;
940
+ }
941
+ const rgb = toSRGB(dtcgObjectToCulori(colorObj));
942
+ const r = roundComponent(rgb?.r ?? 0);
943
+ const g = roundComponent(rgb?.g ?? 0);
944
+ const b = roundComponent(rgb?.b ?? 0);
945
+ return `Color(${r}f, ${g}f, ${b}f, ${roundComponent(alpha)}f)`;
946
+ }
947
+ formatHexColor(colorObj, alpha) {
948
+ const hex = colorObjectToHex(colorObj);
949
+ const hexClean = hex.replace("#", "");
950
+ if (hexClean.length === 8) {
951
+ const rrggbb = hexClean.slice(0, 6);
952
+ const aa = hexClean.slice(6, 8);
953
+ return `Color(0x${aa.toUpperCase()}${rrggbb.toUpperCase()})`;
954
+ }
955
+ const alphaHex = alpha < 1 ? Math.round(alpha * 255).toString(16).padStart(2, "0").toUpperCase() : "FF";
956
+ return `Color(0x${alphaHex}${hexClean.toUpperCase()})`;
957
+ }
958
+ formatDimensionValue(value) {
959
+ if (isDimensionObject(value)) {
960
+ const dim = value;
961
+ const dpValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
962
+ return `${dpValue}.dp`;
963
+ }
964
+ return typeof value === "number" ? `${value}.dp` : `0.dp`;
965
+ }
966
+ formatFontFamilyValue(value) {
967
+ if (Array.isArray(value)) {
968
+ const primary = value[0];
969
+ if (typeof primary === "string") {
970
+ return this.mapKotlinFontFamily(primary);
971
+ }
972
+ return "FontFamily.Default";
973
+ }
974
+ return typeof value === "string" ? this.mapKotlinFontFamily(value) : "FontFamily.Default";
975
+ }
976
+ mapKotlinFontFamily(family) {
977
+ const normalized = family.toLowerCase().replace(/['"]/g, "").trim();
978
+ const builtIn = {
979
+ "sans-serif": "FontFamily.SansSerif",
980
+ serif: "FontFamily.Serif",
981
+ monospace: "FontFamily.Monospace",
982
+ cursive: "FontFamily.Cursive"
983
+ };
984
+ return builtIn[normalized] ?? `FontFamily.Default // TODO: load "${family}" via Font(R.font.${toResourceName(family)})`;
985
+ }
986
+ formatFontWeightValue(value) {
987
+ if (typeof value === "number") {
988
+ return this.numericFontWeight(value);
989
+ }
990
+ if (typeof value === "string") {
991
+ return this.namedFontWeight(value) ?? "FontWeight.Normal";
992
+ }
993
+ return "FontWeight.Normal";
994
+ }
995
+ numericFontWeight(weight) {
996
+ if (weight <= 100) {
997
+ return "FontWeight.Thin";
998
+ }
999
+ if (weight <= 200) {
1000
+ return "FontWeight.ExtraLight";
1001
+ }
1002
+ if (weight <= 300) {
1003
+ return "FontWeight.Light";
1004
+ }
1005
+ if (weight <= 400) {
1006
+ return "FontWeight.Normal";
1007
+ }
1008
+ if (weight <= 500) {
1009
+ return "FontWeight.Medium";
1010
+ }
1011
+ if (weight <= 600) {
1012
+ return "FontWeight.SemiBold";
1013
+ }
1014
+ if (weight <= 700) {
1015
+ return "FontWeight.Bold";
1016
+ }
1017
+ if (weight <= 800) {
1018
+ return "FontWeight.ExtraBold";
1019
+ }
1020
+ return "FontWeight.Black";
1021
+ }
1022
+ namedFontWeight(name) {
1023
+ const map = {
1024
+ thin: "FontWeight.Thin",
1025
+ extralight: "FontWeight.ExtraLight",
1026
+ ultralight: "FontWeight.ExtraLight",
1027
+ light: "FontWeight.Light",
1028
+ regular: "FontWeight.Normal",
1029
+ normal: "FontWeight.Normal",
1030
+ medium: "FontWeight.Medium",
1031
+ semibold: "FontWeight.SemiBold",
1032
+ demibold: "FontWeight.SemiBold",
1033
+ bold: "FontWeight.Bold",
1034
+ extrabold: "FontWeight.ExtraBold",
1035
+ heavy: "FontWeight.ExtraBold",
1036
+ black: "FontWeight.Black",
1037
+ ultrabold: "FontWeight.Black"
1038
+ };
1039
+ return map[name.toLowerCase()];
1040
+ }
1041
+ formatDurationValue(value) {
1042
+ if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
1043
+ const dur = value;
1044
+ return dur.unit === "ms" ? `${dur.value}.milliseconds` : `${dur.value}.seconds`;
1045
+ }
1046
+ return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
1047
+ }
1048
+ formatShadowValue(value, options, depth) {
1049
+ if (Array.isArray(value) && value.length > 0) {
1050
+ return this.formatSingleShadow(value[0], options, depth);
1051
+ }
1052
+ if (typeof value === "object" && value !== null) {
1053
+ return this.formatSingleShadow(value, options, depth);
1054
+ }
1055
+ return "ShadowToken(color = Color.Unspecified, elevation = 0.dp, offsetX = 0.dp, offsetY = 0.dp)";
1056
+ }
1057
+ formatSingleShadow(shadow, options, depth) {
1058
+ const color = isColorObject(shadow.color) ? this.formatColorValue(shadow.color, options) : "Color.Black";
1059
+ const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
1060
+ const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
1061
+ const offsetY = isDimensionObject(shadow.offsetY) ? this.formatDimensionValue(shadow.offsetY) : "0.dp";
1062
+ const propIndent = indent(options.indent, depth + 1);
1063
+ const closeIndent = indent(options.indent, depth);
1064
+ return [
1065
+ "ShadowToken(",
1066
+ `${propIndent}color = ${color},`,
1067
+ `${propIndent}elevation = ${elevation},`,
1068
+ `${propIndent}offsetX = ${offsetX},`,
1069
+ `${propIndent}offsetY = ${offsetY},`,
1070
+ `${closeIndent})`
1071
+ ].join("\n");
1072
+ }
1073
+ formatBorderValue(value, options) {
1074
+ if (typeof value !== "object" || value === null) {
1075
+ return "BorderStroke(0.dp, Color.Unspecified)";
1076
+ }
1077
+ const border = value;
1078
+ const width = isDimensionObject(border.width) ? this.formatDimensionValue(border.width) : "0.dp";
1079
+ const color = isColorObject(border.color) ? this.formatColorValue(border.color, options) : "Color.Unspecified";
1080
+ return `BorderStroke(${width}, ${color})`;
1081
+ }
1082
+ formatTypographyValue(value, options, depth) {
1083
+ if (typeof value !== "object" || value === null) {
1084
+ return "TextStyle()";
1085
+ }
1086
+ const typo = value;
1087
+ const parts = [];
1088
+ if (isDimensionObject(typo.fontSize)) {
1089
+ const dim = typo.fontSize;
1090
+ const spValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
1091
+ parts.push(`fontSize = ${spValue}.sp`);
1092
+ }
1093
+ if (typo.fontWeight != null) {
1094
+ parts.push(`fontWeight = ${this.formatFontWeightValue(typo.fontWeight)}`);
1095
+ }
1096
+ if (typo.lineHeight != null && typeof typo.lineHeight === "number") {
1097
+ if (isDimensionObject(typo.fontSize)) {
1098
+ const dim = typo.fontSize;
1099
+ const spValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
1100
+ const lineHeightSp = Math.round(spValue * typo.lineHeight * 100) / 100;
1101
+ parts.push(`lineHeight = ${lineHeightSp}.sp`);
1102
+ }
1103
+ }
1104
+ if (isDimensionObject(typo.letterSpacing)) {
1105
+ const dim = typo.letterSpacing;
1106
+ const spValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
1107
+ parts.push(`letterSpacing = ${spValue}.sp`);
1108
+ }
1109
+ if (parts.length === 0) {
1110
+ return "TextStyle()";
1111
+ }
1112
+ const propIndent = indent(options.indent, depth + 1);
1113
+ const closeIndent = indent(options.indent, depth);
1114
+ return `TextStyle(
1115
+ ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
1116
+ ${closeIndent})`;
1117
+ }
1118
+ // -----------------------------------------------------------------------
1119
+ // Output: standalone
1120
+ // -----------------------------------------------------------------------
1121
+ async formatStandalone(context, options) {
1122
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1123
+ if (!context.output.file && requiresFile) {
1124
+ throw new ConfigurationError(
1125
+ `Output "${context.output.name}": file is required for standalone Android output`
1126
+ );
1127
+ }
1128
+ const files = {};
1129
+ for (const { tokens, modifierInputs } of context.permutations) {
1130
+ const processedTokens = stripInternalMetadata(tokens);
1131
+ const content = this.formatTokens(processedTokens, options);
1132
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
1133
+ outputName: context.output.name,
1134
+ extension: "kt",
1135
+ modifierInputs,
1136
+ resolver: context.resolver,
1137
+ defaults: context.meta.defaults
1138
+ });
1139
+ files[fileName] = content;
1140
+ }
1141
+ return outputTree(files);
1142
+ }
1143
+ // -----------------------------------------------------------------------
1144
+ // Output: bundle
1145
+ // -----------------------------------------------------------------------
1146
+ async formatBundle(context, options) {
1147
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1148
+ if (!context.output.file && requiresFile) {
1149
+ throw new ConfigurationError(
1150
+ `Output "${context.output.name}": file is required for bundle Android output`
1151
+ );
1152
+ }
1153
+ const content = this.formatBundleContent(context, options);
1154
+ const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
1155
+ outputName: context.output.name,
1156
+ extension: "kt",
1157
+ modifierInputs: context.meta.basePermutation,
1158
+ resolver: context.resolver,
1159
+ defaults: context.meta.defaults
1160
+ });
1161
+ return outputTree({ [fileName]: content });
1162
+ }
1163
+ formatBundleContent(context, options) {
1164
+ const allTokenTypes = this.collectAllPermutationTypes(context);
1165
+ return this.buildFile(allTokenTypes, options, (lines, vis) => {
1166
+ const i1 = indent(options.indent, 1);
1167
+ lines.push(`@Suppress("unused")`);
1168
+ lines.push(`${vis}object ${options.objectName} {`);
1169
+ for (let idx = 0; idx < context.permutations.length; idx++) {
1170
+ const { tokens, modifierInputs } = context.permutations[idx];
1171
+ const processedTokens = stripInternalMetadata(tokens);
1172
+ const permName = this.buildPermutationName(modifierInputs);
1173
+ lines.push(`${i1}${vis}object ${permName} {`);
1174
+ this.renderBundleTokens(lines, processedTokens, options, 2);
1175
+ lines.push(`${i1}}`);
1176
+ if (idx < context.permutations.length - 1) {
1177
+ lines.push("");
1178
+ }
1179
+ }
1180
+ lines.push("}");
1181
+ });
1182
+ }
1183
+ collectAllPermutationTypes(context) {
1184
+ const allTokenTypes = /* @__PURE__ */ new Set();
1185
+ for (const { tokens } of context.permutations) {
1186
+ const processed = stripInternalMetadata(tokens);
1187
+ for (const [, token] of Object.entries(processed)) {
1188
+ if (token.$type) {
1189
+ allTokenTypes.add(token.$type);
1190
+ }
1191
+ }
1192
+ }
1193
+ return allTokenTypes;
1194
+ }
1195
+ renderBundleTokens(lines, tokens, options, baseDepth) {
1196
+ if (options.structure === "flat") {
1197
+ const groups = this.groupTokensByType(tokens);
1198
+ this.renderFlatGroups(lines, groups, baseDepth, options);
1199
+ return;
1200
+ }
1201
+ const tree = this.buildTokenTree(tokens);
1202
+ this.renderTreeChildren(lines, tree, baseDepth, options);
1203
+ }
1204
+ buildPermutationName(modifierInputs) {
1205
+ const values = Object.values(modifierInputs);
1206
+ if (values.length === 0) {
1207
+ return "Default";
1208
+ }
1209
+ return values.map((v) => toPascalCase(v)).join("");
1210
+ }
1211
+ };
1212
+ function androidRenderer() {
1213
+ const rendererInstance = new AndroidRenderer();
1214
+ return {
1215
+ format: (context, options) => rendererInstance.format(
1216
+ context,
1217
+ options ?? context.output.options
1218
+ )
1219
+ };
1220
+ }
1221
+
471
1222
  // src/renderers/css.ts
472
1223
  init_errors();
473
1224
  init_token_utils();
@@ -781,11 +1532,11 @@ var CssRenderer = class _CssRenderer {
781
1532
  mediaQuery: "",
782
1533
  minify: false,
783
1534
  preserveReferences: false,
784
- referenceTokens: void 0,
785
- ...options
1535
+ ...options,
1536
+ referenceTokens: options?.referenceTokens ?? tokens
786
1537
  };
787
1538
  const groups = this.groupTokens(tokens, opts);
788
- const referenceTokens = opts.referenceTokens ?? tokens;
1539
+ const referenceTokens = opts.referenceTokens;
789
1540
  const lines = [];
790
1541
  for (const [selector, groupTokens] of Object.entries(groups)) {
791
1542
  this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
@@ -794,14 +1545,14 @@ var CssRenderer = class _CssRenderer {
794
1545
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
795
1546
  }
796
1547
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
797
- const indent = opts.minify ? "" : " ";
1548
+ const indent2 = opts.minify ? "" : " ";
798
1549
  const newline = opts.minify ? "" : "\n";
799
1550
  const space = opts.minify ? "" : " ";
800
1551
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
801
- const tokenIndent = hasMediaQuery ? indent + indent : indent;
1552
+ const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
802
1553
  if (hasMediaQuery) {
803
1554
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
804
- lines.push(`${indent}${selector}${space}{${newline}`);
1555
+ lines.push(`${indent2}${selector}${space}{${newline}`);
805
1556
  } else {
806
1557
  lines.push(`${selector}${space}{${newline}`);
807
1558
  }
@@ -818,21 +1569,21 @@ var CssRenderer = class _CssRenderer {
818
1569
  );
819
1570
  }
820
1571
  if (hasMediaQuery) {
821
- lines.push(`${indent}}${newline}`);
1572
+ lines.push(`${indent2}}${newline}`);
822
1573
  }
823
1574
  lines.push(`}${newline}${newline}`);
824
1575
  }
825
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
1576
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
826
1577
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
827
1578
  if (token.$deprecated != null && token.$deprecated !== false) {
828
1579
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
829
- lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1580
+ lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
830
1581
  }
831
1582
  if (token.$description && token.$description !== "") {
832
- lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1583
+ lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
833
1584
  }
834
1585
  for (const entry of entries) {
835
- lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
1586
+ lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
836
1587
  }
837
1588
  }
838
1589
  async formatWithPrettier(css2) {
@@ -1427,69 +2178,682 @@ function cssRenderer() {
1427
2178
  };
1428
2179
  }
1429
2180
 
1430
- // src/renderers/js-module.ts
1431
- init_utils();
2181
+ // src/renderers/ios.ts
1432
2182
  init_errors();
1433
2183
  init_token_utils();
1434
-
1435
- // src/renderers/output-tree.ts
1436
- var outputTree = (files) => {
1437
- return {
1438
- kind: "outputTree",
1439
- files
1440
- };
2184
+ init_utils();
2185
+ var toSRGB2 = culori.converter("rgb");
2186
+ var toP32 = culori.converter("p3");
2187
+ var SWIFT_TYPE_GROUP_MAP = {
2188
+ color: "Colors",
2189
+ dimension: "Spacing",
2190
+ fontFamily: "Fonts",
2191
+ fontWeight: "FontWeights",
2192
+ duration: "Durations",
2193
+ shadow: "Shadows",
2194
+ typography: "Typography",
2195
+ number: "Numbers",
2196
+ cubicBezier: "Animations",
2197
+ border: "Borders",
2198
+ gradient: "Gradients"
1441
2199
  };
1442
-
1443
- // src/renderers/js-module.ts
1444
- var JsModuleRenderer = class {
2200
+ var SWIFT_KEYWORDS = /* @__PURE__ */ new Set([
2201
+ "associatedtype",
2202
+ "class",
2203
+ "deinit",
2204
+ "enum",
2205
+ "extension",
2206
+ "fileprivate",
2207
+ "func",
2208
+ "import",
2209
+ "init",
2210
+ "inout",
2211
+ "internal",
2212
+ "let",
2213
+ "open",
2214
+ "operator",
2215
+ "private",
2216
+ "protocol",
2217
+ "public",
2218
+ "rethrows",
2219
+ "static",
2220
+ "struct",
2221
+ "subscript",
2222
+ "typealias",
2223
+ "var",
2224
+ "break",
2225
+ "case",
2226
+ "continue",
2227
+ "default",
2228
+ "defer",
2229
+ "do",
2230
+ "else",
2231
+ "fallthrough",
2232
+ "for",
2233
+ "guard",
2234
+ "if",
2235
+ "in",
2236
+ "repeat",
2237
+ "return",
2238
+ "switch",
2239
+ "where",
2240
+ "while",
2241
+ "as",
2242
+ "catch",
2243
+ "false",
2244
+ "is",
2245
+ "nil",
2246
+ "super",
2247
+ "self",
2248
+ "Self",
2249
+ "throw",
2250
+ "throws",
2251
+ "true",
2252
+ "try",
2253
+ "Type",
2254
+ "Protocol"
2255
+ ]);
2256
+ var IosRenderer = class {
1445
2257
  async format(context, options) {
1446
2258
  const opts = {
1447
2259
  preset: options?.preset ?? "standalone",
1448
- structure: options?.structure ?? "nested",
1449
- minify: options?.minify ?? false,
1450
- moduleName: options?.moduleName ?? "tokens",
1451
- generateHelper: options?.generateHelper ?? false
2260
+ accessLevel: options?.accessLevel ?? "public",
2261
+ structure: options?.structure ?? "enum",
2262
+ enumName: options?.enumName ?? "DesignTokens",
2263
+ extensionNamespace: options?.extensionNamespace ?? "DesignTokens",
2264
+ colorSpace: options?.colorSpace ?? "sRGB",
2265
+ swiftVersion: options?.swiftVersion ?? "5.9",
2266
+ indent: options?.indent ?? 4,
2267
+ frozen: options?.frozen ?? false
1452
2268
  };
1453
- if (opts.preset === "bundle") {
1454
- const { bundleAsJsModule: bundleAsJsModule2 } = await Promise.resolve().then(() => (init_js(), js_exports));
1455
- const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
1456
- tokens: stripInternalMetadata(tokens),
1457
- modifierInputs,
1458
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
1459
- }));
1460
- return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
1461
- return await this.formatTokens(tokens, opts);
1462
- });
1463
- }
1464
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1465
- if (!context.output.file && requiresFile) {
1466
- throw new ConfigurationError(
1467
- `Output "${context.output.name}": file is required for JS module output`
1468
- );
1469
- }
1470
- const files = {};
1471
- for (const { tokens, modifierInputs } of context.permutations) {
1472
- const cleanTokens = stripInternalMetadata(tokens);
1473
- const content = await this.formatTokens(cleanTokens, opts);
1474
- const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
1475
- outputName: context.output.name,
1476
- extension: "js",
1477
- modifierInputs,
1478
- resolver: context.resolver,
1479
- defaults: context.meta.defaults
1480
- });
1481
- files[fileName] = content;
2269
+ return await this.formatStandalone(context, opts);
2270
+ }
2271
+ formatTokens(tokens, options) {
2272
+ if (options.structure === "grouped") {
2273
+ return this.formatAsGrouped(tokens, options);
1482
2274
  }
1483
- return outputTree(files);
2275
+ return this.formatAsEnum(tokens, options);
1484
2276
  }
1485
- async formatTokens(tokens, options) {
1486
- const opts = {
1487
- preset: options.preset ?? "standalone",
1488
- structure: options.structure ?? "nested",
1489
- minify: options.minify ?? false,
1490
- moduleName: options.moduleName ?? "tokens",
1491
- generateHelper: options.generateHelper ?? false
1492
- };
2277
+ formatAsEnum(tokens, options) {
2278
+ const access = options.accessLevel;
2279
+ const groups = this.groupTokensByType(tokens);
2280
+ const imports = this.collectImports(tokens);
2281
+ const i1 = this.indentStr(options.indent, 1);
2282
+ const i2 = this.indentStr(options.indent, 2);
2283
+ const staticPrefix = this.staticLetPrefix(options);
2284
+ const frozen = this.frozenPrefix(options);
2285
+ const lines = [];
2286
+ lines.push(this.buildFileHeader());
2287
+ lines.push("");
2288
+ for (const imp of imports) {
2289
+ lines.push(`import ${imp}`);
2290
+ }
2291
+ lines.push(...this.buildStructDefinitions(tokens, access, options));
2292
+ lines.push("");
2293
+ lines.push(`${frozen}${access} enum ${options.enumName} {`);
2294
+ for (const group of groups) {
2295
+ lines.push(`${i1}${frozen}${access} enum ${group.name} {`);
2296
+ for (const token of group.tokens) {
2297
+ const swiftName = this.buildQualifiedSwiftName(token);
2298
+ const swiftValue = this.formatSwiftValue(token, options);
2299
+ const typeAnnotation = this.getTypeAnnotation(token);
2300
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2301
+ const docComment = this.buildDocComment(token, i2);
2302
+ if (docComment) {
2303
+ lines.push(docComment);
2304
+ }
2305
+ lines.push(`${i2}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2306
+ }
2307
+ lines.push(`${i1}}`);
2308
+ lines.push("");
2309
+ }
2310
+ lines.push("}");
2311
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2312
+ lines.push("");
2313
+ return lines.join("\n");
2314
+ }
2315
+ formatAsGrouped(tokens, options) {
2316
+ const access = options.accessLevel;
2317
+ const namespace = options.extensionNamespace;
2318
+ const groups = this.groupTokensByType(tokens);
2319
+ const imports = this.collectImports(tokens);
2320
+ const i1 = this.indentStr(options.indent, 1);
2321
+ const i2 = this.indentStr(options.indent, 2);
2322
+ const staticPrefix = this.staticLetPrefix(options);
2323
+ const frozen = this.frozenPrefix(options);
2324
+ const lines = [];
2325
+ lines.push(this.buildFileHeader());
2326
+ lines.push("");
2327
+ for (const imp of imports) {
2328
+ lines.push(`import ${imp}`);
2329
+ }
2330
+ lines.push(...this.buildStructDefinitions(tokens, access, options));
2331
+ lines.push("");
2332
+ lines.push(`${frozen}${access} enum ${namespace} {}`);
2333
+ lines.push("");
2334
+ for (const group of groups) {
2335
+ lines.push(`${access} extension ${namespace} {`);
2336
+ lines.push(`${i1}${frozen}enum ${group.name} {`);
2337
+ for (const token of group.tokens) {
2338
+ const swiftName = this.buildQualifiedSwiftName(token);
2339
+ const swiftValue = this.formatSwiftValue(token, options);
2340
+ const typeAnnotation = this.getTypeAnnotation(token);
2341
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2342
+ const docComment = this.buildDocComment(token, i2);
2343
+ if (docComment) {
2344
+ lines.push(docComment);
2345
+ }
2346
+ lines.push(`${i2}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2347
+ }
2348
+ lines.push(`${i1}}`);
2349
+ lines.push("}");
2350
+ lines.push("");
2351
+ }
2352
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2353
+ return lines.join("\n");
2354
+ }
2355
+ buildFileHeader() {
2356
+ return [
2357
+ "// Generated by Dispersa - do not edit manually",
2358
+ "// https://github.com/timges/dispersa"
2359
+ ].join("\n");
2360
+ }
2361
+ collectImports(tokens) {
2362
+ const imports = /* @__PURE__ */ new Set();
2363
+ imports.add("SwiftUI");
2364
+ for (const [, token] of Object.entries(tokens)) {
2365
+ if (token.$type === "duration") {
2366
+ imports.add("Foundation");
2367
+ }
2368
+ }
2369
+ return Array.from(imports).sort();
2370
+ }
2371
+ /**
2372
+ * Builds a `///` doc comment from a token's `$description`, if present.
2373
+ */
2374
+ buildDocComment(token, indent2) {
2375
+ if (!token.$description) {
2376
+ return void 0;
2377
+ }
2378
+ return `${indent2}/// ${token.$description}`;
2379
+ }
2380
+ groupTokensByType(tokens) {
2381
+ const groupMap = /* @__PURE__ */ new Map();
2382
+ for (const [, token] of getSortedTokenEntries(tokens)) {
2383
+ const groupName = SWIFT_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
2384
+ const existing = groupMap.get(groupName) ?? [];
2385
+ existing.push(token);
2386
+ groupMap.set(groupName, existing);
2387
+ }
2388
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
2389
+ name,
2390
+ tokens: groupTokens
2391
+ }));
2392
+ }
2393
+ /**
2394
+ * Builds a qualified Swift name from a token's path, preserving parent
2395
+ * hierarchy segments to avoid duplicate identifiers.
2396
+ *
2397
+ * For example, `color.blue.400` in the `Colors` group becomes `blue400`
2398
+ * instead of just `_400`.
2399
+ */
2400
+ buildQualifiedSwiftName(token) {
2401
+ const path = token.path;
2402
+ const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
2403
+ const joined = withoutTypePrefix.join("_");
2404
+ return this.toSwiftIdentifier(joined);
2405
+ }
2406
+ formatSwiftValue(token, options) {
2407
+ const value = token.$value;
2408
+ if (token.$type === "color") {
2409
+ return this.formatColorValue(value, options);
2410
+ }
2411
+ if (token.$type === "dimension") {
2412
+ return this.formatDimensionValue(value);
2413
+ }
2414
+ if (token.$type === "fontFamily") {
2415
+ return this.formatFontFamilyValue(value);
2416
+ }
2417
+ if (token.$type === "fontWeight") {
2418
+ return this.formatFontWeightValue(value);
2419
+ }
2420
+ if (token.$type === "duration") {
2421
+ return this.formatDurationValue(value);
2422
+ }
2423
+ if (token.$type === "shadow") {
2424
+ return this.formatShadowValue(value, options);
2425
+ }
2426
+ if (token.$type === "typography") {
2427
+ return this.formatTypographyValue(value);
2428
+ }
2429
+ if (token.$type === "border") {
2430
+ return this.formatBorderValue(value, options);
2431
+ }
2432
+ if (token.$type === "gradient") {
2433
+ return this.formatGradientValue(value, options);
2434
+ }
2435
+ if (token.$type === "number") {
2436
+ return String(value);
2437
+ }
2438
+ if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
2439
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2440
+ }
2441
+ if (typeof value === "string") {
2442
+ return `"${this.escapeSwiftString(value)}"`;
2443
+ }
2444
+ if (typeof value === "number") {
2445
+ return String(value);
2446
+ }
2447
+ if (typeof value === "boolean") {
2448
+ return value ? "true" : "false";
2449
+ }
2450
+ return `"${this.escapeSwiftString(String(value))}"`;
2451
+ }
2452
+ formatColorValue(value, options) {
2453
+ if (!isColorObject(value)) {
2454
+ return typeof value === "string" ? `Color("${this.escapeSwiftString(value)}")` : "Color.clear";
2455
+ }
2456
+ const colorObj = value;
2457
+ const alpha = colorObj.alpha ?? 1;
2458
+ if (options.colorSpace === "displayP3") {
2459
+ const p3 = toP32(dtcgObjectToCulori(colorObj));
2460
+ const r2 = this.roundComponent(p3?.r ?? 0);
2461
+ const g2 = this.roundComponent(p3?.g ?? 0);
2462
+ const b2 = this.roundComponent(p3?.b ?? 0);
2463
+ return alpha < 1 ? `Color(.displayP3, red: ${r2}, green: ${g2}, blue: ${b2}, opacity: ${this.roundComponent(alpha)})` : `Color(.displayP3, red: ${r2}, green: ${g2}, blue: ${b2})`;
2464
+ }
2465
+ const rgb = toSRGB2(dtcgObjectToCulori(colorObj));
2466
+ const r = this.roundComponent(rgb?.r ?? 0);
2467
+ const g = this.roundComponent(rgb?.g ?? 0);
2468
+ const b = this.roundComponent(rgb?.b ?? 0);
2469
+ return alpha < 1 ? `Color(red: ${r}, green: ${g}, blue: ${b}, opacity: ${this.roundComponent(alpha)})` : `Color(red: ${r}, green: ${g}, blue: ${b})`;
2470
+ }
2471
+ formatDimensionValue(value) {
2472
+ if (isDimensionObject(value)) {
2473
+ const dim = value;
2474
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2475
+ return String(ptValue);
2476
+ }
2477
+ return String(value);
2478
+ }
2479
+ formatFontFamilyValue(value) {
2480
+ if (Array.isArray(value)) {
2481
+ const primary = value[0];
2482
+ return typeof primary === "string" ? `"${this.escapeSwiftString(primary)}"` : '"system"';
2483
+ }
2484
+ return typeof value === "string" ? `"${this.escapeSwiftString(value)}"` : '"system"';
2485
+ }
2486
+ formatFontWeightValue(value) {
2487
+ if (typeof value === "number") {
2488
+ return this.numericFontWeight(value);
2489
+ }
2490
+ if (typeof value === "string") {
2491
+ return this.namedFontWeight(value) ?? "Font.Weight.regular";
2492
+ }
2493
+ return "Font.Weight.regular";
2494
+ }
2495
+ numericFontWeight(weight) {
2496
+ if (weight <= 100) {
2497
+ return "Font.Weight.ultraLight";
2498
+ }
2499
+ if (weight <= 200) {
2500
+ return "Font.Weight.thin";
2501
+ }
2502
+ if (weight <= 300) {
2503
+ return "Font.Weight.light";
2504
+ }
2505
+ if (weight <= 400) {
2506
+ return "Font.Weight.regular";
2507
+ }
2508
+ if (weight <= 500) {
2509
+ return "Font.Weight.medium";
2510
+ }
2511
+ if (weight <= 600) {
2512
+ return "Font.Weight.semibold";
2513
+ }
2514
+ if (weight <= 700) {
2515
+ return "Font.Weight.bold";
2516
+ }
2517
+ if (weight <= 800) {
2518
+ return "Font.Weight.heavy";
2519
+ }
2520
+ return "Font.Weight.black";
2521
+ }
2522
+ namedFontWeight(name) {
2523
+ const map = {
2524
+ thin: "Font.Weight.thin",
2525
+ ultralight: "Font.Weight.ultraLight",
2526
+ extralight: "Font.Weight.ultraLight",
2527
+ light: "Font.Weight.light",
2528
+ regular: "Font.Weight.regular",
2529
+ normal: "Font.Weight.regular",
2530
+ medium: "Font.Weight.medium",
2531
+ semibold: "Font.Weight.semibold",
2532
+ demibold: "Font.Weight.semibold",
2533
+ bold: "Font.Weight.bold",
2534
+ heavy: "Font.Weight.heavy",
2535
+ extrabold: "Font.Weight.heavy",
2536
+ black: "Font.Weight.black",
2537
+ ultrabold: "Font.Weight.black"
2538
+ };
2539
+ return map[name.toLowerCase()];
2540
+ }
2541
+ formatDurationValue(value) {
2542
+ if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
2543
+ const dur = value;
2544
+ const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
2545
+ return String(seconds);
2546
+ }
2547
+ return typeof value === "number" ? String(value) : "0";
2548
+ }
2549
+ formatShadowValue(value, options) {
2550
+ if (Array.isArray(value) && value.length > 0) {
2551
+ return this.formatSingleShadow(value[0], options);
2552
+ }
2553
+ if (typeof value === "object" && value !== null) {
2554
+ return this.formatSingleShadow(value, options);
2555
+ }
2556
+ return "ShadowStyle(color: .clear, radius: 0, x: 0, y: 0, spread: 0)";
2557
+ }
2558
+ formatSingleShadow(shadow, options) {
2559
+ const color = isColorObject(shadow.color) ? this.formatColorValue(shadow.color, options) : "Color.black.opacity(0.25)";
2560
+ const radius = isDimensionObject(shadow.blur) ? this.dimensionToCGFloat(shadow.blur) : "8";
2561
+ const x = isDimensionObject(shadow.offsetX) ? this.dimensionToCGFloat(shadow.offsetX) : "0";
2562
+ const y = isDimensionObject(shadow.offsetY) ? this.dimensionToCGFloat(shadow.offsetY) : "0";
2563
+ const spread = isDimensionObject(shadow.spread) ? this.dimensionToCGFloat(shadow.spread) : "0";
2564
+ return `ShadowStyle(color: ${color}, radius: ${radius}, x: ${x}, y: ${y}, spread: ${spread})`;
2565
+ }
2566
+ formatTypographyValue(value) {
2567
+ if (typeof value !== "object" || value === null) {
2568
+ return "TypographyStyle(font: Font.body, tracking: 0, lineSpacing: 0)";
2569
+ }
2570
+ const typo = value;
2571
+ const size = isDimensionObject(typo.fontSize) ? this.dimensionToPoints(typo.fontSize) : "16";
2572
+ const weight = typo.fontWeight != null ? this.formatFontWeightValue(typo.fontWeight) : "Font.Weight.regular";
2573
+ const fontExpr = this.buildFontExpression(typo, size, weight);
2574
+ const tracking = this.extractTracking(typo);
2575
+ const lineSpacing = this.extractLineSpacing(typo);
2576
+ return `TypographyStyle(font: ${fontExpr}, tracking: ${tracking}, lineSpacing: ${lineSpacing})`;
2577
+ }
2578
+ buildFontExpression(typo, size, weight) {
2579
+ if (typo.fontFamily != null) {
2580
+ const family = Array.isArray(typo.fontFamily) ? typo.fontFamily[0] : typo.fontFamily;
2581
+ if (typeof family === "string") {
2582
+ return `Font.custom("${this.escapeSwiftString(family)}", size: ${size}).weight(${weight})`;
2583
+ }
2584
+ }
2585
+ return `Font.system(size: ${size}, weight: ${weight})`;
2586
+ }
2587
+ extractTracking(typo) {
2588
+ if (!isDimensionObject(typo.letterSpacing)) {
2589
+ return "0";
2590
+ }
2591
+ const dim = typo.letterSpacing;
2592
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2593
+ return String(ptValue);
2594
+ }
2595
+ extractLineSpacing(typo) {
2596
+ if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
2597
+ return "0";
2598
+ }
2599
+ if (!isDimensionObject(typo.fontSize)) {
2600
+ return "0";
2601
+ }
2602
+ const dim = typo.fontSize;
2603
+ const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
2604
+ const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
2605
+ return String(lineHeightPt - basePt);
2606
+ }
2607
+ dimensionToPoints(dim) {
2608
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2609
+ return String(ptValue);
2610
+ }
2611
+ /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
2612
+ dimensionToCGFloat(dim) {
2613
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2614
+ return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
2615
+ }
2616
+ getTypeAnnotation(token) {
2617
+ switch (token.$type) {
2618
+ case "dimension":
2619
+ return "CGFloat";
2620
+ case "duration":
2621
+ return "TimeInterval";
2622
+ case "number":
2623
+ return "Double";
2624
+ case "fontWeight":
2625
+ return "Font.Weight";
2626
+ case "fontFamily":
2627
+ return "String";
2628
+ default:
2629
+ return void 0;
2630
+ }
2631
+ }
2632
+ toSwiftIdentifier(name) {
2633
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
2634
+ const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
2635
+ const safe = /^\d/.test(identifier) ? `_${identifier}` : identifier;
2636
+ return SWIFT_KEYWORDS.has(safe) ? `\`${safe}\`` : safe;
2637
+ }
2638
+ escapeSwiftString(str) {
2639
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2640
+ }
2641
+ roundComponent(value) {
2642
+ return Math.round(value * 1e4) / 1e4;
2643
+ }
2644
+ indentStr(width, level) {
2645
+ return " ".repeat(width * level);
2646
+ }
2647
+ /**
2648
+ * Returns the prefix for `static let` declarations.
2649
+ * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
2650
+ */
2651
+ staticLetPrefix(options) {
2652
+ return options.swiftVersion === "6.0" ? "nonisolated(unsafe) static let " : "static let ";
2653
+ }
2654
+ /** Returns `@frozen ` when the frozen option is enabled, empty string otherwise. */
2655
+ frozenPrefix(options) {
2656
+ return options.frozen ? "@frozen " : "";
2657
+ }
2658
+ /** Returns `: Sendable` when targeting Swift 6, empty string otherwise. */
2659
+ structConformances(options) {
2660
+ return options.swiftVersion === "6.0" ? ": Sendable" : "";
2661
+ }
2662
+ hasShadowTokens(tokens) {
2663
+ return Object.values(tokens).some((t) => t.$type === "shadow");
2664
+ }
2665
+ hasTypographyTokens(tokens) {
2666
+ return Object.values(tokens).some((t) => t.$type === "typography");
2667
+ }
2668
+ hasBorderTokens(tokens) {
2669
+ return Object.values(tokens).some((t) => t.$type === "border");
2670
+ }
2671
+ /** Emits all struct definitions needed by the token set. */
2672
+ buildStructDefinitions(tokens, access, options) {
2673
+ const lines = [];
2674
+ if (this.hasShadowTokens(tokens)) {
2675
+ lines.push("");
2676
+ lines.push(...this.buildShadowStyleStruct(access, options));
2677
+ }
2678
+ if (this.hasTypographyTokens(tokens)) {
2679
+ lines.push("");
2680
+ lines.push(...this.buildTypographyStyleStruct(access, options));
2681
+ }
2682
+ if (this.hasBorderTokens(tokens)) {
2683
+ lines.push("");
2684
+ lines.push(...this.buildBorderStyleStruct(access, options));
2685
+ }
2686
+ return lines;
2687
+ }
2688
+ buildShadowStyleStruct(access, options) {
2689
+ const i1 = this.indentStr(options.indent, 1);
2690
+ const conformances = this.structConformances(options);
2691
+ const frozen = this.frozenPrefix(options);
2692
+ return [
2693
+ `${frozen}${access} struct ShadowStyle${conformances} {`,
2694
+ `${i1}${access} let color: Color`,
2695
+ `${i1}${access} let radius: CGFloat`,
2696
+ `${i1}${access} let x: CGFloat`,
2697
+ `${i1}${access} let y: CGFloat`,
2698
+ `${i1}${access} let spread: CGFloat`,
2699
+ "}"
2700
+ ];
2701
+ }
2702
+ buildTypographyStyleStruct(access, options) {
2703
+ const i1 = this.indentStr(options.indent, 1);
2704
+ const conformances = this.structConformances(options);
2705
+ const frozen = this.frozenPrefix(options);
2706
+ return [
2707
+ `${frozen}${access} struct TypographyStyle${conformances} {`,
2708
+ `${i1}${access} let font: Font`,
2709
+ `${i1}${access} let tracking: CGFloat`,
2710
+ `${i1}${access} let lineSpacing: CGFloat`,
2711
+ "}"
2712
+ ];
2713
+ }
2714
+ buildBorderStyleStruct(access, options) {
2715
+ const i1 = this.indentStr(options.indent, 1);
2716
+ const conformances = this.structConformances(options);
2717
+ const frozen = this.frozenPrefix(options);
2718
+ return [
2719
+ `${frozen}${access} struct BorderStyle${conformances} {`,
2720
+ `${i1}${access} let color: Color`,
2721
+ `${i1}${access} let width: CGFloat`,
2722
+ "}"
2723
+ ];
2724
+ }
2725
+ /** Emits convenience View extensions for shadow and typography application. */
2726
+ buildViewExtensions(tokens, access, options) {
2727
+ const lines = [];
2728
+ const i1 = this.indentStr(options.indent, 1);
2729
+ const i2 = this.indentStr(options.indent, 2);
2730
+ if (this.hasShadowTokens(tokens)) {
2731
+ lines.push("");
2732
+ lines.push(`${access} extension View {`);
2733
+ lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
2734
+ lines.push(
2735
+ `${i2}self.shadow(color: style.color, radius: style.radius, x: style.x, y: style.y)`
2736
+ );
2737
+ lines.push(`${i1}}`);
2738
+ lines.push("}");
2739
+ }
2740
+ if (this.hasTypographyTokens(tokens)) {
2741
+ lines.push("");
2742
+ lines.push(`${access} extension View {`);
2743
+ lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
2744
+ lines.push(
2745
+ `${i2}self.font(style.font).tracking(style.tracking).lineSpacing(style.lineSpacing)`
2746
+ );
2747
+ lines.push(`${i1}}`);
2748
+ lines.push("}");
2749
+ }
2750
+ return lines;
2751
+ }
2752
+ formatBorderValue(value, options) {
2753
+ if (typeof value !== "object" || value === null) {
2754
+ return "BorderStyle(color: .clear, width: 0)";
2755
+ }
2756
+ const border = value;
2757
+ const color = isColorObject(border.color) ? this.formatColorValue(border.color, options) : "Color.clear";
2758
+ const width = isDimensionObject(border.width) ? this.dimensionToCGFloat(border.width) : "1.0";
2759
+ return `BorderStyle(color: ${color}, width: ${width})`;
2760
+ }
2761
+ formatGradientValue(value, options) {
2762
+ if (!Array.isArray(value) || value.length === 0) {
2763
+ return "Gradient(stops: [])";
2764
+ }
2765
+ const stops = value.map((stop) => {
2766
+ const color = isColorObject(stop.color) ? this.formatColorValue(stop.color, options) : "Color.clear";
2767
+ return `.init(color: ${color}, location: ${stop.position})`;
2768
+ });
2769
+ return `Gradient(stops: [${stops.join(", ")}])`;
2770
+ }
2771
+ async formatStandalone(context, options) {
2772
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2773
+ if (!context.output.file && requiresFile) {
2774
+ throw new ConfigurationError(
2775
+ `Output "${context.output.name}": file is required for standalone iOS output`
2776
+ );
2777
+ }
2778
+ const files = {};
2779
+ for (const { tokens, modifierInputs } of context.permutations) {
2780
+ const processedTokens = stripInternalMetadata(tokens);
2781
+ const content = this.formatTokens(processedTokens, options);
2782
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
2783
+ outputName: context.output.name,
2784
+ extension: "swift",
2785
+ modifierInputs,
2786
+ resolver: context.resolver,
2787
+ defaults: context.meta.defaults
2788
+ });
2789
+ files[fileName] = content;
2790
+ }
2791
+ return outputTree(files);
2792
+ }
2793
+ };
2794
+ function iosRenderer() {
2795
+ const rendererInstance = new IosRenderer();
2796
+ return {
2797
+ format: (context, options) => rendererInstance.format(
2798
+ context,
2799
+ options ?? context.output.options
2800
+ )
2801
+ };
2802
+ }
2803
+
2804
+ // src/renderers/js-module.ts
2805
+ init_utils();
2806
+ init_errors();
2807
+ init_token_utils();
2808
+ var JsModuleRenderer = class {
2809
+ async format(context, options) {
2810
+ const opts = {
2811
+ preset: options?.preset ?? "standalone",
2812
+ structure: options?.structure ?? "nested",
2813
+ minify: options?.minify ?? false,
2814
+ moduleName: options?.moduleName ?? "tokens",
2815
+ generateHelper: options?.generateHelper ?? false
2816
+ };
2817
+ if (opts.preset === "bundle") {
2818
+ const { bundleAsJsModule: bundleAsJsModule2 } = await Promise.resolve().then(() => (init_js(), js_exports));
2819
+ const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2820
+ tokens: stripInternalMetadata(tokens),
2821
+ modifierInputs,
2822
+ isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2823
+ }));
2824
+ return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
2825
+ return await this.formatTokens(tokens, opts);
2826
+ });
2827
+ }
2828
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2829
+ if (!context.output.file && requiresFile) {
2830
+ throw new ConfigurationError(
2831
+ `Output "${context.output.name}": file is required for JS module output`
2832
+ );
2833
+ }
2834
+ const files = {};
2835
+ for (const { tokens, modifierInputs } of context.permutations) {
2836
+ const cleanTokens = stripInternalMetadata(tokens);
2837
+ const content = await this.formatTokens(cleanTokens, opts);
2838
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
2839
+ outputName: context.output.name,
2840
+ extension: "js",
2841
+ modifierInputs,
2842
+ resolver: context.resolver,
2843
+ defaults: context.meta.defaults
2844
+ });
2845
+ files[fileName] = content;
2846
+ }
2847
+ return outputTree(files);
2848
+ }
2849
+ async formatTokens(tokens, options) {
2850
+ const opts = {
2851
+ preset: options.preset ?? "standalone",
2852
+ structure: options.structure ?? "nested",
2853
+ minify: options.minify ?? false,
2854
+ moduleName: options.moduleName ?? "tokens",
2855
+ generateHelper: options.generateHelper ?? false
2856
+ };
1493
2857
  const lines = [];
1494
2858
  lines.push(...this.formatAsObject(tokens, opts));
1495
2859
  const code = lines.join("\n");
@@ -1554,8 +2918,8 @@ var JsModuleRenderer = class {
1554
2918
  /**
1555
2919
  * Add object properties to lines
1556
2920
  */
1557
- addObjectProperties(lines, obj, indent) {
1558
- const indentStr = " ".repeat(indent);
2921
+ addObjectProperties(lines, obj, indent2) {
2922
+ const indentStr = " ".repeat(indent2);
1559
2923
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
1560
2924
  for (let i = 0; i < entries.length; i++) {
1561
2925
  const entry = entries[i];
@@ -1566,7 +2930,7 @@ var JsModuleRenderer = class {
1566
2930
  const isLast = i === entries.length - 1;
1567
2931
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1568
2932
  lines.push(`${indentStr}${this.quoteKey(key)}: {`);
1569
- this.addObjectProperties(lines, value, indent + 1);
2933
+ this.addObjectProperties(lines, value, indent2 + 1);
1570
2934
  lines.push(`${indentStr}}${isLast ? "" : ","}`);
1571
2935
  } else {
1572
2936
  const valueStr = JSON.stringify(value);
@@ -1758,6 +3122,349 @@ function jsonRenderer() {
1758
3122
  };
1759
3123
  }
1760
3124
 
3125
+ // src/renderers/tailwind.ts
3126
+ init_errors();
3127
+ init_token_utils();
3128
+
3129
+ // src/renderers/bundlers/tailwind.ts
3130
+ init_errors();
3131
+ init_utils();
3132
+ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOverrideBlock) {
3133
+ const baseItem = bundleData.find((item) => item.isBase);
3134
+ if (!baseItem) {
3135
+ throw new BasePermutationError("Base permutation not found in bundle data");
3136
+ }
3137
+ const resolvedOpts = resolveOptions(options);
3138
+ const cssBlocks = [];
3139
+ const variantDeclarations = collectVariantDeclarations(bundleData, baseItem, resolvedOpts);
3140
+ const themeOpts = { ...resolvedOpts, variantDeclarations };
3141
+ const baseTokens = stripInternalMetadata(baseItem.tokens);
3142
+ const themeBlock = await formatThemeTokens(baseTokens, themeOpts);
3143
+ cssBlocks.push(themeBlock);
3144
+ for (const item of bundleData) {
3145
+ if (item.isBase) {
3146
+ continue;
3147
+ }
3148
+ const block = await formatModifierOverride(item, baseItem, resolvedOpts, formatOverrideBlock);
3149
+ if (block) {
3150
+ cssBlocks.push(block);
3151
+ }
3152
+ }
3153
+ return cssBlocks.join("\n");
3154
+ }
3155
+ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
3156
+ const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
3157
+ if (differenceCount > 1) {
3158
+ return void 0;
3159
+ }
3160
+ const tokensToInclude = filterTokensByValueChange(tokens, baseItem.tokens);
3161
+ if (Object.keys(tokensToInclude).length === 0) {
3162
+ return void 0;
3163
+ }
3164
+ const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
3165
+ const [modifier, context] = parseModifierSource(expectedSource);
3166
+ const cleanTokens = stripInternalMetadata(tokensToInclude);
3167
+ const selector = resolveSelector(
3168
+ options.selector,
3169
+ modifier,
3170
+ context,
3171
+ false,
3172
+ normalizeModifierInputs(modifierInputs)
3173
+ );
3174
+ const mediaQuery = resolveMediaQuery(
3175
+ options.mediaQuery,
3176
+ modifier,
3177
+ context,
3178
+ false,
3179
+ normalizeModifierInputs(modifierInputs)
3180
+ );
3181
+ const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
3182
+ return `/* Modifier: ${modifier}=${context} */
3183
+ ${css2}`;
3184
+ }
3185
+ function filterTokensByValueChange(currentTokens, baseTokens) {
3186
+ const changed = {};
3187
+ for (const [name, token] of Object.entries(currentTokens)) {
3188
+ const baseToken = baseTokens[name];
3189
+ if (!baseToken) {
3190
+ changed[name] = token;
3191
+ continue;
3192
+ }
3193
+ if (JSON.stringify(token.$value) !== JSON.stringify(baseToken.$value)) {
3194
+ changed[name] = token;
3195
+ }
3196
+ }
3197
+ return changed;
3198
+ }
3199
+ function collectVariantDeclarations(bundleData, baseItem, options) {
3200
+ const declarations = [];
3201
+ for (const item of bundleData) {
3202
+ if (item.isBase) {
3203
+ continue;
3204
+ }
3205
+ const differenceCount = countModifierDifferences(item.modifierInputs, baseItem.modifierInputs);
3206
+ if (differenceCount > 1) {
3207
+ continue;
3208
+ }
3209
+ const expectedSource = getExpectedSource(item.modifierInputs, baseItem.modifierInputs);
3210
+ const [modifier, context] = parseModifierSource(expectedSource);
3211
+ const variantName = `${modifier}-${context}`;
3212
+ const normalized = normalizeModifierInputs(item.modifierInputs);
3213
+ const mediaQuery = resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized);
3214
+ if (mediaQuery !== "") {
3215
+ declarations.push(`@custom-variant ${variantName} (@media ${mediaQuery});`);
3216
+ continue;
3217
+ }
3218
+ const selector = resolveSelector(options.selector, modifier, context, false, normalized);
3219
+ declarations.push(`@custom-variant ${variantName} (&:where(${selector}, ${selector} *));`);
3220
+ }
3221
+ return declarations;
3222
+ }
3223
+ function resolveOptions(options) {
3224
+ return {
3225
+ preset: options.preset ?? "bundle",
3226
+ includeImport: options.includeImport ?? true,
3227
+ namespace: options.namespace ?? "",
3228
+ minify: options.minify ?? false,
3229
+ selector: options.selector,
3230
+ mediaQuery: options.mediaQuery,
3231
+ variantDeclarations: []
3232
+ };
3233
+ }
3234
+
3235
+ // src/renderers/tailwind.ts
3236
+ init_utils();
3237
+ var TAILWIND_NAMESPACE_MAP = {
3238
+ color: "color",
3239
+ dimension: "spacing",
3240
+ fontFamily: "font",
3241
+ fontWeight: "font-weight",
3242
+ duration: "duration",
3243
+ shadow: "shadow",
3244
+ number: "number",
3245
+ cubicBezier: "ease"
3246
+ };
3247
+ var TailwindRenderer = class {
3248
+ async format(context, options) {
3249
+ const opts = {
3250
+ preset: options?.preset ?? "bundle",
3251
+ includeImport: options?.includeImport ?? true,
3252
+ namespace: options?.namespace ?? "",
3253
+ minify: options?.minify ?? false,
3254
+ selector: options?.selector,
3255
+ mediaQuery: options?.mediaQuery,
3256
+ variantDeclarations: []
3257
+ };
3258
+ if (opts.preset === "bundle") {
3259
+ return await this.formatBundle(context, opts);
3260
+ }
3261
+ return await this.formatStandalone(context, opts);
3262
+ }
3263
+ /**
3264
+ * Format tokens as Tailwind v4 @theme CSS variables
3265
+ */
3266
+ async formatTokens(tokens, options) {
3267
+ const lines = [];
3268
+ const indent2 = options.minify ? "" : " ";
3269
+ const newline = options.minify ? "" : "\n";
3270
+ const space = options.minify ? "" : " ";
3271
+ if (options.includeImport) {
3272
+ lines.push(`@import "tailwindcss";${newline}`);
3273
+ }
3274
+ if (options.variantDeclarations.length > 0) {
3275
+ if (options.includeImport) {
3276
+ lines.push(newline);
3277
+ }
3278
+ for (const declaration of options.variantDeclarations) {
3279
+ lines.push(`${declaration}${newline}`);
3280
+ }
3281
+ }
3282
+ if (options.includeImport || options.variantDeclarations.length > 0) {
3283
+ lines.push(newline);
3284
+ }
3285
+ const themeDirective = options.namespace ? `@theme namespace(${options.namespace})` : "@theme";
3286
+ lines.push(`${themeDirective}${space}{${newline}`);
3287
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3288
+ const varName = this.buildVariableName(token);
3289
+ const varValue = this.formatValue(token);
3290
+ lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
3291
+ }
3292
+ lines.push(`}${newline}`);
3293
+ const cssString = lines.join("");
3294
+ return options.minify ? cssString : await this.formatWithPrettier(cssString);
3295
+ }
3296
+ /**
3297
+ * Format tokens as plain CSS custom property overrides inside a selector block.
3298
+ * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
3299
+ */
3300
+ async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
3301
+ const indent2 = minify ? "" : " ";
3302
+ const newline = minify ? "" : "\n";
3303
+ const space = minify ? "" : " ";
3304
+ const hasMediaQuery = mediaQuery !== "";
3305
+ const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
3306
+ const lines = [];
3307
+ if (hasMediaQuery) {
3308
+ lines.push(`@media ${mediaQuery}${space}{${newline}`);
3309
+ lines.push(`${indent2}${selector}${space}{${newline}`);
3310
+ } else {
3311
+ lines.push(`${selector}${space}{${newline}`);
3312
+ }
3313
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3314
+ const varName = this.buildVariableName(token);
3315
+ const varValue = this.formatValue(token);
3316
+ lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
3317
+ }
3318
+ if (hasMediaQuery) {
3319
+ lines.push(`${indent2}}${newline}`);
3320
+ lines.push(`}${newline}`);
3321
+ } else {
3322
+ lines.push(`}${newline}`);
3323
+ }
3324
+ return lines.join("");
3325
+ }
3326
+ buildVariableName(token) {
3327
+ const prefix = TAILWIND_NAMESPACE_MAP[token.$type ?? ""];
3328
+ if (!prefix) {
3329
+ return token.name;
3330
+ }
3331
+ const nameLower = token.name.toLowerCase();
3332
+ const prefixLower = prefix.toLowerCase();
3333
+ if (nameLower.startsWith(`${prefixLower}-`) || nameLower.startsWith(`${prefixLower}.`)) {
3334
+ return token.name;
3335
+ }
3336
+ return `${prefix}-${token.name}`;
3337
+ }
3338
+ formatValue(token) {
3339
+ const value = token.$value;
3340
+ if (token.$type === "color" && isColorObject(value)) {
3341
+ return colorObjectToHex(value);
3342
+ }
3343
+ if (token.$type === "dimension" && isDimensionObject(value)) {
3344
+ return dimensionObjectToString(value);
3345
+ }
3346
+ if (token.$type === "duration" && this.isDurationObject(value)) {
3347
+ return `${value.value}${value.unit}`;
3348
+ }
3349
+ if (token.$type === "fontFamily") {
3350
+ if (Array.isArray(value)) {
3351
+ return value.map((v) => typeof v === "string" && v.includes(" ") ? `"${v}"` : v).join(", ");
3352
+ }
3353
+ return typeof value === "string" ? value : String(value);
3354
+ }
3355
+ if (token.$type === "shadow") {
3356
+ return this.formatShadowValue(value);
3357
+ }
3358
+ if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
3359
+ return `cubic-bezier(${value.join(", ")})`;
3360
+ }
3361
+ if (typeof value === "string") {
3362
+ return value;
3363
+ }
3364
+ if (typeof value === "number") {
3365
+ return String(value);
3366
+ }
3367
+ return String(value);
3368
+ }
3369
+ formatShadowValue(value) {
3370
+ if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object") {
3371
+ return value.map((s) => this.formatSingleShadow(s)).join(", ");
3372
+ }
3373
+ if (typeof value === "object" && value !== null) {
3374
+ return this.formatSingleShadow(value);
3375
+ }
3376
+ return String(value);
3377
+ }
3378
+ formatSingleShadow(shadow) {
3379
+ const parts = [];
3380
+ if (shadow.inset === true) {
3381
+ parts.push("inset");
3382
+ }
3383
+ if (isDimensionObject(shadow.offsetX)) {
3384
+ parts.push(dimensionObjectToString(shadow.offsetX));
3385
+ }
3386
+ if (isDimensionObject(shadow.offsetY)) {
3387
+ parts.push(dimensionObjectToString(shadow.offsetY));
3388
+ }
3389
+ if (isDimensionObject(shadow.blur)) {
3390
+ parts.push(dimensionObjectToString(shadow.blur));
3391
+ }
3392
+ if (shadow.spread != null && isDimensionObject(shadow.spread)) {
3393
+ parts.push(dimensionObjectToString(shadow.spread));
3394
+ }
3395
+ if (isColorObject(shadow.color)) {
3396
+ parts.push(colorObjectToHex(shadow.color));
3397
+ } else if (shadow.color != null) {
3398
+ parts.push(String(shadow.color));
3399
+ }
3400
+ return parts.join(" ");
3401
+ }
3402
+ isDurationObject(value) {
3403
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
3404
+ }
3405
+ async formatWithPrettier(css2) {
3406
+ try {
3407
+ return await prettier__default.default.format(css2, {
3408
+ parser: "css",
3409
+ printWidth: 80,
3410
+ tabWidth: 2,
3411
+ useTabs: false
3412
+ });
3413
+ } catch {
3414
+ return css2;
3415
+ }
3416
+ }
3417
+ async formatBundle(context, options) {
3418
+ const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3419
+ tokens,
3420
+ modifierInputs,
3421
+ isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3422
+ }));
3423
+ return await bundleAsTailwind(
3424
+ bundleData,
3425
+ options,
3426
+ async (tokens, opts) => await this.formatTokens(tokens, opts),
3427
+ async (tokens, selector, mediaQuery, minify) => await this.formatOverrideBlock(tokens, selector, mediaQuery, minify)
3428
+ );
3429
+ }
3430
+ async formatStandalone(context, options) {
3431
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
3432
+ if (!context.output.file && requiresFile) {
3433
+ throw new ConfigurationError(
3434
+ `Output "${context.output.name}": file is required for standalone Tailwind output`
3435
+ );
3436
+ }
3437
+ const files = {};
3438
+ for (const { tokens, modifierInputs } of context.permutations) {
3439
+ const processedTokens = stripInternalMetadata(tokens);
3440
+ const content = await this.formatTokens(processedTokens, options);
3441
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
3442
+ outputName: context.output.name,
3443
+ extension: "css",
3444
+ modifierInputs,
3445
+ resolver: context.resolver,
3446
+ defaults: context.meta.defaults
3447
+ });
3448
+ files[fileName] = content;
3449
+ }
3450
+ return outputTree(files);
3451
+ }
3452
+ isBasePermutation(modifierInputs, defaults) {
3453
+ return Object.entries(defaults).every(
3454
+ ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
3455
+ );
3456
+ }
3457
+ };
3458
+ function tailwindRenderer() {
3459
+ const rendererInstance = new TailwindRenderer();
3460
+ return {
3461
+ format: (context, options) => rendererInstance.format(
3462
+ context,
3463
+ options ?? context.output.options
3464
+ )
3465
+ };
3466
+ }
3467
+
1761
3468
  // src/builders.ts
1762
3469
  function css(config) {
1763
3470
  const {
@@ -1819,14 +3526,76 @@ function js(config) {
1819
3526
  hooks
1820
3527
  };
1821
3528
  }
3529
+ function tailwind(config) {
3530
+ const { name, file, transforms, filters, hooks, preset = "bundle", ...rendererOptions } = config;
3531
+ return {
3532
+ name,
3533
+ file,
3534
+ renderer: tailwindRenderer(),
3535
+ options: { preset, ...rendererOptions },
3536
+ transforms,
3537
+ filters,
3538
+ hooks
3539
+ };
3540
+ }
3541
+ function ios(config) {
3542
+ const {
3543
+ name,
3544
+ file,
3545
+ transforms,
3546
+ filters,
3547
+ hooks,
3548
+ preset = "standalone",
3549
+ ...rendererOptions
3550
+ } = config;
3551
+ return {
3552
+ name,
3553
+ file,
3554
+ renderer: iosRenderer(),
3555
+ options: { preset, ...rendererOptions },
3556
+ transforms,
3557
+ filters,
3558
+ hooks
3559
+ };
3560
+ }
3561
+ function android(config) {
3562
+ const {
3563
+ name,
3564
+ file,
3565
+ transforms,
3566
+ filters,
3567
+ hooks,
3568
+ preset = "standalone",
3569
+ ...rendererOptions
3570
+ } = config;
3571
+ return {
3572
+ name,
3573
+ file,
3574
+ renderer: androidRenderer(),
3575
+ options: { preset, ...rendererOptions },
3576
+ transforms,
3577
+ filters,
3578
+ hooks
3579
+ };
3580
+ }
3581
+ /**
3582
+ * @license MIT
3583
+ * Copyright (c) 2025-present Dispersa Contributors
3584
+ *
3585
+ * This source code is licensed under the MIT license found in the
3586
+ * LICENSE file in the root directory of this source tree.
3587
+ */
1822
3588
  /**
1823
3589
  * @license
1824
3590
  * Copyright (c) 2025 Dispersa Contributors
1825
3591
  * SPDX-License-Identifier: MIT
1826
3592
  */
1827
3593
 
3594
+ exports.android = android;
1828
3595
  exports.css = css;
3596
+ exports.ios = ios;
1829
3597
  exports.js = js;
1830
3598
  exports.json = json;
3599
+ exports.tailwind = tailwind;
1831
3600
  //# sourceMappingURL=builders.cjs.map
1832
3601
  //# sourceMappingURL=builders.cjs.map