dispersa 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/builders.js CHANGED
@@ -46,13 +46,12 @@ function formatDeprecationMessage(token, description = "", format = "bracket") {
46
46
  }
47
47
  const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
48
48
  if (format === "comment") {
49
- const msg = deprecationMsg ? ` ${deprecationMsg}` : "";
50
- return `DEPRECATED${msg}`;
51
- } else {
52
- const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
53
- const prefix = `[DEPRECATED${msg}]`;
54
- return description ? `${prefix} ${description}` : prefix;
49
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
50
+ return `DEPRECATED${msg2}`;
55
51
  }
52
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
53
+ const prefix = `[DEPRECATED${msg}]`;
54
+ return description ? `${prefix} ${description}` : prefix;
56
55
  }
57
56
  function stripInternalTokenMetadata(tokens) {
58
57
  const cleaned = {};
@@ -65,6 +64,30 @@ function stripInternalTokenMetadata(tokens) {
65
64
  function getSortedTokenEntries(tokens) {
66
65
  return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
67
66
  }
67
+ function buildNestedTokenObject(tokens, extractValue) {
68
+ const result = {};
69
+ for (const [, token] of getSortedTokenEntries(tokens)) {
70
+ setNestedValue(result, token.path, extractValue(token));
71
+ }
72
+ return result;
73
+ }
74
+ function setNestedValue(root, path, value) {
75
+ let current = root;
76
+ for (let i = 0; i < path.length - 1; i++) {
77
+ const part = path[i];
78
+ if (part == null) {
79
+ continue;
80
+ }
81
+ if (!(part in current)) {
82
+ current[part] = {};
83
+ }
84
+ current = current[part];
85
+ }
86
+ const lastPart = path[path.length - 1];
87
+ if (lastPart != null) {
88
+ current[lastPart] = value;
89
+ }
90
+ }
68
91
  function getPureAliasReferenceName(value) {
69
92
  if (typeof value !== "string") {
70
93
  return void 0;
@@ -84,6 +107,35 @@ function sanitizeDataAttributeName(value) {
84
107
  function escapeCssString(value) {
85
108
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
86
109
  }
110
+ function groupTokensByType(tokens, typeGroupMap) {
111
+ const groupMap = /* @__PURE__ */ new Map();
112
+ for (const [, token] of getSortedTokenEntries(tokens)) {
113
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
114
+ const existing = groupMap.get(groupName) ?? [];
115
+ existing.push(token);
116
+ groupMap.set(groupName, existing);
117
+ }
118
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
119
+ name,
120
+ tokens: groupTokens
121
+ }));
122
+ }
123
+ function indentStr(width, level) {
124
+ return " ".repeat(width * level);
125
+ }
126
+ function buildGeneratedFileHeader() {
127
+ return [
128
+ "// Generated by Dispersa - do not edit manually",
129
+ "// https://github.com/timges/dispersa"
130
+ ].join("\n");
131
+ }
132
+ function toSafeIdentifier(name, keywords, capitalize) {
133
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
134
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
135
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
136
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
137
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
138
+ }
87
139
  function normalizeModifierInputs(inputs) {
88
140
  const normalized = {};
89
141
  for (const [key, value] of Object.entries(inputs)) {
@@ -91,6 +143,14 @@ function normalizeModifierInputs(inputs) {
91
143
  }
92
144
  return normalized;
93
145
  }
146
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
147
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
148
+ if (!outputFile && requiresFile) {
149
+ throw new ConfigurationError(
150
+ `Output "${outputName}": file is required for ${presetLabel} output`
151
+ );
152
+ }
153
+ }
94
154
  function buildStablePermutationKey(modifierInputs, dimensions) {
95
155
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
96
156
  }
@@ -140,14 +200,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
140
200
  }
141
201
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
142
202
  }
