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