dispersa 0.1.3 → 0.3.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.
Files changed (53) hide show
  1. package/README.md +65 -23
  2. package/dist/builders.cjs +1923 -47
  3. package/dist/builders.cjs.map +1 -1
  4. package/dist/builders.d.cts +155 -6
  5. package/dist/builders.d.ts +155 -6
  6. package/dist/builders.js +1922 -49
  7. package/dist/builders.js.map +1 -1
  8. package/dist/cli/cli.d.ts +11 -0
  9. package/dist/cli/cli.js +201 -0
  10. package/dist/cli/cli.js.map +1 -0
  11. package/dist/cli/config.d.ts +8 -0
  12. package/dist/cli/config.js +8 -0
  13. package/dist/cli/config.js.map +1 -0
  14. package/dist/cli/index.d.ts +1 -0
  15. package/dist/cli/index.js +203 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/filters.cjs +8 -7
  18. package/dist/filters.cjs.map +1 -1
  19. package/dist/filters.d.cts +13 -16
  20. package/dist/filters.d.ts +13 -16
  21. package/dist/filters.js +8 -7
  22. package/dist/filters.js.map +1 -1
  23. package/dist/{index-CPB9Ea9U.d.ts → index-BkvV7Z54.d.cts} +183 -60
  24. package/dist/{index-DKf9WMQG.d.cts → index-DJ_UHSQG.d.ts} +183 -60
  25. package/dist/index.cjs +2121 -226
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +8 -8
  28. package/dist/index.d.ts +8 -8
  29. package/dist/index.js +2120 -228
  30. package/dist/index.js.map +1 -1
  31. package/dist/preprocessors.cjs.map +1 -1
  32. package/dist/preprocessors.d.cts +2 -2
  33. package/dist/preprocessors.d.ts +2 -2
  34. package/dist/preprocessors.js.map +1 -1
  35. package/dist/renderers.cjs.map +1 -1
  36. package/dist/renderers.d.cts +6 -6
  37. package/dist/renderers.d.ts +6 -6
  38. package/dist/renderers.js.map +1 -1
  39. package/dist/transforms.cjs +5 -5
  40. package/dist/transforms.cjs.map +1 -1
  41. package/dist/transforms.d.cts +2 -2
  42. package/dist/transforms.d.ts +2 -2
  43. package/dist/transforms.js +5 -5
  44. package/dist/transforms.js.map +1 -1
  45. package/dist/{types-DM5faYUn.d.cts → types-BAv39mum.d.cts} +1 -1
  46. package/dist/{types-C1GpiJ6q.d.ts → types-Bc0kA7De.d.cts} +10 -10
  47. package/dist/{types-C1GpiJ6q.d.cts → types-Bc0kA7De.d.ts} +10 -10
  48. package/dist/{types-Cl-1UYGD.d.ts → types-BzNcG-rI.d.ts} +1 -1
  49. package/dist/{types-DJH6_4U9.d.ts → types-CZb19kiq.d.ts} +1 -1
  50. package/dist/{types-DbufGPrb.d.cts → types-CussyWwe.d.cts} +1 -1
  51. package/dist/{types-DdPWYkgh.d.ts → types-CzHa7YkW.d.ts} +1 -1
  52. package/dist/{types-BDY1xBmD.d.cts → types-DWKq-eJj.d.cts} +1 -1
  53. package/package.json +18 -1
package/dist/builders.cjs CHANGED
@@ -210,32 +210,55 @@ function filterTokensBySource(tokens, expectedSource) {
210
210
  }
211
211
  return filtered;
212
212
  }
