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