dispersa 0.4.1 → 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.cjs CHANGED
@@ -52,13 +52,12 @@ function formatDeprecationMessage(token, description = "", format = "bracket") {
52
52
  }
53
53
  const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
54
54
  if (format === "comment") {
55
- const msg = deprecationMsg ? ` ${deprecationMsg}` : "";
56
- return `DEPRECATED${msg}`;
57
- } else {
58
- const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
59
- const prefix = `[DEPRECATED${msg}]`;
60
- return description ? `${prefix} ${description}` : prefix;
55
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
56
+ return `DEPRECATED${msg2}`;
61
57
  }
58
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
59
+ const prefix = `[DEPRECATED${msg}]`;
60
+ return description ? `${prefix} ${description}` : prefix;
62
61
  }
63
62
  function stripInternalTokenMetadata(tokens) {
64
63
  const cleaned = {};
@@ -71,6 +70,30 @@ function stripInternalTokenMetadata(tokens) {
71
70
  function getSortedTokenEntries(tokens) {
72
71
  return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
73
72
  }
73
+ function buildNestedTokenObject(tokens, extractValue) {
74
+ const result = {};
75
+ for (const [, token] of getSortedTokenEntries(tokens)) {
76
+ setNestedValue(result, token.path, extractValue(token));
77
+ }
78
+ return result;
79
+ }
80
+ function setNestedValue(root, path, value) {
81
+ let current = root;
82
+ for (let i = 0; i < path.length - 1; i++) {
83
+ const part = path[i];
84
+ if (part == null) {
85
+ continue;
86
+ }
87
+ if (!(part in current)) {
88
+ current[part] = {};
89
+ }
90
+ current = current[part];
91
+ }
92
+ const lastPart = path[path.length - 1];
93
+ if (lastPart != null) {
94
+ current[lastPart] = value;
95
+ }
96
+ }
74
97
  function getPureAliasReferenceName(value) {
75
98
  if (typeof value !== "string") {
76
99
  return void 0;
@@ -90,6 +113,35 @@ function sanitizeDataAttributeName(value) {
90
113
  function escapeCssString(value) {
91
114
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
92
115
  }
116
+ function groupTokensByType(tokens, typeGroupMap) {
117
+ const groupMap = /* @__PURE__ */ new Map();
118
+ for (const [, token] of getSortedTokenEntries(tokens)) {
119
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
120
+ const existing = groupMap.get(groupName) ?? [];
121
+ existing.push(token);
122
+ groupMap.set(groupName, existing);
123
+ }
124
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
125
+ name,
126
+ tokens: groupTokens
127
+ }));
128
+ }
129
+ function indentStr(width, level) {
130
+ return " ".repeat(width * level);
131
+ }
132
+ function buildGeneratedFileHeader() {
133
+ return [
134
+ "// Generated by Dispersa - do not edit manually",
135
+ "// https://github.com/timges/dispersa"
136
+ ].join("\n");
137
+ }
138
+ function toSafeIdentifier(name, keywords, capitalize) {
139
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
140
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
141
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
142
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
143
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
144
+ }
93
145
  function normalizeModifierInputs(inputs) {
94
146
  const normalized = {};
95
147
  for (const [key, value] of Object.entries(inputs)) {
@@ -97,6 +149,14 @@ function normalizeModifierInputs(inputs) {
97
149
  }
98
150
  return normalized;
99
151
  }
152
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
153
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
154
+ if (!outputFile && requiresFile) {
155
+ throw new ConfigurationError(
156
+ `Output "${outputName}": file is required for ${presetLabel} output`
157
+ );
158
+ }
159
+ }
100
160
  function buildStablePermutationKey(modifierInputs, dimensions) {
101
161
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
102
162
  }
@@ -146,14 +206,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
146
206
  }
147
207
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
148
208
  }