213
- function interpolatePattern(pattern, modifierInputs, modifierName, context) {
214
- let result = pattern;
215
- if (modifierName !== void 0) {
216
- result = result.replace(/\{modifierName\}/g, modifierName);
213
+ function filterTokensFromSets(tokens) {
214
+ const filtered = {};
215
+ for (const [name, token] of Object.entries(tokens)) {
216
+ const hasModifierSource = typeof token._sourceModifier === "string" && token._sourceModifier !== "";
217
+ if (!hasModifierSource) {
218
+ filtered[name] = token;
219
+ }
220
+ }
221
+ return filtered;
222
+ }
223
+ function resolveBaseFileName(fileName, defaults) {
224
+ if (typeof fileName === "function") {
225
+ return fileName({ ...defaults, _base: "true" });
226
+ }
227
+ if (/\{.+?\}/.test(fileName)) {
228
+ const baseInputs = Object.fromEntries(Object.keys(defaults).map((k) => [k, "base"]));
229
+ return collapseBaseSegments(interpolatePattern(fileName, baseInputs));
217
230
  }
218
- if (context !== void 0) {
219
- result = result.replace(/\{context\}/g, context);
231
+ const extMatch = fileName.match(/(\.[^.]+)$/);
232
+ const extension = extMatch ? extMatch[1] : "";
233
+ const baseName = extension ? fileName.slice(0, -extension.length) : fileName;
234
+ return `${baseName}-base${extension}`;
235
+ }
236
+ function collapseBaseSegments(value) {
237
+ let result = value;
238
+ let previous = "";
239
+ while (result !== previous) {
240
+ previous = result;
241
+ result = result.replace(/\bbase([/-])base\b/, "base");
220
242
  }
243
+ return result;
244
+ }
245
+ function interpolatePattern(pattern, modifierInputs) {
246
+ let result = pattern;
221
247
  for (const [key, value] of Object.entries(modifierInputs)) {
222
248
  result = result.replaceAll(`{${key}}`, value);
223
249
  }
224
250
  return result;
225
251
  }
226
- function resolveFileName(fileName, modifierInputs, modifierName, context) {
252
+ function resolveFileName(fileName, modifierInputs) {
227
253
  if (typeof fileName === "function") {
228
254
  return fileName(modifierInputs);
229
255
  }
230
256
  if (/\{.+?\}/.test(fileName)) {
231
- return interpolatePattern(fileName, modifierInputs, modifierName, context);
257
+ return interpolatePattern(fileName, modifierInputs);
232
258
  }
233
259
  const extMatch = fileName.match(/(\.[^.]+)$/);
234
260
  const extension = extMatch ? extMatch[1] : "";
235
261
  const baseName = extension ? fileName.slice(0, -extension.length) : fileName;
236
- if (modifierName !== void 0 && context !== void 0) {
237
- return `${baseName}-${modifierName}-${context}${extension}`;
238
- }
239
262
  const modifierSuffix = Object.entries(modifierInputs).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => `${key}-${value}`).join("-");
240
263
  if (modifierSuffix) {
241
264
  return `${baseName}-${modifierSuffix}${extension}`;
@@ -460,7 +483,7 @@ function colorObjectToHex(color) {
460
483
  return culori.formatHex(culoriColor);
461
484
  }
462
485
 
463
- // src/lib/processing/processors/transforms/built-in/dimension-converter.ts
486
+ // src/processing/processors/transforms/built-in/dimension-converter.ts
464
487
  function isDimensionObject(value) {
465
488
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
466
489
  }
@@ -468,6 +491,757 @@ function dimensionObjectToString(dimension) {
468
491
  return `${dimension.value}${dimension.unit}`;
469
492
  }
470
493
 
494
+ // src/renderers/android.ts
495
+ init_errors();
496
+ init_token_utils();
497
+ init_utils();
498
+
499
+ // src/renderers/output-tree.ts
500
+ var outputTree = (files) => {
501
+ return {
502
+ kind: "outputTree",
503
+ files
504
+ };
505
+ };
506
+
507
+ // src/renderers/android.ts
508
+ var toSRGB = culori.converter("rgb");
509
+ var toP3 = culori.converter("p3");
510
+ var KOTLIN_KEYWORDS = /* @__PURE__ */ new Set([
511
+ "val",
512
+ "var",
513
+ "fun",
514
+ "class",
515
+ "object",
516
+ "when",
517
+ "is",
518
+ "in",
519
+ "return",
520
+ "break",
521
+ "continue",
522
+ "do",
523
+ "while",
524
+ "for",
525
+ "if",
526
+ "else",
527
+ "try",
528
+ "catch",
529
+ "throw",
530
+ "as",
531
+ "this",
532
+ "super",
533
+ "null",
534
+ "true",
535
+ "false"
536
+ ]);
537
+ var KOTLIN_TYPE_GROUP_MAP = {
538
+ color: "Colors",
539
+ dimension: "Spacing",
540
+ fontFamily: "Fonts",
541
+ fontWeight: "FontWeights",
542
+ duration: "Durations",
543
+ shadow: "Shadows",
544
+ typography: "Typography",
545
+ number: "Numbers",
546
+ cubicBezier: "Animations",
547
+ border: "Borders"
548
+ };
549
+ function resolveColorFormat(format) {
550
+ if (format === "argb_floats" || format === "argb_float") {
551
+ return "argb_float";
552
+ }
553
+ return "argb_hex";
554
+ }
555
+ function indent(width, level) {
556
+ return " ".repeat(width * level);
557
+ }
558
+ function escapeKotlinString(str) {
559
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
560
+ }
561
+ function escapeKDoc(str) {
562
+ return str.replace(/\*\//g, "* /").replace(/\r?\n/g, " ").trim();
563
+ }
564
+ function formatKotlinNumber(value) {
565
+ return Number.isInteger(value) ? `${value}.0` : String(value);
566
+ }
567
+ function roundComponent(value) {
568
+ return Math.round(value * 1e3) / 1e3;
569
+ }
570
+ function toResourceName(family) {
571
+ return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
572
+ }
573
+ function toPascalCase(name) {
574
+ const pascal = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
575
+ const result = pascal.charAt(0).toUpperCase() + pascal.slice(1);
576
+ if (/^\d/.test(result)) {
577
+ return `_${result}`;
578
+ }
579
+ return KOTLIN_KEYWORDS.has(result.charAt(0).toLowerCase() + result.slice(1)) ? `\`${result}\`` : result;
580
+ }
581
+ function toKotlinIdentifier(name) {
582
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
583
+ const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
584
+ if (/^\d/.test(identifier)) {
585
+ return `_${identifier}`;
586
+ }
587
+ return KOTLIN_KEYWORDS.has(identifier) ? `\`${identifier}\`` : identifier;
588
+ }
589
+ var AndroidRenderer = class {
590
+ async format(context, options) {
591
+ if (!options?.packageName) {
592
+ throw new ConfigurationError(
593
+ `Output "${context.output.name}": packageName is required for Android output`
594
+ );
595
+ }
596
+ const opts = {
597
+ preset: options?.preset ?? "standalone",
598
+ packageName: options.packageName,
599
+ objectName: options?.objectName ?? "DesignTokens",
600
+ colorFormat: resolveColorFormat(options?.colorFormat),
601
+ colorSpace: options?.colorSpace ?? "sRGB",
602
+ structure: options?.structure ?? "nested",
603
+ visibility: options?.visibility,
604
+ indent: options?.indent ?? 4
605
+ };
606
+ if (opts.preset === "bundle") {
607
+ return await this.formatBundle(context, opts);
608
+ }
609
+ return await this.formatStandalone(context, opts);
610
+ }
611
+ // -----------------------------------------------------------------------
612
+ // Token tree (nested mode)
613
+ // -----------------------------------------------------------------------
614
+ buildTokenTree(tokens) {
615
+ const root = { children: /* @__PURE__ */ new Map() };
616
+ for (const [, token] of getSortedTokenEntries(tokens)) {
617
+ let current = root;
618
+ const segments = token.path;
619
+ for (let i = 0; i < segments.length - 1; i++) {
620
+ const seg = segments[i];
621
+ if (!current.children.has(seg)) {
622
+ current.children.set(seg, { children: /* @__PURE__ */ new Map() });
623
+ }
624
+ current = current.children.get(seg);
625
+ }
626
+ const leafName = segments[segments.length - 1] ?? token.name;
627
+ const leaf = current.children.get(leafName) ?? { children: /* @__PURE__ */ new Map() };
628
+ leaf.token = token;
629
+ current.children.set(leafName, leaf);
630
+ }
631
+ return root;
632
+ }
633
+ // -----------------------------------------------------------------------
634
+ // Flat structure grouping
635
+ // -----------------------------------------------------------------------
636
+ groupTokensByType(tokens) {
637
+ const groupMap = /* @__PURE__ */ new Map();
638
+ for (const [, token] of getSortedTokenEntries(tokens)) {
639
+ const groupName = KOTLIN_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
640
+ const existing = groupMap.get(groupName) ?? [];
641
+ existing.push(token);
642
+ groupMap.set(groupName, existing);
643
+ }
644
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
645
+ name,
646
+ tokens: groupTokens
647
+ }));
648
+ }
649
+ /**
650
+ * Builds a flattened camelCase name from a token's path, stripping the
651
+ * type prefix segment (which is already represented by the group object).
652
+ */
653
+ buildFlatKotlinName(token) {
654
+ const path = token.path;
655
+ const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
656
+ const joined = withoutTypePrefix.join("_");
657
+ return toKotlinIdentifier(joined);
658
+ }
659
+ // -----------------------------------------------------------------------
660
+ // Rendering
661
+ // -----------------------------------------------------------------------
662
+ formatTokens(tokens, options) {
663
+ if (options.structure === "flat") {
664
+ return this.formatAsFlat(tokens, options);
665
+ }
666
+ return this.formatAsNested(tokens, options);
667
+ }
668
+ formatAsNested(tokens, options) {
669
+ const tree = this.buildTokenTree(tokens);
670
+ const tokenTypes = /* @__PURE__ */ new Set();
671
+ this.collectTokenTypes(tree, tokenTypes);
672
+ return this.buildFile(tokenTypes, options, (lines, vis) => {
673
+ lines.push(`@Suppress("unused")`);
674
+ lines.push(`${vis}object ${options.objectName} {`);
675
+ this.renderTreeChildren(lines, tree, 1, options);
676
+ lines.push("}");
677
+ });
678
+ }
679
+ formatAsFlat(tokens, options) {
680
+ const groups = this.groupTokensByType(tokens);
681
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
682
+ return this.buildFile(tokenTypes, options, (lines, vis) => {
683
+ lines.push(`@Suppress("unused")`);
684
+ lines.push(`${vis}object ${options.objectName} {`);
685
+ this.renderFlatGroups(lines, groups, 1, options);
686
+ lines.push("}");
687
+ });
688
+ }
689
+ /**
690
+ * Shared file preamble: header, package, imports, optional ShadowToken class.
691
+ * The `renderBody` callback appends the main object(s) to `lines`.
692
+ */
693
+ buildFile(tokenTypes, options, renderBody) {
694
+ const imports = this.collectImports(tokenTypes, options);
695
+ const vis = options.visibility ? `${options.visibility} ` : "";
696
+ const lines = [];
697
+ lines.push(this.buildFileHeader());
698
+ lines.push("");
699
+ lines.push(`package ${options.packageName}`);
700
+ lines.push("");
701
+ for (const imp of imports) {
702
+ lines.push(`import ${imp}`);
703
+ }
704
+ if (imports.length > 0) {
705
+ lines.push("");
706
+ }
707
+ if (tokenTypes.has("shadow")) {
708
+ lines.push(...this.buildShadowTokenClass(vis, options));
709
+ lines.push("");
710
+ }
711
+ renderBody(lines, vis);
712
+ lines.push("");
713
+ return lines.join("\n");
714
+ }
715
+ renderFlatGroups(lines, groups, baseDepth, options) {
716
+ const vis = options.visibility ? `${options.visibility} ` : "";
717
+ const groupIndent = indent(options.indent, baseDepth);
718
+ const valIndent = indent(options.indent, baseDepth + 1);
719
+ for (const group of groups) {
720
+ lines.push(`${groupIndent}${vis}object ${group.name} {`);
721
+ for (const token of group.tokens) {
722
+ const kotlinName = this.buildFlatKotlinName(token);
723
+ const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
724
+ const annotation = this.typeAnnotationSuffix(token);
725
+ if (token.$description) {
726
+ lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
727
+ }
728
+ lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
729
+ }
730
+ lines.push(`${groupIndent}}`);
731
+ lines.push("");
732
+ }
733
+ }
734
+ renderTreeChildren(lines, node, depth, options) {
735
+ const vis = options.visibility ? `${options.visibility} ` : "";
736
+ const pad = indent(options.indent, depth);
737
+ const entries = Array.from(node.children.entries());
738
+ for (let idx = 0; idx < entries.length; idx++) {
739
+ const [key, child] = entries[idx];
740
+ if (child.token && child.children.size === 0) {
741
+ this.renderLeaf(lines, key, child.token, depth, options);
742
+ } else if (child.children.size > 0 && !child.token) {
743
+ const objectName = toPascalCase(key);
744
+ lines.push(`${pad}${vis}object ${objectName} {`);
745
+ this.renderTreeChildren(lines, child, depth + 1, options);
746
+ lines.push(`${pad}}`);
747
+ if (idx < entries.length - 1) {
748
+ lines.push("");
749
+ }
750
+ } else {
751
+ this.renderLeaf(lines, key, child.token, depth, options);
752
+ this.renderTreeChildren(lines, child, depth, options);
753
+ }
754
+ }
755
+ }
756
+ renderLeaf(lines, key, token, depth, options) {
757
+ const vis = options.visibility ? `${options.visibility} ` : "";
758
+ const pad = indent(options.indent, depth);
759
+ const kotlinName = toKotlinIdentifier(key);
760
+ const kotlinValue = this.formatKotlinValue(token, options, depth);
761
+ const annotation = this.typeAnnotationSuffix(token);
762
+ if (token.$description) {
763
+ lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
764
+ }
765
+ lines.push(`${pad}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
766
+ }
767
+ buildFileHeader() {
768
+ return [
769
+ "// Generated by Dispersa - do not edit manually",
770
+ "// https://github.com/timges/dispersa"
771
+ ].join("\n");
772
+ }
773
+ // -----------------------------------------------------------------------
774
+ // Shadow data class
775
+ // -----------------------------------------------------------------------
776
+ buildShadowTokenClass(vis, options) {
777
+ const i1 = indent(options.indent, 1);
778
+ return [
779
+ "@Immutable",
780
+ `${vis}data class ShadowToken(`,
781
+ `${i1}val color: Color,`,
782
+ `${i1}val elevation: Dp,`,
783
+ `${i1}val offsetX: Dp,`,
784
+ `${i1}val offsetY: Dp,`,
785
+ ")"
786
+ ];
787
+ }
788
+ // -----------------------------------------------------------------------
789
+ // Imports (tree-shaken)
790
+ // -----------------------------------------------------------------------
791
+ collectImports(tokenTypes, options) {
792
+ const imports = /* @__PURE__ */ new Set();
793
+ const ns = "androidx.compose";
794
+ const hasColors = tokenTypes.has("color") || tokenTypes.has("shadow") || tokenTypes.has("border");
795
+ if (hasColors) {
796
+ imports.add(`${ns}.ui.graphics.Color`);
797
+ }
798
+ if (tokenTypes.has("dimension") || tokenTypes.has("shadow") || tokenTypes.has("border")) {
799
+ imports.add(`${ns}.ui.unit.Dp`);
800
+ imports.add(`${ns}.ui.unit.dp`);
801
+ }
802
+ if (tokenTypes.has("typography") || tokenTypes.has("fontFamily")) {
803
+ imports.add(`${ns}.ui.text.TextStyle`);
804
+ imports.add(`${ns}.ui.unit.sp`);
805
+ }
806
+ if (tokenTypes.has("typography") || tokenTypes.has("fontWeight")) {
807
+ imports.add(`${ns}.ui.text.font.FontWeight`);
808
+ }
809
+ if (tokenTypes.has("fontFamily")) {
810
+ imports.add(`${ns}.ui.text.font.FontFamily`);
811
+ }
812
+ if (tokenTypes.has("duration")) {
813
+ imports.add("kotlin.time.Duration");
814
+ imports.add("kotlin.time.Duration.Companion.milliseconds");
815
+ imports.add("kotlin.time.Duration.Companion.seconds");
816
+ }
817
+ if (tokenTypes.has("cubicBezier")) {
818
+ imports.add(`${ns}.animation.core.CubicBezierEasing`);
819
+ }
820
+ if (tokenTypes.has("shadow")) {
821
+ imports.add(`${ns}.runtime.Immutable`);
822
+ }
823
+ if (tokenTypes.has("border")) {
824
+ imports.add(`${ns}.foundation.BorderStroke`);
825
+ }
826
+ if (options.colorSpace === "displayP3" && hasColors) {
827
+ imports.add(`${ns}.ui.graphics.colorspace.ColorSpaces`);
828
+ }
829
+ return Array.from(imports).sort();
830
+ }
831
+ collectTokenTypes(node, types) {
832
+ if (node.token?.$type) {
833
+ types.add(node.token.$type);
834
+ }
835
+ for (const child of node.children.values()) {
836
+ this.collectTokenTypes(child, types);
837
+ }
838
+ }
839
+ collectTokenTypesFromEntries(tokens) {
840
+ const types = /* @__PURE__ */ new Set();
841
+ for (const [, token] of Object.entries(tokens)) {
842
+ if (token.$type) {
843
+ types.add(token.$type);
844
+ }
845
+ }
846
+ return types;
847
+ }
848
+ // -----------------------------------------------------------------------
849
+ // Type annotations
850
+ // -----------------------------------------------------------------------
851
+ getTypeAnnotation(token) {
852
+ switch (token.$type) {
853
+ case "color":
854
+ return "Color";
855
+ case "dimension":
856
+ return "Dp";
857
+ case "fontFamily":
858
+ return "FontFamily";
859
+ case "fontWeight":
860
+ return "FontWeight";
861
+ case "duration":
862
+ return "Duration";
863
+ case "shadow":
864
+ return "ShadowToken";
865
+ case "cubicBezier":
866
+ return "CubicBezierEasing";
867
+ case "number":
868
+ return "Double";
869
+ case "typography":
870
+ return "TextStyle";
871
+ case "border":
872
+ return "BorderStroke";
873
+ default: {
874
+ const value = token.$value;
875
+ if (typeof value === "string") {
876
+ return "String";
877
+ }
878
+ if (typeof value === "boolean") {
879
+ return "Boolean";
880
+ }
881
+ if (typeof value === "number") {
882
+ return "Double";
883
+ }
884
+ return void 0;
885
+ }
886
+ }
887
+ }
888
+ typeAnnotationSuffix(token) {
889
+ const type = this.getTypeAnnotation(token);
890
+ return type ? `: ${type}` : "";
891
+ }
892
+ // -----------------------------------------------------------------------
893
+ // Value formatting
894
+ // -----------------------------------------------------------------------
895
+ formatKotlinValue(token, options, depth) {
896
+ const value = token.$value;
897
+ if (token.$type === "color") {
898
+ return this.formatColorValue(value, options);
899
+ }
900
+ if (token.$type === "dimension") {
901
+ return this.formatDimensionValue(value);
902
+ }
903
+ if (token.$type === "fontFamily") {
904
+ return this.formatFontFamilyValue(value);
905
+ }
906
+ if (token.$type === "fontWeight") {
907
+ return this.formatFontWeightValue(value);
908
+ }
909
+ if (token.$type === "duration") {
910
+ return this.formatDurationValue(value);
911
+ }
912
+ if (token.$type === "shadow") {
913
+ return this.formatShadowValue(value, options, depth);
914
+ }
915
+ if (token.$type === "typography") {
916
+ return this.formatTypographyValue(value, options, depth);
917
+ }
918
+ if (token.$type === "border") {
919
+ return this.formatBorderValue(value, options);
920
+ }
921
+ if (token.$type === "number") {
922
+ return typeof value === "number" ? formatKotlinNumber(value) : String(value);
923
+ }
924
+ if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
925
+ return `CubicBezierEasing(${value[0]}f, ${value[1]}f, ${value[2]}f, ${value[3]}f)`;
926
+ }
927
+ if (typeof value === "string") {
928
+ return `"${escapeKotlinString(value)}"`;
929
+ }
930
+ if (typeof value === "number") {
931
+ return formatKotlinNumber(value);
932
+ }
933
+ if (typeof value === "boolean") {
934
+ return value ? "true" : "false";
935
+ }
936
+ return `"${escapeKotlinString(String(value))}"`;
937
+ }
938
+ formatColorValue(value, options) {
939
+ if (!isColorObject(value)) {
940
+ if (typeof value === "string") {
941
+ const hex = value.replace("#", "");
942
+ if (/^[0-9a-fA-F]{6,8}$/.test(hex)) {
943
+ const argb = hex.length === 8 ? hex : `FF${hex}`;
944
+ return `Color(0x${argb.toUpperCase()})`;
945
+ }
946
+ }
947
+ return "Color.Unspecified";
948
+ }
949
+ const colorObj = value;
950
+ const alpha = colorObj.alpha ?? 1;
951
+ if (options.colorFormat === "argb_float" || options.colorSpace === "displayP3") {
952
+ return this.formatFloatColor(colorObj, alpha, options);
953
+ }
954
+ return this.formatHexColor(colorObj, alpha);
955
+ }
956
+ formatFloatColor(colorObj, alpha, options) {
957
+ if (options.colorSpace === "displayP3") {
958
+ const p3 = toP3(dtcgObjectToCulori(colorObj));
959
+ const r2 = roundComponent(p3?.r ?? 0);
960
+ const g2 = roundComponent(p3?.g ?? 0);
961
+ const b2 = roundComponent(p3?.b ?? 0);
962
+ return `Color(${r2}f, ${g2}f, ${b2}f, ${roundComponent(alpha)}f, ColorSpaces.DisplayP3)`;
963
+ }
964
+ const rgb = toSRGB(dtcgObjectToCulori(colorObj));
965
+ const r = roundComponent(rgb?.r ?? 0);
966
+ const g = roundComponent(rgb?.g ?? 0);
967
+ const b = roundComponent(rgb?.b ?? 0);
968
+ return `Color(${r}f, ${g}f, ${b}f, ${roundComponent(alpha)}f)`;
969
+ }
970
+ formatHexColor(colorObj, alpha) {
971
+ const hex = colorObjectToHex(colorObj);
972
+ const hexClean = hex.replace("#", "");
973
+ if (hexClean.length === 8) {
974
+ const rrggbb = hexClean.slice(0, 6);
975
+ const aa = hexClean.slice(6, 8);
976
+ return `Color(0x${aa.toUpperCase()}${rrggbb.toUpperCase()})`;
977
+ }
978
+ const alphaHex = alpha < 1 ? Math.round(alpha * 255).toString(16).padStart(2, "0").toUpperCase() : "FF";
979
+ return `Color(0x${alphaHex}${hexClean.toUpperCase()})`;
980
+ }
981
+ formatDimensionValue(value) {
982
+ if (isDimensionObject(value)) {
983
+ const dim = value;
984
+ const dpValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
985
+ return `${dpValue}.dp`;
986
+ }
987
+ return typeof value === "number" ? `${value}.dp` : `0.dp`;
988
+ }
989
+ formatFontFamilyValue(value) {
990
+ if (Array.isArray(value)) {
991
+ const primary = value[0];
992
+ if (typeof primary === "string") {
993
+ return this.mapKotlinFontFamily(primary);
994
+ }
995
+ return "FontFamily.Default";
996
+ }
997
+ return typeof value === "string" ? this.mapKotlinFontFamily(value) : "FontFamily.Default";
998
+ }
999
+ mapKotlinFontFamily(family) {
1000
+ const normalized = family.toLowerCase().replace(/['"]/g, "").trim();
1001
+ const builtIn = {
1002
+ "sans-serif": "FontFamily.SansSerif",
1003
+ serif: "FontFamily.Serif",
1004
+ monospace: "FontFamily.Monospace",
1005
+ cursive: "FontFamily.Cursive"
1006
+ };
1007
+ return builtIn[normalized] ?? `FontFamily.Default // TODO: load "${family}" via Font(R.font.${toResourceName(family)})`;
1008
+ }
1009
+ formatFontWeightValue(value) {
1010
+ if (typeof value === "number") {
1011
+ return this.numericFontWeight(value);
1012
+ }
1013
+ if (typeof value === "string") {
1014
+ return this.namedFontWeight(value) ?? "FontWeight.Normal";
1015
+ }
1016
+ return "FontWeight.Normal";
1017
+ }
1018
+ numericFontWeight(weight) {
1019
+ if (weight <= 100) {
1020
+ return "FontWeight.Thin";
1021
+ }
1022
+ if (weight <= 200) {
1023
+ return "FontWeight.ExtraLight";
1024
+ }
1025
+ if (weight <= 300) {
1026
+ return "FontWeight.Light";
1027
+ }
1028
+ if (weight <= 400) {
1029
+ return "FontWeight.Normal";
1030
+ }
1031
+ if (weight <= 500) {
1032
+ return "FontWeight.Medium";
1033
+ }
1034
+ if (weight <= 600) {
1035
+ return "FontWeight.SemiBold";
1036
+ }
1037
+ if (weight <= 700) {
1038
+ return "FontWeight.Bold";
1039
+ }
1040
+ if (weight <= 800) {
1041
+ return "FontWeight.ExtraBold";
1042
+ }
1043
+ return "FontWeight.Black";
1044
+ }
1045
+ namedFontWeight(name) {
1046
+ const map = {
1047
+ thin: "FontWeight.Thin",
1048
+ extralight: "FontWeight.ExtraLight",
1049
+ ultralight: "FontWeight.ExtraLight",
1050
+ light: "FontWeight.Light",
1051
+ regular: "FontWeight.Normal",
1052
+ normal: "FontWeight.Normal",
1053
+ medium: "FontWeight.Medium",
1054
+ semibold: "FontWeight.SemiBold",
1055
+ demibold: "FontWeight.SemiBold",
1056
+ bold: "FontWeight.Bold",
1057
+ extrabold: "FontWeight.ExtraBold",
1058
+ heavy: "FontWeight.ExtraBold",
1059
+ black: "FontWeight.Black",
1060
+ ultrabold: "FontWeight.Black"
1061
+ };
1062
+ return map[name.toLowerCase()];
1063
+ }
1064
+ formatDurationValue(value) {
1065
+ if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
1066
+ const dur = value;
1067
+ return dur.unit === "ms" ? `${dur.value}.milliseconds` : `${dur.value}.seconds`;
1068
+ }
1069
+ return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
1070
+ }
1071
+ formatShadowValue(value, options, depth) {
1072
+ if (Array.isArray(value) && value.length > 0) {
1073
+ return this.formatSingleShadow(value[0], options, depth);
1074
+ }
1075
+ if (typeof value === "object" && value !== null) {
1076
+ return this.formatSingleShadow(value, options, depth);
1077
+ }
1078
+ return "ShadowToken(color = Color.Unspecified, elevation = 0.dp, offsetX = 0.dp, offsetY = 0.dp)";
1079
+ }
1080
+ formatSingleShadow(shadow, options, depth) {
1081
+ const color = isColorObject(shadow.color) ? this.formatColorValue(shadow.color, options) : "Color.Black";
1082
+ const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
1083
+ const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
1084
+ const offsetY = isDimensionObject(shadow.offsetY) ? this.formatDimensionValue(shadow.offsetY) : "0.dp";
1085
+ const propIndent = indent(options.indent, depth + 1);
1086
+ const closeIndent = indent(options.indent, depth);
1087
+ return [
1088
+ "ShadowToken(",
1089
+ `${propIndent}color = ${color},`,
1090
+ `${propIndent}elevation = ${elevation},`,
1091
+ `${propIndent}offsetX = ${offsetX},`,
1092
+ `${propIndent}offsetY = ${offsetY},`,
1093
+ `${closeIndent})`
1094
+ ].join("\n");
1095
+ }
1096
+ formatBorderValue(value, options) {
1097
+ if (typeof value !== "object" || value === null) {
1098
+ return "BorderStroke(0.dp, Color.Unspecified)";
1099
+ }
1100
+ const border = value;
1101
+ const width = isDimensionObject(border.width) ? this.formatDimensionValue(border.width) : "0.dp";
1102
+ const color = isColorObject(border.color) ? this.formatColorValue(border.color, options) : "Color.Unspecified";
1103
+ return `BorderStroke(${width}, ${color})`;
1104
+ }
1105
+ formatTypographyValue(value, options, depth) {
1106
+ if (typeof value !== "object" || value === null) {
1107
+ return "TextStyle()";
1108
+ }
1109
+ const typo = value;
1110
+ const parts = [];
1111
+ if (isDimensionObject(typo.fontSize)) {
1112
+ const dim = typo.fontSize;
1113
+ const spValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
1114
+ parts.push(`fontSize = ${spValue}.sp`);
1115
+ }
1116
+ if (typo.fontWeight != null) {
1117
+ parts.push(`fontWeight = ${this.formatFontWeightValue(typo.fontWeight)}`);
1118
+ }
1119
+ if (typo.lineHeight != null && typeof typo.lineHeight === "number") {
1120
+ if (isDimensionObject(typo.fontSize)) {
1121
+ const dim = typo.fontSize;
1122
+ const spValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
1123
+ const lineHeightSp = Math.round(spValue * typo.lineHeight * 100) / 100;
1124
+ parts.push(`lineHeight = ${lineHeightSp}.sp`);
1125
+ }
1126
+ }
1127
+ if (isDimensionObject(typo.letterSpacing)) {
1128
+ const dim = typo.letterSpacing;
1129
+ const spValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
1130
+ parts.push(`letterSpacing = ${spValue}.sp`);
1131
+ }
1132
+ if (parts.length === 0) {
1133
+ return "TextStyle()";
1134
+ }
1135
+ const propIndent = indent(options.indent, depth + 1);
1136
+ const closeIndent = indent(options.indent, depth);
1137
+ return `TextStyle(
1138
+ ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
1139
+ ${closeIndent})`;
1140
+ }
1141
+ // -----------------------------------------------------------------------
1142
+ // Output: standalone
1143
+ // -----------------------------------------------------------------------
1144
+ async formatStandalone(context, options) {
1145
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1146
+ if (!context.output.file && requiresFile) {
1147
+ throw new ConfigurationError(
1148
+ `Output "${context.output.name}": file is required for standalone Android output`
1149
+ );
1150
+ }
1151
+ const files = {};
1152
+ for (const { tokens, modifierInputs } of context.permutations) {
1153
+ const processedTokens = stripInternalMetadata(tokens);
1154
+ const content = this.formatTokens(processedTokens, options);
1155
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
1156
+ outputName: context.output.name,
1157
+ extension: "kt",
1158
+ modifierInputs,
1159
+ resolver: context.resolver,
1160
+ defaults: context.meta.defaults
1161
+ });
1162
+ files[fileName] = content;
1163
+ }
1164
+ return outputTree(files);
1165
+ }
1166
+ // -----------------------------------------------------------------------
1167
+ // Output: bundle
1168
+ // -----------------------------------------------------------------------
1169
+ async formatBundle(context, options) {
1170
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1171
+ if (!context.output.file && requiresFile) {
1172
+ throw new ConfigurationError(
1173
+ `Output "${context.output.name}": file is required for bundle Android output`
1174
+ );
1175
+ }
1176
+ const content = this.formatBundleContent(context, options);
1177
+ const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
1178
+ outputName: context.output.name,
1179
+ extension: "kt",
1180
+ modifierInputs: context.meta.basePermutation,
1181
+ resolver: context.resolver,
1182
+ defaults: context.meta.defaults
1183
+ });
1184
+ return outputTree({ [fileName]: content });
1185
+ }
1186
+ formatBundleContent(context, options) {
1187
+ const allTokenTypes = this.collectAllPermutationTypes(context);
1188
+ return this.buildFile(allTokenTypes, options, (lines, vis) => {
1189
+ const i1 = indent(options.indent, 1);
1190
+ lines.push(`@Suppress("unused")`);
1191
+ lines.push(`${vis}object ${options.objectName} {`);
1192
+ for (let idx = 0; idx < context.permutations.length; idx++) {
1193
+ const { tokens, modifierInputs } = context.permutations[idx];
1194
+ const processedTokens = stripInternalMetadata(tokens);
1195
+ const permName = this.buildPermutationName(modifierInputs);
1196
+ lines.push(`${i1}${vis}object ${permName} {`);
1197
+ this.renderBundleTokens(lines, processedTokens, options, 2);
1198
+ lines.push(`${i1}}`);
1199
+ if (idx < context.permutations.length - 1) {
1200
+ lines.push("");
1201
+ }
1202
+ }
1203
+ lines.push("}");
1204
+ });
1205
+ }
1206
+ collectAllPermutationTypes(context) {
1207
+ const allTokenTypes = /* @__PURE__ */ new Set();
1208
+ for (const { tokens } of context.permutations) {
1209
+ const processed = stripInternalMetadata(tokens);
1210
+ for (const [, token] of Object.entries(processed)) {
1211
+ if (token.$type) {
1212
+ allTokenTypes.add(token.$type);
1213
+ }
1214
+ }
1215
+ }
1216
+ return allTokenTypes;
1217
+ }
1218
+ renderBundleTokens(lines, tokens, options, baseDepth) {
1219
+ if (options.structure === "flat") {
1220
+ const groups = this.groupTokensByType(tokens);
1221
+ this.renderFlatGroups(lines, groups, baseDepth, options);
1222
+ return;
1223
+ }
1224
+ const tree = this.buildTokenTree(tokens);
1225
+ this.renderTreeChildren(lines, tree, baseDepth, options);
1226
+ }
1227
+ buildPermutationName(modifierInputs) {
1228
+ const values = Object.values(modifierInputs);
1229
+ if (values.length === 0) {
1230
+ return "Default";
1231
+ }
1232
+ return values.map((v) => toPascalCase(v)).join("");
1233
+ }
1234
+ };
1235
+ function androidRenderer() {
1236
+ const rendererInstance = new AndroidRenderer();
1237
+ return {
1238
+ format: (context, options) => rendererInstance.format(
1239
+ context,
1240
+ options ?? context.output.options
1241
+ )
1242
+ };
1243
+ }
1244
+
471
1245
  // src/renderers/css.ts