143
- function buildInMemoryOutputKey(params) {
144
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
203
+ function isBasePermutation(modifierInputs, defaults) {
145
204
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
146
205
  const normalizedDefaults = normalizeModifierInputs(defaults);
147
- const isBase = Object.entries(normalizedDefaults).every(
148
- ([key, value]) => normalizedInputs[key] === value
206
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
207
+ }
208
+ function buildInMemoryOutputKey(params) {
209
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
210
+ const permutationKey = generatePermutationKey(
211
+ modifierInputs,
212
+ resolver,
213
+ isBasePermutation(modifierInputs, defaults)
149
214
  );
150
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
151
215
  return `${outputName}-${permutationKey}.${extension}`;
152
216
  }
153
217
  function buildMetadata(resolver) {
@@ -261,6 +325,7 @@ function resolveFileName(fileName, modifierInputs) {
261
325
  }
262
326
  var init_utils = __esm({
263
327
  "src/renderers/bundlers/utils.ts"() {
328
+ init_errors();
264
329
  init_token_utils();
265
330
  }
266
331
  });
@@ -270,36 +335,38 @@ var js_exports = {};
270
335
  __export(js_exports, {
271
336
  bundleAsJsModule: () => bundleAsJsModule
272
337
  });
338
+ function updateStringTracking(state, char) {
339
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
340
+ if (!state.inString) {
341
+ state.inString = true;
342
+ state.stringChar = char;
343
+ } else if (char === state.stringChar) {
344
+ state.inString = false;
345
+ state.stringChar = "";
346
+ }
347
+ }
348
+ state.escaped = !state.escaped && char === "\\";
349
+ }
273
350
  function extractObjectFromJsModule(formattedJs) {
274
351
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
275
352
  if (!assignmentMatch) {
276
353
  return "{}";
277
354
  }
278
355
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
356
+ const state = { inString: false, stringChar: "", escaped: false };
279
357
  let braceCount = 0;
280
- let inString = false;
281
- let stringChar = "";
282
- let escaped = false;
283
358
  for (let i = startIndex; i < formattedJs.length; i++) {
284
359
  const char = formattedJs[i];
285
- if (!escaped && (char === '"' || char === "'" || char === "`")) {
286
- if (!inString) {
287
- inString = true;
288
- stringChar = char;
289
- } else if (char === stringChar) {
290
- inString = false;
291
- stringChar = "";
292
- }
360
+ updateStringTracking(state, char);
361
+ if (state.inString) {
362
+ continue;
293
363
  }
294
- escaped = !escaped && char === "\\";
295
- if (!inString) {
296
- if (char === "{") {
297
- braceCount++;
298
- } else if (char === "}") {
299
- braceCount--;
300
- if (braceCount === 0) {
301
- return formattedJs.substring(startIndex, i + 1);
302
- }
364
+ if (char === "{") {
365
+ braceCount++;
366
+ } else if (char === "}") {
367
+ braceCount--;
368
+ if (braceCount === 0) {
369
+ return formattedJs.substring(startIndex, i + 1);
303
370
  }
304
371
  }
305
372
  }
@@ -396,22 +463,19 @@ __export(json_exports, {
396
463
  bundleAsJson: () => bundleAsJson
397
464
  });
398
465
  async function bundleAsJson(bundleData, resolver, formatTokens) {
466
+ if (!formatTokens) {
467
+ throw new ConfigurationError("JSON formatter was not provided");
468
+ }
399
469
  const metadata = buildMetadata(resolver);
400
470
  const tokens = {};
401
471
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
402
472
  const cleanTokens = stripInternalMetadata(tokenSet);
403
- if (!formatTokens) {
404
- throw new ConfigurationError("JSON formatter was not provided");
405
- }
406
473
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
407
474
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
408
475
  const themeJson = await formatTokens(cleanTokens);
409
476
  tokens[key] = JSON.parse(themeJson);
410
477
  }
411
- const bundle = {
412
- _meta: metadata,
413
- tokens
414
- };
478
+ const bundle = { _meta: metadata, tokens };
415
479
  return JSON.stringify(bundle, null, 2);
416
480
  }
417
481
  var init_json = __esm({
@@ -477,7 +541,7 @@ function colorObjectToHex(color) {
477
541
  return formatHex(culoriColor);
478
542
  }
479
543
 
480
- // src/processing/processors/transforms/built-in/dimension-converter.ts
544
+ // src/processing/transforms/built-in/dimension-converter.ts
481
545
  function isDimensionObject(value) {
482
546
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
483
547
  }
@@ -485,6 +549,14 @@ function dimensionObjectToString(dimension) {
485
549
  return `${dimension.value}${dimension.unit}`;
486
550
  }
487
551
 
552
+ // src/processing/transforms/built-in/duration-converter.ts
553
+ function isDurationObject(value) {
554
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
555
+ }
556
+ function durationObjectToString(duration) {
557
+ return `${duration.value}${duration.unit}`;
558
+ }
559
+
488
560
  // src/renderers/android.ts
489
561
  init_errors();
490
562
  init_token_utils();
@@ -546,9 +618,6 @@ function resolveColorFormat(format) {
546
618
  }
547
619
  return "argb_hex";
548
620
  }
549
- function indent(width, level) {
550
- return " ".repeat(width * level);
551
- }
552
621
  function escapeKotlinString(str) {
553
622
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
554
623
  }
@@ -564,22 +633,6 @@ function roundComponent(value) {
564
633
  function toResourceName(family) {
565
634
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
566
635
  }
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
636
  var AndroidRenderer = class {
584
637
  async format(context, options) {
585
638
  if (!options?.packageName) {
@@ -587,6 +640,7 @@ var AndroidRenderer = class {
587
640
  `Output "${context.output.name}": packageName is required for Android output`
588
641
  );
589
642
  }
643
+ const visibility = options?.visibility;
590
644
  const opts = {
591
645
  preset: options?.preset ?? "standalone",
592
646
  packageName: options.packageName,
@@ -594,7 +648,8 @@ var AndroidRenderer = class {
594
648
  colorFormat: resolveColorFormat(options?.colorFormat),
595
649
  colorSpace: options?.colorSpace ?? "sRGB",
596
650
  structure: options?.structure ?? "nested",
597
- visibility: options?.visibility,
651
+ visibility,
652
+ visPrefix: visibility ? `${visibility} ` : "",
598
653
  indent: options?.indent ?? 4
599
654
  };
600
655
  if (opts.preset === "bundle") {
@@ -627,19 +682,6 @@ var AndroidRenderer = class {
627
682
  // -----------------------------------------------------------------------
628
683
  // Flat structure grouping
629
684
  // -----------------------------------------------------------------------
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
685
  /**
644
686
  * Builds a flattened camelCase name from a token's path, stripping the
645
687
  * type prefix segment (which is already represented by the group object).
@@ -648,7 +690,7 @@ var AndroidRenderer = class {
648
690
  const path = token.path;
649
691
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
650
692
  const joined = withoutTypePrefix.join("_");
651
- return toKotlinIdentifier(joined);
693
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
652
694
  }
653
695
  // -----------------------------------------------------------------------
654
696
  // Rendering
@@ -660,22 +702,21 @@ var AndroidRenderer = class {
660
702
  return this.formatAsNested(tokens, options);
661
703
  }
662
704
  formatAsNested(tokens, options) {
705
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
663
706
  const tree = this.buildTokenTree(tokens);
664
- const tokenTypes = /* @__PURE__ */ new Set();
665
- this.collectTokenTypes(tree, tokenTypes);
666
- return this.buildFile(tokenTypes, options, (lines, vis) => {
707
+ return this.buildFile(tokenTypes, options, (lines) => {
667
708
  lines.push(`@Suppress("unused")`);
668
- lines.push(`${vis}object ${options.objectName} {`);
709
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
669
710
  this.renderTreeChildren(lines, tree, 1, options);
670
711
  lines.push("}");
671
712
  });
672
713
  }
673
714
  formatAsFlat(tokens, options) {
674
- const groups = this.groupTokensByType(tokens);
715
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
675
716
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
676
- return this.buildFile(tokenTypes, options, (lines, vis) => {
717
+ return this.buildFile(tokenTypes, options, (lines) => {
677
718
  lines.push(`@Suppress("unused")`);
678
- lines.push(`${vis}object ${options.objectName} {`);
719
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
679
720
  this.renderFlatGroups(lines, groups, 1, options);
680
721
  lines.push("}");
681
722
  });
@@ -686,9 +727,8 @@ var AndroidRenderer = class {
686
727
  */
687
728
  buildFile(tokenTypes, options, renderBody) {
688
729
  const imports = this.collectImports(tokenTypes, options);
689
- const vis = options.visibility ? `${options.visibility} ` : "";
690
730
  const lines = [];
691
- lines.push(this.buildFileHeader());
731
+ lines.push(buildGeneratedFileHeader());
692
732
  lines.push("");
693
733
  lines.push(`package ${options.packageName}`);
694
734
  lines.push("");
@@ -699,19 +739,18 @@ var AndroidRenderer = class {
699
739
  lines.push("");
700
740
  }
701
741
  if (tokenTypes.has("shadow")) {
702
- lines.push(...this.buildShadowTokenClass(vis, options));
742
+ lines.push(...this.buildShadowTokenClass(options));
703
743
  lines.push("");
704
744
  }
705
- renderBody(lines, vis);
745
+ renderBody(lines);
706
746
  lines.push("");
707
747
  return lines.join("\n");
708
748
  }
709
749
  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);
750
+ const groupIndent = indentStr(options.indent, baseDepth);
751
+ const valIndent = indentStr(options.indent, baseDepth + 1);
713
752
  for (const group of groups) {
714
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
753
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
715
754
  for (const token of group.tokens) {
716
755
  const kotlinName = this.buildFlatKotlinName(token);
717
756
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -719,23 +758,24 @@ var AndroidRenderer = class {
719
758
  if (token.$description) {
720
759
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
721
760
  }
722
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
761
+ lines.push(
762
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
763
+ );
723
764
  }
724
765
  lines.push(`${groupIndent}}`);
725
766
  lines.push("");
726
767
  }
727
768
  }
728
769
  renderTreeChildren(lines, node, depth, options) {
729
- const vis = options.visibility ? `${options.visibility} ` : "";
730
- const pad = indent(options.indent, depth);
770
+ const pad = indentStr(options.indent, depth);
731
771
  const entries = Array.from(node.children.entries());
732
772
  for (let idx = 0; idx < entries.length; idx++) {
733
773
  const [key, child] = entries[idx];
734
774
  if (child.token && child.children.size === 0) {
735
775
  this.renderLeaf(lines, key, child.token, depth, options);
736
776
  } else if (child.children.size > 0 && !child.token) {
737
- const objectName = toPascalCase(key);
738
- lines.push(`${pad}${vis}object ${objectName} {`);
777
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
778
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
739
779
  this.renderTreeChildren(lines, child, depth + 1, options);
740
780
  lines.push(`${pad}}`);
741
781
  if (idx < entries.length - 1) {
@@ -748,30 +788,23 @@ var AndroidRenderer = class {
748
788
  }
749
789
  }
750
790
  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);
791
+ const pad = indentStr(options.indent, depth);
792
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
754
793
  const kotlinValue = this.formatKotlinValue(token, options, depth);
755
794
  const annotation = this.typeAnnotationSuffix(token);
756
795
  if (token.$description) {
757
796
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
758
797
  }
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");
798
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
766
799
  }
767
800
  // -----------------------------------------------------------------------
768
801
  // Shadow data class
769
802
  // -----------------------------------------------------------------------
770
- buildShadowTokenClass(vis, options) {
771
- const i1 = indent(options.indent, 1);
803
+ buildShadowTokenClass(options) {
804
+ const i1 = indentStr(options.indent, 1);
772
805
  return [
773
806
  "@Immutable",
774
- `${vis}data class ShadowToken(`,
807
+ `${options.visPrefix}data class ShadowToken(`,
775
808
  `${i1}val color: Color,`,
776
809
  `${i1}val elevation: Dp,`,
777
810
  `${i1}val offsetX: Dp,`,
@@ -822,14 +855,6 @@ var AndroidRenderer = class {
822
855
  }
823
856
  return Array.from(imports).sort();
824
857
  }
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
858
  collectTokenTypesFromEntries(tokens) {
834
859
  const types = /* @__PURE__ */ new Set();
835
860
  for (const [, token] of Object.entries(tokens)) {
@@ -1056,9 +1081,8 @@ var AndroidRenderer = class {
1056
1081
  return map[name.toLowerCase()];
1057
1082
  }
1058
1083
  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`;
1084
+ if (isDurationObject(value)) {
1085
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
1062
1086
  }
1063
1087
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
1064
1088
  }
@@ -1076,8 +1100,8 @@ var AndroidRenderer = class {
1076
1100
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
1077
1101
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
1078
1102
  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);
1103
+ const propIndent = indentStr(options.indent, depth + 1);
1104
+ const closeIndent = indentStr(options.indent, depth);
1081
1105
  return [
1082
1106
  "ShadowToken(",
1083
1107
  `${propIndent}color = ${color},`,
@@ -1126,8 +1150,8 @@ var AndroidRenderer = class {
1126
1150
  if (parts.length === 0) {
1127
1151
  return "TextStyle()";
1128
1152
  }
1129
- const propIndent = indent(options.indent, depth + 1);
1130
- const closeIndent = indent(options.indent, depth);
1153
+ const propIndent = indentStr(options.indent, depth + 1);
1154
+ const closeIndent = indentStr(options.indent, depth);
1131
1155
  return `TextStyle(
1132
1156
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
1133
1157
  ${closeIndent})`;
@@ -1136,12 +1160,12 @@ ${closeIndent})`;
1136
1160
  // Output: standalone
1137
1161
  // -----------------------------------------------------------------------
1138
1162
  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
- }
1163
+ assertFileRequired(
1164
+ context.buildPath,
1165
+ context.output.file,
1166
+ context.output.name,
1167
+ "standalone Android"
1168
+ );
1145
1169
  const files = {};
1146
1170
  for (const { tokens, modifierInputs } of context.permutations) {
1147
1171
  const processedTokens = stripInternalMetadata(tokens);
@@ -1161,12 +1185,12 @@ ${closeIndent})`;
1161
1185
  // Output: bundle
1162
1186
  // -----------------------------------------------------------------------
1163
1187
  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
- }
1188
+ assertFileRequired(
1189
+ context.buildPath,
1190
+ context.output.file,
1191
+ context.output.name,
1192
+ "bundle Android"
1193
+ );
1170
1194
  const content = this.formatBundleContent(context, options);
1171
1195
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
1172
1196
  outputName: context.output.name,
@@ -1179,15 +1203,15 @@ ${closeIndent})`;
1179
1203
  }
1180
1204
  formatBundleContent(context, options) {
1181
1205
  const allTokenTypes = this.collectAllPermutationTypes(context);
1182
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
1183
- const i1 = indent(options.indent, 1);
1206
+ return this.buildFile(allTokenTypes, options, (lines) => {
1207
+ const i1 = indentStr(options.indent, 1);
1184
1208
  lines.push(`@Suppress("unused")`);
1185
- lines.push(`${vis}object ${options.objectName} {`);
1209
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
1186
1210
  for (let idx = 0; idx < context.permutations.length; idx++) {
1187
1211
  const { tokens, modifierInputs } = context.permutations[idx];
1188
1212
  const processedTokens = stripInternalMetadata(tokens);
1189
1213
  const permName = this.buildPermutationName(modifierInputs);
1190
- lines.push(`${i1}${vis}object ${permName} {`);
1214
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
1191
1215
  this.renderBundleTokens(lines, processedTokens, options, 2);
1192
1216
  lines.push(`${i1}}`);
1193
1217
  if (idx < context.permutations.length - 1) {
@@ -1198,20 +1222,17 @@ ${closeIndent})`;
1198
1222
  });
1199
1223
  }
1200
1224
  collectAllPermutationTypes(context) {
1201
- const allTokenTypes = /* @__PURE__ */ new Set();
1225
+ const types = /* @__PURE__ */ new Set();
1202
1226
  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
- }
1227
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
1228
+ types.add(t);
1208
1229
  }
1209
1230
  }
1210
- return allTokenTypes;
1231
+ return types;
1211
1232
  }
1212
1233
  renderBundleTokens(lines, tokens, options, baseDepth) {
1213
1234
  if (options.structure === "flat") {
1214
- const groups = this.groupTokensByType(tokens);
1235
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
1215
1236
  this.renderFlatGroups(lines, groups, baseDepth, options);
1216
1237
  return;
1217
1238
  }
@@ -1223,7 +1244,7 @@ ${closeIndent})`;
1223
1244
  if (values.length === 0) {
1224
1245
  return "Default";
1225
1246
  }
1226
- return values.map((v) => toPascalCase(v)).join("");
1247
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
1227
1248
  }
1228
1249
  };
1229
1250
  function androidRenderer() {
@@ -1243,19 +1264,19 @@ init_token_utils();
1243
1264
  // src/renderers/bundlers/css.ts
1244
1265
  init_errors();
1245
1266
  init_utils();
1267
+ var REF_PREFIX_SETS = "#/sets/";
1268
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
1246
1269
  var getSourceSet = (token) => {
1247
1270
  if (typeof token !== "object" || token === null) {
1248
1271
  return void 0;
1249
1272
  }
1250
- const maybe = token;
1251
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
1273
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
1252
1274
  };
1253
1275
  var getSourceModifier = (token) => {
1254
1276
  if (typeof token !== "object" || token === null) {
1255
1277
  return void 0;
1256
1278
  }
1257
- const maybe = token;
1258
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
1279
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
1259
1280
  };
1260
1281
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
1261
1282
  const baseItem = bundleData.find((item) => item.isBase);
@@ -1340,6 +1361,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
1340
1361
  return `/* Modifier: ${modifier}=${context} */
1341
1362
  ${css2}`;
1342
1363
  }
1364
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
1365
+ if (Object.keys(blockTokens).length === 0) {
1366
+ return;
1367
+ }
1368
+ for (const k of Object.keys(blockTokens)) {
1369
+ included.add(k);
1370
+ }
1371
+ blocks.push({ key, description, tokens: blockTokens });
1372
+ }
1343
1373
  function collectSetTokens(tokens, setName, included) {
1344
1374
  const result = {};
1345
1375
  for (const [name, token] of Object.entries(tokens)) {
@@ -1370,75 +1400,67 @@ function collectRemainder(tokens, included) {
1370
1400
  function buildSetLayerBlocks(tokens, resolver) {
1371
1401
  const blocks = [];
1372
1402
  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
1403
  for (const item of resolver.resolutionOrder) {
1383
1404
  const ref = item.$ref;
1384
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
1405
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
1385
1406
  continue;
1386
1407
  }
1387
- const setName = ref.slice("#/sets/".length);
1388
- addBlock(
1408
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1409
+ addLayerBlock(
1410
+ blocks,
1411
+ included,
1389
1412
  `Set: ${setName}`,
1390
1413
  collectSetTokens(tokens, setName, included),
1391
1414
  resolver.sets?.[setName]?.description
1392
1415
  );
1393
1416
  }
1394
- addBlock("Unattributed", collectRemainder(tokens, included));
1417
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1395
1418
  return blocks;
1396
1419
  }
1397
1420
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
1398
1421
  const blocks = [];
1399
1422
  const included = /* @__PURE__ */ new Set();
1400
1423
  const baseInputs = normalizeModifierInputs(baseModifierInputs);
1401
- const addBlock = (key, blockTokens, description) => {
1402
- if (Object.keys(blockTokens).length === 0) {
1403
- return;
1404
- }
1405
- for (const k of Object.keys(blockTokens)) {
1406
- included.add(k);
1407
- }
1408
- blocks.push({ key, description, tokens: blockTokens });
1409
- };
1410
1424
  for (const item of resolver.resolutionOrder) {
1411
1425
  const ref = item.$ref;
1412
1426
  if (typeof ref !== "string") {
1413
1427
  continue;
1414
1428
  }
1415
- if (ref.startsWith("#/sets/")) {
1416
- const setName = ref.slice("#/sets/".length);
1417
- addBlock(
1418
- `Set: ${setName}`,
1419
- collectSetTokens(tokens, setName, included),
1420
- resolver.sets?.[setName]?.description
1421
- );
1422
- continue;
1423
- }
1424
- if (ref.startsWith("#/modifiers/")) {
1425
- const modifierName = ref.slice("#/modifiers/".length);
1426
- const modifier = resolver.modifiers?.[modifierName];
1427
- const selectedContext = baseInputs[modifierName.toLowerCase()];
1428
- if (!modifier || !selectedContext) {
1429
- continue;
1430
- }
1431
- const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
1432
- addBlock(
1433
- `Modifier: ${modifierName}=${selectedContext} (default)`,
1434
- collectModifierTokens(tokens, expectedSource, included),
1435
- modifier.description
1436
- );
1437
- }
1429
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
1438
1430
  }
1439
- addBlock("Unattributed", collectRemainder(tokens, included));
1431
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1440
1432
  return blocks;
1441
1433
  }
1434
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
1435
+ if (ref.startsWith(REF_PREFIX_SETS)) {
1436
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1437
+ addLayerBlock(
1438
+ blocks,
1439
+ included,
1440
+ `Set: ${setName}`,
1441
+ collectSetTokens(tokens, setName, included),
1442
+ resolver.sets?.[setName]?.description
1443
+ );
1444
+ return;
1445
+ }
1446
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1447
+ return;
1448
+ }
1449
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
1450
+ const modifier = resolver.modifiers?.[modifierName];
1451
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
1452
+ if (!modifier || !selectedContext) {
1453
+ return;
1454
+ }
1455
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
1456
+ addLayerBlock(
1457
+ blocks,
1458
+ included,
1459
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
1460
+ collectModifierTokens(tokens, expectedSource, included),
1461
+ modifier.description
1462
+ );
1463
+ }
1442
1464
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
1443
1465
  const normalizedModifier = modifierName.toLowerCase();
1444
1466
  const normalizedContext = context.toLowerCase();
@@ -1453,6 +1475,36 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
1453
1475
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
1454
1476
  });
1455
1477
  }
1478
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
1479
+ if (!item) {
1480
+ return;
1481
+ }
1482
+ const key = stableInputsKey(item.modifierInputs);
1483
+ if (includedKeys.has(key)) {
1484
+ return;
1485
+ }
1486
+ includedKeys.add(key);
1487
+ ordered.push(item);
1488
+ }
1489
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
1490
+ for (const modifierName of orderedNames) {
1491
+ const modifierDef = modifiers[modifierName];
1492
+ if (!modifierDef) {
1493
+ continue;
1494
+ }
1495
+ const defaultValue = baseInputs[modifierName.toLowerCase()] ?? "";
1496
+ for (const ctx of Object.keys(modifierDef.contexts)) {
1497
+ if (defaultValue === ctx.toLowerCase()) {
1498
+ continue;
1499
+ }
1500
+ pushUniqueBundleItem(
1501
+ ordered,
1502
+ includedKeys,
1503
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
1504
+ );
1505
+ }
1506
+ }
1507
+ }
1456
1508
  function orderBundleData(bundleData, resolver, baseItem) {
1457
1509
  const modifiers = resolver.modifiers;
1458
1510
  if (!modifiers) {
@@ -1469,31 +1521,15 @@ function orderBundleData(bundleData, resolver, baseItem) {
1469
1521
  }
1470
1522
  const includedKeys = /* @__PURE__ */ new Set();
1471
1523
  const ordered = [];
1472
- const pushUnique = (item) => {
1473
- if (!item) {
1474
- return;
1475
- }
1476
- const key = stableInputsKey(item.modifierInputs);
1477
- if (includedKeys.has(key)) {
1478
- return;
1479
- }
1480
- includedKeys.add(key);
1481
- ordered.push(item);
1482
- };
1483
- pushUnique(baseItem);
1484
- for (const modifierName of orderedModifierNames) {
1485
- const modifierDef = modifiers[modifierName];
1486
- if (!modifierDef) {
1487
- continue;
1488
- }
1489
- const defaultValue = baseInputs[modifierName.toLowerCase()] ?? "";
1490
- for (const ctx of Object.keys(modifierDef.contexts)) {
1491
- if (defaultValue === ctx.toLowerCase()) {
1492
- continue;
1493
- }
1494
- pushUnique(findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs));
1495
- }
1496
- }
1524
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
1525
+ appendModifierPermutations(
1526
+ bundleData,
1527
+ modifiers,
1528
+ orderedModifierNames,
1529
+ baseInputs,
1530
+ ordered,
1531
+ includedKeys
1532
+ );
1497
1533
  return ordered.length > 0 ? ordered : bundleData;
1498
1534
  }
1499
1535
  function getOrderedModifierNames(resolver) {
@@ -1505,10 +1541,10 @@ function getOrderedModifierNames(resolver) {
1505
1541
  if (typeof ref !== "string") {
1506
1542
  continue;
1507
1543
  }
1508
- if (!ref.startsWith("#/modifiers/")) {
1544
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1509
1545
  continue;
1510
1546
  }
1511
- const name = ref.slice("#/modifiers/".length);
1547
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
1512
1548
  if (seen.has(name)) {
1513
1549
  continue;
1514
1550
  }
@@ -1579,24 +1615,22 @@ var CssRenderer = class _CssRenderer {
1579
1615
  ...options,
1580
1616
  referenceTokens: options?.referenceTokens ?? tokens
1581
1617
  };
1582
- const groups = this.groupTokens(tokens, opts);
1618
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
1583
1619
  const referenceTokens = opts.referenceTokens;
1584
1620
  const lines = [];
1585
- for (const [selector, groupTokens] of Object.entries(groups)) {
1586
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
1587
- }
1621
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
1588
1622
  const cssString = lines.join("");
1589
1623
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
1590
1624
  }
1591
1625
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
1592
- const indent2 = opts.minify ? "" : " ";
1626
+ const indent = opts.minify ? "" : " ";
1593
1627
  const newline = opts.minify ? "" : "\n";
1594
1628
  const space = opts.minify ? "" : " ";
1595
1629
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
1596
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
1630
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
1597
1631
  if (hasMediaQuery) {
1598
1632
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
1599
- lines.push(`${indent2}${selector}${space}{${newline}`);
1633
+ lines.push(`${indent}${selector}${space}{${newline}`);
1600
1634
  } else {
1601
1635
  lines.push(`${selector}${space}{${newline}`);
1602
1636
  }
@@ -1613,21 +1647,21 @@ var CssRenderer = class _CssRenderer {
1613
1647
  );
1614
1648
  }
1615
1649
  if (hasMediaQuery) {
1616
- lines.push(`${indent2}}${newline}`);
1650
+ lines.push(`${indent}}${newline}`);
1617
1651
  }
1618
1652
  lines.push(`}${newline}${newline}`);
1619
1653
  }
1620
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
1654
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
1621
1655
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
1622
1656
  if (token.$deprecated != null && token.$deprecated !== false) {
1623
1657
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
1624
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1658
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1625
1659
  }
1626
1660
  if (token.$description && token.$description !== "") {
1627
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1661
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1628
1662
  }
1629
1663
  for (const entry of entries) {
1630
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
1664
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
1631
1665
  }
1632
1666
  }
1633
1667
  async formatWithPrettier(css2) {
@@ -1642,15 +1676,6 @@ var CssRenderer = class _CssRenderer {
1642
1676
  return css2;
1643
1677
  }
1644
1678
  }
1645
- /**
1646
- * Group tokens by selector (for theme support)
1647
- */
1648
- groupTokens(tokens, options) {
1649
- const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
1650
- return {
1651
- [options.selector]: sortedTokens
1652
- };
1653
- }
1654
1679
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
1655
1680
  if (preserveReferences) {
1656
1681
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -1792,7 +1817,7 @@ var CssRenderer = class _CssRenderer {
1792
1817
  leaves.push({ path, value });
1793
1818
  return;
1794
1819
  }
1795
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
1820
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
1796
1821
  leaves.push({ path, value });
1797
1822
  return;
1798
1823
  }
@@ -1835,8 +1860,8 @@ var CssRenderer = class _CssRenderer {
1835
1860
  if (isDimensionObject(value)) {
1836
1861
  return dimensionObjectToString(value);
1837
1862
  }
1838
- if (this.isDurationObject(value)) {
1839
- return this.formatDurationValue(value);
1863
+ if (isDurationObject(value)) {
1864
+ return durationObjectToString(value);
1840
1865
  }
1841
1866
  if (typeof value === "string") {
1842
1867
  return value;
@@ -1899,15 +1924,6 @@ var CssRenderer = class _CssRenderer {
1899
1924
  isPrimitiveValue(value) {
1900
1925
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1901
1926
  }
1902
- isDurationObject(value) {
1903
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
1904
- }
1905
- formatDurationValue(value) {
1906
- if (typeof value === "string") {
1907
- return value;
1908
- }
1909
- return `${value.value}${value.unit}`;
1910
- }
1911
1927
  /**
1912
1928
  * Format token value for CSS
1913
1929
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -1928,8 +1944,8 @@ var CssRenderer = class _CssRenderer {
1928
1944
  return typeof value === "string" ? value : dimensionObjectToString(value);
1929
1945
  }
1930
1946
  if (type === "duration") {
1931
- if (this.isDurationObject(value)) {
1932
- return this.formatDurationValue(value);
1947
+ if (isDurationObject(value)) {
1948
+ return durationObjectToString(value);
1933
1949
  }
1934
1950
  if (typeof value === "string") {
1935
1951
  return value;
@@ -2019,16 +2035,16 @@ var CssRenderer = class _CssRenderer {
2019
2035
  */
2020
2036
  formatTransition(value) {
2021
2037
  const parts = [];
2022
- if (this.isDurationObject(value.duration)) {
2023
- parts.push(this.formatDurationValue(value.duration));
2038
+ if (isDurationObject(value.duration)) {
2039
+ parts.push(durationObjectToString(value.duration));
2024
2040
  } else if (value.duration != null) {
2025
2041
  parts.push(String(value.duration));
2026
2042
  }
2027
2043
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
2028
2044
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
2029
2045
  }
2030
- if (this.isDurationObject(value.delay)) {
2031
- parts.push(this.formatDurationValue(value.delay));
2046
+ if (isDurationObject(value.delay)) {
2047
+ parts.push(durationObjectToString(value.delay));
2032
2048
  } else if (value.delay != null) {
2033
2049
  parts.push(String(value.delay));
2034
2050
  }
@@ -2038,7 +2054,7 @@ var CssRenderer = class _CssRenderer {
2038
2054
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2039
2055
  tokens,
2040
2056
  modifierInputs,
2041
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2057
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2042
2058
  }));
2043
2059
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
2044
2060
  return await this.formatTokens(tokens, {
@@ -2048,12 +2064,12 @@ var CssRenderer = class _CssRenderer {
2048
2064
  });
2049
2065
  }
2050
2066
  async formatStandalone(context, options) {
2051
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2052
- if (!context.output.file && requiresFile) {
2053
- throw new ConfigurationError(
2054
- `Output "${context.output.name}": file is required for standalone CSS output`
2055
- );
2056
- }
2067
+ assertFileRequired(
2068
+ context.buildPath,
2069
+ context.output.file,
2070
+ context.output.name,
2071
+ "standalone CSS"
2072
+ );
2057
2073
  const files = {};
2058
2074
  for (const { tokens, modifierInputs } of context.permutations) {
2059
2075
  const { fileName, content } = await this.buildStandaloneFile(
@@ -2067,7 +2083,7 @@ var CssRenderer = class _CssRenderer {
2067
2083
  return { kind: "outputTree", files };
2068
2084
  }
2069
2085
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
2070
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
2086
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
2071
2087
  const { modifierName, modifierContext } = this.resolveModifierContext(
2072
2088
  modifierInputs,
2073
2089
  context,
@@ -2104,12 +2120,7 @@ var CssRenderer = class _CssRenderer {
2104
2120
  return { fileName, content };
2105
2121
  }
2106
2122
  async formatModifier(context, options) {
2107
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2108
- if (!context.output.file && requiresFile) {
2109
- throw new ConfigurationError(
2110
- `Output "${context.output.name}": file is required for modifier CSS output`
2111
- );
2112
- }
2123
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
2113
2124
  if (!context.resolver.modifiers) {
2114
2125
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
2115
2126
  }
@@ -2135,7 +2146,7 @@ var CssRenderer = class _CssRenderer {
2135
2146
  }
2136
2147
  async buildModifierBaseFile(context, options) {
2137
2148
  const basePermutation = context.permutations.find(
2138
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
2149
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
2139
2150
  );
2140
2151
  if (!basePermutation) {
2141
2152
  return void 0;
@@ -2148,25 +2159,40 @@ var CssRenderer = class _CssRenderer {
2148
2159
  if (setBlocks.length === 0) {
2149
2160
  return void 0;
2150
2161
  }
2162
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
2163
+ const content = await this.formatSetBlocksCss(
2164
+ setBlocks,
2165
+ basePermutation.tokens,
2166
+ selector,
2167
+ mediaQuery,
2168
+ options
2169
+ );
2170
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
2171
+ return { fileName, content };
2172
+ }
2173
+ resolveBaseModifierContext(context, options) {
2151
2174
  const modifiers = context.resolver.modifiers;
2152
2175
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
2153
2176
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
2154
2177
  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;
2178
+ return {
2179
+ selector: resolveSelector(
2180
+ options.selector,
2181
+ firstModifierName,
2182
+ firstModifierContext,
2183
+ true,
2184
+ baseModifierInputs
2185
+ ),
2186
+ mediaQuery: resolveMediaQuery(
2187
+ options.mediaQuery,
2188
+ firstModifierName,
2189
+ firstModifierContext,
2190
+ true,
2191
+ baseModifierInputs
2192
+ )
2193
+ };
2194
+ }
2195
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
2170
2196
  const cssBlocks = [];
2171
2197
  for (const block of setBlocks) {
2172
2198
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -2182,9 +2208,7 @@ var CssRenderer = class _CssRenderer {
2182
2208
  cssBlocks.push(`${header}
2183
2209
  ${css2}`);
2184
2210
  }
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 };
2211
+ return cssBlocks.join("\n");
2188
2212
  }
2189
2213
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
2190
2214
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -2261,13 +2285,6 @@ ${css2}`);
2261
2285
  }
2262
2286
  return { modifierName: "", modifierContext: "" };
2263
2287
  }
2264
- isBasePermutation(modifierInputs, defaults) {
2265
- const normalizedInputs = normalizeModifierInputs(modifierInputs);
2266
- const normalizedDefaults = normalizeModifierInputs(defaults);
2267
- return Object.entries(normalizedDefaults).every(
2268
- ([key, value]) => normalizedInputs[key] === value
2269
- );
2270
- }
2271
2288
  };
2272
2289
  function cssRenderer() {
2273
2290
  const rendererInstance = new CssRenderer();
@@ -2279,9 +2296,18 @@ function cssRenderer() {
2279
2296
  };
2280
2297
  }
2281
2298
 
2299
+ // src/tokens/types.ts
2300
+ function isShadowToken(token) {
2301
+ return token.$type === "shadow";
2302
+ }
2303
+ function isTypographyToken(token) {
2304
+ return token.$type === "typography";
2305
+ }
2306
+ function isBorderToken(token) {
2307
+ return token.$type === "border";
2308
+ }
2309
+
2282
2310
  // src/renderers/ios.ts
2283
- init_errors();
2284
- init_token_utils();
2285
2311
  init_utils();
2286
2312
  var toSRGB2 = converter("rgb");
2287
2313
  var toP32 = converter("p3");
@@ -2370,94 +2396,68 @@ var IosRenderer = class {
2370
2396
  return await this.formatStandalone(context, opts);
2371
2397
  }
2372
2398
  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
2399
  const access = options.accessLevel;
2380
- const groups = this.groupTokensByType(tokens);
2400
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
2381
2401
  const imports = this.collectImports(tokens);
2382
- const i1 = this.indentStr(options.indent, 1);
2383
- const i2 = this.indentStr(options.indent, 2);
2384
2402
  const staticPrefix = this.staticLetPrefix(options);
2385
2403
  const frozen = this.frozenPrefix(options);
2386
2404
  const lines = [];
2387
- lines.push(this.buildFileHeader());
2405
+ lines.push(buildGeneratedFileHeader());
2388
2406
  lines.push("");
2389
2407
  for (const imp of imports) {
2390
2408
  lines.push(`import ${imp}`);
2391
2409
  }
2392
2410
  lines.push(...this.buildStructDefinitions(tokens, access, options));
2411
+ this.pushTokenLayout(lines, groups, options, access, staticPrefix, frozen);
2412
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2413
+ if (options.structure !== "grouped") {
2414
+ lines.push("");
2415
+ }
2416
+ return lines.join("\n");
2417
+ }
2418
+ pushTokenLayout(lines, groups, options, access, staticPrefix, frozen) {
2419
+ const i1 = indentStr(options.indent, 1);
2420
+ const i2 = indentStr(options.indent, 2);
2421
+ if (options.structure === "grouped") {
2422
+ this.pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen);
2423
+ return;
2424
+ }
2393
2425
  lines.push("");
2394
2426
  lines.push(`${frozen}${access} enum ${options.enumName} {`);
2395
2427
  for (const group of groups) {
2396
2428
  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
- }
2429
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2408
2430
  lines.push(`${i1}}`);
2409
2431
  lines.push("");
2410
2432
  }
2411
2433
  lines.push("}");
2412
- lines.push(...this.buildViewExtensions(tokens, access, options));
2413
- lines.push("");
2414
- return lines.join("\n");
2415
2434
  }
2416
- formatAsGrouped(tokens, options) {
2417
- const access = options.accessLevel;
2435
+ pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen) {
2418
2436
  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
2437
  lines.push("");
2433
2438
  lines.push(`${frozen}${access} enum ${namespace} {}`);
2434
2439
  lines.push("");
2435
2440
  for (const group of groups) {
2436
2441
  lines.push(`${access} extension ${namespace} {`);
2437
2442
  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
- }
2443
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2449
2444
  lines.push(`${i1}}`);
2450
2445
  lines.push("}");
2451
2446
  lines.push("");
2452
2447
  }
2453
- lines.push(...this.buildViewExtensions(tokens, access, options));
2454
- return lines.join("\n");
2455
2448
  }
2456
- buildFileHeader() {
2457
- return [
2458
- "// Generated by Dispersa - do not edit manually",
2459
- "// https://github.com/timges/dispersa"
2460
- ].join("\n");
2449
+ pushTokenDeclarations(lines, tokens, options, access, indent, staticPrefix) {
2450
+ for (const token of tokens) {
2451
+ const swiftName = this.buildQualifiedSwiftName(token);
2452
+ const swiftValue = this.formatSwiftValue(token, options);
2453
+ const typeAnnotation = this.getTypeAnnotation(token);
2454
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2455
+ const docComment = this.buildDocComment(token, indent);
2456
+ if (docComment) {
2457
+ lines.push(docComment);
2458
+ }
2459
+ lines.push(`${indent}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2460
+ }
2461
2461
  }
2462
2462
  collectImports(tokens) {
2463
2463
  const imports = /* @__PURE__ */ new Set();
@@ -2472,24 +2472,11 @@ var IosRenderer = class {
2472
2472
  /**
2473
2473
  * Builds a `///` doc comment from a token's `$description`, if present.
2474
2474
  */
2475
- buildDocComment(token, indent2) {
2475
+ buildDocComment(token, indent) {
2476
2476
  if (!token.$description) {
2477
2477
  return void 0;
2478
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
- }));
2479
+ return `${indent}/// ${token.$description}`;
2493
2480
  }
2494
2481
  /**
2495
2482
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -2502,43 +2489,40 @@ var IosRenderer = class {
2502
2489
  const path = token.path;
2503
2490
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
2504
2491
  const joined = withoutTypePrefix.join("_");
2505
- return this.toSwiftIdentifier(joined);
2492
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
2506
2493
  }
2507
2494
  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]}))`;
2495
+ const { $type, $value: value } = token;
2496
+ switch ($type) {
2497
+ case "color":
2498
+ return this.formatColorValue(value, options);
2499
+ case "dimension":
2500
+ return this.formatDimensionValue(value);
2501
+ case "fontFamily":
2502
+ return this.formatFontFamilyValue(value);
2503
+ case "fontWeight":
2504
+ return this.formatFontWeightValue(value);
2505
+ case "duration":
2506
+ return this.formatDurationValue(value);
2507
+ case "shadow":
2508
+ return this.formatShadowValue(value, options);
2509
+ case "typography":
2510
+ return this.formatTypographyValue(value);
2511
+ case "border":
2512
+ return this.formatBorderValue(value, options);
2513
+ case "gradient":
2514
+ return this.formatGradientValue(value, options);
2515
+ case "number":
2516
+ return String(value);
2517
+ case "cubicBezier":
2518
+ if (Array.isArray(value) && value.length === 4) {
2519
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2520
+ }
2521
+ break;
2541
2522
  }
2523
+ return this.formatSwiftPrimitive(value);
2524
+ }
2525
+ formatSwiftPrimitive(value) {
2542
2526
  if (typeof value === "string") {
2543
2527
  return `"${this.escapeSwiftString(value)}"`;
2544
2528
  }
@@ -2571,9 +2555,7 @@ var IosRenderer = class {
2571
2555
  }
2572
2556
  formatDimensionValue(value) {
2573
2557
  if (isDimensionObject(value)) {
2574
- const dim = value;
2575
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2576
- return String(ptValue);
2558
+ return this.dimensionToPoints(value);
2577
2559
  }
2578
2560
  return String(value);
2579
2561
  }
@@ -2640,7 +2622,7 @@ var IosRenderer = class {
2640
2622
  return map[name.toLowerCase()];
2641
2623
  }
2642
2624
  formatDurationValue(value) {
2643
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
2625
+ if (isDurationObject(value)) {
2644
2626
  const dur = value;
2645
2627
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
2646
2628
  return String(seconds);
@@ -2689,9 +2671,7 @@ var IosRenderer = class {
2689
2671
  if (!isDimensionObject(typo.letterSpacing)) {
2690
2672
  return "0";
2691
2673
  }
2692
- const dim = typo.letterSpacing;
2693
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2694
- return String(ptValue);
2674
+ return this.dimensionToPoints(typo.letterSpacing);
2695
2675
  }
2696
2676
  extractLineSpacing(typo) {
2697
2677
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -2700,18 +2680,19 @@ var IosRenderer = class {
2700
2680
  if (!isDimensionObject(typo.fontSize)) {
2701
2681
  return "0";
2702
2682
  }
2703
- const dim = typo.fontSize;
2704
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
2683
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
2705
2684
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
2706
2685
  return String(lineHeightPt - basePt);
2707
2686
  }
2687
+ dimensionToNumericPoints(dim) {
2688
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
2689
+ }
2708
2690
  dimensionToPoints(dim) {
2709
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2710
- return String(ptValue);
2691
+ return String(this.dimensionToNumericPoints(dim));
2711
2692
  }
2712
2693
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
2713
2694
  dimensionToCGFloat(dim) {
2714
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2695
+ const ptValue = this.dimensionToNumericPoints(dim);
2715
2696
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
2716
2697
  }
2717
2698
  getTypeAnnotation(token) {
@@ -2730,21 +2711,12 @@ var IosRenderer = class {
2730
2711
  return void 0;
2731
2712
  }
2732
2713
  }
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
2714
  escapeSwiftString(str) {
2740
2715
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2741
2716
  }
2742
2717
  roundComponent(value) {
2743
2718
  return Math.round(value * 1e4) / 1e4;
2744
2719
  }
2745
- indentStr(width, level) {
2746
- return " ".repeat(width * level);
2747
- }
2748
2720
  /**
2749
2721
  * Returns the prefix for `static let` declarations.
2750
2722
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -2760,34 +2732,25 @@ var IosRenderer = class {
2760
2732
  structConformances(options) {
2761
2733
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
2762
2734
  }
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
2735
  /** Emits all struct definitions needed by the token set. */
2773
2736
  buildStructDefinitions(tokens, access, options) {
2774
2737
  const lines = [];
2775
- if (this.hasShadowTokens(tokens)) {
2738
+ if (Object.values(tokens).some(isShadowToken)) {
2776
2739
  lines.push("");
2777
2740
  lines.push(...this.buildShadowStyleStruct(access, options));
2778
2741
  }
2779
- if (this.hasTypographyTokens(tokens)) {
2742
+ if (Object.values(tokens).some(isTypographyToken)) {
2780
2743
  lines.push("");
2781
2744
  lines.push(...this.buildTypographyStyleStruct(access, options));
2782
2745
  }
2783
- if (this.hasBorderTokens(tokens)) {
2746
+ if (Object.values(tokens).some(isBorderToken)) {
2784
2747
  lines.push("");
2785
2748
  lines.push(...this.buildBorderStyleStruct(access, options));
2786
2749
  }
2787
2750
  return lines;
2788
2751
  }
2789
2752
  buildShadowStyleStruct(access, options) {
2790
- const i1 = this.indentStr(options.indent, 1);
2753
+ const i1 = indentStr(options.indent, 1);
2791
2754
  const conformances = this.structConformances(options);
2792
2755
  const frozen = this.frozenPrefix(options);
2793
2756
  return [
@@ -2801,7 +2764,7 @@ var IosRenderer = class {
2801
2764
  ];
2802
2765
  }
2803
2766
  buildTypographyStyleStruct(access, options) {
2804
- const i1 = this.indentStr(options.indent, 1);
2767
+ const i1 = indentStr(options.indent, 1);
2805
2768
  const conformances = this.structConformances(options);
2806
2769
  const frozen = this.frozenPrefix(options);
2807
2770
  return [
@@ -2813,7 +2776,7 @@ var IosRenderer = class {
2813
2776
  ];
2814
2777
  }
2815
2778
  buildBorderStyleStruct(access, options) {
2816
- const i1 = this.indentStr(options.indent, 1);
2779
+ const i1 = indentStr(options.indent, 1);
2817
2780
  const conformances = this.structConformances(options);
2818
2781
  const frozen = this.frozenPrefix(options);
2819
2782
  return [
@@ -2826,9 +2789,9 @@ var IosRenderer = class {
2826
2789
  /** Emits convenience View extensions for shadow and typography application. */
2827
2790
  buildViewExtensions(tokens, access, options) {
2828
2791
  const lines = [];
2829
- const i1 = this.indentStr(options.indent, 1);
2830
- const i2 = this.indentStr(options.indent, 2);
2831
- if (this.hasShadowTokens(tokens)) {
2792
+ const i1 = indentStr(options.indent, 1);
2793
+ const i2 = indentStr(options.indent, 2);
2794
+ if (Object.values(tokens).some(isShadowToken)) {
2832
2795
  lines.push("");
2833
2796
  lines.push(`${access} extension View {`);
2834
2797
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -2838,7 +2801,7 @@ var IosRenderer = class {
2838
2801
  lines.push(`${i1}}`);
2839
2802
  lines.push("}");
2840
2803
  }
2841
- if (this.hasTypographyTokens(tokens)) {
2804
+ if (Object.values(tokens).some(isTypographyToken)) {
2842
2805
  lines.push("");
2843
2806
  lines.push(`${access} extension View {`);
2844
2807
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -2870,12 +2833,12 @@ var IosRenderer = class {
2870
2833
  return `Gradient(stops: [${stops.join(", ")}])`;
2871
2834
  }
2872
2835
  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
- }
2836
+ assertFileRequired(
2837
+ context.buildPath,
2838
+ context.output.file,
2839
+ context.output.name,
2840
+ "standalone iOS"
2841
+ );
2879
2842
  const files = {};
2880
2843
  for (const { tokens, modifierInputs } of context.permutations) {
2881
2844
  const processedTokens = stripInternalMetadata(tokens);
@@ -2904,7 +2867,6 @@ function iosRenderer() {
2904
2867
 
2905
2868
  // src/renderers/js-module.ts
2906
2869
  init_utils();
2907
- init_errors();
2908
2870
  init_token_utils();
2909
2871
  var JsModuleRenderer = class {
2910
2872
  async format(context, options) {
@@ -2920,18 +2882,13 @@ var JsModuleRenderer = class {
2920
2882
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2921
2883
  tokens: stripInternalMetadata(tokens),
2922
2884
  modifierInputs,
2923
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2885
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2924
2886
  }));
2925
2887
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
2926
2888
  return await this.formatTokens(tokens, opts);
2927
2889
  });
2928
2890
  }
2929
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2930
- if (!context.output.file && requiresFile) {
2931
- throw new ConfigurationError(
2932
- `Output "${context.output.name}": file is required for JS module output`
2933
- );
2934
- }
2891
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
2935
2892
  const files = {};
2936
2893
  for (const { tokens, modifierInputs } of context.permutations) {
2937
2894
  const cleanTokens = stripInternalMetadata(tokens);
@@ -2985,42 +2942,18 @@ var JsModuleRenderer = class {
2985
2942
  lines.push(`export default ${varName}`);
2986
2943
  return lines;
2987
2944
  }
2988
- /**
2989
- * Convert tokens to plain object with flat or nested structure
2990
- */
2991
2945
  tokensToPlainObject(tokens, structure) {
2946
+ if (structure === "nested") {
2947
+ return buildNestedTokenObject(tokens, (token) => token.$value);
2948
+ }
2992
2949
  const result = {};
2993
- if (structure === "flat") {
2994
- for (const [name, token] of getSortedTokenEntries(tokens)) {
2995
- result[name] = token.$value;
2996
- }
2997
- } else {
2998
- for (const [, token] of getSortedTokenEntries(tokens)) {
2999
- const parts = token.path;
3000
- let current = result;
3001
- for (let i = 0; i < parts.length - 1; i++) {
3002
- const part = parts[i];
3003
- if (part == null) {
3004
- continue;
3005
- }
3006
- if (!(part in current)) {
3007
- current[part] = {};
3008
- }
3009
- current = current[part];
3010
- }
3011
- const lastPart = parts[parts.length - 1];
3012
- if (lastPart != null) {
3013
- current[lastPart] = token.$value;
3014
- }
3015
- }
2950
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
2951
+ result[name] = token.$value;
3016
2952
  }
3017
2953
  return result;
3018
2954
  }
3019
- /**
3020
- * Add object properties to lines
3021
- */
3022
- addObjectProperties(lines, obj, indent2) {
3023
- const indentStr = " ".repeat(indent2);
2955
+ addObjectProperties(lines, obj, indent) {
2956
+ const indentStr2 = " ".repeat(indent);
3024
2957
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
3025
2958
  for (let i = 0; i < entries.length; i++) {
3026
2959
  const entry = entries[i];
@@ -3029,14 +2962,16 @@ var JsModuleRenderer = class {
3029
2962
  }
3030
2963
  const [key, value] = entry;
3031
2964
  const isLast = i === entries.length - 1;
3032
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3033
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
3034
- this.addObjectProperties(lines, value, indent2 + 1);
3035
- lines.push(`${indentStr}}${isLast ? "" : ","}`);
3036
- } else {
3037
- const valueStr = JSON.stringify(value);
3038
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueStr}${isLast ? "" : ","}`);
2965
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
2966
+ if (!isNestedObject) {
2967
+ lines.push(
2968
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
2969
+ );
2970
+ continue;
3039
2971
  }
2972
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
2973
+ this.addObjectProperties(lines, value, indent + 1);
2974
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
3040
2975
  }
3041
2976
  }
3042
2977
  /**
@@ -3048,9 +2983,6 @@ var JsModuleRenderer = class {
3048
2983
  }
3049
2984
  return `"${key}"`;
3050
2985
  }
3051
- isBasePermutation(modifierInputs, defaults) {
3052
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3053
- }
3054
2986
  };
3055
2987
  function jsRenderer() {
3056
2988
  const rendererInstance = new JsModuleRenderer();
@@ -3064,7 +2996,6 @@ function jsRenderer() {
3064
2996
 
3065
2997
  // src/renderers/json.ts
3066
2998
  init_utils();
3067
- init_errors();
3068
2999
  init_token_utils();
3069
3000
  var JsonRenderer = class {
3070
3001
  async format(context, options) {
@@ -3079,18 +3010,13 @@ var JsonRenderer = class {
3079
3010
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3080
3011
  tokens: stripInternalMetadata(tokens),
3081
3012
  modifierInputs,
3082
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3013
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3083
3014
  }));
3084
3015
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
3085
3016
  return await this.formatTokens(tokens, opts);
3086
3017
  });
3087
3018
  }
3088
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
3089
- if (!context.output.file && requiresFile) {
3090
- throw new ConfigurationError(
3091
- `Output "${context.output.name}": file is required for JSON output`
3092
- );
3093
- }
3019
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
3094
3020
  const files = {};
3095
3021
  for (const { tokens, modifierInputs } of context.permutations) {
3096
3022
  const processedTokens = stripInternalMetadata(tokens);
@@ -3150,55 +3076,11 @@ var JsonRenderer = class {
3150
3076
  }
3151
3077
  return result;
3152
3078
  }
3153
- /**
3154
- * Nest tokens by path (values only)
3155
- */
3156
3079
  nestValues(tokens) {
3157
- const result = {};
3158
- for (const [, token] of getSortedTokenEntries(tokens)) {
3159
- const parts = token.path;
3160
- let current = result;
3161
- for (let i = 0; i < parts.length - 1; i++) {
3162
- const part = parts[i];
3163
- if (part === null || part === void 0) {
3164
- continue;
3165
- }
3166
- if (!(part in current)) {
3167
- current[part] = {};
3168
- }
3169
- current = current[part];
3170
- }
3171
- const lastPart = parts[parts.length - 1];
3172
- if (lastPart !== null && lastPart !== void 0) {
3173
- current[lastPart] = token.$value;
3174
- }
3175
- }
3176
- return result;
3080
+ return buildNestedTokenObject(tokens, (token) => token.$value);
3177
3081
  }
3178
- /**
3179
- * Nest tokens by path (with metadata)
3180
- */
3181
3082
  nestTokens(tokens) {
3182
- const result = {};
3183
- for (const [, token] of getSortedTokenEntries(tokens)) {
3184
- const parts = token.path;
3185
- let current = result;
3186
- for (let i = 0; i < parts.length - 1; i++) {
3187
- const part = parts[i];
3188
- if (part === null || part === void 0) {
3189
- continue;
3190
- }
3191
- if (!(part in current)) {
3192
- current[part] = {};
3193
- }
3194
- current = current[part];
3195
- }
3196
- const lastPart = parts[parts.length - 1];
3197
- if (lastPart !== null && lastPart !== void 0) {
3198
- current[lastPart] = this.serializeToken(token);
3199
- }
3200
- }
3201
- return result;
3083
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
3202
3084
  }
3203
3085
  serializeToken(token) {
3204
3086
  return {
@@ -3209,9 +3091,6 @@ var JsonRenderer = class {
3209
3091
  ...token.$extensions != null && { $extensions: token.$extensions }
3210
3092
  };
3211
3093
  }
3212
- isBasePermutation(modifierInputs, defaults) {
3213
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3214
- }
3215
3094
  };
3216
3095
  function jsonRenderer() {
3217
3096
  const rendererInstance = new JsonRenderer();
@@ -3224,7 +3103,6 @@ function jsonRenderer() {
3224
3103
  }
3225
3104
 
3226
3105
  // src/renderers/tailwind.ts
3227
- init_errors();
3228
3106
  init_token_utils();
3229
3107
 
3230
3108
  // src/renderers/bundlers/tailwind.ts
@@ -3253,6 +3131,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
3253
3131
  }
3254
3132
  return cssBlocks.join("\n");
3255
3133
  }
3134
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
3135
+ const normalized = normalizeModifierInputs(modifierInputs);
3136
+ return {
3137
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
3138
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
3139
+ };
3140
+ }
3256
3141
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
3257
3142
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
3258
3143
  if (differenceCount > 1) {
@@ -3265,19 +3150,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
3265
3150
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
3266
3151
  const [modifier, context] = parseModifierSource(expectedSource);
3267
3152
  const cleanTokens = stripInternalMetadata(tokensToInclude);
3268
- const selector = resolveSelector(
3269
- options.selector,
3153
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
3154
+ options,
3270
3155
  modifier,
3271
3156
  context,
3272
- false,
3273
- normalizeModifierInputs(modifierInputs)
3274
- );
3275
- const mediaQuery = resolveMediaQuery(
3276
- options.mediaQuery,
3277
- modifier,
3278
- context,
3279
- false,
3280
- normalizeModifierInputs(modifierInputs)
3157
+ modifierInputs
3281
3158
  );
3282
3159
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
3283
3160
  return `/* Modifier: ${modifier}=${context} */
@@ -3366,7 +3243,7 @@ var TailwindRenderer = class {
3366
3243
  */
3367
3244
  async formatTokens(tokens, options) {
3368
3245
  const lines = [];
3369
- const indent2 = options.minify ? "" : " ";
3246
+ const indent = options.minify ? "" : " ";
3370
3247
  const newline = options.minify ? "" : "\n";
3371
3248
  const space = options.minify ? "" : " ";
3372
3249
  if (options.includeImport) {
@@ -3388,7 +3265,7 @@ var TailwindRenderer = class {
3388
3265
  for (const [, token] of getSortedTokenEntries(tokens)) {
3389
3266
  const varName = this.buildVariableName(token);
3390
3267
  const varValue = this.formatValue(token);
3391
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
3268
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
3392
3269
  }
3393
3270
  lines.push(`}${newline}`);
3394
3271
  const cssString = lines.join("");
@@ -3399,15 +3276,15 @@ var TailwindRenderer = class {
3399
3276
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
3400
3277
  */
3401
3278
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
3402
- const indent2 = minify ? "" : " ";
3279
+ const indent = minify ? "" : " ";
3403
3280
  const newline = minify ? "" : "\n";
3404
3281
  const space = minify ? "" : " ";
3405
3282
  const hasMediaQuery = mediaQuery !== "";
3406
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
3283
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
3407
3284
  const lines = [];
3408
3285
  if (hasMediaQuery) {
3409
3286
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
3410
- lines.push(`${indent2}${selector}${space}{${newline}`);
3287
+ lines.push(`${indent}${selector}${space}{${newline}`);
3411
3288
  } else {
3412
3289
  lines.push(`${selector}${space}{${newline}`);
3413
3290
  }
@@ -3417,7 +3294,7 @@ var TailwindRenderer = class {
3417
3294
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
3418
3295
  }
3419
3296
  if (hasMediaQuery) {
3420
- lines.push(`${indent2}}${newline}`);
3297
+ lines.push(`${indent}}${newline}`);
3421
3298
  lines.push(`}${newline}`);
3422
3299
  } else {
3423
3300
  lines.push(`}${newline}`);
@@ -3444,8 +3321,8 @@ var TailwindRenderer = class {
3444
3321
  if (token.$type === "dimension" && isDimensionObject(value)) {
3445
3322
  return dimensionObjectToString(value);
3446
3323
  }
3447
- if (token.$type === "duration" && this.isDurationObject(value)) {
3448
- return `${value.value}${value.unit}`;
3324
+ if (token.$type === "duration" && isDurationObject(value)) {
3325
+ return durationObjectToString(value);
3449
3326
  }
3450
3327
  if (token.$type === "fontFamily") {
3451
3328
  if (Array.isArray(value)) {
@@ -3500,9 +3377,6 @@ var TailwindRenderer = class {
3500
3377
  }
3501
3378
  return parts.join(" ");
3502
3379
  }
3503
- isDurationObject(value) {
3504
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
3505
- }
3506
3380
  async formatWithPrettier(css2) {
3507
3381
  try {
3508
3382
  return await prettier.format(css2, {
@@ -3519,7 +3393,7 @@ var TailwindRenderer = class {
3519
3393
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3520
3394
  tokens,
3521
3395
  modifierInputs,
3522
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3396
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3523
3397
  }));
3524
3398
  return await bundleAsTailwind(
3525
3399
  bundleData,
@@ -3529,12 +3403,12 @@ var TailwindRenderer = class {
3529
3403
  );
3530
3404
  }
3531
3405
  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
- }
3406
+ assertFileRequired(
3407
+ context.buildPath,
3408
+ context.output.file,
3409
+ context.output.name,
3410
+ "standalone Tailwind"
3411
+ );
3538
3412
  const files = {};
3539
3413
  for (const { tokens, modifierInputs } of context.permutations) {
3540
3414
  const processedTokens = stripInternalMetadata(tokens);
@@ -3550,11 +3424,6 @@ var TailwindRenderer = class {
3550
3424
  }
3551
3425
  return outputTree(files);
3552
3426
  }
3553
- isBasePermutation(modifierInputs, defaults) {
3554
- return Object.entries(defaults).every(
3555
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
3556
- );
3557
- }
3558
3427
  };
3559
3428
  function tailwindRenderer() {
3560
3429
  const rendererInstance = new TailwindRenderer();