149
- function buildInMemoryOutputKey(params) {
150
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
209
+ function isBasePermutation(modifierInputs, defaults) {
151
210
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
152
211
  const normalizedDefaults = normalizeModifierInputs(defaults);
153
- const isBase = Object.entries(normalizedDefaults).every(
154
- ([key, value]) => normalizedInputs[key] === value
212
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
213
+ }
214
+ function buildInMemoryOutputKey(params) {
215
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
216
+ const permutationKey = generatePermutationKey(
217
+ modifierInputs,
218
+ resolver,
219
+ isBasePermutation(modifierInputs, defaults)
155
220
  );
156
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
157
221
  return `${outputName}-${permutationKey}.${extension}`;
158
222
  }
159
223
  function buildMetadata(resolver) {
@@ -267,6 +331,7 @@ function resolveFileName(fileName, modifierInputs) {
267
331
  }
268
332
  var init_utils = __esm({
269
333
  "src/renderers/bundlers/utils.ts"() {
334
+ init_errors();
270
335
  init_token_utils();
271
336
  }
272
337
  });
@@ -276,36 +341,38 @@ var js_exports = {};
276
341
  __export(js_exports, {
277
342
  bundleAsJsModule: () => bundleAsJsModule
278
343
  });
344
+ function updateStringTracking(state, char) {
345
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
346
+ if (!state.inString) {
347
+ state.inString = true;
348
+ state.stringChar = char;
349
+ } else if (char === state.stringChar) {
350
+ state.inString = false;
351
+ state.stringChar = "";
352
+ }
353
+ }
354
+ state.escaped = !state.escaped && char === "\\";
355
+ }
279
356
  function extractObjectFromJsModule(formattedJs) {
280
357
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
281
358
  if (!assignmentMatch) {
282
359
  return "{}";
283
360
  }
284
361
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
362
+ const state = { inString: false, stringChar: "", escaped: false };
285
363
  let braceCount = 0;
286
- let inString = false;
287
- let stringChar = "";
288
- let escaped = false;
289
364
  for (let i = startIndex; i < formattedJs.length; i++) {
290
365
  const char = formattedJs[i];
291
- if (!escaped && (char === '"' || char === "'" || char === "`")) {
292
- if (!inString) {
293
- inString = true;
294
- stringChar = char;
295
- } else if (char === stringChar) {
296
- inString = false;
297
- stringChar = "";
298
- }
366
+ updateStringTracking(state, char);
367
+ if (state.inString) {
368
+ continue;
299
369
  }
300
- escaped = !escaped && char === "\\";
301
- if (!inString) {
302
- if (char === "{") {
303
- braceCount++;
304
- } else if (char === "}") {
305
- braceCount--;
306
- if (braceCount === 0) {
307
- return formattedJs.substring(startIndex, i + 1);
308
- }
370
+ if (char === "{") {
371
+ braceCount++;
372
+ } else if (char === "}") {
373
+ braceCount--;
374
+ if (braceCount === 0) {
375
+ return formattedJs.substring(startIndex, i + 1);
309
376
  }
310
377
  }
311
378
  }
@@ -402,22 +469,19 @@ __export(json_exports, {
402
469
  bundleAsJson: () => bundleAsJson
403
470
  });
404
471
  async function bundleAsJson(bundleData, resolver, formatTokens) {
472
+ if (!formatTokens) {
473
+ throw new ConfigurationError("JSON formatter was not provided");
474
+ }
405
475
  const metadata = buildMetadata(resolver);
406
476
  const tokens = {};
407
477
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
408
478
  const cleanTokens = stripInternalMetadata(tokenSet);
409
- if (!formatTokens) {
410
- throw new ConfigurationError("JSON formatter was not provided");
411
- }
412
479
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
413
480
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
414
481
  const themeJson = await formatTokens(cleanTokens);
415
482
  tokens[key] = JSON.parse(themeJson);
416
483
  }
417
- const bundle = {
418
- _meta: metadata,
419
- tokens
420
- };
484
+ const bundle = { _meta: metadata, tokens };
421
485
  return JSON.stringify(bundle, null, 2);
422
486
  }
423
487
  var init_json = __esm({
@@ -483,7 +547,7 @@ function colorObjectToHex(color) {
483
547
  return culori.formatHex(culoriColor);
484
548
  }
485
549
 
486
- // src/processing/processors/transforms/built-in/dimension-converter.ts
550
+ // src/processing/transforms/built-in/dimension-converter.ts
487
551
  function isDimensionObject(value) {
488
552
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
489
553
  }
@@ -491,6 +555,14 @@ function dimensionObjectToString(dimension) {
491
555
  return `${dimension.value}${dimension.unit}`;
492
556
  }
493
557
 
558
+ // src/processing/transforms/built-in/duration-converter.ts
559
+ function isDurationObject(value) {
560
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
561
+ }
562
+ function durationObjectToString(duration) {
563
+ return `${duration.value}${duration.unit}`;
564
+ }
565
+
494
566
  // src/renderers/android.ts
495
567
  init_errors();
496
568
  init_token_utils();
@@ -552,9 +624,6 @@ function resolveColorFormat(format) {
552
624
  }
553
625
  return "argb_hex";
554
626
  }
555
- function indent(width, level) {
556
- return " ".repeat(width * level);
557
- }
558
627
  function escapeKotlinString(str) {
559
628
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
560
629
  }
@@ -570,22 +639,6 @@ function roundComponent(value) {
570
639
  function toResourceName(family) {
571
640
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
572
641
  }
573
- function toPascalCase(name) {
574
- const pascal = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
575
- const result = pascal.charAt(0).toUpperCase() + pascal.slice(1);
576
- if (/^\d/.test(result)) {
577
- return `_${result}`;
578
- }
579
- return KOTLIN_KEYWORDS.has(result.charAt(0).toLowerCase() + result.slice(1)) ? `\`${result}\`` : result;
580
- }
581
- function toKotlinIdentifier(name) {
582
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
583
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
584
- if (/^\d/.test(identifier)) {
585
- return `_${identifier}`;
586
- }
587
- return KOTLIN_KEYWORDS.has(identifier) ? `\`${identifier}\`` : identifier;
588
- }
589
642
  var AndroidRenderer = class {
590
643
  async format(context, options) {
591
644
  if (!options?.packageName) {
@@ -593,6 +646,7 @@ var AndroidRenderer = class {
593
646
  `Output "${context.output.name}": packageName is required for Android output`
594
647
  );
595
648
  }
649
+ const visibility = options?.visibility;
596
650
  const opts = {
597
651
  preset: options?.preset ?? "standalone",
598
652
  packageName: options.packageName,
@@ -600,7 +654,8 @@ var AndroidRenderer = class {
600
654
  colorFormat: resolveColorFormat(options?.colorFormat),
601
655
  colorSpace: options?.colorSpace ?? "sRGB",
602
656
  structure: options?.structure ?? "nested",
603
- visibility: options?.visibility,
657
+ visibility,
658
+ visPrefix: visibility ? `${visibility} ` : "",
604
659
  indent: options?.indent ?? 4
605
660
  };
606
661
  if (opts.preset === "bundle") {
@@ -633,19 +688,6 @@ var AndroidRenderer = class {
633
688
  // -----------------------------------------------------------------------
634
689
  // Flat structure grouping
635
690
  // -----------------------------------------------------------------------
636
- groupTokensByType(tokens) {
637
- const groupMap = /* @__PURE__ */ new Map();
638
- for (const [, token] of getSortedTokenEntries(tokens)) {
639
- const groupName = KOTLIN_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
640
- const existing = groupMap.get(groupName) ?? [];
641
- existing.push(token);
642
- groupMap.set(groupName, existing);
643
- }
644
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
645
- name,
646
- tokens: groupTokens
647
- }));
648
- }
649
691
  /**
650
692
  * Builds a flattened camelCase name from a token's path, stripping the
651
693
  * type prefix segment (which is already represented by the group object).
@@ -654,7 +696,7 @@ var AndroidRenderer = class {
654
696
  const path = token.path;
655
697
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
656
698
  const joined = withoutTypePrefix.join("_");
657
- return toKotlinIdentifier(joined);
699
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
658
700
  }
659
701
  // -----------------------------------------------------------------------
660
702
  // Rendering
@@ -666,22 +708,21 @@ var AndroidRenderer = class {
666
708
  return this.formatAsNested(tokens, options);
667
709
  }
668
710
  formatAsNested(tokens, options) {
711
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
669
712
  const tree = this.buildTokenTree(tokens);
670
- const tokenTypes = /* @__PURE__ */ new Set();
671
- this.collectTokenTypes(tree, tokenTypes);
672
- return this.buildFile(tokenTypes, options, (lines, vis) => {
713
+ return this.buildFile(tokenTypes, options, (lines) => {
673
714
  lines.push(`@Suppress("unused")`);
674
- lines.push(`${vis}object ${options.objectName} {`);
715
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
675
716
  this.renderTreeChildren(lines, tree, 1, options);
676
717
  lines.push("}");
677
718
  });
678
719
  }
679
720
  formatAsFlat(tokens, options) {
680
- const groups = this.groupTokensByType(tokens);
721
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
681
722
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
682
- return this.buildFile(tokenTypes, options, (lines, vis) => {
723
+ return this.buildFile(tokenTypes, options, (lines) => {
683
724
  lines.push(`@Suppress("unused")`);
684
- lines.push(`${vis}object ${options.objectName} {`);
725
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
685
726
  this.renderFlatGroups(lines, groups, 1, options);
686
727
  lines.push("}");
687
728
  });
@@ -692,9 +733,8 @@ var AndroidRenderer = class {
692
733
  */
693
734
  buildFile(tokenTypes, options, renderBody) {
694
735
  const imports = this.collectImports(tokenTypes, options);
695
- const vis = options.visibility ? `${options.visibility} ` : "";
696
736
  const lines = [];
697
- lines.push(this.buildFileHeader());
737
+ lines.push(buildGeneratedFileHeader());
698
738
  lines.push("");
699
739
  lines.push(`package ${options.packageName}`);
700
740
  lines.push("");
@@ -705,19 +745,18 @@ var AndroidRenderer = class {
705
745
  lines.push("");
706
746
  }
707
747
  if (tokenTypes.has("shadow")) {
708
- lines.push(...this.buildShadowTokenClass(vis, options));
748
+ lines.push(...this.buildShadowTokenClass(options));
709
749
  lines.push("");
710
750
  }
711
- renderBody(lines, vis);
751
+ renderBody(lines);
712
752
  lines.push("");
713
753
  return lines.join("\n");
714
754
  }
715
755
  renderFlatGroups(lines, groups, baseDepth, options) {
716
- const vis = options.visibility ? `${options.visibility} ` : "";
717
- const groupIndent = indent(options.indent, baseDepth);
718
- const valIndent = indent(options.indent, baseDepth + 1);
756
+ const groupIndent = indentStr(options.indent, baseDepth);
757
+ const valIndent = indentStr(options.indent, baseDepth + 1);
719
758
  for (const group of groups) {
720
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
759
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
721
760
  for (const token of group.tokens) {
722
761
  const kotlinName = this.buildFlatKotlinName(token);
723
762
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -725,23 +764,24 @@ var AndroidRenderer = class {
725
764
  if (token.$description) {
726
765
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
727
766
  }
728
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
767
+ lines.push(
768
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
769
+ );
729
770
  }
730
771
  lines.push(`${groupIndent}}`);
731
772
  lines.push("");
732
773
  }
733
774
  }
734
775
  renderTreeChildren(lines, node, depth, options) {
735
- const vis = options.visibility ? `${options.visibility} ` : "";
736
- const pad = indent(options.indent, depth);
776
+ const pad = indentStr(options.indent, depth);
737
777
  const entries = Array.from(node.children.entries());
738
778
  for (let idx = 0; idx < entries.length; idx++) {
739
779
  const [key, child] = entries[idx];
740
780
  if (child.token && child.children.size === 0) {
741
781
  this.renderLeaf(lines, key, child.token, depth, options);
742
782
  } else if (child.children.size > 0 && !child.token) {
743
- const objectName = toPascalCase(key);
744
- lines.push(`${pad}${vis}object ${objectName} {`);
783
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
784
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
745
785
  this.renderTreeChildren(lines, child, depth + 1, options);
746
786
  lines.push(`${pad}}`);
747
787
  if (idx < entries.length - 1) {
@@ -754,30 +794,23 @@ var AndroidRenderer = class {
754
794
  }
755
795
  }
756
796
  renderLeaf(lines, key, token, depth, options) {
757
- const vis = options.visibility ? `${options.visibility} ` : "";
758
- const pad = indent(options.indent, depth);
759
- const kotlinName = toKotlinIdentifier(key);
797
+ const pad = indentStr(options.indent, depth);
798
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
760
799
  const kotlinValue = this.formatKotlinValue(token, options, depth);
761
800
  const annotation = this.typeAnnotationSuffix(token);
762
801
  if (token.$description) {
763
802
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
764
803
  }
765
- lines.push(`${pad}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
766
- }
767
- buildFileHeader() {
768
- return [
769
- "// Generated by Dispersa - do not edit manually",
770
- "// https://github.com/timges/dispersa"
771
- ].join("\n");
804
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
772
805
  }
773
806
  // -----------------------------------------------------------------------
774
807
  // Shadow data class
775
808
  // -----------------------------------------------------------------------
776
- buildShadowTokenClass(vis, options) {
777
- const i1 = indent(options.indent, 1);
809
+ buildShadowTokenClass(options) {
810
+ const i1 = indentStr(options.indent, 1);
778
811
  return [
779
812
  "@Immutable",
780
- `${vis}data class ShadowToken(`,
813
+ `${options.visPrefix}data class ShadowToken(`,
781
814
  `${i1}val color: Color,`,
782
815
  `${i1}val elevation: Dp,`,
783
816
  `${i1}val offsetX: Dp,`,
@@ -828,14 +861,6 @@ var AndroidRenderer = class {
828
861
  }
829
862
  return Array.from(imports).sort();
830
863
  }
831
- collectTokenTypes(node, types) {
832
- if (node.token?.$type) {
833
- types.add(node.token.$type);
834
- }
835
- for (const child of node.children.values()) {
836
- this.collectTokenTypes(child, types);
837
- }
838
- }
839
864
  collectTokenTypesFromEntries(tokens) {
840
865
  const types = /* @__PURE__ */ new Set();
841
866
  for (const [, token] of Object.entries(tokens)) {
@@ -1062,9 +1087,8 @@ var AndroidRenderer = class {
1062
1087
  return map[name.toLowerCase()];
1063
1088
  }
1064
1089
  formatDurationValue(value) {
1065
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
1066
- const dur = value;
1067
- return dur.unit === "ms" ? `${dur.value}.milliseconds` : `${dur.value}.seconds`;
1090
+ if (isDurationObject(value)) {
1091
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
1068
1092
  }
1069
1093
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
1070
1094
  }
@@ -1082,8 +1106,8 @@ var AndroidRenderer = class {
1082
1106
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
1083
1107
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
1084
1108
  const offsetY = isDimensionObject(shadow.offsetY) ? this.formatDimensionValue(shadow.offsetY) : "0.dp";
1085
- const propIndent = indent(options.indent, depth + 1);
1086
- const closeIndent = indent(options.indent, depth);
1109
+ const propIndent = indentStr(options.indent, depth + 1);
1110
+ const closeIndent = indentStr(options.indent, depth);
1087
1111
  return [
1088
1112
  "ShadowToken(",
1089
1113
  `${propIndent}color = ${color},`,
@@ -1132,8 +1156,8 @@ var AndroidRenderer = class {
1132
1156
  if (parts.length === 0) {
1133
1157
  return "TextStyle()";
1134
1158
  }
1135
- const propIndent = indent(options.indent, depth + 1);
1136
- const closeIndent = indent(options.indent, depth);
1159
+ const propIndent = indentStr(options.indent, depth + 1);
1160
+ const closeIndent = indentStr(options.indent, depth);
1137
1161
  return `TextStyle(
1138
1162
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
1139
1163
  ${closeIndent})`;
@@ -1142,12 +1166,12 @@ ${closeIndent})`;
1142
1166
  // Output: standalone
1143
1167
  // -----------------------------------------------------------------------
1144
1168
  async formatStandalone(context, options) {
1145
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1146
- if (!context.output.file && requiresFile) {
1147
- throw new ConfigurationError(
1148
- `Output "${context.output.name}": file is required for standalone Android output`
1149
- );
1150
- }
1169
+ assertFileRequired(
1170
+ context.buildPath,
1171
+ context.output.file,
1172
+ context.output.name,
1173
+ "standalone Android"
1174
+ );
1151
1175
  const files = {};
1152
1176
  for (const { tokens, modifierInputs } of context.permutations) {
1153
1177
  const processedTokens = stripInternalMetadata(tokens);
@@ -1167,12 +1191,12 @@ ${closeIndent})`;
1167
1191
  // Output: bundle
1168
1192
  // -----------------------------------------------------------------------
1169
1193
  async formatBundle(context, options) {
1170
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
1171
- if (!context.output.file && requiresFile) {
1172
- throw new ConfigurationError(
1173
- `Output "${context.output.name}": file is required for bundle Android output`
1174
- );
1175
- }
1194
+ assertFileRequired(
1195
+ context.buildPath,
1196
+ context.output.file,
1197
+ context.output.name,
1198
+ "bundle Android"
1199
+ );
1176
1200
  const content = this.formatBundleContent(context, options);
1177
1201
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
1178
1202
  outputName: context.output.name,
@@ -1185,15 +1209,15 @@ ${closeIndent})`;
1185
1209
  }
1186
1210
  formatBundleContent(context, options) {
1187
1211
  const allTokenTypes = this.collectAllPermutationTypes(context);
1188
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
1189
- const i1 = indent(options.indent, 1);
1212
+ return this.buildFile(allTokenTypes, options, (lines) => {
1213
+ const i1 = indentStr(options.indent, 1);
1190
1214
  lines.push(`@Suppress("unused")`);
1191
- lines.push(`${vis}object ${options.objectName} {`);
1215
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
1192
1216
  for (let idx = 0; idx < context.permutations.length; idx++) {
1193
1217
  const { tokens, modifierInputs } = context.permutations[idx];
1194
1218
  const processedTokens = stripInternalMetadata(tokens);
1195
1219
  const permName = this.buildPermutationName(modifierInputs);
1196
- lines.push(`${i1}${vis}object ${permName} {`);
1220
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
1197
1221
  this.renderBundleTokens(lines, processedTokens, options, 2);
1198
1222
  lines.push(`${i1}}`);
1199
1223
  if (idx < context.permutations.length - 1) {
@@ -1204,20 +1228,17 @@ ${closeIndent})`;
1204
1228
  });
1205
1229
  }
1206
1230
  collectAllPermutationTypes(context) {
1207
- const allTokenTypes = /* @__PURE__ */ new Set();
1231
+ const types = /* @__PURE__ */ new Set();
1208
1232
  for (const { tokens } of context.permutations) {
1209
- const processed = stripInternalMetadata(tokens);
1210
- for (const [, token] of Object.entries(processed)) {
1211
- if (token.$type) {
1212
- allTokenTypes.add(token.$type);
1213
- }
1233
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
1234
+ types.add(t);
1214
1235
  }
1215
1236
  }
1216
- return allTokenTypes;
1237
+ return types;
1217
1238
  }
1218
1239
  renderBundleTokens(lines, tokens, options, baseDepth) {
1219
1240
  if (options.structure === "flat") {
1220
- const groups = this.groupTokensByType(tokens);
1241
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
1221
1242
  this.renderFlatGroups(lines, groups, baseDepth, options);
1222
1243
  return;
1223
1244
  }
@@ -1229,7 +1250,7 @@ ${closeIndent})`;
1229
1250
  if (values.length === 0) {
1230
1251
  return "Default";
1231
1252
  }
1232
- return values.map((v) => toPascalCase(v)).join("");
1253
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
1233
1254
  }
1234
1255
  };
1235
1256
  function androidRenderer() {
@@ -1249,19 +1270,19 @@ init_token_utils();
1249
1270
  // src/renderers/bundlers/css.ts
1250
1271
  init_errors();
1251
1272
  init_utils();
1273
+ var REF_PREFIX_SETS = "#/sets/";
1274
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
1252
1275
  var getSourceSet = (token) => {
1253
1276
  if (typeof token !== "object" || token === null) {
1254
1277
  return void 0;
1255
1278
  }
1256
- const maybe = token;
1257
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
1279
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
1258
1280
  };
1259
1281
  var getSourceModifier = (token) => {
1260
1282
  if (typeof token !== "object" || token === null) {
1261
1283
  return void 0;
1262
1284
  }
1263
- const maybe = token;
1264
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
1285
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
1265
1286
  };
1266
1287
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
1267
1288
  const baseItem = bundleData.find((item) => item.isBase);
@@ -1346,6 +1367,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
1346
1367
  return `/* Modifier: ${modifier}=${context} */
1347
1368
  ${css2}`;
1348
1369
  }
1370
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
1371
+ if (Object.keys(blockTokens).length === 0) {
1372
+ return;
1373
+ }
1374
+ for (const k of Object.keys(blockTokens)) {
1375
+ included.add(k);
1376
+ }
1377
+ blocks.push({ key, description, tokens: blockTokens });
1378
+ }
1349
1379
  function collectSetTokens(tokens, setName, included) {
1350
1380
  const result = {};
1351
1381
  for (const [name, token] of Object.entries(tokens)) {
@@ -1376,75 +1406,67 @@ function collectRemainder(tokens, included) {
1376
1406
  function buildSetLayerBlocks(tokens, resolver) {
1377
1407
  const blocks = [];
1378
1408
  const included = /* @__PURE__ */ new Set();
1379
- const addBlock = (key, blockTokens, description) => {
1380
- if (Object.keys(blockTokens).length === 0) {
1381
- return;
1382
- }
1383
- for (const k of Object.keys(blockTokens)) {
1384
- included.add(k);
1385
- }
1386
- blocks.push({ key, description, tokens: blockTokens });
1387
- };
1388
1409
  for (const item of resolver.resolutionOrder) {
1389
1410
  const ref = item.$ref;
1390
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
1411
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
1391
1412
  continue;
1392
1413
  }
1393
- const setName = ref.slice("#/sets/".length);
1394
- addBlock(
1414
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1415
+ addLayerBlock(
1416
+ blocks,
1417
+ included,
1395
1418
  `Set: ${setName}`,
1396
1419
  collectSetTokens(tokens, setName, included),
1397
1420
  resolver.sets?.[setName]?.description
1398
1421
  );
1399
1422
  }
1400
- addBlock("Unattributed", collectRemainder(tokens, included));
1423
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1401
1424
  return blocks;
1402
1425
  }
1403
1426
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
1404
1427
  const blocks = [];
1405
1428
  const included = /* @__PURE__ */ new Set();
1406
1429
  const baseInputs = normalizeModifierInputs(baseModifierInputs);
1407
- const addBlock = (key, blockTokens, description) => {
1408
- if (Object.keys(blockTokens).length === 0) {
1409
- return;
1410
- }
1411
- for (const k of Object.keys(blockTokens)) {
1412
- included.add(k);
1413
- }
1414
- blocks.push({ key, description, tokens: blockTokens });
1415
- };
1416
1430
  for (const item of resolver.resolutionOrder) {
1417
1431
  const ref = item.$ref;
1418
1432
  if (typeof ref !== "string") {
1419
1433
  continue;
1420
1434
  }
1421
- if (ref.startsWith("#/sets/")) {
1422
- const setName = ref.slice("#/sets/".length);
1423
- addBlock(
1424
- `Set: ${setName}`,
1425
- collectSetTokens(tokens, setName, included),
1426
- resolver.sets?.[setName]?.description
1427
- );
1428
- continue;
1429
- }
1430
- if (ref.startsWith("#/modifiers/")) {
1431
- const modifierName = ref.slice("#/modifiers/".length);
1432
- const modifier = resolver.modifiers?.[modifierName];
1433
- const selectedContext = baseInputs[modifierName.toLowerCase()];
1434
- if (!modifier || !selectedContext) {
1435
- continue;
1436
- }
1437
- const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
1438
- addBlock(
1439
- `Modifier: ${modifierName}=${selectedContext} (default)`,
1440
- collectModifierTokens(tokens, expectedSource, included),
1441
- modifier.description
1442
- );
1443
- }
1435
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
1444
1436
  }
1445
- addBlock("Unattributed", collectRemainder(tokens, included));
1437
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1446
1438
  return blocks;
1447
1439
  }
1440
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
1441
+ if (ref.startsWith(REF_PREFIX_SETS)) {
1442
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1443
+ addLayerBlock(
1444
+ blocks,
1445
+ included,
1446
+ `Set: ${setName}`,
1447
+ collectSetTokens(tokens, setName, included),
1448
+ resolver.sets?.[setName]?.description
1449
+ );
1450
+ return;
1451
+ }
1452
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1453
+ return;
1454
+ }
1455
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
1456
+ const modifier = resolver.modifiers?.[modifierName];
1457
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
1458
+ if (!modifier || !selectedContext) {
1459
+ return;
1460
+ }
1461
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
1462
+ addLayerBlock(
1463
+ blocks,
1464
+ included,
1465
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
1466
+ collectModifierTokens(tokens, expectedSource, included),
1467
+ modifier.description
1468
+ );
1469
+ }
1448
1470
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
1449
1471
  const normalizedModifier = modifierName.toLowerCase();
1450
1472
  const normalizedContext = context.toLowerCase();
@@ -1459,6 +1481,36 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
1459
1481
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
1460
1482
  });
1461
1483
  }
1484
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
1485
+ if (!item) {
1486
+ return;
1487
+ }
1488
+ const key = stableInputsKey(item.modifierInputs);
1489
+ if (includedKeys.has(key)) {
1490
+ return;
1491
+ }
1492
+ includedKeys.add(key);
1493
+ ordered.push(item);
1494
+ }
1495
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
1496
+ for (const modifierName of orderedNames) {
1497
+ const modifierDef = modifiers[modifierName];
1498
+ if (!modifierDef) {
1499
+ continue;
1500
+ }
1501
+ const defaultValue = baseInputs[modifierName.toLowerCase()] ?? "";
1502
+ for (const ctx of Object.keys(modifierDef.contexts)) {
1503
+ if (defaultValue === ctx.toLowerCase()) {
1504
+ continue;
1505
+ }
1506
+ pushUniqueBundleItem(
1507
+ ordered,
1508
+ includedKeys,
1509
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
1510
+ );
1511
+ }
1512
+ }
1513
+ }
1462
1514
  function orderBundleData(bundleData, resolver, baseItem) {
1463
1515
  const modifiers = resolver.modifiers;
1464
1516
  if (!modifiers) {
@@ -1475,31 +1527,15 @@ function orderBundleData(bundleData, resolver, baseItem) {
1475
1527
  }
1476
1528
  const includedKeys = /* @__PURE__ */ new Set();
1477
1529
  const ordered = [];
1478
- const pushUnique = (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
- pushUnique(baseItem);
1490
- for (const modifierName of orderedModifierNames) {
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
- pushUnique(findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs));
1501
- }
1502
- }
1530
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
1531
+ appendModifierPermutations(
1532
+ bundleData,
1533
+ modifiers,
1534
+ orderedModifierNames,
1535
+ baseInputs,
1536
+ ordered,
1537
+ includedKeys
1538
+ );
1503
1539
  return ordered.length > 0 ? ordered : bundleData;
1504
1540
  }
1505
1541
  function getOrderedModifierNames(resolver) {
@@ -1511,10 +1547,10 @@ function getOrderedModifierNames(resolver) {
1511
1547
  if (typeof ref !== "string") {
1512
1548
  continue;
1513
1549
  }
1514
- if (!ref.startsWith("#/modifiers/")) {
1550
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1515
1551
  continue;
1516
1552
  }
1517
- const name = ref.slice("#/modifiers/".length);
1553
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
1518
1554
  if (seen.has(name)) {
1519
1555
  continue;
1520
1556
  }
@@ -1585,24 +1621,22 @@ var CssRenderer = class _CssRenderer {
1585
1621
  ...options,
1586
1622
  referenceTokens: options?.referenceTokens ?? tokens
1587
1623
  };
1588
- const groups = this.groupTokens(tokens, opts);
1624
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
1589
1625
  const referenceTokens = opts.referenceTokens;
1590
1626
  const lines = [];
1591
- for (const [selector, groupTokens] of Object.entries(groups)) {
1592
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
1593
- }
1627
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
1594
1628
  const cssString = lines.join("");
1595
1629
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
1596
1630
  }
1597
1631
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
1598
- const indent2 = opts.minify ? "" : " ";
1632
+ const indent = opts.minify ? "" : " ";
1599
1633
  const newline = opts.minify ? "" : "\n";
1600
1634
  const space = opts.minify ? "" : " ";
1601
1635
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
1602
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
1636
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
1603
1637
  if (hasMediaQuery) {
1604
1638
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
1605
- lines.push(`${indent2}${selector}${space}{${newline}`);
1639
+ lines.push(`${indent}${selector}${space}{${newline}`);
1606
1640
  } else {
1607
1641
  lines.push(`${selector}${space}{${newline}`);
1608
1642
  }
@@ -1619,21 +1653,21 @@ var CssRenderer = class _CssRenderer {
1619
1653
  );
1620
1654
  }
1621
1655
  if (hasMediaQuery) {
1622
- lines.push(`${indent2}}${newline}`);
1656
+ lines.push(`${indent}}${newline}`);
1623
1657
  }
1624
1658
  lines.push(`}${newline}${newline}`);
1625
1659
  }
1626
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
1660
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
1627
1661
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
1628
1662
  if (token.$deprecated != null && token.$deprecated !== false) {
1629
1663
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
1630
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1664
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1631
1665
  }
1632
1666
  if (token.$description && token.$description !== "") {
1633
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1667
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1634
1668
  }
1635
1669
  for (const entry of entries) {
1636
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
1670
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
1637
1671
  }
1638
1672
  }
1639
1673
  async formatWithPrettier(css2) {
@@ -1648,15 +1682,6 @@ var CssRenderer = class _CssRenderer {
1648
1682
  return css2;
1649
1683
  }
1650
1684
  }
1651
- /**
1652
- * Group tokens by selector (for theme support)
1653
- */
1654
- groupTokens(tokens, options) {
1655
- const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
1656
- return {
1657
- [options.selector]: sortedTokens
1658
- };
1659
- }
1660
1685
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
1661
1686
  if (preserveReferences) {
1662
1687
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -1798,7 +1823,7 @@ var CssRenderer = class _CssRenderer {
1798
1823
  leaves.push({ path, value });
1799
1824
  return;
1800
1825
  }
1801
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
1826
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
1802
1827
  leaves.push({ path, value });
1803
1828
  return;
1804
1829
  }
@@ -1841,8 +1866,8 @@ var CssRenderer = class _CssRenderer {
1841
1866
  if (isDimensionObject(value)) {
1842
1867
  return dimensionObjectToString(value);
1843
1868
  }
1844
- if (this.isDurationObject(value)) {
1845
- return this.formatDurationValue(value);
1869
+ if (isDurationObject(value)) {
1870
+ return durationObjectToString(value);
1846
1871
  }
1847
1872
  if (typeof value === "string") {
1848
1873
  return value;
@@ -1905,15 +1930,6 @@ var CssRenderer = class _CssRenderer {
1905
1930
  isPrimitiveValue(value) {
1906
1931
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1907
1932
  }
1908
- isDurationObject(value) {
1909
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
1910
- }
1911
- formatDurationValue(value) {
1912
- if (typeof value === "string") {
1913
- return value;
1914
- }
1915
- return `${value.value}${value.unit}`;
1916
- }
1917
1933
  /**
1918
1934
  * Format token value for CSS
1919
1935
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -1934,8 +1950,8 @@ var CssRenderer = class _CssRenderer {
1934
1950
  return typeof value === "string" ? value : dimensionObjectToString(value);
1935
1951
  }
1936
1952
  if (type === "duration") {
1937
- if (this.isDurationObject(value)) {
1938
- return this.formatDurationValue(value);
1953
+ if (isDurationObject(value)) {
1954
+ return durationObjectToString(value);
1939
1955
  }
1940
1956
  if (typeof value === "string") {
1941
1957
  return value;
@@ -2025,16 +2041,16 @@ var CssRenderer = class _CssRenderer {
2025
2041
  */
2026
2042
  formatTransition(value) {
2027
2043
  const parts = [];
2028
- if (this.isDurationObject(value.duration)) {
2029
- parts.push(this.formatDurationValue(value.duration));
2044
+ if (isDurationObject(value.duration)) {
2045
+ parts.push(durationObjectToString(value.duration));
2030
2046
  } else if (value.duration != null) {
2031
2047
  parts.push(String(value.duration));
2032
2048
  }
2033
2049
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
2034
2050
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
2035
2051
  }
2036
- if (this.isDurationObject(value.delay)) {
2037
- parts.push(this.formatDurationValue(value.delay));
2052
+ if (isDurationObject(value.delay)) {
2053
+ parts.push(durationObjectToString(value.delay));
2038
2054
  } else if (value.delay != null) {
2039
2055
  parts.push(String(value.delay));
2040
2056
  }
@@ -2044,7 +2060,7 @@ var CssRenderer = class _CssRenderer {
2044
2060
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2045
2061
  tokens,
2046
2062
  modifierInputs,
2047
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2063
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2048
2064
  }));
2049
2065
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
2050
2066
  return await this.formatTokens(tokens, {
@@ -2054,12 +2070,12 @@ var CssRenderer = class _CssRenderer {
2054
2070
  });
2055
2071
  }
2056
2072
  async formatStandalone(context, options) {
2057
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2058
- if (!context.output.file && requiresFile) {
2059
- throw new ConfigurationError(
2060
- `Output "${context.output.name}": file is required for standalone CSS output`
2061
- );
2062
- }
2073
+ assertFileRequired(
2074
+ context.buildPath,
2075
+ context.output.file,
2076
+ context.output.name,
2077
+ "standalone CSS"
2078
+ );
2063
2079
  const files = {};
2064
2080
  for (const { tokens, modifierInputs } of context.permutations) {
2065
2081
  const { fileName, content } = await this.buildStandaloneFile(
@@ -2073,7 +2089,7 @@ var CssRenderer = class _CssRenderer {
2073
2089
  return { kind: "outputTree", files };
2074
2090
  }
2075
2091
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
2076
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
2092
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
2077
2093
  const { modifierName, modifierContext } = this.resolveModifierContext(
2078
2094
  modifierInputs,
2079
2095
  context,
@@ -2110,12 +2126,7 @@ var CssRenderer = class _CssRenderer {
2110
2126
  return { fileName, content };
2111
2127
  }
2112
2128
  async formatModifier(context, options) {
2113
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2114
- if (!context.output.file && requiresFile) {
2115
- throw new ConfigurationError(
2116
- `Output "${context.output.name}": file is required for modifier CSS output`
2117
- );
2118
- }
2129
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
2119
2130
  if (!context.resolver.modifiers) {
2120
2131
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
2121
2132
  }
@@ -2141,7 +2152,7 @@ var CssRenderer = class _CssRenderer {
2141
2152
  }
2142
2153
  async buildModifierBaseFile(context, options) {
2143
2154
  const basePermutation = context.permutations.find(
2144
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
2155
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
2145
2156
  );
2146
2157
  if (!basePermutation) {
2147
2158
  return void 0;
@@ -2154,25 +2165,40 @@ var CssRenderer = class _CssRenderer {
2154
2165
  if (setBlocks.length === 0) {
2155
2166
  return void 0;
2156
2167
  }
2168
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
2169
+ const content = await this.formatSetBlocksCss(
2170
+ setBlocks,
2171
+ basePermutation.tokens,
2172
+ selector,
2173
+ mediaQuery,
2174
+ options
2175
+ );
2176
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
2177
+ return { fileName, content };
2178
+ }
2179
+ resolveBaseModifierContext(context, options) {
2157
2180
  const modifiers = context.resolver.modifiers;
2158
2181
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
2159
2182
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
2160
2183
  const baseModifierInputs = { ...context.meta.defaults };
2161
- const selector = resolveSelector(
2162
- options.selector,
2163
- firstModifierName,
2164
- firstModifierContext,
2165
- true,
2166
- baseModifierInputs
2167
- );
2168
- const mediaQuery = resolveMediaQuery(
2169
- options.mediaQuery,
2170
- firstModifierName,
2171
- firstModifierContext,
2172
- true,
2173
- baseModifierInputs
2174
- );
2175
- const referenceTokens = basePermutation.tokens;
2184
+ return {
2185
+ selector: resolveSelector(
2186
+ options.selector,
2187
+ firstModifierName,
2188
+ firstModifierContext,
2189
+ true,
2190
+ baseModifierInputs
2191
+ ),
2192
+ mediaQuery: resolveMediaQuery(
2193
+ options.mediaQuery,
2194
+ firstModifierName,
2195
+ firstModifierContext,
2196
+ true,
2197
+ baseModifierInputs
2198
+ )
2199
+ };
2200
+ }
2201
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
2176
2202
  const cssBlocks = [];
2177
2203
  for (const block of setBlocks) {
2178
2204
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -2188,9 +2214,7 @@ var CssRenderer = class _CssRenderer {
2188
2214
  cssBlocks.push(`${header}
2189
2215
  ${css2}`);
2190
2216
  }
2191
- const content = cssBlocks.join("\n");
2192
- const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
2193
- return { fileName, content };
2217
+ return cssBlocks.join("\n");
2194
2218
  }
2195
2219
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
2196
2220
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -2267,13 +2291,6 @@ ${css2}`);
2267
2291
  }
2268
2292
  return { modifierName: "", modifierContext: "" };
2269
2293
  }
2270
- isBasePermutation(modifierInputs, defaults) {
2271
- const normalizedInputs = normalizeModifierInputs(modifierInputs);
2272
- const normalizedDefaults = normalizeModifierInputs(defaults);
2273
- return Object.entries(normalizedDefaults).every(
2274
- ([key, value]) => normalizedInputs[key] === value
2275
- );
2276
- }
2277
2294
  };
2278
2295
  function cssRenderer() {
2279
2296
  const rendererInstance = new CssRenderer();
@@ -2285,9 +2302,18 @@ function cssRenderer() {
2285
2302
  };
2286
2303
  }
2287
2304
 
2305
+ // src/tokens/types.ts
2306
+ function isShadowToken(token) {
2307
+ return token.$type === "shadow";
2308
+ }
2309
+ function isTypographyToken(token) {
2310
+ return token.$type === "typography";
2311
+ }
2312
+ function isBorderToken(token) {
2313
+ return token.$type === "border";
2314
+ }
2315
+
2288
2316
  // src/renderers/ios.ts
2289
- init_errors();
2290
- init_token_utils();
2291
2317
  init_utils();
2292
2318
  var toSRGB2 = culori.converter("rgb");
2293
2319
  var toP32 = culori.converter("p3");
@@ -2376,94 +2402,68 @@ var IosRenderer = class {
2376
2402
  return await this.formatStandalone(context, opts);
2377
2403
  }
2378
2404
  formatTokens(tokens, options) {
2379
- if (options.structure === "grouped") {
2380
- return this.formatAsGrouped(tokens, options);
2381
- }
2382
- return this.formatAsEnum(tokens, options);
2383
- }
2384
- formatAsEnum(tokens, options) {
2385
2405
  const access = options.accessLevel;
2386
- const groups = this.groupTokensByType(tokens);
2406
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
2387
2407
  const imports = this.collectImports(tokens);
2388
- const i1 = this.indentStr(options.indent, 1);
2389
- const i2 = this.indentStr(options.indent, 2);
2390
2408
  const staticPrefix = this.staticLetPrefix(options);
2391
2409
  const frozen = this.frozenPrefix(options);
2392
2410
  const lines = [];
2393
- lines.push(this.buildFileHeader());
2411
+ lines.push(buildGeneratedFileHeader());
2394
2412
  lines.push("");
2395
2413
  for (const imp of imports) {
2396
2414
  lines.push(`import ${imp}`);
2397
2415
  }
2398
2416
  lines.push(...this.buildStructDefinitions(tokens, access, options));
2417
+ this.pushTokenLayout(lines, groups, options, access, staticPrefix, frozen);
2418
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2419
+ if (options.structure !== "grouped") {
2420
+ lines.push("");
2421
+ }
2422
+ return lines.join("\n");
2423
+ }
2424
+ pushTokenLayout(lines, groups, options, access, staticPrefix, frozen) {
2425
+ const i1 = indentStr(options.indent, 1);
2426
+ const i2 = indentStr(options.indent, 2);
2427
+ if (options.structure === "grouped") {
2428
+ this.pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen);
2429
+ return;
2430
+ }
2399
2431
  lines.push("");
2400
2432
  lines.push(`${frozen}${access} enum ${options.enumName} {`);
2401
2433
  for (const group of groups) {
2402
2434
  lines.push(`${i1}${frozen}${access} enum ${group.name} {`);
2403
- for (const token of group.tokens) {
2404
- const swiftName = this.buildQualifiedSwiftName(token);
2405
- const swiftValue = this.formatSwiftValue(token, options);
2406
- const typeAnnotation = this.getTypeAnnotation(token);
2407
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2408
- const docComment = this.buildDocComment(token, i2);
2409
- if (docComment) {
2410
- lines.push(docComment);
2411
- }
2412
- lines.push(`${i2}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2413
- }
2435
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2414
2436
  lines.push(`${i1}}`);
2415
2437
  lines.push("");
2416
2438
  }
2417
2439
  lines.push("}");
2418
- lines.push(...this.buildViewExtensions(tokens, access, options));
2419
- lines.push("");
2420
- return lines.join("\n");
2421
2440
  }
2422
- formatAsGrouped(tokens, options) {
2423
- const access = options.accessLevel;
2441
+ pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen) {
2424
2442
  const namespace = options.extensionNamespace;
2425
- const groups = this.groupTokensByType(tokens);
2426
- const imports = this.collectImports(tokens);
2427
- const i1 = this.indentStr(options.indent, 1);
2428
- const i2 = this.indentStr(options.indent, 2);
2429
- const staticPrefix = this.staticLetPrefix(options);
2430
- const frozen = this.frozenPrefix(options);
2431
- const lines = [];
2432
- lines.push(this.buildFileHeader());
2433
- lines.push("");
2434
- for (const imp of imports) {
2435
- lines.push(`import ${imp}`);
2436
- }
2437
- lines.push(...this.buildStructDefinitions(tokens, access, options));
2438
2443
  lines.push("");
2439
2444
  lines.push(`${frozen}${access} enum ${namespace} {}`);
2440
2445
  lines.push("");
2441
2446
  for (const group of groups) {
2442
2447
  lines.push(`${access} extension ${namespace} {`);
2443
2448
  lines.push(`${i1}${frozen}enum ${group.name} {`);
2444
- for (const token of group.tokens) {
2445
- const swiftName = this.buildQualifiedSwiftName(token);
2446
- const swiftValue = this.formatSwiftValue(token, options);
2447
- const typeAnnotation = this.getTypeAnnotation(token);
2448
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2449
- const docComment = this.buildDocComment(token, i2);
2450
- if (docComment) {
2451
- lines.push(docComment);
2452
- }
2453
- lines.push(`${i2}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2454
- }
2449
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2455
2450
  lines.push(`${i1}}`);
2456
2451
  lines.push("}");
2457
2452
  lines.push("");
2458
2453
  }
2459
- lines.push(...this.buildViewExtensions(tokens, access, options));
2460
- return lines.join("\n");
2461
2454
  }
2462
- buildFileHeader() {
2463
- return [
2464
- "// Generated by Dispersa - do not edit manually",
2465
- "// https://github.com/timges/dispersa"
2466
- ].join("\n");
2455
+ pushTokenDeclarations(lines, tokens, options, access, indent, staticPrefix) {
2456
+ for (const token of tokens) {
2457
+ const swiftName = this.buildQualifiedSwiftName(token);
2458
+ const swiftValue = this.formatSwiftValue(token, options);
2459
+ const typeAnnotation = this.getTypeAnnotation(token);
2460
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2461
+ const docComment = this.buildDocComment(token, indent);
2462
+ if (docComment) {
2463
+ lines.push(docComment);
2464
+ }
2465
+ lines.push(`${indent}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2466
+ }
2467
2467
  }
2468
2468
  collectImports(tokens) {
2469
2469
  const imports = /* @__PURE__ */ new Set();
@@ -2478,24 +2478,11 @@ var IosRenderer = class {
2478
2478
  /**
2479
2479
  * Builds a `///` doc comment from a token's `$description`, if present.
2480
2480
  */
2481
- buildDocComment(token, indent2) {
2481
+ buildDocComment(token, indent) {
2482
2482
  if (!token.$description) {
2483
2483
  return void 0;
2484
2484
  }
2485
- return `${indent2}/// ${token.$description}`;
2486
- }
2487
- groupTokensByType(tokens) {
2488
- const groupMap = /* @__PURE__ */ new Map();
2489
- for (const [, token] of getSortedTokenEntries(tokens)) {
2490
- const groupName = SWIFT_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
2491
- const existing = groupMap.get(groupName) ?? [];
2492
- existing.push(token);
2493
- groupMap.set(groupName, existing);
2494
- }
2495
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
2496
- name,
2497
- tokens: groupTokens
2498
- }));
2485
+ return `${indent}/// ${token.$description}`;
2499
2486
  }
2500
2487
  /**
2501
2488
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -2508,43 +2495,40 @@ var IosRenderer = class {
2508
2495
  const path = token.path;
2509
2496
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
2510
2497
  const joined = withoutTypePrefix.join("_");
2511
- return this.toSwiftIdentifier(joined);
2498
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
2512
2499
  }
2513
2500
  formatSwiftValue(token, options) {
2514
- const value = token.$value;
2515
- if (token.$type === "color") {
2516
- return this.formatColorValue(value, options);
2517
- }
2518
- if (token.$type === "dimension") {
2519
- return this.formatDimensionValue(value);
2520
- }
2521
- if (token.$type === "fontFamily") {
2522
- return this.formatFontFamilyValue(value);
2523
- }
2524
- if (token.$type === "fontWeight") {
2525
- return this.formatFontWeightValue(value);
2526
- }
2527
- if (token.$type === "duration") {
2528
- return this.formatDurationValue(value);
2529
- }
2530
- if (token.$type === "shadow") {
2531
- return this.formatShadowValue(value, options);
2532
- }
2533
- if (token.$type === "typography") {
2534
- return this.formatTypographyValue(value);
2535
- }
2536
- if (token.$type === "border") {
2537
- return this.formatBorderValue(value, options);
2538
- }
2539
- if (token.$type === "gradient") {
2540
- return this.formatGradientValue(value, options);
2541
- }
2542
- if (token.$type === "number") {
2543
- return String(value);
2544
- }
2545
- if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
2546
- return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2501
+ const { $type, $value: value } = token;
2502
+ switch ($type) {
2503
+ case "color":
2504
+ return this.formatColorValue(value, options);
2505
+ case "dimension":
2506
+ return this.formatDimensionValue(value);
2507
+ case "fontFamily":
2508
+ return this.formatFontFamilyValue(value);
2509
+ case "fontWeight":
2510
+ return this.formatFontWeightValue(value);
2511
+ case "duration":
2512
+ return this.formatDurationValue(value);
2513
+ case "shadow":
2514
+ return this.formatShadowValue(value, options);
2515
+ case "typography":
2516
+ return this.formatTypographyValue(value);
2517
+ case "border":
2518
+ return this.formatBorderValue(value, options);
2519
+ case "gradient":
2520
+ return this.formatGradientValue(value, options);
2521
+ case "number":
2522
+ return String(value);
2523
+ case "cubicBezier":
2524
+ if (Array.isArray(value) && value.length === 4) {
2525
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2526
+ }
2527
+ break;
2547
2528
  }
2529
+ return this.formatSwiftPrimitive(value);
2530
+ }
2531
+ formatSwiftPrimitive(value) {
2548
2532
  if (typeof value === "string") {
2549
2533
  return `"${this.escapeSwiftString(value)}"`;
2550
2534
  }
@@ -2577,9 +2561,7 @@ var IosRenderer = class {
2577
2561
  }
2578
2562
  formatDimensionValue(value) {
2579
2563
  if (isDimensionObject(value)) {
2580
- const dim = value;
2581
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2582
- return String(ptValue);
2564
+ return this.dimensionToPoints(value);
2583
2565
  }
2584
2566
  return String(value);
2585
2567
  }
@@ -2646,7 +2628,7 @@ var IosRenderer = class {
2646
2628
  return map[name.toLowerCase()];
2647
2629
  }
2648
2630
  formatDurationValue(value) {
2649
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
2631
+ if (isDurationObject(value)) {
2650
2632
  const dur = value;
2651
2633
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
2652
2634
  return String(seconds);
@@ -2695,9 +2677,7 @@ var IosRenderer = class {
2695
2677
  if (!isDimensionObject(typo.letterSpacing)) {
2696
2678
  return "0";
2697
2679
  }
2698
- const dim = typo.letterSpacing;
2699
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2700
- return String(ptValue);
2680
+ return this.dimensionToPoints(typo.letterSpacing);
2701
2681
  }
2702
2682
  extractLineSpacing(typo) {
2703
2683
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -2706,18 +2686,19 @@ var IosRenderer = class {
2706
2686
  if (!isDimensionObject(typo.fontSize)) {
2707
2687
  return "0";
2708
2688
  }
2709
- const dim = typo.fontSize;
2710
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
2689
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
2711
2690
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
2712
2691
  return String(lineHeightPt - basePt);
2713
2692
  }
2693
+ dimensionToNumericPoints(dim) {
2694
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
2695
+ }
2714
2696
  dimensionToPoints(dim) {
2715
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2716
- return String(ptValue);
2697
+ return String(this.dimensionToNumericPoints(dim));
2717
2698
  }
2718
2699
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
2719
2700
  dimensionToCGFloat(dim) {
2720
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2701
+ const ptValue = this.dimensionToNumericPoints(dim);
2721
2702
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
2722
2703
  }
2723
2704
  getTypeAnnotation(token) {
@@ -2736,21 +2717,12 @@ var IosRenderer = class {
2736
2717
  return void 0;
2737
2718
  }
2738
2719
  }
2739
- toSwiftIdentifier(name) {
2740
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
2741
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
2742
- const safe = /^\d/.test(identifier) ? `_${identifier}` : identifier;
2743
- return SWIFT_KEYWORDS.has(safe) ? `\`${safe}\`` : safe;
2744
- }
2745
2720
  escapeSwiftString(str) {
2746
2721
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2747
2722
  }
2748
2723
  roundComponent(value) {
2749
2724
  return Math.round(value * 1e4) / 1e4;
2750
2725
  }
2751
- indentStr(width, level) {
2752
- return " ".repeat(width * level);
2753
- }
2754
2726
  /**
2755
2727
  * Returns the prefix for `static let` declarations.
2756
2728
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -2766,34 +2738,25 @@ var IosRenderer = class {
2766
2738
  structConformances(options) {
2767
2739
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
2768
2740
  }
2769
- hasShadowTokens(tokens) {
2770
- return Object.values(tokens).some((t) => t.$type === "shadow");
2771
- }
2772
- hasTypographyTokens(tokens) {
2773
- return Object.values(tokens).some((t) => t.$type === "typography");
2774
- }
2775
- hasBorderTokens(tokens) {
2776
- return Object.values(tokens).some((t) => t.$type === "border");
2777
- }
2778
2741
  /** Emits all struct definitions needed by the token set. */
2779
2742
  buildStructDefinitions(tokens, access, options) {
2780
2743
  const lines = [];
2781
- if (this.hasShadowTokens(tokens)) {
2744
+ if (Object.values(tokens).some(isShadowToken)) {
2782
2745
  lines.push("");
2783
2746
  lines.push(...this.buildShadowStyleStruct(access, options));
2784
2747
  }
2785
- if (this.hasTypographyTokens(tokens)) {
2748
+ if (Object.values(tokens).some(isTypographyToken)) {
2786
2749
  lines.push("");
2787
2750
  lines.push(...this.buildTypographyStyleStruct(access, options));
2788
2751
  }
2789
- if (this.hasBorderTokens(tokens)) {
2752
+ if (Object.values(tokens).some(isBorderToken)) {
2790
2753
  lines.push("");
2791
2754
  lines.push(...this.buildBorderStyleStruct(access, options));
2792
2755
  }
2793
2756
  return lines;
2794
2757
  }
2795
2758
  buildShadowStyleStruct(access, options) {
2796
- const i1 = this.indentStr(options.indent, 1);
2759
+ const i1 = indentStr(options.indent, 1);
2797
2760
  const conformances = this.structConformances(options);
2798
2761
  const frozen = this.frozenPrefix(options);
2799
2762
  return [
@@ -2807,7 +2770,7 @@ var IosRenderer = class {
2807
2770
  ];
2808
2771
  }
2809
2772
  buildTypographyStyleStruct(access, options) {
2810
- const i1 = this.indentStr(options.indent, 1);
2773
+ const i1 = indentStr(options.indent, 1);
2811
2774
  const conformances = this.structConformances(options);
2812
2775
  const frozen = this.frozenPrefix(options);
2813
2776
  return [
@@ -2819,7 +2782,7 @@ var IosRenderer = class {
2819
2782
  ];
2820
2783
  }
2821
2784
  buildBorderStyleStruct(access, options) {
2822
- const i1 = this.indentStr(options.indent, 1);
2785
+ const i1 = indentStr(options.indent, 1);
2823
2786
  const conformances = this.structConformances(options);
2824
2787
  const frozen = this.frozenPrefix(options);
2825
2788
  return [
@@ -2832,9 +2795,9 @@ var IosRenderer = class {
2832
2795
  /** Emits convenience View extensions for shadow and typography application. */
2833
2796
  buildViewExtensions(tokens, access, options) {
2834
2797
  const lines = [];
2835
- const i1 = this.indentStr(options.indent, 1);
2836
- const i2 = this.indentStr(options.indent, 2);
2837
- if (this.hasShadowTokens(tokens)) {
2798
+ const i1 = indentStr(options.indent, 1);
2799
+ const i2 = indentStr(options.indent, 2);
2800
+ if (Object.values(tokens).some(isShadowToken)) {
2838
2801
  lines.push("");
2839
2802
  lines.push(`${access} extension View {`);
2840
2803
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -2844,7 +2807,7 @@ var IosRenderer = class {
2844
2807
  lines.push(`${i1}}`);
2845
2808
  lines.push("}");
2846
2809
  }
2847
- if (this.hasTypographyTokens(tokens)) {
2810
+ if (Object.values(tokens).some(isTypographyToken)) {
2848
2811
  lines.push("");
2849
2812
  lines.push(`${access} extension View {`);
2850
2813
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -2876,12 +2839,12 @@ var IosRenderer = class {
2876
2839
  return `Gradient(stops: [${stops.join(", ")}])`;
2877
2840
  }
2878
2841
  async formatStandalone(context, options) {
2879
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2880
- if (!context.output.file && requiresFile) {
2881
- throw new ConfigurationError(
2882
- `Output "${context.output.name}": file is required for standalone iOS output`
2883
- );
2884
- }
2842
+ assertFileRequired(
2843
+ context.buildPath,
2844
+ context.output.file,
2845
+ context.output.name,
2846
+ "standalone iOS"
2847
+ );
2885
2848
  const files = {};
2886
2849
  for (const { tokens, modifierInputs } of context.permutations) {
2887
2850
  const processedTokens = stripInternalMetadata(tokens);
@@ -2910,7 +2873,6 @@ function iosRenderer() {
2910
2873
 
2911
2874
  // src/renderers/js-module.ts
2912
2875
  init_utils();
2913
- init_errors();
2914
2876
  init_token_utils();
2915
2877
  var JsModuleRenderer = class {
2916
2878
  async format(context, options) {
@@ -2926,18 +2888,13 @@ var JsModuleRenderer = class {
2926
2888
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2927
2889
  tokens: stripInternalMetadata(tokens),
2928
2890
  modifierInputs,
2929
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2891
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2930
2892
  }));
2931
2893
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
2932
2894
  return await this.formatTokens(tokens, opts);
2933
2895
  });
2934
2896
  }
2935
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
2936
- if (!context.output.file && requiresFile) {
2937
- throw new ConfigurationError(
2938
- `Output "${context.output.name}": file is required for JS module output`
2939
- );
2940
- }
2897
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
2941
2898
  const files = {};
2942
2899
  for (const { tokens, modifierInputs } of context.permutations) {
2943
2900
  const cleanTokens = stripInternalMetadata(tokens);
@@ -2991,42 +2948,18 @@ var JsModuleRenderer = class {
2991
2948
  lines.push(`export default ${varName}`);
2992
2949
  return lines;
2993
2950
  }
2994
- /**
2995
- * Convert tokens to plain object with flat or nested structure
2996
- */
2997
2951
  tokensToPlainObject(tokens, structure) {
2952
+ if (structure === "nested") {
2953
+ return buildNestedTokenObject(tokens, (token) => token.$value);
2954
+ }
2998
2955
  const result = {};
2999
- if (structure === "flat") {
3000
- for (const [name, token] of getSortedTokenEntries(tokens)) {
3001
- result[name] = token.$value;
3002
- }
3003
- } else {
3004
- for (const [, token] of getSortedTokenEntries(tokens)) {
3005
- const parts = token.path;
3006
- let current = result;
3007
- for (let i = 0; i < parts.length - 1; i++) {
3008
- const part = parts[i];
3009
- if (part == null) {
3010
- continue;
3011
- }
3012
- if (!(part in current)) {
3013
- current[part] = {};
3014
- }
3015
- current = current[part];
3016
- }
3017
- const lastPart = parts[parts.length - 1];
3018
- if (lastPart != null) {
3019
- current[lastPart] = token.$value;
3020
- }
3021
- }
2956
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
2957
+ result[name] = token.$value;
3022
2958
  }
3023
2959
  return result;
3024
2960
  }
3025
- /**
3026
- * Add object properties to lines
3027
- */
3028
- addObjectProperties(lines, obj, indent2) {
3029
- const indentStr = " ".repeat(indent2);
2961
+ addObjectProperties(lines, obj, indent) {
2962
+ const indentStr2 = " ".repeat(indent);
3030
2963
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
3031
2964
  for (let i = 0; i < entries.length; i++) {
3032
2965
  const entry = entries[i];
@@ -3035,14 +2968,16 @@ var JsModuleRenderer = class {
3035
2968
  }
3036
2969
  const [key, value] = entry;
3037
2970
  const isLast = i === entries.length - 1;
3038
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3039
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
3040
- this.addObjectProperties(lines, value, indent2 + 1);
3041
- lines.push(`${indentStr}}${isLast ? "" : ","}`);
3042
- } else {
3043
- const valueStr = JSON.stringify(value);
3044
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueStr}${isLast ? "" : ","}`);
2971
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
2972
+ if (!isNestedObject) {
2973
+ lines.push(
2974
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
2975
+ );
2976
+ continue;
3045
2977
  }
2978
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
2979
+ this.addObjectProperties(lines, value, indent + 1);
2980
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
3046
2981
  }
3047
2982
  }
3048
2983
  /**
@@ -3054,9 +2989,6 @@ var JsModuleRenderer = class {
3054
2989
  }
3055
2990
  return `"${key}"`;
3056
2991
  }
3057
- isBasePermutation(modifierInputs, defaults) {
3058
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3059
- }
3060
2992
  };
3061
2993
  function jsRenderer() {
3062
2994
  const rendererInstance = new JsModuleRenderer();
@@ -3070,7 +3002,6 @@ function jsRenderer() {
3070
3002
 
3071
3003
  // src/renderers/json.ts
3072
3004
  init_utils();
3073
- init_errors();
3074
3005
  init_token_utils();
3075
3006
  var JsonRenderer = class {
3076
3007
  async format(context, options) {
@@ -3085,18 +3016,13 @@ var JsonRenderer = class {
3085
3016
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3086
3017
  tokens: stripInternalMetadata(tokens),
3087
3018
  modifierInputs,
3088
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3019
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3089
3020
  }));
3090
3021
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
3091
3022
  return await this.formatTokens(tokens, opts);
3092
3023
  });
3093
3024
  }
3094
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
3095
- if (!context.output.file && requiresFile) {
3096
- throw new ConfigurationError(
3097
- `Output "${context.output.name}": file is required for JSON output`
3098
- );
3099
- }
3025
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
3100
3026
  const files = {};
3101
3027
  for (const { tokens, modifierInputs } of context.permutations) {
3102
3028
  const processedTokens = stripInternalMetadata(tokens);
@@ -3156,55 +3082,11 @@ var JsonRenderer = class {
3156
3082
  }
3157
3083
  return result;
3158
3084
  }
3159
- /**
3160
- * Nest tokens by path (values only)
3161
- */
3162
3085
  nestValues(tokens) {
3163
- const result = {};
3164
- for (const [, token] of getSortedTokenEntries(tokens)) {
3165
- const parts = token.path;
3166
- let current = result;
3167
- for (let i = 0; i < parts.length - 1; i++) {
3168
- const part = parts[i];
3169
- if (part === null || part === void 0) {
3170
- continue;
3171
- }
3172
- if (!(part in current)) {
3173
- current[part] = {};
3174
- }
3175
- current = current[part];
3176
- }
3177
- const lastPart = parts[parts.length - 1];
3178
- if (lastPart !== null && lastPart !== void 0) {
3179
- current[lastPart] = token.$value;
3180
- }
3181
- }
3182
- return result;
3086
+ return buildNestedTokenObject(tokens, (token) => token.$value);
3183
3087
  }
3184
- /**
3185
- * Nest tokens by path (with metadata)
3186
- */
3187
3088
  nestTokens(tokens) {
3188
- const result = {};
3189
- for (const [, token] of getSortedTokenEntries(tokens)) {
3190
- const parts = token.path;
3191
- let current = result;
3192
- for (let i = 0; i < parts.length - 1; i++) {
3193
- const part = parts[i];
3194
- if (part === null || part === void 0) {
3195
- continue;
3196
- }
3197
- if (!(part in current)) {
3198
- current[part] = {};
3199
- }
3200
- current = current[part];
3201
- }
3202
- const lastPart = parts[parts.length - 1];
3203
- if (lastPart !== null && lastPart !== void 0) {
3204
- current[lastPart] = this.serializeToken(token);
3205
- }
3206
- }
3207
- return result;
3089
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
3208
3090
  }
3209
3091
  serializeToken(token) {
3210
3092
  return {
@@ -3215,9 +3097,6 @@ var JsonRenderer = class {
3215
3097
  ...token.$extensions != null && { $extensions: token.$extensions }
3216
3098
  };
3217
3099
  }
3218
- isBasePermutation(modifierInputs, defaults) {
3219
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3220
- }
3221
3100
  };
3222
3101
  function jsonRenderer() {
3223
3102
  const rendererInstance = new JsonRenderer();
@@ -3230,7 +3109,6 @@ function jsonRenderer() {
3230
3109
  }
3231
3110
 
3232
3111
  // src/renderers/tailwind.ts
3233
- init_errors();
3234
3112
  init_token_utils();
3235
3113
 
3236
3114
  // src/renderers/bundlers/tailwind.ts
@@ -3259,6 +3137,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
3259
3137
  }
3260
3138
  return cssBlocks.join("\n");
3261
3139
  }
3140
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
3141
+ const normalized = normalizeModifierInputs(modifierInputs);
3142
+ return {
3143
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
3144
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
3145
+ };
3146
+ }
3262
3147
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
3263
3148
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
3264
3149
  if (differenceCount > 1) {
@@ -3271,19 +3156,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
3271
3156
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
3272
3157
  const [modifier, context] = parseModifierSource(expectedSource);
3273
3158
  const cleanTokens = stripInternalMetadata(tokensToInclude);
3274
- const selector = resolveSelector(
3275
- options.selector,
3159
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
3160
+ options,
3276
3161
  modifier,
3277
3162
  context,
3278
- false,
3279
- normalizeModifierInputs(modifierInputs)
3280
- );
3281
- const mediaQuery = resolveMediaQuery(
3282
- options.mediaQuery,
3283
- modifier,
3284
- context,
3285
- false,
3286
- normalizeModifierInputs(modifierInputs)
3163
+ modifierInputs
3287
3164
  );
3288
3165
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
3289
3166
  return `/* Modifier: ${modifier}=${context} */
@@ -3372,7 +3249,7 @@ var TailwindRenderer = class {
3372
3249
  */
3373
3250
  async formatTokens(tokens, options) {
3374
3251
  const lines = [];
3375
- const indent2 = options.minify ? "" : " ";
3252
+ const indent = options.minify ? "" : " ";
3376
3253
  const newline = options.minify ? "" : "\n";
3377
3254
  const space = options.minify ? "" : " ";
3378
3255
  if (options.includeImport) {
@@ -3394,7 +3271,7 @@ var TailwindRenderer = class {
3394
3271
  for (const [, token] of getSortedTokenEntries(tokens)) {
3395
3272
  const varName = this.buildVariableName(token);
3396
3273
  const varValue = this.formatValue(token);
3397
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
3274
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
3398
3275
  }
3399
3276
  lines.push(`}${newline}`);
3400
3277
  const cssString = lines.join("");
@@ -3405,15 +3282,15 @@ var TailwindRenderer = class {
3405
3282
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
3406
3283
  */
3407
3284
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
3408
- const indent2 = minify ? "" : " ";
3285
+ const indent = minify ? "" : " ";
3409
3286
  const newline = minify ? "" : "\n";
3410
3287
  const space = minify ? "" : " ";
3411
3288
  const hasMediaQuery = mediaQuery !== "";
3412
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
3289
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
3413
3290
  const lines = [];
3414
3291
  if (hasMediaQuery) {
3415
3292
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
3416
- lines.push(`${indent2}${selector}${space}{${newline}`);
3293
+ lines.push(`${indent}${selector}${space}{${newline}`);
3417
3294
  } else {
3418
3295
  lines.push(`${selector}${space}{${newline}`);
3419
3296
  }
@@ -3423,7 +3300,7 @@ var TailwindRenderer = class {
3423
3300
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
3424
3301
  }
3425
3302
  if (hasMediaQuery) {
3426
- lines.push(`${indent2}}${newline}`);
3303
+ lines.push(`${indent}}${newline}`);
3427
3304
  lines.push(`}${newline}`);
3428
3305
  } else {
3429
3306
  lines.push(`}${newline}`);
@@ -3450,8 +3327,8 @@ var TailwindRenderer = class {
3450
3327
  if (token.$type === "dimension" && isDimensionObject(value)) {
3451
3328
  return dimensionObjectToString(value);
3452
3329
  }
3453
- if (token.$type === "duration" && this.isDurationObject(value)) {
3454
- return `${value.value}${value.unit}`;
3330
+ if (token.$type === "duration" && isDurationObject(value)) {
3331
+ return durationObjectToString(value);
3455
3332
  }
3456
3333
  if (token.$type === "fontFamily") {
3457
3334
  if (Array.isArray(value)) {
@@ -3506,9 +3383,6 @@ var TailwindRenderer = class {
3506
3383
  }
3507
3384
  return parts.join(" ");
3508
3385
  }
3509
- isDurationObject(value) {
3510
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
3511
- }
3512
3386
  async formatWithPrettier(css2) {
3513
3387
  try {
3514
3388
  return await prettier__default.default.format(css2, {
@@ -3525,7 +3399,7 @@ var TailwindRenderer = class {
3525
3399
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3526
3400
  tokens,
3527
3401
  modifierInputs,
3528
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3402
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3529
3403
  }));
3530
3404
  return await bundleAsTailwind(
3531
3405
  bundleData,
@@ -3535,12 +3409,12 @@ var TailwindRenderer = class {
3535
3409
  );
3536
3410
  }
3537
3411
  async formatStandalone(context, options) {
3538
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
3539
- if (!context.output.file && requiresFile) {
3540
- throw new ConfigurationError(
3541
- `Output "${context.output.name}": file is required for standalone Tailwind output`
3542
- );
3543
- }
3412
+ assertFileRequired(
3413
+ context.buildPath,
3414
+ context.output.file,
3415
+ context.output.name,
3416
+ "standalone Tailwind"
3417
+ );
3544
3418
  const files = {};
3545
3419
  for (const { tokens, modifierInputs } of context.permutations) {
3546
3420
  const processedTokens = stripInternalMetadata(tokens);
@@ -3556,11 +3430,6 @@ var TailwindRenderer = class {
3556
3430
  }
3557
3431
  return outputTree(files);
3558
3432
  }
3559
- isBasePermutation(modifierInputs, defaults) {
3560
- return Object.entries(defaults).every(
3561
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
3562
- );
3563
- }
3564
3433
  };
3565
3434
  function tailwindRenderer() {
3566
3435
  const rendererInstance = new TailwindRenderer();