472
1246
  init_errors();
473
1247
  init_token_utils();
@@ -599,6 +1373,33 @@ function collectRemainder(tokens, included) {
599
1373
  }
600
1374
  return result;
601
1375
  }
1376
+ function buildSetLayerBlocks(tokens, resolver) {
1377
+ const blocks = [];
1378
+ const included = /* @__PURE__ */ new Set();
1379
+ const addBlock = (key, blockTokens, description) => {
1380
+ if (Object.keys(blockTokens).length === 0) {
1381
+ return;
1382
+ }
1383
+ for (const k of Object.keys(blockTokens)) {
1384
+ included.add(k);
1385
+ }
1386
+ blocks.push({ key, description, tokens: blockTokens });
1387
+ };
1388
+ for (const item of resolver.resolutionOrder) {
1389
+ const ref = item.$ref;
1390
+ if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
1391
+ continue;
1392
+ }
1393
+ const setName = ref.slice("#/sets/".length);
1394
+ addBlock(
1395
+ `Set: ${setName}`,
1396
+ collectSetTokens(tokens, setName, included),
1397
+ resolver.sets?.[setName]?.description
1398
+ );
1399
+ }
1400
+ addBlock("Unattributed", collectRemainder(tokens, included));
1401
+ return blocks;
1402
+ }
602
1403
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
603
1404
  const blocks = [];
604
1405
  const included = /* @__PURE__ */ new Set();
@@ -794,14 +1595,14 @@ var CssRenderer = class _CssRenderer {
794
1595
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
795
1596
  }
796
1597
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
797
- const indent = opts.minify ? "" : " ";
1598
+ const indent2 = opts.minify ? "" : " ";
798
1599
  const newline = opts.minify ? "" : "\n";
799
1600
  const space = opts.minify ? "" : " ";
800
1601
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
801
- const tokenIndent = hasMediaQuery ? indent + indent : indent;
1602
+ const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
802
1603
  if (hasMediaQuery) {
803
1604
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
804
- lines.push(`${indent}${selector}${space}{${newline}`);
1605
+ lines.push(`${indent2}${selector}${space}{${newline}`);
805
1606
  } else {
806
1607
  lines.push(`${selector}${space}{${newline}`);
807
1608
  }
@@ -818,21 +1619,21 @@ var CssRenderer = class _CssRenderer {
818
1619
  );
819
1620
  }
820
1621
  if (hasMediaQuery) {
821
- lines.push(`${indent}}${newline}`);
1622
+ lines.push(`${indent2}}${newline}`);
822
1623
  }
823
1624
  lines.push(`}${newline}${newline}`);
824
1625
  }
825
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
1626
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
826
1627
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
827
1628
  if (token.$deprecated != null && token.$deprecated !== false) {
828
1629
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
829
- lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1630
+ lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
830
1631
  }
831
1632
  if (token.$description && token.$description !== "") {
832
- lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1633
+ lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
833
1634
  }
834
1635
  for (const entry of entries) {
835
- lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
1636
+ lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
836
1637
  }
837
1638
  }
838
1639
  async formatWithPrettier(css2) {
@@ -1319,6 +2120,10 @@ var CssRenderer = class _CssRenderer {
1319
2120
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
1320
2121
  }
1321
2122
  const files = {};
2123
+ const baseResult = await this.buildModifierBaseFile(context, options);
2124
+ if (baseResult) {
2125
+ files[baseResult.fileName] = baseResult.content;
2126
+ }
1322
2127
  for (const [modifierName, modifierDef] of Object.entries(context.resolver.modifiers)) {
1323
2128
  for (const contextValue of Object.keys(modifierDef.contexts)) {
1324
2129
  const result = await this.buildModifierContextFile(
@@ -1334,22 +2139,75 @@ var CssRenderer = class _CssRenderer {
1334
2139
  }
1335
2140
  return { kind: "outputTree", files };
1336
2141
  }
1337
- collectTokensForModifierContext(modifierName, contextValue, permutations) {
1338
- const expectedSource = `${modifierName}-${contextValue}`;
1339
- let tokensFromSource = {};
1340
- let referenceTokens = {};
1341
- for (const { tokens, modifierInputs } of permutations) {
1342
- if (modifierInputs[modifierName] !== contextValue) {
1343
- continue;
1344
- }
1345
- tokensFromSource = { ...tokensFromSource, ...filterTokensBySource(tokens, expectedSource) };
1346
- referenceTokens = { ...referenceTokens, ...tokens };
2142
+ async buildModifierBaseFile(context, options) {
2143
+ const basePermutation = context.permutations.find(
2144
+ ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
2145
+ );
2146
+ if (!basePermutation) {
2147
+ return void 0;
1347
2148
  }
1348
- return { tokensFromSource, referenceTokens };
1349
- }
1350
- async buildModifierContextFile(modifierName, contextValue, context, options) {
1351
- const { tokensFromSource, referenceTokens } = this.collectTokensForModifierContext(
1352
- modifierName,
2149
+ const setTokens = filterTokensFromSets(basePermutation.tokens);
2150
+ if (Object.keys(setTokens).length === 0) {
2151
+ return void 0;
2152
+ }
2153
+ const setBlocks = buildSetLayerBlocks(setTokens, context.resolver);
2154
+ if (setBlocks.length === 0) {
2155
+ return void 0;
2156
+ }
2157
+ const modifiers = context.resolver.modifiers;
2158
+ const firstModifierName = Object.keys(modifiers)[0] ?? "";
2159
+ const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
2160
+ const baseModifierInputs = { ...context.meta.defaults };
2161
+ const selector = resolveSelector(
2162
+ options.selector,
2163
+ firstModifierName,
2164
+ firstModifierContext,
2165
+ true,
2166
+ baseModifierInputs
2167
+ );
2168
+ const mediaQuery = resolveMediaQuery(
2169
+ options.mediaQuery,
2170
+ firstModifierName,
2171
+ firstModifierContext,
2172
+ true,
2173
+ baseModifierInputs
2174
+ );
2175
+ const referenceTokens = basePermutation.tokens;
2176
+ const cssBlocks = [];
2177
+ for (const block of setBlocks) {
2178
+ const cleanTokens = stripInternalMetadata(block.tokens);
2179
+ const css2 = await this.formatTokens(cleanTokens, {
2180
+ selector,
2181
+ mediaQuery,
2182
+ minify: options.minify ?? false,
2183
+ preserveReferences: options.preserveReferences ?? false,
2184
+ referenceTokens
2185
+ });
2186
+ const header = block.description ? `/* ${block.key} */
2187
+ /* ${block.description} */` : `/* ${block.key} */`;
2188
+ cssBlocks.push(`${header}
2189
+ ${css2}`);
2190
+ }
2191
+ const content = cssBlocks.join("\n");
2192
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
2193
+ return { fileName, content };
2194
+ }
2195
+ collectTokensForModifierContext(modifierName, contextValue, permutations) {
2196
+ const expectedSource = `${modifierName}-${contextValue}`;
2197
+ let tokensFromSource = {};
2198
+ let referenceTokens = {};
2199
+ for (const { tokens, modifierInputs } of permutations) {
2200
+ if (modifierInputs[modifierName] !== contextValue) {
2201
+ continue;
2202
+ }
2203
+ tokensFromSource = { ...tokensFromSource, ...filterTokensBySource(tokens, expectedSource) };
2204
+ referenceTokens = { ...referenceTokens, ...tokens };
2205
+ }
2206
+ return { tokensFromSource, referenceTokens };
2207
+ }
2208
+ async buildModifierContextFile(modifierName, contextValue, context, options) {
2209
+ const { tokensFromSource, referenceTokens } = this.collectTokensForModifierContext(
2210
+ modifierName,
1353
2211
  contextValue,
1354
2212
  context.permutations
1355
2213
  );
@@ -1380,7 +2238,7 @@ var CssRenderer = class _CssRenderer {
1380
2238
  preserveReferences: options.preserveReferences ?? false,
1381
2239
  referenceTokens
1382
2240
  });
1383
- const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs, modifierName, contextValue) : buildInMemoryOutputKey({
2241
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
1384
2242
  outputName: context.output.name,
1385
2243
  extension: "css",
1386
2244
  modifierInputs,
@@ -1427,20 +2285,633 @@ function cssRenderer() {
1427
2285
  };
1428
2286
  }
1429
2287
 
1430
- // src/renderers/js-module.ts
1431
- init_utils();
2288
+ // src/renderers/ios.ts
1432
2289
  init_errors();
1433
2290
  init_token_utils();
1434
-
1435
- // src/renderers/output-tree.ts
1436
- var outputTree = (files) => {
2291
+ init_utils();
2292
+ var toSRGB2 = culori.converter("rgb");
2293
+ var toP32 = culori.converter("p3");
2294
+ var SWIFT_TYPE_GROUP_MAP = {
2295
+ color: "Colors",
2296
+ dimension: "Spacing",
2297
+ fontFamily: "Fonts",
2298
+ fontWeight: "FontWeights",
2299
+ duration: "Durations",
2300
+ shadow: "Shadows",
2301
+ typography: "Typography",
2302
+ number: "Numbers",
2303
+ cubicBezier: "Animations",
2304
+ border: "Borders",
2305
+ gradient: "Gradients"
2306
+ };
2307
+ var SWIFT_KEYWORDS = /* @__PURE__ */ new Set([
2308
+ "associatedtype",
2309
+ "class",
2310
+ "deinit",
2311
+ "enum",
2312
+ "extension",
2313
+ "fileprivate",
2314
+ "func",
2315
+ "import",
2316
+ "init",
2317
+ "inout",
2318
+ "internal",
2319
+ "let",
2320
+ "open",
2321
+ "operator",
2322
+ "private",
2323
+ "protocol",
2324
+ "public",
2325
+ "rethrows",
2326
+ "static",
2327
+ "struct",
2328
+ "subscript",
2329
+ "typealias",
2330
+ "var",
2331
+ "break",
2332
+ "case",
2333
+ "continue",
2334
+ "default",
2335
+ "defer",
2336
+ "do",
2337
+ "else",
2338
+ "fallthrough",
2339
+ "for",
2340
+ "guard",
2341
+ "if",
2342
+ "in",
2343
+ "repeat",
2344
+ "return",
2345
+ "switch",
2346
+ "where",
2347
+ "while",
2348
+ "as",
2349
+ "catch",
2350
+ "false",
2351
+ "is",
2352
+ "nil",
2353
+ "super",
2354
+ "self",
2355
+ "Self",
2356
+ "throw",
2357
+ "throws",
2358
+ "true",
2359
+ "try",
2360
+ "Type",
2361
+ "Protocol"
2362
+ ]);
2363
+ var IosRenderer = class {
2364
+ async format(context, options) {
2365
+ const opts = {
2366
+ preset: options?.preset ?? "standalone",
2367
+ accessLevel: options?.accessLevel ?? "public",
2368
+ structure: options?.structure ?? "enum",
2369
+ enumName: options?.enumName ?? "DesignTokens",
2370
+ extensionNamespace: options?.extensionNamespace ?? "DesignTokens",
2371
+ colorSpace: options?.colorSpace ?? "sRGB",
2372
+ swiftVersion: options?.swiftVersion ?? "5.9",
2373
+ indent: options?.indent ?? 4,
2374
+ frozen: options?.frozen ?? false
2375
+ };
2376
+ return await this.formatStandalone(context, opts);
2377
+ }
2378
+ formatTokens(tokens, options) {
2379
+ if (options.structure === "grouped") {
2380
+ return this.formatAsGrouped(tokens, options);
2381
+ }
2382
+ return this.formatAsEnum(tokens, options);
2383
+ }
2384
+ formatAsEnum(tokens, options) {
2385
+ const access = options.accessLevel;
2386
+ const groups = this.groupTokensByType(tokens);
2387
+ const imports = this.collectImports(tokens);
2388
+ const i1 = this.indentStr(options.indent, 1);
2389
+ const i2 = this.indentStr(options.indent, 2);
2390
+ const staticPrefix = this.staticLetPrefix(options);
2391
+ const frozen = this.frozenPrefix(options);
2392
+ const lines = [];
2393
+ lines.push(this.buildFileHeader());
2394
+ lines.push("");
2395
+ for (const imp of imports) {
2396
+ lines.push(`import ${imp}`);
2397
+ }
2398
+ lines.push(...this.buildStructDefinitions(tokens, access, options));
2399
+ lines.push("");
2400
+ lines.push(`${frozen}${access} enum ${options.enumName} {`);
2401
+ for (const group of groups) {
2402
+ lines.push(`${i1}${frozen}${access} enum ${group.name} {`);
2403
+ for (const token of group.tokens) {
2404
+ const swiftName = this.buildQualifiedSwiftName(token);
2405
+ const swiftValue = this.formatSwiftValue(token, options);
2406
+ const typeAnnotation = this.getTypeAnnotation(token);
2407
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2408
+ const docComment = this.buildDocComment(token, i2);
2409
+ if (docComment) {
2410
+ lines.push(docComment);
2411
+ }
2412
+ lines.push(`${i2}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2413
+ }
2414
+ lines.push(`${i1}}`);
2415
+ lines.push("");
2416
+ }
2417
+ lines.push("}");
2418
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2419
+ lines.push("");
2420
+ return lines.join("\n");
2421
+ }
2422
+ formatAsGrouped(tokens, options) {
2423
+ const access = options.accessLevel;
2424
+ const namespace = options.extensionNamespace;
2425
+ const groups = this.groupTokensByType(tokens);
2426
+ const imports = this.collectImports(tokens);
2427
+ const i1 = this.indentStr(options.indent, 1);
2428
+ const i2 = this.indentStr(options.indent, 2);
2429
+ const staticPrefix = this.staticLetPrefix(options);
2430
+ const frozen = this.frozenPrefix(options);
2431
+ const lines = [];
2432
+ lines.push(this.buildFileHeader());
2433
+ lines.push("");
2434
+ for (const imp of imports) {
2435
+ lines.push(`import ${imp}`);
2436
+ }
2437
+ lines.push(...this.buildStructDefinitions(tokens, access, options));
2438
+ lines.push("");
2439
+ lines.push(`${frozen}${access} enum ${namespace} {}`);
2440
+ lines.push("");
2441
+ for (const group of groups) {
2442
+ lines.push(`${access} extension ${namespace} {`);
2443
+ lines.push(`${i1}${frozen}enum ${group.name} {`);
2444
+ for (const token of group.tokens) {
2445
+ const swiftName = this.buildQualifiedSwiftName(token);
2446
+ const swiftValue = this.formatSwiftValue(token, options);
2447
+ const typeAnnotation = this.getTypeAnnotation(token);
2448
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2449
+ const docComment = this.buildDocComment(token, i2);
2450
+ if (docComment) {
2451
+ lines.push(docComment);
2452
+ }
2453
+ lines.push(`${i2}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2454
+ }
2455
+ lines.push(`${i1}}`);
2456
+ lines.push("}");
2457
+ lines.push("");
2458
+ }
2459
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2460
+ return lines.join("\n");
2461
+ }
2462
+ buildFileHeader() {
2463
+ return [
2464
+ "// Generated by Dispersa - do not edit manually",
2465
+ "// https://github.com/timges/dispersa"
2466
+ ].join("\n");
2467
+ }
2468
+ collectImports(tokens) {
2469
+ const imports = /* @__PURE__ */ new Set();
2470
+ imports.add("SwiftUI");
2471
+ for (const [, token] of Object.entries(tokens)) {
2472
+ if (token.$type === "duration") {
2473
+ imports.add("Foundation");
2474
+ }
2475
+ }
2476
+ return Array.from(imports).sort();
2477
+ }
2478
+ /**
2479
+ * Builds a `///` doc comment from a token's `$description`, if present.
2480
+ */
2481
+ buildDocComment(token, indent2) {
2482
+ if (!token.$description) {
2483
+ return void 0;
2484
+ }
2485
+ return `${indent2}/// ${token.$description}`;
2486
+ }
2487
+ groupTokensByType(tokens) {
2488
+ const groupMap = /* @__PURE__ */ new Map();
2489
+ for (const [, token] of getSortedTokenEntries(tokens)) {
2490
+ const groupName = SWIFT_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
2491
+ const existing = groupMap.get(groupName) ?? [];
2492
+ existing.push(token);
2493
+ groupMap.set(groupName, existing);
2494
+ }
2495
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
2496
+ name,
2497
+ tokens: groupTokens
2498
+ }));
2499
+ }
2500
+ /**
2501
+ * Builds a qualified Swift name from a token's path, preserving parent
2502
+ * hierarchy segments to avoid duplicate identifiers.
2503
+ *
2504
+ * For example, `color.blue.400` in the `Colors` group becomes `blue400`
2505
+ * instead of just `_400`.
2506
+ */
2507
+ buildQualifiedSwiftName(token) {
2508
+ const path = token.path;
2509
+ const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
2510
+ const joined = withoutTypePrefix.join("_");
2511
+ return this.toSwiftIdentifier(joined);
2512
+ }
2513
+ formatSwiftValue(token, options) {
2514
+ const value = token.$value;
2515
+ if (token.$type === "color") {
2516
+ return this.formatColorValue(value, options);
2517
+ }
2518
+ if (token.$type === "dimension") {
2519
+ return this.formatDimensionValue(value);
2520
+ }
2521
+ if (token.$type === "fontFamily") {
2522
+ return this.formatFontFamilyValue(value);
2523
+ }
2524
+ if (token.$type === "fontWeight") {
2525
+ return this.formatFontWeightValue(value);
2526
+ }
2527
+ if (token.$type === "duration") {
2528
+ return this.formatDurationValue(value);
2529
+ }
2530
+ if (token.$type === "shadow") {
2531
+ return this.formatShadowValue(value, options);
2532
+ }
2533
+ if (token.$type === "typography") {
2534
+ return this.formatTypographyValue(value);
2535
+ }
2536
+ if (token.$type === "border") {
2537
+ return this.formatBorderValue(value, options);
2538
+ }
2539
+ if (token.$type === "gradient") {
2540
+ return this.formatGradientValue(value, options);
2541
+ }
2542
+ if (token.$type === "number") {
2543
+ return String(value);
2544
+ }
2545
+ if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
2546
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2547
+ }
2548
+ if (typeof value === "string") {
2549
+ return `"${this.escapeSwiftString(value)}"`;
2550
+ }
2551
+ if (typeof value === "number") {
2552
+ return String(value);
2553
+ }
2554
+ if (typeof value === "boolean") {
2555
+ return value ? "true" : "false";
2556
+ }
2557
+ return `"${this.escapeSwiftString(String(value))}"`;
2558
+ }
2559
+ formatColorValue(value, options) {
2560
+ if (!isColorObject(value)) {
2561
+ return typeof value === "string" ? `Color("${this.escapeSwiftString(value)}")` : "Color.clear";
2562
+ }
2563
+ const colorObj = value;
2564
+ const alpha = colorObj.alpha ?? 1;
2565
+ if (options.colorSpace === "displayP3") {
2566
+ const p3 = toP32(dtcgObjectToCulori(colorObj));
2567
+ const r2 = this.roundComponent(p3?.r ?? 0);
2568
+ const g2 = this.roundComponent(p3?.g ?? 0);
2569
+ const b2 = this.roundComponent(p3?.b ?? 0);
2570
+ return alpha < 1 ? `Color(.displayP3, red: ${r2}, green: ${g2}, blue: ${b2}, opacity: ${this.roundComponent(alpha)})` : `Color(.displayP3, red: ${r2}, green: ${g2}, blue: ${b2})`;
2571
+ }
2572
+ const rgb = toSRGB2(dtcgObjectToCulori(colorObj));
2573
+ const r = this.roundComponent(rgb?.r ?? 0);
2574
+ const g = this.roundComponent(rgb?.g ?? 0);
2575
+ const b = this.roundComponent(rgb?.b ?? 0);
2576
+ return alpha < 1 ? `Color(red: ${r}, green: ${g}, blue: ${b}, opacity: ${this.roundComponent(alpha)})` : `Color(red: ${r}, green: ${g}, blue: ${b})`;
2577
+ }
2578
+ formatDimensionValue(value) {
2579
+ if (isDimensionObject(value)) {
2580
+ const dim = value;
2581
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2582
+ return String(ptValue);
2583
+ }
2584
+ return String(value);
2585
+ }
2586
+ formatFontFamilyValue(value) {
2587
+ if (Array.isArray(value)) {
2588
+ const primary = value[0];
2589
+ return typeof primary === "string" ? `"${this.escapeSwiftString(primary)}"` : '"system"';
2590
+ }
2591
+ return typeof value === "string" ? `"${this.escapeSwiftString(value)}"` : '"system"';
2592
+ }
2593
+ formatFontWeightValue(value) {
2594
+ if (typeof value === "number") {
2595
+ return this.numericFontWeight(value);
2596
+ }
2597
+ if (typeof value === "string") {
2598
+ return this.namedFontWeight(value) ?? "Font.Weight.regular";
2599
+ }
2600
+ return "Font.Weight.regular";
2601
+ }
2602
+ numericFontWeight(weight) {
2603
+ if (weight <= 100) {
2604
+ return "Font.Weight.ultraLight";
2605
+ }
2606
+ if (weight <= 200) {
2607
+ return "Font.Weight.thin";
2608
+ }
2609
+ if (weight <= 300) {
2610
+ return "Font.Weight.light";
2611
+ }
2612
+ if (weight <= 400) {
2613
+ return "Font.Weight.regular";
2614
+ }
2615
+ if (weight <= 500) {
2616
+ return "Font.Weight.medium";
2617
+ }
2618
+ if (weight <= 600) {
2619
+ return "Font.Weight.semibold";
2620
+ }
2621
+ if (weight <= 700) {
2622
+ return "Font.Weight.bold";
2623
+ }
2624
+ if (weight <= 800) {
2625
+ return "Font.Weight.heavy";
2626
+ }
2627
+ return "Font.Weight.black";
2628
+ }
2629
+ namedFontWeight(name) {
2630
+ const map = {
2631
+ thin: "Font.Weight.thin",
2632
+ ultralight: "Font.Weight.ultraLight",
2633
+ extralight: "Font.Weight.ultraLight",
2634
+ light: "Font.Weight.light",
2635
+ regular: "Font.Weight.regular",
2636
+ normal: "Font.Weight.regular",
2637
+ medium: "Font.Weight.medium",
2638
+ semibold: "Font.Weight.semibold",
2639
+ demibold: "Font.Weight.semibold",
2640
+ bold: "Font.Weight.bold",
2641
+ heavy: "Font.Weight.heavy",
2642
+ extrabold: "Font.Weight.heavy",
2643
+ black: "Font.Weight.black",
2644
+ ultrabold: "Font.Weight.black"
2645
+ };
2646
+ return map[name.toLowerCase()];
2647
+ }
2648
+ formatDurationValue(value) {
2649
+ if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
2650
+ const dur = value;
2651
+ const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
2652
+ return String(seconds);
2653
+ }
2654
+ return typeof value === "number" ? String(value) : "0";
2655
+ }
2656
+ formatShadowValue(value, options) {
2657
+ if (Array.isArray(value) && value.length > 0) {
2658
+ return this.formatSingleShadow(value[0], options);
2659
+ }
2660
+ if (typeof value === "object" && value !== null) {
2661
+ return this.formatSingleShadow(value, options);
2662
+ }
2663
+ return "ShadowStyle(color: .clear, radius: 0, x: 0, y: 0, spread: 0)";
2664
+ }
2665
+ formatSingleShadow(shadow, options) {
2666
+ const color = isColorObject(shadow.color) ? this.formatColorValue(shadow.color, options) : "Color.black.opacity(0.25)";
2667
+ const radius = isDimensionObject(shadow.blur) ? this.dimensionToCGFloat(shadow.blur) : "8";
2668
+ const x = isDimensionObject(shadow.offsetX) ? this.dimensionToCGFloat(shadow.offsetX) : "0";
2669
+ const y = isDimensionObject(shadow.offsetY) ? this.dimensionToCGFloat(shadow.offsetY) : "0";
2670
+ const spread = isDimensionObject(shadow.spread) ? this.dimensionToCGFloat(shadow.spread) : "0";
2671
+ return `ShadowStyle(color: ${color}, radius: ${radius}, x: ${x}, y: ${y}, spread: ${spread})`;
2672
+ }
2673
+ formatTypographyValue(value) {
2674
+ if (typeof value !== "object" || value === null) {
2675
+ return "TypographyStyle(font: Font.body, tracking: 0, lineSpacing: 0)";
2676
+ }
2677
+ const typo = value;
2678
+ const size = isDimensionObject(typo.fontSize) ? this.dimensionToPoints(typo.fontSize) : "16";
2679
+ const weight = typo.fontWeight != null ? this.formatFontWeightValue(typo.fontWeight) : "Font.Weight.regular";
2680
+ const fontExpr = this.buildFontExpression(typo, size, weight);
2681
+ const tracking = this.extractTracking(typo);
2682
+ const lineSpacing = this.extractLineSpacing(typo);
2683
+ return `TypographyStyle(font: ${fontExpr}, tracking: ${tracking}, lineSpacing: ${lineSpacing})`;
2684
+ }
2685
+ buildFontExpression(typo, size, weight) {
2686
+ if (typo.fontFamily != null) {
2687
+ const family = Array.isArray(typo.fontFamily) ? typo.fontFamily[0] : typo.fontFamily;
2688
+ if (typeof family === "string") {
2689
+ return `Font.custom("${this.escapeSwiftString(family)}", size: ${size}).weight(${weight})`;
2690
+ }
2691
+ }
2692
+ return `Font.system(size: ${size}, weight: ${weight})`;
2693
+ }
2694
+ extractTracking(typo) {
2695
+ if (!isDimensionObject(typo.letterSpacing)) {
2696
+ return "0";
2697
+ }
2698
+ const dim = typo.letterSpacing;
2699
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2700
+ return String(ptValue);
2701
+ }
2702
+ extractLineSpacing(typo) {
2703
+ if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
2704
+ return "0";
2705
+ }
2706
+ if (!isDimensionObject(typo.fontSize)) {
2707
+ return "0";
2708
+ }
2709
+ const dim = typo.fontSize;
2710
+ const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
2711
+ const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
2712
+ return String(lineHeightPt - basePt);
2713
+ }
2714
+ dimensionToPoints(dim) {
2715
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2716
+ return String(ptValue);
2717
+ }
2718
+ /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
2719
+ dimensionToCGFloat(dim) {
2720
+ const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2721
+ return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
2722
+ }
2723
+ getTypeAnnotation(token) {
2724
+ switch (token.$type) {
2725
+ case "dimension":
2726
+ return "CGFloat";
2727
+ case "duration":
2728
+ return "TimeInterval";
2729
+ case "number":
2730
+ return "Double";
2731
+ case "fontWeight":
2732
+ return "Font.Weight";
2733
+ case "fontFamily":
2734
+ return "String";
2735
+ default:
2736
+ return void 0;
2737
+ }
2738
+ }
2739
+ toSwiftIdentifier(name) {
2740
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
2741
+ const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
2742
+ const safe = /^\d/.test(identifier) ? `_${identifier}` : identifier;
2743
+ return SWIFT_KEYWORDS.has(safe) ? `\`${safe}\`` : safe;
2744
+ }
2745
+ escapeSwiftString(str) {
2746
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2747
+ }
2748
+ roundComponent(value) {
2749
+ return Math.round(value * 1e4) / 1e4;
2750
+ }
2751
+ indentStr(width, level) {
2752
+ return " ".repeat(width * level);
2753
+ }
2754
+ /**
2755
+ * Returns the prefix for `static let` declarations.
2756
+ * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
2757
+ */
2758
+ staticLetPrefix(options) {
2759
+ return options.swiftVersion === "6.0" ? "nonisolated(unsafe) static let " : "static let ";
2760
+ }
2761
+ /** Returns `@frozen ` when the frozen option is enabled, empty string otherwise. */
2762
+ frozenPrefix(options) {
2763
+ return options.frozen ? "@frozen " : "";
2764
+ }
2765
+ /** Returns `: Sendable` when targeting Swift 6, empty string otherwise. */
2766
+ structConformances(options) {
2767
+ return options.swiftVersion === "6.0" ? ": Sendable" : "";
2768
+ }
2769
+ hasShadowTokens(tokens) {
2770
+ return Object.values(tokens).some((t) => t.$type === "shadow");
2771
+ }
2772
+ hasTypographyTokens(tokens) {
2773
+ return Object.values(tokens).some((t) => t.$type === "typography");
2774
+ }
2775
+ hasBorderTokens(tokens) {
2776
+ return Object.values(tokens).some((t) => t.$type === "border");
2777
+ }
2778
+ /** Emits all struct definitions needed by the token set. */
2779
+ buildStructDefinitions(tokens, access, options) {
2780
+ const lines = [];
2781
+ if (this.hasShadowTokens(tokens)) {
2782
+ lines.push("");
2783
+ lines.push(...this.buildShadowStyleStruct(access, options));
2784
+ }
2785
+ if (this.hasTypographyTokens(tokens)) {
2786
+ lines.push("");
2787
+ lines.push(...this.buildTypographyStyleStruct(access, options));
2788
+ }
2789
+ if (this.hasBorderTokens(tokens)) {
2790
+ lines.push("");
2791
+ lines.push(...this.buildBorderStyleStruct(access, options));
2792
+ }
2793
+ return lines;
2794
+ }
2795
+ buildShadowStyleStruct(access, options) {
2796
+ const i1 = this.indentStr(options.indent, 1);
2797
+ const conformances = this.structConformances(options);
2798
+ const frozen = this.frozenPrefix(options);
2799
+ return [
2800
+ `${frozen}${access} struct ShadowStyle${conformances} {`,
2801
+ `${i1}${access} let color: Color`,
2802
+ `${i1}${access} let radius: CGFloat`,
2803
+ `${i1}${access} let x: CGFloat`,
2804
+ `${i1}${access} let y: CGFloat`,
2805
+ `${i1}${access} let spread: CGFloat`,
2806
+ "}"
2807
+ ];
2808
+ }
2809
+ buildTypographyStyleStruct(access, options) {
2810
+ const i1 = this.indentStr(options.indent, 1);
2811
+ const conformances = this.structConformances(options);
2812
+ const frozen = this.frozenPrefix(options);
2813
+ return [
2814
+ `${frozen}${access} struct TypographyStyle${conformances} {`,
2815
+ `${i1}${access} let font: Font`,
2816
+ `${i1}${access} let tracking: CGFloat`,
2817
+ `${i1}${access} let lineSpacing: CGFloat`,
2818
+ "}"
2819
+ ];
2820
+ }
2821
+ buildBorderStyleStruct(access, options) {
2822
+ const i1 = this.indentStr(options.indent, 1);
2823
+ const conformances = this.structConformances(options);
2824
+ const frozen = this.frozenPrefix(options);
2825
+ return [
2826
+ `${frozen}${access} struct BorderStyle${conformances} {`,
2827
+ `${i1}${access} let color: Color`,
2828
+ `${i1}${access} let width: CGFloat`,
2829
+ "}"
2830
+ ];
2831
+ }
2832
+ /** Emits convenience View extensions for shadow and typography application. */
2833
+ buildViewExtensions(tokens, access, options) {
2834
+ const lines = [];
2835
+ const i1 = this.indentStr(options.indent, 1);
2836
+ const i2 = this.indentStr(options.indent, 2);
2837
+ if (this.hasShadowTokens(tokens)) {
2838
+ lines.push("");
2839
+ lines.push(`${access} extension View {`);
2840
+ lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
2841
+ lines.push(
2842
+ `${i2}self.shadow(color: style.color, radius: style.radius, x: style.x, y: style.y)`
2843
+ );
2844
+ lines.push(`${i1}}`);
2845
+ lines.push("}");
2846
+ }
2847
+ if (this.hasTypographyTokens(tokens)) {
2848
+ lines.push("");
2849
+ lines.push(`${access} extension View {`);
2850
+ lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
2851
+ lines.push(
2852
+ `${i2}self.font(style.font).tracking(style.tracking).lineSpacing(style.lineSpacing)`
2853
+ );
2854
+ lines.push(`${i1}}`);
2855
+ lines.push("}");
2856
+ }
2857
+ return lines;
2858
+ }
2859
+ formatBorderValue(value, options) {
2860
+ if (typeof value !== "object" || value === null) {
2861
+ return "BorderStyle(color: .clear, width: 0)";
2862
+ }
2863
+ const border = value;
2864
+ const color = isColorObject(border.color) ? this.formatColorValue(border.color, options) : "Color.clear";
2865
+ const width = isDimensionObject(border.width) ? this.dimensionToCGFloat(border.width) : "1.0";
2866
+ return `BorderStyle(color: ${color}, width: ${width})`;
2867
+ }
2868
+ formatGradientValue(value, options) {
2869
+ if (!Array.isArray(value) || value.length === 0) {
2870
+ return "Gradient(stops: [])";
2871
+ }
2872
+ const stops = value.map((stop) => {
2873
+ const color = isColorObject(stop.color) ? this.formatColorValue(stop.color, options) : "Color.clear";
2874
+ return `.init(color: ${color}, location: ${stop.position})`;
2875
+ });
2876
+ return `Gradient(stops: [${stops.join(", ")}])`;
2877
+ }
2878
+ async formatStandalone(context, options) {
2879
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2880
+ if (!context.output.file && requiresFile) {
2881
+ throw new ConfigurationError(
2882
+ `Output "${context.output.name}": file is required for standalone iOS output`
2883
+ );
2884
+ }
2885
+ const files = {};
2886
+ for (const { tokens, modifierInputs } of context.permutations) {
2887
+ const processedTokens = stripInternalMetadata(tokens);
2888
+ const content = this.formatTokens(processedTokens, options);
2889
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
2890
+ outputName: context.output.name,
2891
+ extension: "swift",
2892
+ modifierInputs,
2893
+ resolver: context.resolver,
2894
+ defaults: context.meta.defaults
2895
+ });
2896
+ files[fileName] = content;
2897
+ }
2898
+ return outputTree(files);
2899
+ }
2900
+ };
2901
+ function iosRenderer() {
2902
+ const rendererInstance = new IosRenderer();
1437
2903
  return {
1438
- kind: "outputTree",
1439
- files
2904
+ format: (context, options) => rendererInstance.format(
2905
+ context,
2906
+ options ?? context.output.options
2907
+ )
1440
2908
  };
1441
- };
2909
+ }
1442
2910
 
1443
2911
  // src/renderers/js-module.ts
2912
+ init_utils();
2913
+ init_errors();
2914
+ init_token_utils();
1444
2915
  var JsModuleRenderer = class {
1445
2916
  async format(context, options) {
1446
2917
  const opts = {
@@ -1554,8 +3025,8 @@ var JsModuleRenderer = class {
1554
3025
  /**
1555
3026
  * Add object properties to lines
1556
3027
  */
1557
- addObjectProperties(lines, obj, indent) {
1558
- const indentStr = " ".repeat(indent);
3028
+ addObjectProperties(lines, obj, indent2) {
3029
+ const indentStr = " ".repeat(indent2);
1559
3030
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
1560
3031
  for (let i = 0; i < entries.length; i++) {
1561
3032
  const entry = entries[i];
@@ -1566,7 +3037,7 @@ var JsModuleRenderer = class {
1566
3037
  const isLast = i === entries.length - 1;
1567
3038
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1568
3039
  lines.push(`${indentStr}${this.quoteKey(key)}: {`);
1569
- this.addObjectProperties(lines, value, indent + 1);
3040
+ this.addObjectProperties(lines, value, indent2 + 1);
1570
3041
  lines.push(`${indentStr}}${isLast ? "" : ","}`);
1571
3042
  } else {
1572
3043
  const valueStr = JSON.stringify(value);
@@ -1758,6 +3229,349 @@ function jsonRenderer() {
1758
3229
  };
1759
3230
  }
1760
3231
 
3232
+ // src/renderers/tailwind.ts
3233
+ init_errors();
3234
+ init_token_utils();
3235
+
3236
+ // src/renderers/bundlers/tailwind.ts
3237
+ init_errors();
3238
+ init_utils();
3239
+ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOverrideBlock) {
3240
+ const baseItem = bundleData.find((item) => item.isBase);
3241
+ if (!baseItem) {
3242
+ throw new BasePermutationError("Base permutation not found in bundle data");
3243
+ }
3244
+ const resolvedOpts = resolveOptions(options);
3245
+ const cssBlocks = [];
3246
+ const variantDeclarations = collectVariantDeclarations(bundleData, baseItem, resolvedOpts);
3247
+ const themeOpts = { ...resolvedOpts, variantDeclarations };
3248
+ const baseTokens = stripInternalMetadata(baseItem.tokens);
3249
+ const themeBlock = await formatThemeTokens(baseTokens, themeOpts);
3250
+ cssBlocks.push(themeBlock);
3251
+ for (const item of bundleData) {
3252
+ if (item.isBase) {
3253
+ continue;
3254
+ }
3255
+ const block = await formatModifierOverride(item, baseItem, resolvedOpts, formatOverrideBlock);
3256
+ if (block) {
3257
+ cssBlocks.push(block);
3258
+ }
3259
+ }
3260
+ return cssBlocks.join("\n");
3261
+ }
3262
+ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
3263
+ const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
3264
+ if (differenceCount > 1) {
3265
+ return void 0;
3266
+ }
3267
+ const tokensToInclude = filterTokensByValueChange(tokens, baseItem.tokens);
3268
+ if (Object.keys(tokensToInclude).length === 0) {
3269
+ return void 0;
3270
+ }
3271
+ const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
3272
+ const [modifier, context] = parseModifierSource(expectedSource);
3273
+ const cleanTokens = stripInternalMetadata(tokensToInclude);
3274
+ const selector = resolveSelector(
3275
+ options.selector,
3276
+ modifier,
3277
+ context,
3278
+ false,
3279
+ normalizeModifierInputs(modifierInputs)
3280
+ );
3281
+ const mediaQuery = resolveMediaQuery(
3282
+ options.mediaQuery,
3283
+ modifier,
3284
+ context,
3285
+ false,
3286
+ normalizeModifierInputs(modifierInputs)
3287
+ );
3288
+ const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
3289
+ return `/* Modifier: ${modifier}=${context} */
3290
+ ${css2}`;
3291
+ }
3292
+ function filterTokensByValueChange(currentTokens, baseTokens) {
3293
+ const changed = {};
3294
+ for (const [name, token] of Object.entries(currentTokens)) {
3295
+ const baseToken = baseTokens[name];
3296
+ if (!baseToken) {
3297
+ changed[name] = token;
3298
+ continue;
3299
+ }
3300
+ if (JSON.stringify(token.$value) !== JSON.stringify(baseToken.$value)) {
3301
+ changed[name] = token;
3302
+ }
3303
+ }
3304
+ return changed;
3305
+ }
3306
+ function collectVariantDeclarations(bundleData, baseItem, options) {
3307
+ const declarations = [];
3308
+ for (const item of bundleData) {
3309
+ if (item.isBase) {
3310
+ continue;
3311
+ }
3312
+ const differenceCount = countModifierDifferences(item.modifierInputs, baseItem.modifierInputs);
3313
+ if (differenceCount > 1) {
3314
+ continue;
3315
+ }
3316
+ const expectedSource = getExpectedSource(item.modifierInputs, baseItem.modifierInputs);
3317
+ const [modifier, context] = parseModifierSource(expectedSource);
3318
+ const variantName = `${modifier}-${context}`;
3319
+ const normalized = normalizeModifierInputs(item.modifierInputs);
3320
+ const mediaQuery = resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized);
3321
+ if (mediaQuery !== "") {
3322
+ declarations.push(`@custom-variant ${variantName} (@media ${mediaQuery});`);
3323
+ continue;
3324
+ }
3325
+ const selector = resolveSelector(options.selector, modifier, context, false, normalized);
3326
+ declarations.push(`@custom-variant ${variantName} (&:where(${selector}, ${selector} *));`);
3327
+ }
3328
+ return declarations;
3329
+ }
3330
+ function resolveOptions(options) {
3331
+ return {
3332
+ preset: options.preset ?? "bundle",
3333
+ includeImport: options.includeImport ?? true,
3334
+ namespace: options.namespace ?? "",
3335
+ minify: options.minify ?? false,
3336
+ selector: options.selector,
3337
+ mediaQuery: options.mediaQuery,
3338
+ variantDeclarations: []
3339
+ };
3340
+ }
3341
+
3342
+ // src/renderers/tailwind.ts
3343
+ init_utils();
3344
+ var TAILWIND_NAMESPACE_MAP = {
3345
+ color: "color",
3346
+ dimension: "spacing",
3347
+ fontFamily: "font",
3348
+ fontWeight: "font-weight",
3349
+ duration: "duration",
3350
+ shadow: "shadow",
3351
+ number: "number",
3352
+ cubicBezier: "ease"
3353
+ };
3354
+ var TailwindRenderer = class {
3355
+ async format(context, options) {
3356
+ const opts = {
3357
+ preset: options?.preset ?? "bundle",
3358
+ includeImport: options?.includeImport ?? true,
3359
+ namespace: options?.namespace ?? "",
3360
+ minify: options?.minify ?? false,
3361
+ selector: options?.selector,
3362
+ mediaQuery: options?.mediaQuery,
3363
+ variantDeclarations: []
3364
+ };
3365
+ if (opts.preset === "bundle") {
3366
+ return await this.formatBundle(context, opts);
3367
+ }
3368
+ return await this.formatStandalone(context, opts);
3369
+ }
3370
+ /**
3371
+ * Format tokens as Tailwind v4 @theme CSS variables
3372
+ */
3373
+ async formatTokens(tokens, options) {
3374
+ const lines = [];
3375
+ const indent2 = options.minify ? "" : " ";
3376
+ const newline = options.minify ? "" : "\n";
3377
+ const space = options.minify ? "" : " ";
3378
+ if (options.includeImport) {
3379
+ lines.push(`@import "tailwindcss";${newline}`);
3380
+ }
3381
+ if (options.variantDeclarations.length > 0) {
3382
+ if (options.includeImport) {
3383
+ lines.push(newline);
3384
+ }
3385
+ for (const declaration of options.variantDeclarations) {
3386
+ lines.push(`${declaration}${newline}`);
3387
+ }
3388
+ }
3389
+ if (options.includeImport || options.variantDeclarations.length > 0) {
3390
+ lines.push(newline);
3391
+ }
3392
+ const themeDirective = options.namespace ? `@theme namespace(${options.namespace})` : "@theme";
3393
+ lines.push(`${themeDirective}${space}{${newline}`);
3394
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3395
+ const varName = this.buildVariableName(token);
3396
+ const varValue = this.formatValue(token);
3397
+ lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
3398
+ }
3399
+ lines.push(`}${newline}`);
3400
+ const cssString = lines.join("");
3401
+ return options.minify ? cssString : await this.formatWithPrettier(cssString);
3402
+ }
3403
+ /**
3404
+ * Format tokens as plain CSS custom property overrides inside a selector block.
3405
+ * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
3406
+ */
3407
+ async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
3408
+ const indent2 = minify ? "" : " ";
3409
+ const newline = minify ? "" : "\n";
3410
+ const space = minify ? "" : " ";
3411
+ const hasMediaQuery = mediaQuery !== "";
3412
+ const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
3413
+ const lines = [];
3414
+ if (hasMediaQuery) {
3415
+ lines.push(`@media ${mediaQuery}${space}{${newline}`);
3416
+ lines.push(`${indent2}${selector}${space}{${newline}`);
3417
+ } else {
3418
+ lines.push(`${selector}${space}{${newline}`);
3419
+ }
3420
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3421
+ const varName = this.buildVariableName(token);
3422
+ const varValue = this.formatValue(token);
3423
+ lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
3424
+ }
3425
+ if (hasMediaQuery) {
3426
+ lines.push(`${indent2}}${newline}`);
3427
+ lines.push(`}${newline}`);
3428
+ } else {
3429
+ lines.push(`}${newline}`);
3430
+ }
3431
+ return lines.join("");
3432
+ }
3433
+ buildVariableName(token) {
3434
+ const prefix = TAILWIND_NAMESPACE_MAP[token.$type ?? ""];
3435
+ if (!prefix) {
3436
+ return token.name;
3437
+ }
3438
+ const nameLower = token.name.toLowerCase();
3439
+ const prefixLower = prefix.toLowerCase();
3440
+ if (nameLower.startsWith(`${prefixLower}-`) || nameLower.startsWith(`${prefixLower}.`)) {
3441
+ return token.name;
3442
+ }
3443
+ return `${prefix}-${token.name}`;
3444
+ }
3445
+ formatValue(token) {
3446
+ const value = token.$value;
3447
+ if (token.$type === "color" && isColorObject(value)) {
3448
+ return colorObjectToHex(value);
3449
+ }
3450
+ if (token.$type === "dimension" && isDimensionObject(value)) {
3451
+ return dimensionObjectToString(value);
3452
+ }
3453
+ if (token.$type === "duration" && this.isDurationObject(value)) {
3454
+ return `${value.value}${value.unit}`;
3455
+ }
3456
+ if (token.$type === "fontFamily") {
3457
+ if (Array.isArray(value)) {
3458
+ return value.map((v) => typeof v === "string" && v.includes(" ") ? `"${v}"` : v).join(", ");
3459
+ }
3460
+ return typeof value === "string" ? value : String(value);
3461
+ }
3462
+ if (token.$type === "shadow") {
3463
+ return this.formatShadowValue(value);
3464
+ }
3465
+ if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
3466
+ return `cubic-bezier(${value.join(", ")})`;
3467
+ }
3468
+ if (typeof value === "string") {
3469
+ return value;
3470
+ }
3471
+ if (typeof value === "number") {
3472
+ return String(value);
3473
+ }
3474
+ return String(value);
3475
+ }
3476
+ formatShadowValue(value) {
3477
+ if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object") {
3478
+ return value.map((s) => this.formatSingleShadow(s)).join(", ");
3479
+ }
3480
+ if (typeof value === "object" && value !== null) {
3481
+ return this.formatSingleShadow(value);
3482
+ }
3483
+ return String(value);
3484
+ }
3485
+ formatSingleShadow(shadow) {
3486
+ const parts = [];
3487
+ if (shadow.inset === true) {
3488
+ parts.push("inset");
3489
+ }
3490
+ if (isDimensionObject(shadow.offsetX)) {
3491
+ parts.push(dimensionObjectToString(shadow.offsetX));
3492
+ }
3493
+ if (isDimensionObject(shadow.offsetY)) {
3494
+ parts.push(dimensionObjectToString(shadow.offsetY));
3495
+ }
3496
+ if (isDimensionObject(shadow.blur)) {
3497
+ parts.push(dimensionObjectToString(shadow.blur));
3498
+ }
3499
+ if (shadow.spread != null && isDimensionObject(shadow.spread)) {
3500
+ parts.push(dimensionObjectToString(shadow.spread));
3501
+ }
3502
+ if (isColorObject(shadow.color)) {
3503
+ parts.push(colorObjectToHex(shadow.color));
3504
+ } else if (shadow.color != null) {
3505
+ parts.push(String(shadow.color));
3506
+ }
3507
+ return parts.join(" ");
3508
+ }
3509
+ isDurationObject(value) {
3510
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
3511
+ }
3512
+ async formatWithPrettier(css2) {
3513
+ try {
3514
+ return await prettier__default.default.format(css2, {
3515
+ parser: "css",
3516
+ printWidth: 80,
3517
+ tabWidth: 2,
3518
+ useTabs: false
3519
+ });
3520
+ } catch {
3521
+ return css2;
3522
+ }
3523
+ }
3524
+ async formatBundle(context, options) {
3525
+ const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3526
+ tokens,
3527
+ modifierInputs,
3528
+ isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3529
+ }));
3530
+ return await bundleAsTailwind(
3531
+ bundleData,
3532
+ options,
3533
+ async (tokens, opts) => await this.formatTokens(tokens, opts),
3534
+ async (tokens, selector, mediaQuery, minify) => await this.formatOverrideBlock(tokens, selector, mediaQuery, minify)
3535
+ );
3536
+ }
3537
+ async formatStandalone(context, options) {
3538
+ const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
3539
+ if (!context.output.file && requiresFile) {
3540
+ throw new ConfigurationError(
3541
+ `Output "${context.output.name}": file is required for standalone Tailwind output`
3542
+ );
3543
+ }
3544
+ const files = {};
3545
+ for (const { tokens, modifierInputs } of context.permutations) {
3546
+ const processedTokens = stripInternalMetadata(tokens);
3547
+ const content = await this.formatTokens(processedTokens, options);
3548
+ const fileName = context.output.file ? resolveFileName(context.output.file, modifierInputs) : buildInMemoryOutputKey({
3549
+ outputName: context.output.name,
3550
+ extension: "css",
3551
+ modifierInputs,
3552
+ resolver: context.resolver,
3553
+ defaults: context.meta.defaults
3554
+ });
3555
+ files[fileName] = content;
3556
+ }
3557
+ return outputTree(files);
3558
+ }
3559
+ isBasePermutation(modifierInputs, defaults) {
3560
+ return Object.entries(defaults).every(
3561
+ ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
3562
+ );
3563
+ }
3564
+ };
3565
+ function tailwindRenderer() {
3566
+ const rendererInstance = new TailwindRenderer();
3567
+ return {
3568
+ format: (context, options) => rendererInstance.format(
3569
+ context,
3570
+ options ?? context.output.options
3571
+ )
3572
+ };
3573
+ }
3574
+
1761
3575
  // src/builders.ts
1762
3576
  function css(config) {
1763
3577
  const {
@@ -1819,14 +3633,76 @@ function js(config) {
1819
3633
  hooks
1820
3634
  };
1821
3635
  }
3636
+ function tailwind(config) {
3637
+ const { name, file, transforms, filters, hooks, preset = "bundle", ...rendererOptions } = config;
3638
+ return {
3639
+ name,
3640
+ file,
3641
+ renderer: tailwindRenderer(),
3642
+ options: { preset, ...rendererOptions },
3643
+ transforms,
3644
+ filters,
3645
+ hooks
3646
+ };
3647
+ }
3648
+ function ios(config) {
3649
+ const {
3650
+ name,
3651
+ file,
3652
+ transforms,
3653
+ filters,
3654
+ hooks,
3655
+ preset = "standalone",
3656
+ ...rendererOptions
3657
+ } = config;
3658
+ return {
3659
+ name,
3660
+ file,
3661
+ renderer: iosRenderer(),
3662
+ options: { preset, ...rendererOptions },
3663
+ transforms,
3664
+ filters,
3665
+ hooks
3666
+ };
3667
+ }
3668
+ function android(config) {
3669
+ const {
3670
+ name,
3671
+ file,
3672
+ transforms,
3673
+ filters,
3674
+ hooks,
3675
+ preset = "standalone",
3676
+ ...rendererOptions
3677
+ } = config;
3678
+ return {
3679
+ name,
3680
+ file,
3681
+ renderer: androidRenderer(),
3682
+ options: { preset, ...rendererOptions },
3683
+ transforms,
3684
+ filters,
3685
+ hooks
3686
+ };
3687
+ }
3688
+ /**
3689
+ * @license MIT
3690
+ * Copyright (c) 2025-present Dispersa Contributors
3691
+ *
3692
+ * This source code is licensed under the MIT license found in the
3693
+ * LICENSE file in the root directory of this source tree.
3694
+ */
1822
3695
  /**
1823
3696
  * @license
1824
3697
  * Copyright (c) 2025 Dispersa Contributors
1825
3698
  * SPDX-License-Identifier: MIT
1826
3699
  */
1827
3700
 
3701
+ exports.android = android;
1828
3702
  exports.css = css;
3703
+ exports.ios = ios;
1829
3704
  exports.js = js;
1830
3705
  exports.json = json;
3706
+ exports.tailwind = tailwind;
1831
3707
  //# sourceMappingURL=builders.cjs.map
1832
3708
  //# sourceMappingURL=builders.cjs.map