dispersa 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/builders.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { kebabCase } from 'change-case';
1
2
  import { converter, formatHex8, formatHex } from 'culori';
2
3
  import prettier from 'prettier';
3
4
 
@@ -46,13 +47,12 @@ function formatDeprecationMessage(token, description = "", format = "bracket") {
46
47
  }
47
48
  const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
48
49
  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;
50
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
51
+ return `DEPRECATED${msg2}`;
55
52
  }
53
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
54
+ const prefix = `[DEPRECATED${msg}]`;
55
+ return description ? `${prefix} ${description}` : prefix;
56
56
  }
57
57
  function stripInternalTokenMetadata(tokens) {
58
58
  const cleaned = {};
@@ -65,6 +65,30 @@ function stripInternalTokenMetadata(tokens) {
65
65
  function getSortedTokenEntries(tokens) {
66
66
  return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
67
67
  }
68
+ function buildNestedTokenObject(tokens, extractValue) {
69
+ const result = {};
70
+ for (const [, token] of getSortedTokenEntries(tokens)) {
71
+ setNestedValue(result, token.path, extractValue(token));
72
+ }
73
+ return result;
74
+ }
75
+ function setNestedValue(root, path, value) {
76
+ let current = root;
77
+ for (let i = 0; i < path.length - 1; i++) {
78
+ const part = path[i];
79
+ if (part == null) {
80
+ continue;
81
+ }
82
+ if (!(part in current)) {
83
+ current[part] = {};
84
+ }
85
+ current = current[part];
86
+ }
87
+ const lastPart = path[path.length - 1];
88
+ if (lastPart != null) {
89
+ current[lastPart] = value;
90
+ }
91
+ }
68
92
  function getPureAliasReferenceName(value) {
69
93
  if (typeof value !== "string") {
70
94
  return void 0;
@@ -84,6 +108,35 @@ function sanitizeDataAttributeName(value) {
84
108
  function escapeCssString(value) {
85
109
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
86
110
  }
111
+ function groupTokensByType(tokens, typeGroupMap) {
112
+ const groupMap = /* @__PURE__ */ new Map();
113
+ for (const [, token] of getSortedTokenEntries(tokens)) {
114
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
115
+ const existing = groupMap.get(groupName) ?? [];
116
+ existing.push(token);
117
+ groupMap.set(groupName, existing);
118
+ }
119
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
120
+ name,
121
+ tokens: groupTokens
122
+ }));
123
+ }
124
+ function indentStr(width, level) {
125
+ return " ".repeat(width * level);
126
+ }
127
+ function buildGeneratedFileHeader() {
128
+ return [
129
+ "// Generated by Dispersa - do not edit manually",
130
+ "// https://github.com/dispersa-core/dispersa"
131
+ ].join("\n");
132
+ }
133
+ function toSafeIdentifier(name, keywords, capitalize) {
134
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
135
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
136
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
137
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
138
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
139
+ }
87
140
  function normalizeModifierInputs(inputs) {
88
141
  const normalized = {};
89
142
  for (const [key, value] of Object.entries(inputs)) {
@@ -91,6 +144,14 @@ function normalizeModifierInputs(inputs) {
91
144
  }
92
145
  return normalized;
93
146
  }
147
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
148
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
149
+ if (!outputFile && requiresFile) {
150
+ throw new ConfigurationError(
151
+ `Output "${outputName}": file is required for ${presetLabel} output`
152
+ );
153
+ }
154
+ }
94
155
  function buildStablePermutationKey(modifierInputs, dimensions) {
95
156
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
96
157
  }
@@ -140,14 +201,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
140
201
  }
141
202
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
142
203
  }
143
- function buildInMemoryOutputKey(params) {
144
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
204
+ function isBasePermutation(modifierInputs, defaults) {
145
205
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
146
206
  const normalizedDefaults = normalizeModifierInputs(defaults);
147
- const isBase = Object.entries(normalizedDefaults).every(
148
- ([key, value]) => normalizedInputs[key] === value
207
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
208
+ }
209
+ function buildInMemoryOutputKey(params) {
210
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
211
+ const permutationKey = generatePermutationKey(
212
+ modifierInputs,
213
+ resolver,
214
+ isBasePermutation(modifierInputs, defaults)
149
215
  );
150
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
151
216
  return `${outputName}-${permutationKey}.${extension}`;
152
217
  }
153
218
  function buildMetadata(resolver) {
@@ -261,6 +326,7 @@ function resolveFileName(fileName, modifierInputs) {
261
326
  }
262
327
  var init_utils = __esm({
263
328
  "src/renderers/bundlers/utils.ts"() {
329
+ init_errors();
264
330
  init_token_utils();
265
331
  }
266
332
  });
@@ -270,36 +336,38 @@ var js_exports = {};
270
336
  __export(js_exports, {
271
337
  bundleAsJsModule: () => bundleAsJsModule
272
338
  });
339
+ function updateStringTracking(state, char) {
340
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
341
+ if (!state.inString) {
342
+ state.inString = true;
343
+ state.stringChar = char;
344
+ } else if (char === state.stringChar) {
345
+ state.inString = false;
346
+ state.stringChar = "";
347
+ }
348
+ }
349
+ state.escaped = !state.escaped && char === "\\";
350
+ }
273
351
  function extractObjectFromJsModule(formattedJs) {
274
352
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
275
353
  if (!assignmentMatch) {
276
354
  return "{}";
277
355
  }
278
356
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
357
+ const state = { inString: false, stringChar: "", escaped: false };
279
358
  let braceCount = 0;
280
- let inString = false;
281
- let stringChar = "";
282
- let escaped = false;
283
359
  for (let i = startIndex; i < formattedJs.length; i++) {
284
360
  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
- }
361
+ updateStringTracking(state, char);
362
+ if (state.inString) {
363
+ continue;
293
364
  }
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
- }
365
+ if (char === "{") {
366
+ braceCount++;
367
+ } else if (char === "}") {
368
+ braceCount--;
369
+ if (braceCount === 0) {
370
+ return formattedJs.substring(startIndex, i + 1);
303
371
  }
304
372
  }
305
373
  }
@@ -396,22 +464,19 @@ __export(json_exports, {
396
464
  bundleAsJson: () => bundleAsJson
397
465
  });
398
466
  async function bundleAsJson(bundleData, resolver, formatTokens) {
467
+ if (!formatTokens) {
468
+ throw new ConfigurationError("JSON formatter was not provided");
469
+ }
399
470
  const metadata = buildMetadata(resolver);
400
471
  const tokens = {};
401
472
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
402
473
  const cleanTokens = stripInternalMetadata(tokenSet);
403
- if (!formatTokens) {
404
- throw new ConfigurationError("JSON formatter was not provided");
405
- }
406
474
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
407
475
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
408
476
  const themeJson = await formatTokens(cleanTokens);
409
477
  tokens[key] = JSON.parse(themeJson);
410
478
  }
411
- const bundle = {
412
- _meta: metadata,
413
- tokens
414
- };
479
+ const bundle = { _meta: metadata, tokens };
415
480
  return JSON.stringify(bundle, null, 2);
416
481
  }
417
482
  var init_json = __esm({
@@ -420,6 +485,17 @@ var init_json = __esm({
420
485
  init_utils();
421
486
  }
422
487
  });
488
+ function nameKebabCase() {
489
+ return {
490
+ transform: (token) => {
491
+ const name = kebabCase(token.path.join(" "));
492
+ return {
493
+ ...token,
494
+ name
495
+ };
496
+ }
497
+ };
498
+ }
423
499
  function isColorObject(value) {
424
500
  return typeof value === "object" && value !== null && "colorSpace" in value && "components" in value;
425
501
  }
@@ -477,7 +553,7 @@ function colorObjectToHex(color) {
477
553
  return formatHex(culoriColor);
478
554
  }
479
555
 
480
- // src/processing/processors/transforms/built-in/dimension-converter.ts
556
+ // src/processing/transforms/built-in/dimension-converter.ts
481
557
  function isDimensionObject(value) {
482
558
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
483
559
  }
@@ -485,6 +561,14 @@ function dimensionObjectToString(dimension) {
485
561
  return `${dimension.value}${dimension.unit}`;
486
562
  }
487
563
 
564
+ // src/processing/transforms/built-in/duration-converter.ts
565
+ function isDurationObject(value) {
566
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
567
+ }
568
+ function durationObjectToString(duration) {
569
+ return `${duration.value}${duration.unit}`;
570
+ }
571
+
488
572
  // src/renderers/android.ts
489
573
  init_errors();
490
574
  init_token_utils();
@@ -546,9 +630,6 @@ function resolveColorFormat(format) {
546
630
  }
547
631
  return "argb_hex";
548
632
  }
549
- function indent(width, level) {
550
- return " ".repeat(width * level);
551
- }
552
633
  function escapeKotlinString(str) {
553
634
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
554
635
  }
@@ -564,22 +645,6 @@ function roundComponent(value) {
564
645
  function toResourceName(family) {
565
646
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
566
647
  }
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
648
  var AndroidRenderer = class {
584
649
  async format(context, options) {
585
650
  if (!options?.packageName) {
@@ -587,6 +652,7 @@ var AndroidRenderer = class {
587
652
  `Output "${context.output.name}": packageName is required for Android output`
588
653
  );
589
654
  }
655
+ const visibility = options?.visibility;
590
656
  const opts = {
591
657
  preset: options?.preset ?? "standalone",
592
658
  packageName: options.packageName,
@@ -594,7 +660,8 @@ var AndroidRenderer = class {
594
660
  colorFormat: resolveColorFormat(options?.colorFormat),
595
661
  colorSpace: options?.colorSpace ?? "sRGB",
596
662
  structure: options?.structure ?? "nested",
597
- visibility: options?.visibility,
663
+ visibility,
664
+ visPrefix: visibility ? `${visibility} ` : "",
598
665
  indent: options?.indent ?? 4
599
666
  };
600
667
  if (opts.preset === "bundle") {
@@ -627,19 +694,6 @@ var AndroidRenderer = class {
627
694
  // -----------------------------------------------------------------------
628
695
  // Flat structure grouping
629
696
  // -----------------------------------------------------------------------
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
697
  /**
644
698
  * Builds a flattened camelCase name from a token's path, stripping the
645
699
  * type prefix segment (which is already represented by the group object).
@@ -648,7 +702,7 @@ var AndroidRenderer = class {
648
702
  const path = token.path;
649
703
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
650
704
  const joined = withoutTypePrefix.join("_");
651
- return toKotlinIdentifier(joined);
705
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
652
706
  }
653
707
  // -----------------------------------------------------------------------
654
708
  // Rendering
@@ -660,22 +714,21 @@ var AndroidRenderer = class {
660
714
  return this.formatAsNested(tokens, options);
661
715
  }
662
716
  formatAsNested(tokens, options) {
717
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
663
718
  const tree = this.buildTokenTree(tokens);
664
- const tokenTypes = /* @__PURE__ */ new Set();
665
- this.collectTokenTypes(tree, tokenTypes);
666
- return this.buildFile(tokenTypes, options, (lines, vis) => {
719
+ return this.buildFile(tokenTypes, options, (lines) => {
667
720
  lines.push(`@Suppress("unused")`);
668
- lines.push(`${vis}object ${options.objectName} {`);
721
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
669
722
  this.renderTreeChildren(lines, tree, 1, options);
670
723
  lines.push("}");
671
724
  });
672
725
  }
673
726
  formatAsFlat(tokens, options) {
674
- const groups = this.groupTokensByType(tokens);
727
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
675
728
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
676
- return this.buildFile(tokenTypes, options, (lines, vis) => {
729
+ return this.buildFile(tokenTypes, options, (lines) => {
677
730
  lines.push(`@Suppress("unused")`);
678
- lines.push(`${vis}object ${options.objectName} {`);
731
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
679
732
  this.renderFlatGroups(lines, groups, 1, options);
680
733
  lines.push("}");
681
734
  });
@@ -686,9 +739,8 @@ var AndroidRenderer = class {
686
739
  */
687
740
  buildFile(tokenTypes, options, renderBody) {
688
741
  const imports = this.collectImports(tokenTypes, options);
689
- const vis = options.visibility ? `${options.visibility} ` : "";
690
742
  const lines = [];
691
- lines.push(this.buildFileHeader());
743
+ lines.push(buildGeneratedFileHeader());
692
744
  lines.push("");
693
745
  lines.push(`package ${options.packageName}`);
694
746
  lines.push("");
@@ -699,19 +751,18 @@ var AndroidRenderer = class {
699
751
  lines.push("");
700
752
  }
701
753
  if (tokenTypes.has("shadow")) {
702
- lines.push(...this.buildShadowTokenClass(vis, options));
754
+ lines.push(...this.buildShadowTokenClass(options));
703
755
  lines.push("");
704
756
  }
705
- renderBody(lines, vis);
757
+ renderBody(lines);
706
758
  lines.push("");
707
759
  return lines.join("\n");
708
760
  }
709
761
  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);
762
+ const groupIndent = indentStr(options.indent, baseDepth);
763
+ const valIndent = indentStr(options.indent, baseDepth + 1);
713
764
  for (const group of groups) {
714
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
765
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
715
766
  for (const token of group.tokens) {
716
767
  const kotlinName = this.buildFlatKotlinName(token);
717
768
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -719,23 +770,24 @@ var AndroidRenderer = class {
719
770
  if (token.$description) {
720
771
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
721
772
  }
722
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
773
+ lines.push(
774
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
775
+ );
723
776
  }
724
777
  lines.push(`${groupIndent}}`);
725
778
  lines.push("");
726
779
  }
727
780
  }
728
781
  renderTreeChildren(lines, node, depth, options) {
729
- const vis = options.visibility ? `${options.visibility} ` : "";
730
- const pad = indent(options.indent, depth);
782
+ const pad = indentStr(options.indent, depth);
731
783
  const entries = Array.from(node.children.entries());
732
784
  for (let idx = 0; idx < entries.length; idx++) {
733
785
  const [key, child] = entries[idx];
734
786
  if (child.token && child.children.size === 0) {
735
787
  this.renderLeaf(lines, key, child.token, depth, options);
736
788
  } else if (child.children.size > 0 && !child.token) {
737
- const objectName = toPascalCase(key);
738
- lines.push(`${pad}${vis}object ${objectName} {`);
789
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
790
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
739
791
  this.renderTreeChildren(lines, child, depth + 1, options);
740
792
  lines.push(`${pad}}`);
741
793
  if (idx < entries.length - 1) {
@@ -748,30 +800,23 @@ var AndroidRenderer = class {
748
800
  }
749
801
  }
750
802
  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);
803
+ const pad = indentStr(options.indent, depth);
804
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
754
805
  const kotlinValue = this.formatKotlinValue(token, options, depth);
755
806
  const annotation = this.typeAnnotationSuffix(token);
756
807
  if (token.$description) {
757
808
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
758
809
  }
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");
810
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
766
811
  }
767
812
  // -----------------------------------------------------------------------
768
813
  // Shadow data class
769
814
  // -----------------------------------------------------------------------
770
- buildShadowTokenClass(vis, options) {
771
- const i1 = indent(options.indent, 1);
815
+ buildShadowTokenClass(options) {
816
+ const i1 = indentStr(options.indent, 1);
772
817
  return [
773
818
  "@Immutable",
774
- `${vis}data class ShadowToken(`,
819
+ `${options.visPrefix}data class ShadowToken(`,
775
820
  `${i1}val color: Color,`,
776
821
  `${i1}val elevation: Dp,`,
777
822
  `${i1}val offsetX: Dp,`,
@@ -822,14 +867,6 @@ var AndroidRenderer = class {
822
867
  }
823
868
  return Array.from(imports).sort();
824
869
  }
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
870
  collectTokenTypesFromEntries(tokens) {
834
871
  const types = /* @__PURE__ */ new Set();
835
872
  for (const [, token] of Object.entries(tokens)) {
@@ -1056,9 +1093,8 @@ var AndroidRenderer = class {
1056
1093
  return map[name.toLowerCase()];
1057
1094
  }
1058
1095
  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`;
1096
+ if (isDurationObject(value)) {
1097
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
1062
1098
  }
1063
1099
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
1064
1100
  }
@@ -1076,8 +1112,8 @@ var AndroidRenderer = class {
1076
1112
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
1077
1113
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
1078
1114
  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);
1115
+ const propIndent = indentStr(options.indent, depth + 1);
1116
+ const closeIndent = indentStr(options.indent, depth);
1081
1117
  return [
1082
1118
  "ShadowToken(",
1083
1119
  `${propIndent}color = ${color},`,
@@ -1126,8 +1162,8 @@ var AndroidRenderer = class {
1126
1162
  if (parts.length === 0) {
1127
1163
  return "TextStyle()";
1128
1164
  }
1129
- const propIndent = indent(options.indent, depth + 1);
1130
- const closeIndent = indent(options.indent, depth);
1165
+ const propIndent = indentStr(options.indent, depth + 1);
1166
+ const closeIndent = indentStr(options.indent, depth);
1131
1167
  return `TextStyle(
1132
1168
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
1133
1169
  ${closeIndent})`;
@@ -1136,12 +1172,12 @@ ${closeIndent})`;
1136
1172
  // Output: standalone
1137
1173
  // -----------------------------------------------------------------------
1138
1174
  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
- }
1175
+ assertFileRequired(
1176
+ context.buildPath,
1177
+ context.output.file,
1178
+ context.output.name,
1179
+ "standalone Android"
1180
+ );
1145
1181
  const files = {};
1146
1182
  for (const { tokens, modifierInputs } of context.permutations) {
1147
1183
  const processedTokens = stripInternalMetadata(tokens);
@@ -1161,12 +1197,12 @@ ${closeIndent})`;
1161
1197
  // Output: bundle
1162
1198
  // -----------------------------------------------------------------------
1163
1199
  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
- }
1200
+ assertFileRequired(
1201
+ context.buildPath,
1202
+ context.output.file,
1203
+ context.output.name,
1204
+ "bundle Android"
1205
+ );
1170
1206
  const content = this.formatBundleContent(context, options);
1171
1207
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
1172
1208
  outputName: context.output.name,
@@ -1179,15 +1215,15 @@ ${closeIndent})`;
1179
1215
  }
1180
1216
  formatBundleContent(context, options) {
1181
1217
  const allTokenTypes = this.collectAllPermutationTypes(context);
1182
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
1183
- const i1 = indent(options.indent, 1);
1218
+ return this.buildFile(allTokenTypes, options, (lines) => {
1219
+ const i1 = indentStr(options.indent, 1);
1184
1220
  lines.push(`@Suppress("unused")`);
1185
- lines.push(`${vis}object ${options.objectName} {`);
1221
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
1186
1222
  for (let idx = 0; idx < context.permutations.length; idx++) {
1187
1223
  const { tokens, modifierInputs } = context.permutations[idx];
1188
1224
  const processedTokens = stripInternalMetadata(tokens);
1189
1225
  const permName = this.buildPermutationName(modifierInputs);
1190
- lines.push(`${i1}${vis}object ${permName} {`);
1226
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
1191
1227
  this.renderBundleTokens(lines, processedTokens, options, 2);
1192
1228
  lines.push(`${i1}}`);
1193
1229
  if (idx < context.permutations.length - 1) {
@@ -1198,20 +1234,17 @@ ${closeIndent})`;
1198
1234
  });
1199
1235
  }
1200
1236
  collectAllPermutationTypes(context) {
1201
- const allTokenTypes = /* @__PURE__ */ new Set();
1237
+ const types = /* @__PURE__ */ new Set();
1202
1238
  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
- }
1239
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
1240
+ types.add(t);
1208
1241
  }
1209
1242
  }
1210
- return allTokenTypes;
1243
+ return types;
1211
1244
  }
1212
1245
  renderBundleTokens(lines, tokens, options, baseDepth) {
1213
1246
  if (options.structure === "flat") {
1214
- const groups = this.groupTokensByType(tokens);
1247
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
1215
1248
  this.renderFlatGroups(lines, groups, baseDepth, options);
1216
1249
  return;
1217
1250
  }
@@ -1223,7 +1256,7 @@ ${closeIndent})`;
1223
1256
  if (values.length === 0) {
1224
1257
  return "Default";
1225
1258
  }
1226
- return values.map((v) => toPascalCase(v)).join("");
1259
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
1227
1260
  }
1228
1261
  };
1229
1262
  function androidRenderer() {
@@ -1243,19 +1276,19 @@ init_token_utils();
1243
1276
  // src/renderers/bundlers/css.ts
1244
1277
  init_errors();
1245
1278
  init_utils();
1279
+ var REF_PREFIX_SETS = "#/sets/";
1280
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
1246
1281
  var getSourceSet = (token) => {
1247
1282
  if (typeof token !== "object" || token === null) {
1248
1283
  return void 0;
1249
1284
  }
1250
- const maybe = token;
1251
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
1285
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
1252
1286
  };
1253
1287
  var getSourceModifier = (token) => {
1254
1288
  if (typeof token !== "object" || token === null) {
1255
1289
  return void 0;
1256
1290
  }
1257
- const maybe = token;
1258
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
1291
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
1259
1292
  };
1260
1293
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
1261
1294
  const baseItem = bundleData.find((item) => item.isBase);
@@ -1340,6 +1373,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
1340
1373
  return `/* Modifier: ${modifier}=${context} */
1341
1374
  ${css2}`;
1342
1375
  }
1376
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
1377
+ if (Object.keys(blockTokens).length === 0) {
1378
+ return;
1379
+ }
1380
+ for (const k of Object.keys(blockTokens)) {
1381
+ included.add(k);
1382
+ }
1383
+ blocks.push({ key, description, tokens: blockTokens });
1384
+ }
1343
1385
  function collectSetTokens(tokens, setName, included) {
1344
1386
  const result = {};
1345
1387
  for (const [name, token] of Object.entries(tokens)) {
@@ -1370,75 +1412,67 @@ function collectRemainder(tokens, included) {
1370
1412
  function buildSetLayerBlocks(tokens, resolver) {
1371
1413
  const blocks = [];
1372
1414
  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
1415
  for (const item of resolver.resolutionOrder) {
1383
1416
  const ref = item.$ref;
1384
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
1417
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
1385
1418
  continue;
1386
1419
  }
1387
- const setName = ref.slice("#/sets/".length);
1388
- addBlock(
1420
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1421
+ addLayerBlock(
1422
+ blocks,
1423
+ included,
1389
1424
  `Set: ${setName}`,
1390
1425
  collectSetTokens(tokens, setName, included),
1391
1426
  resolver.sets?.[setName]?.description
1392
1427
  );
1393
1428
  }
1394
- addBlock("Unattributed", collectRemainder(tokens, included));
1429
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1395
1430
  return blocks;
1396
1431
  }
1397
1432
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
1398
1433
  const blocks = [];
1399
1434
  const included = /* @__PURE__ */ new Set();
1400
1435
  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
1436
  for (const item of resolver.resolutionOrder) {
1411
1437
  const ref = item.$ref;
1412
1438
  if (typeof ref !== "string") {
1413
1439
  continue;
1414
1440
  }
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
- }
1441
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
1438
1442
  }
1439
- addBlock("Unattributed", collectRemainder(tokens, included));
1443
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1440
1444
  return blocks;
1441
1445
  }
1446
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
1447
+ if (ref.startsWith(REF_PREFIX_SETS)) {
1448
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1449
+ addLayerBlock(
1450
+ blocks,
1451
+ included,
1452
+ `Set: ${setName}`,
1453
+ collectSetTokens(tokens, setName, included),
1454
+ resolver.sets?.[setName]?.description
1455
+ );
1456
+ return;
1457
+ }
1458
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1459
+ return;
1460
+ }
1461
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
1462
+ const modifier = resolver.modifiers?.[modifierName];
1463
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
1464
+ if (!modifier || !selectedContext) {
1465
+ return;
1466
+ }
1467
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
1468
+ addLayerBlock(
1469
+ blocks,
1470
+ included,
1471
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
1472
+ collectModifierTokens(tokens, expectedSource, included),
1473
+ modifier.description
1474
+ );
1475
+ }
1442
1476
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
1443
1477
  const normalizedModifier = modifierName.toLowerCase();
1444
1478
  const normalizedContext = context.toLowerCase();
@@ -1453,6 +1487,36 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
1453
1487
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
1454
1488
  });
1455
1489
  }
1490
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
1491
+ if (!item) {
1492
+ return;
1493
+ }
1494
+ const key = stableInputsKey(item.modifierInputs);
1495
+ if (includedKeys.has(key)) {
1496
+ return;
1497
+ }
1498
+ includedKeys.add(key);
1499
+ ordered.push(item);
1500
+ }
1501
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
1502
+ for (const modifierName of orderedNames) {
1503
+ const modifierDef = modifiers[modifierName];
1504
+ if (!modifierDef) {
1505
+ continue;
1506
+ }
1507
+ const defaultValue = baseInputs[modifierName.toLowerCase()] ?? "";
1508
+ for (const ctx of Object.keys(modifierDef.contexts)) {
1509
+ if (defaultValue === ctx.toLowerCase()) {
1510
+ continue;
1511
+ }
1512
+ pushUniqueBundleItem(
1513
+ ordered,
1514
+ includedKeys,
1515
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
1516
+ );
1517
+ }
1518
+ }
1519
+ }
1456
1520
  function orderBundleData(bundleData, resolver, baseItem) {
1457
1521
  const modifiers = resolver.modifiers;
1458
1522
  if (!modifiers) {
@@ -1469,31 +1533,15 @@ function orderBundleData(bundleData, resolver, baseItem) {
1469
1533
  }
1470
1534
  const includedKeys = /* @__PURE__ */ new Set();
1471
1535
  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
- }
1536
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
1537
+ appendModifierPermutations(
1538
+ bundleData,
1539
+ modifiers,
1540
+ orderedModifierNames,
1541
+ baseInputs,
1542
+ ordered,
1543
+ includedKeys
1544
+ );
1497
1545
  return ordered.length > 0 ? ordered : bundleData;
1498
1546
  }
1499
1547
  function getOrderedModifierNames(resolver) {
@@ -1505,10 +1553,10 @@ function getOrderedModifierNames(resolver) {
1505
1553
  if (typeof ref !== "string") {
1506
1554
  continue;
1507
1555
  }
1508
- if (!ref.startsWith("#/modifiers/")) {
1556
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1509
1557
  continue;
1510
1558
  }
1511
- const name = ref.slice("#/modifiers/".length);
1559
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
1512
1560
  if (seen.has(name)) {
1513
1561
  continue;
1514
1562
  }
@@ -1579,24 +1627,22 @@ var CssRenderer = class _CssRenderer {
1579
1627
  ...options,
1580
1628
  referenceTokens: options?.referenceTokens ?? tokens
1581
1629
  };
1582
- const groups = this.groupTokens(tokens, opts);
1630
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
1583
1631
  const referenceTokens = opts.referenceTokens;
1584
1632
  const lines = [];
1585
- for (const [selector, groupTokens] of Object.entries(groups)) {
1586
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
1587
- }
1633
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
1588
1634
  const cssString = lines.join("");
1589
1635
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
1590
1636
  }
1591
1637
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
1592
- const indent2 = opts.minify ? "" : " ";
1638
+ const indent = opts.minify ? "" : " ";
1593
1639
  const newline = opts.minify ? "" : "\n";
1594
1640
  const space = opts.minify ? "" : " ";
1595
1641
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
1596
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
1642
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
1597
1643
  if (hasMediaQuery) {
1598
1644
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
1599
- lines.push(`${indent2}${selector}${space}{${newline}`);
1645
+ lines.push(`${indent}${selector}${space}{${newline}`);
1600
1646
  } else {
1601
1647
  lines.push(`${selector}${space}{${newline}`);
1602
1648
  }
@@ -1613,21 +1659,21 @@ var CssRenderer = class _CssRenderer {
1613
1659
  );
1614
1660
  }
1615
1661
  if (hasMediaQuery) {
1616
- lines.push(`${indent2}}${newline}`);
1662
+ lines.push(`${indent}}${newline}`);
1617
1663
  }
1618
1664
  lines.push(`}${newline}${newline}`);
1619
1665
  }
1620
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
1666
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
1621
1667
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
1622
1668
  if (token.$deprecated != null && token.$deprecated !== false) {
1623
1669
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
1624
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1670
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1625
1671
  }
1626
1672
  if (token.$description && token.$description !== "") {
1627
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1673
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1628
1674
  }
1629
1675
  for (const entry of entries) {
1630
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
1676
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
1631
1677
  }
1632
1678
  }
1633
1679
  async formatWithPrettier(css2) {
@@ -1642,15 +1688,6 @@ var CssRenderer = class _CssRenderer {
1642
1688
  return css2;
1643
1689
  }
1644
1690
  }
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
1691
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
1655
1692
  if (preserveReferences) {
1656
1693
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -1792,7 +1829,7 @@ var CssRenderer = class _CssRenderer {
1792
1829
  leaves.push({ path, value });
1793
1830
  return;
1794
1831
  }
1795
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
1832
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
1796
1833
  leaves.push({ path, value });
1797
1834
  return;
1798
1835
  }
@@ -1835,8 +1872,8 @@ var CssRenderer = class _CssRenderer {
1835
1872
  if (isDimensionObject(value)) {
1836
1873
  return dimensionObjectToString(value);
1837
1874
  }
1838
- if (this.isDurationObject(value)) {
1839
- return this.formatDurationValue(value);
1875
+ if (isDurationObject(value)) {
1876
+ return durationObjectToString(value);
1840
1877
  }
1841
1878
  if (typeof value === "string") {
1842
1879
  return value;
@@ -1899,15 +1936,6 @@ var CssRenderer = class _CssRenderer {
1899
1936
  isPrimitiveValue(value) {
1900
1937
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1901
1938
  }
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
1939
  /**
1912
1940
  * Format token value for CSS
1913
1941
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -1928,8 +1956,8 @@ var CssRenderer = class _CssRenderer {
1928
1956
  return typeof value === "string" ? value : dimensionObjectToString(value);
1929
1957
  }
1930
1958
  if (type === "duration") {
1931
- if (this.isDurationObject(value)) {
1932
- return this.formatDurationValue(value);
1959
+ if (isDurationObject(value)) {
1960
+ return durationObjectToString(value);
1933
1961
  }
1934
1962
  if (typeof value === "string") {
1935
1963
  return value;
@@ -2019,16 +2047,16 @@ var CssRenderer = class _CssRenderer {
2019
2047
  */
2020
2048
  formatTransition(value) {
2021
2049
  const parts = [];
2022
- if (this.isDurationObject(value.duration)) {
2023
- parts.push(this.formatDurationValue(value.duration));
2050
+ if (isDurationObject(value.duration)) {
2051
+ parts.push(durationObjectToString(value.duration));
2024
2052
  } else if (value.duration != null) {
2025
2053
  parts.push(String(value.duration));
2026
2054
  }
2027
2055
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
2028
2056
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
2029
2057
  }
2030
- if (this.isDurationObject(value.delay)) {
2031
- parts.push(this.formatDurationValue(value.delay));
2058
+ if (isDurationObject(value.delay)) {
2059
+ parts.push(durationObjectToString(value.delay));
2032
2060
  } else if (value.delay != null) {
2033
2061
  parts.push(String(value.delay));
2034
2062
  }
@@ -2038,7 +2066,7 @@ var CssRenderer = class _CssRenderer {
2038
2066
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2039
2067
  tokens,
2040
2068
  modifierInputs,
2041
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2069
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2042
2070
  }));
2043
2071
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
2044
2072
  return await this.formatTokens(tokens, {
@@ -2048,12 +2076,12 @@ var CssRenderer = class _CssRenderer {
2048
2076
  });
2049
2077
  }
2050
2078
  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
- }
2079
+ assertFileRequired(
2080
+ context.buildPath,
2081
+ context.output.file,
2082
+ context.output.name,
2083
+ "standalone CSS"
2084
+ );
2057
2085
  const files = {};
2058
2086
  for (const { tokens, modifierInputs } of context.permutations) {
2059
2087
  const { fileName, content } = await this.buildStandaloneFile(
@@ -2067,7 +2095,7 @@ var CssRenderer = class _CssRenderer {
2067
2095
  return { kind: "outputTree", files };
2068
2096
  }
2069
2097
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
2070
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
2098
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
2071
2099
  const { modifierName, modifierContext } = this.resolveModifierContext(
2072
2100
  modifierInputs,
2073
2101
  context,
@@ -2104,12 +2132,7 @@ var CssRenderer = class _CssRenderer {
2104
2132
  return { fileName, content };
2105
2133
  }
2106
2134
  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
- }
2135
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
2113
2136
  if (!context.resolver.modifiers) {
2114
2137
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
2115
2138
  }
@@ -2135,7 +2158,7 @@ var CssRenderer = class _CssRenderer {
2135
2158
  }
2136
2159
  async buildModifierBaseFile(context, options) {
2137
2160
  const basePermutation = context.permutations.find(
2138
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
2161
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
2139
2162
  );
2140
2163
  if (!basePermutation) {
2141
2164
  return void 0;
@@ -2148,25 +2171,40 @@ var CssRenderer = class _CssRenderer {
2148
2171
  if (setBlocks.length === 0) {
2149
2172
  return void 0;
2150
2173
  }
2174
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
2175
+ const content = await this.formatSetBlocksCss(
2176
+ setBlocks,
2177
+ basePermutation.tokens,
2178
+ selector,
2179
+ mediaQuery,
2180
+ options
2181
+ );
2182
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
2183
+ return { fileName, content };
2184
+ }
2185
+ resolveBaseModifierContext(context, options) {
2151
2186
  const modifiers = context.resolver.modifiers;
2152
2187
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
2153
2188
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
2154
2189
  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;
2190
+ return {
2191
+ selector: resolveSelector(
2192
+ options.selector,
2193
+ firstModifierName,
2194
+ firstModifierContext,
2195
+ true,
2196
+ baseModifierInputs
2197
+ ),
2198
+ mediaQuery: resolveMediaQuery(
2199
+ options.mediaQuery,
2200
+ firstModifierName,
2201
+ firstModifierContext,
2202
+ true,
2203
+ baseModifierInputs
2204
+ )
2205
+ };
2206
+ }
2207
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
2170
2208
  const cssBlocks = [];
2171
2209
  for (const block of setBlocks) {
2172
2210
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -2182,9 +2220,7 @@ var CssRenderer = class _CssRenderer {
2182
2220
  cssBlocks.push(`${header}
2183
2221
  ${css2}`);
2184
2222
  }
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 };
2223
+ return cssBlocks.join("\n");
2188
2224
  }
2189
2225
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
2190
2226
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -2261,13 +2297,6 @@ ${css2}`);
2261
2297
  }
2262
2298
  return { modifierName: "", modifierContext: "" };
2263
2299
  }
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
2300
  };
2272
2301
  function cssRenderer() {
2273
2302
  const rendererInstance = new CssRenderer();
@@ -2279,9 +2308,18 @@ function cssRenderer() {
2279
2308
  };
2280
2309
  }
2281
2310
 
2311
+ // src/tokens/types.ts
2312
+ function isShadowToken(token) {
2313
+ return token.$type === "shadow";
2314
+ }
2315
+ function isTypographyToken(token) {
2316
+ return token.$type === "typography";
2317
+ }
2318
+ function isBorderToken(token) {
2319
+ return token.$type === "border";
2320
+ }
2321
+
2282
2322
  // src/renderers/ios.ts
2283
- init_errors();
2284
- init_token_utils();
2285
2323
  init_utils();
2286
2324
  var toSRGB2 = converter("rgb");
2287
2325
  var toP32 = converter("p3");
@@ -2370,94 +2408,68 @@ var IosRenderer = class {
2370
2408
  return await this.formatStandalone(context, opts);
2371
2409
  }
2372
2410
  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
2411
  const access = options.accessLevel;
2380
- const groups = this.groupTokensByType(tokens);
2412
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
2381
2413
  const imports = this.collectImports(tokens);
2382
- const i1 = this.indentStr(options.indent, 1);
2383
- const i2 = this.indentStr(options.indent, 2);
2384
2414
  const staticPrefix = this.staticLetPrefix(options);
2385
2415
  const frozen = this.frozenPrefix(options);
2386
2416
  const lines = [];
2387
- lines.push(this.buildFileHeader());
2417
+ lines.push(buildGeneratedFileHeader());
2388
2418
  lines.push("");
2389
2419
  for (const imp of imports) {
2390
2420
  lines.push(`import ${imp}`);
2391
2421
  }
2392
2422
  lines.push(...this.buildStructDefinitions(tokens, access, options));
2423
+ this.pushTokenLayout(lines, groups, options, access, staticPrefix, frozen);
2424
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2425
+ if (options.structure !== "grouped") {
2426
+ lines.push("");
2427
+ }
2428
+ return lines.join("\n");
2429
+ }
2430
+ pushTokenLayout(lines, groups, options, access, staticPrefix, frozen) {
2431
+ const i1 = indentStr(options.indent, 1);
2432
+ const i2 = indentStr(options.indent, 2);
2433
+ if (options.structure === "grouped") {
2434
+ this.pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen);
2435
+ return;
2436
+ }
2393
2437
  lines.push("");
2394
2438
  lines.push(`${frozen}${access} enum ${options.enumName} {`);
2395
2439
  for (const group of groups) {
2396
2440
  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
- }
2441
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2408
2442
  lines.push(`${i1}}`);
2409
2443
  lines.push("");
2410
2444
  }
2411
2445
  lines.push("}");
2412
- lines.push(...this.buildViewExtensions(tokens, access, options));
2413
- lines.push("");
2414
- return lines.join("\n");
2415
2446
  }
2416
- formatAsGrouped(tokens, options) {
2417
- const access = options.accessLevel;
2447
+ pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen) {
2418
2448
  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
2449
  lines.push("");
2433
2450
  lines.push(`${frozen}${access} enum ${namespace} {}`);
2434
2451
  lines.push("");
2435
2452
  for (const group of groups) {
2436
2453
  lines.push(`${access} extension ${namespace} {`);
2437
2454
  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
- }
2455
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2449
2456
  lines.push(`${i1}}`);
2450
2457
  lines.push("}");
2451
2458
  lines.push("");
2452
2459
  }
2453
- lines.push(...this.buildViewExtensions(tokens, access, options));
2454
- return lines.join("\n");
2455
2460
  }
2456
- buildFileHeader() {
2457
- return [
2458
- "// Generated by Dispersa - do not edit manually",
2459
- "// https://github.com/timges/dispersa"
2460
- ].join("\n");
2461
+ pushTokenDeclarations(lines, tokens, options, access, indent, staticPrefix) {
2462
+ for (const token of tokens) {
2463
+ const swiftName = this.buildQualifiedSwiftName(token);
2464
+ const swiftValue = this.formatSwiftValue(token, options);
2465
+ const typeAnnotation = this.getTypeAnnotation(token);
2466
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2467
+ const docComment = this.buildDocComment(token, indent);
2468
+ if (docComment) {
2469
+ lines.push(docComment);
2470
+ }
2471
+ lines.push(`${indent}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2472
+ }
2461
2473
  }
2462
2474
  collectImports(tokens) {
2463
2475
  const imports = /* @__PURE__ */ new Set();
@@ -2472,24 +2484,11 @@ var IosRenderer = class {
2472
2484
  /**
2473
2485
  * Builds a `///` doc comment from a token's `$description`, if present.
2474
2486
  */
2475
- buildDocComment(token, indent2) {
2487
+ buildDocComment(token, indent) {
2476
2488
  if (!token.$description) {
2477
2489
  return void 0;
2478
2490
  }
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
- }));
2491
+ return `${indent}/// ${token.$description}`;
2493
2492
  }
2494
2493
  /**
2495
2494
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -2502,43 +2501,40 @@ var IosRenderer = class {
2502
2501
  const path = token.path;
2503
2502
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
2504
2503
  const joined = withoutTypePrefix.join("_");
2505
- return this.toSwiftIdentifier(joined);
2504
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
2506
2505
  }
2507
2506
  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]}))`;
2507
+ const { $type, $value: value } = token;
2508
+ switch ($type) {
2509
+ case "color":
2510
+ return this.formatColorValue(value, options);
2511
+ case "dimension":
2512
+ return this.formatDimensionValue(value);
2513
+ case "fontFamily":
2514
+ return this.formatFontFamilyValue(value);
2515
+ case "fontWeight":
2516
+ return this.formatFontWeightValue(value);
2517
+ case "duration":
2518
+ return this.formatDurationValue(value);
2519
+ case "shadow":
2520
+ return this.formatShadowValue(value, options);
2521
+ case "typography":
2522
+ return this.formatTypographyValue(value);
2523
+ case "border":
2524
+ return this.formatBorderValue(value, options);
2525
+ case "gradient":
2526
+ return this.formatGradientValue(value, options);
2527
+ case "number":
2528
+ return String(value);
2529
+ case "cubicBezier":
2530
+ if (Array.isArray(value) && value.length === 4) {
2531
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2532
+ }
2533
+ break;
2541
2534
  }
2535
+ return this.formatSwiftPrimitive(value);
2536
+ }
2537
+ formatSwiftPrimitive(value) {
2542
2538
  if (typeof value === "string") {
2543
2539
  return `"${this.escapeSwiftString(value)}"`;
2544
2540
  }
@@ -2571,9 +2567,7 @@ var IosRenderer = class {
2571
2567
  }
2572
2568
  formatDimensionValue(value) {
2573
2569
  if (isDimensionObject(value)) {
2574
- const dim = value;
2575
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2576
- return String(ptValue);
2570
+ return this.dimensionToPoints(value);
2577
2571
  }
2578
2572
  return String(value);
2579
2573
  }
@@ -2640,7 +2634,7 @@ var IosRenderer = class {
2640
2634
  return map[name.toLowerCase()];
2641
2635
  }
2642
2636
  formatDurationValue(value) {
2643
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
2637
+ if (isDurationObject(value)) {
2644
2638
  const dur = value;
2645
2639
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
2646
2640
  return String(seconds);
@@ -2689,9 +2683,7 @@ var IosRenderer = class {
2689
2683
  if (!isDimensionObject(typo.letterSpacing)) {
2690
2684
  return "0";
2691
2685
  }
2692
- const dim = typo.letterSpacing;
2693
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2694
- return String(ptValue);
2686
+ return this.dimensionToPoints(typo.letterSpacing);
2695
2687
  }
2696
2688
  extractLineSpacing(typo) {
2697
2689
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -2700,18 +2692,19 @@ var IosRenderer = class {
2700
2692
  if (!isDimensionObject(typo.fontSize)) {
2701
2693
  return "0";
2702
2694
  }
2703
- const dim = typo.fontSize;
2704
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
2695
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
2705
2696
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
2706
2697
  return String(lineHeightPt - basePt);
2707
2698
  }
2699
+ dimensionToNumericPoints(dim) {
2700
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
2701
+ }
2708
2702
  dimensionToPoints(dim) {
2709
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2710
- return String(ptValue);
2703
+ return String(this.dimensionToNumericPoints(dim));
2711
2704
  }
2712
2705
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
2713
2706
  dimensionToCGFloat(dim) {
2714
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2707
+ const ptValue = this.dimensionToNumericPoints(dim);
2715
2708
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
2716
2709
  }
2717
2710
  getTypeAnnotation(token) {
@@ -2730,21 +2723,12 @@ var IosRenderer = class {
2730
2723
  return void 0;
2731
2724
  }
2732
2725
  }
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
2726
  escapeSwiftString(str) {
2740
2727
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2741
2728
  }
2742
2729
  roundComponent(value) {
2743
2730
  return Math.round(value * 1e4) / 1e4;
2744
2731
  }
2745
- indentStr(width, level) {
2746
- return " ".repeat(width * level);
2747
- }
2748
2732
  /**
2749
2733
  * Returns the prefix for `static let` declarations.
2750
2734
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -2760,34 +2744,25 @@ var IosRenderer = class {
2760
2744
  structConformances(options) {
2761
2745
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
2762
2746
  }
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
2747
  /** Emits all struct definitions needed by the token set. */
2773
2748
  buildStructDefinitions(tokens, access, options) {
2774
2749
  const lines = [];
2775
- if (this.hasShadowTokens(tokens)) {
2750
+ if (Object.values(tokens).some(isShadowToken)) {
2776
2751
  lines.push("");
2777
2752
  lines.push(...this.buildShadowStyleStruct(access, options));
2778
2753
  }
2779
- if (this.hasTypographyTokens(tokens)) {
2754
+ if (Object.values(tokens).some(isTypographyToken)) {
2780
2755
  lines.push("");
2781
2756
  lines.push(...this.buildTypographyStyleStruct(access, options));
2782
2757
  }
2783
- if (this.hasBorderTokens(tokens)) {
2758
+ if (Object.values(tokens).some(isBorderToken)) {
2784
2759
  lines.push("");
2785
2760
  lines.push(...this.buildBorderStyleStruct(access, options));
2786
2761
  }
2787
2762
  return lines;
2788
2763
  }
2789
2764
  buildShadowStyleStruct(access, options) {
2790
- const i1 = this.indentStr(options.indent, 1);
2765
+ const i1 = indentStr(options.indent, 1);
2791
2766
  const conformances = this.structConformances(options);
2792
2767
  const frozen = this.frozenPrefix(options);
2793
2768
  return [
@@ -2801,7 +2776,7 @@ var IosRenderer = class {
2801
2776
  ];
2802
2777
  }
2803
2778
  buildTypographyStyleStruct(access, options) {
2804
- const i1 = this.indentStr(options.indent, 1);
2779
+ const i1 = indentStr(options.indent, 1);
2805
2780
  const conformances = this.structConformances(options);
2806
2781
  const frozen = this.frozenPrefix(options);
2807
2782
  return [
@@ -2813,7 +2788,7 @@ var IosRenderer = class {
2813
2788
  ];
2814
2789
  }
2815
2790
  buildBorderStyleStruct(access, options) {
2816
- const i1 = this.indentStr(options.indent, 1);
2791
+ const i1 = indentStr(options.indent, 1);
2817
2792
  const conformances = this.structConformances(options);
2818
2793
  const frozen = this.frozenPrefix(options);
2819
2794
  return [
@@ -2826,9 +2801,9 @@ var IosRenderer = class {
2826
2801
  /** Emits convenience View extensions for shadow and typography application. */
2827
2802
  buildViewExtensions(tokens, access, options) {
2828
2803
  const lines = [];
2829
- const i1 = this.indentStr(options.indent, 1);
2830
- const i2 = this.indentStr(options.indent, 2);
2831
- if (this.hasShadowTokens(tokens)) {
2804
+ const i1 = indentStr(options.indent, 1);
2805
+ const i2 = indentStr(options.indent, 2);
2806
+ if (Object.values(tokens).some(isShadowToken)) {
2832
2807
  lines.push("");
2833
2808
  lines.push(`${access} extension View {`);
2834
2809
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -2838,7 +2813,7 @@ var IosRenderer = class {
2838
2813
  lines.push(`${i1}}`);
2839
2814
  lines.push("}");
2840
2815
  }
2841
- if (this.hasTypographyTokens(tokens)) {
2816
+ if (Object.values(tokens).some(isTypographyToken)) {
2842
2817
  lines.push("");
2843
2818
  lines.push(`${access} extension View {`);
2844
2819
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -2870,12 +2845,12 @@ var IosRenderer = class {
2870
2845
  return `Gradient(stops: [${stops.join(", ")}])`;
2871
2846
  }
2872
2847
  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
- }
2848
+ assertFileRequired(
2849
+ context.buildPath,
2850
+ context.output.file,
2851
+ context.output.name,
2852
+ "standalone iOS"
2853
+ );
2879
2854
  const files = {};
2880
2855
  for (const { tokens, modifierInputs } of context.permutations) {
2881
2856
  const processedTokens = stripInternalMetadata(tokens);
@@ -2904,7 +2879,6 @@ function iosRenderer() {
2904
2879
 
2905
2880
  // src/renderers/js-module.ts
2906
2881
  init_utils();
2907
- init_errors();
2908
2882
  init_token_utils();
2909
2883
  var JsModuleRenderer = class {
2910
2884
  async format(context, options) {
@@ -2920,18 +2894,13 @@ var JsModuleRenderer = class {
2920
2894
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2921
2895
  tokens: stripInternalMetadata(tokens),
2922
2896
  modifierInputs,
2923
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2897
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2924
2898
  }));
2925
2899
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
2926
2900
  return await this.formatTokens(tokens, opts);
2927
2901
  });
2928
2902
  }
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
- }
2903
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
2935
2904
  const files = {};
2936
2905
  for (const { tokens, modifierInputs } of context.permutations) {
2937
2906
  const cleanTokens = stripInternalMetadata(tokens);
@@ -2985,42 +2954,18 @@ var JsModuleRenderer = class {
2985
2954
  lines.push(`export default ${varName}`);
2986
2955
  return lines;
2987
2956
  }
2988
- /**
2989
- * Convert tokens to plain object with flat or nested structure
2990
- */
2991
2957
  tokensToPlainObject(tokens, structure) {
2958
+ if (structure === "nested") {
2959
+ return buildNestedTokenObject(tokens, (token) => token.$value);
2960
+ }
2992
2961
  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
- }
2962
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
2963
+ result[name] = token.$value;
3016
2964
  }
3017
2965
  return result;
3018
2966
  }
3019
- /**
3020
- * Add object properties to lines
3021
- */
3022
- addObjectProperties(lines, obj, indent2) {
3023
- const indentStr = " ".repeat(indent2);
2967
+ addObjectProperties(lines, obj, indent) {
2968
+ const indentStr2 = " ".repeat(indent);
3024
2969
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
3025
2970
  for (let i = 0; i < entries.length; i++) {
3026
2971
  const entry = entries[i];
@@ -3029,14 +2974,16 @@ var JsModuleRenderer = class {
3029
2974
  }
3030
2975
  const [key, value] = entry;
3031
2976
  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 ? "" : ","}`);
2977
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
2978
+ if (!isNestedObject) {
2979
+ lines.push(
2980
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
2981
+ );
2982
+ continue;
3039
2983
  }
2984
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
2985
+ this.addObjectProperties(lines, value, indent + 1);
2986
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
3040
2987
  }
3041
2988
  }
3042
2989
  /**
@@ -3048,9 +2995,6 @@ var JsModuleRenderer = class {
3048
2995
  }
3049
2996
  return `"${key}"`;
3050
2997
  }
3051
- isBasePermutation(modifierInputs, defaults) {
3052
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3053
- }
3054
2998
  };
3055
2999
  function jsRenderer() {
3056
3000
  const rendererInstance = new JsModuleRenderer();
@@ -3064,7 +3008,6 @@ function jsRenderer() {
3064
3008
 
3065
3009
  // src/renderers/json.ts
3066
3010
  init_utils();
3067
- init_errors();
3068
3011
  init_token_utils();
3069
3012
  var JsonRenderer = class {
3070
3013
  async format(context, options) {
@@ -3079,18 +3022,13 @@ var JsonRenderer = class {
3079
3022
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3080
3023
  tokens: stripInternalMetadata(tokens),
3081
3024
  modifierInputs,
3082
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3025
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3083
3026
  }));
3084
3027
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
3085
3028
  return await this.formatTokens(tokens, opts);
3086
3029
  });
3087
3030
  }
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
- }
3031
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
3094
3032
  const files = {};
3095
3033
  for (const { tokens, modifierInputs } of context.permutations) {
3096
3034
  const processedTokens = stripInternalMetadata(tokens);
@@ -3150,55 +3088,11 @@ var JsonRenderer = class {
3150
3088
  }
3151
3089
  return result;
3152
3090
  }
3153
- /**
3154
- * Nest tokens by path (values only)
3155
- */
3156
3091
  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;
3092
+ return buildNestedTokenObject(tokens, (token) => token.$value);
3177
3093
  }
3178
- /**
3179
- * Nest tokens by path (with metadata)
3180
- */
3181
3094
  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;
3095
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
3202
3096
  }
3203
3097
  serializeToken(token) {
3204
3098
  return {
@@ -3209,9 +3103,6 @@ var JsonRenderer = class {
3209
3103
  ...token.$extensions != null && { $extensions: token.$extensions }
3210
3104
  };
3211
3105
  }
3212
- isBasePermutation(modifierInputs, defaults) {
3213
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3214
- }
3215
3106
  };
3216
3107
  function jsonRenderer() {
3217
3108
  const rendererInstance = new JsonRenderer();
@@ -3224,7 +3115,6 @@ function jsonRenderer() {
3224
3115
  }
3225
3116
 
3226
3117
  // src/renderers/tailwind.ts
3227
- init_errors();
3228
3118
  init_token_utils();
3229
3119
 
3230
3120
  // src/renderers/bundlers/tailwind.ts
@@ -3253,6 +3143,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
3253
3143
  }
3254
3144
  return cssBlocks.join("\n");
3255
3145
  }
3146
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
3147
+ const normalized = normalizeModifierInputs(modifierInputs);
3148
+ return {
3149
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
3150
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
3151
+ };
3152
+ }
3256
3153
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
3257
3154
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
3258
3155
  if (differenceCount > 1) {
@@ -3265,19 +3162,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
3265
3162
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
3266
3163
  const [modifier, context] = parseModifierSource(expectedSource);
3267
3164
  const cleanTokens = stripInternalMetadata(tokensToInclude);
3268
- const selector = resolveSelector(
3269
- options.selector,
3270
- modifier,
3271
- context,
3272
- false,
3273
- normalizeModifierInputs(modifierInputs)
3274
- );
3275
- const mediaQuery = resolveMediaQuery(
3276
- options.mediaQuery,
3165
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
3166
+ options,
3277
3167
  modifier,
3278
3168
  context,
3279
- false,
3280
- normalizeModifierInputs(modifierInputs)
3169
+ modifierInputs
3281
3170
  );
3282
3171
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
3283
3172
  return `/* Modifier: ${modifier}=${context} */
@@ -3366,7 +3255,7 @@ var TailwindRenderer = class {
3366
3255
  */
3367
3256
  async formatTokens(tokens, options) {
3368
3257
  const lines = [];
3369
- const indent2 = options.minify ? "" : " ";
3258
+ const indent = options.minify ? "" : " ";
3370
3259
  const newline = options.minify ? "" : "\n";
3371
3260
  const space = options.minify ? "" : " ";
3372
3261
  if (options.includeImport) {
@@ -3388,7 +3277,7 @@ var TailwindRenderer = class {
3388
3277
  for (const [, token] of getSortedTokenEntries(tokens)) {
3389
3278
  const varName = this.buildVariableName(token);
3390
3279
  const varValue = this.formatValue(token);
3391
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
3280
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
3392
3281
  }
3393
3282
  lines.push(`}${newline}`);
3394
3283
  const cssString = lines.join("");
@@ -3399,15 +3288,15 @@ var TailwindRenderer = class {
3399
3288
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
3400
3289
  */
3401
3290
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
3402
- const indent2 = minify ? "" : " ";
3291
+ const indent = minify ? "" : " ";
3403
3292
  const newline = minify ? "" : "\n";
3404
3293
  const space = minify ? "" : " ";
3405
3294
  const hasMediaQuery = mediaQuery !== "";
3406
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
3295
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
3407
3296
  const lines = [];
3408
3297
  if (hasMediaQuery) {
3409
3298
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
3410
- lines.push(`${indent2}${selector}${space}{${newline}`);
3299
+ lines.push(`${indent}${selector}${space}{${newline}`);
3411
3300
  } else {
3412
3301
  lines.push(`${selector}${space}{${newline}`);
3413
3302
  }
@@ -3417,7 +3306,7 @@ var TailwindRenderer = class {
3417
3306
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
3418
3307
  }
3419
3308
  if (hasMediaQuery) {
3420
- lines.push(`${indent2}}${newline}`);
3309
+ lines.push(`${indent}}${newline}`);
3421
3310
  lines.push(`}${newline}`);
3422
3311
  } else {
3423
3312
  lines.push(`}${newline}`);
@@ -3444,8 +3333,8 @@ var TailwindRenderer = class {
3444
3333
  if (token.$type === "dimension" && isDimensionObject(value)) {
3445
3334
  return dimensionObjectToString(value);
3446
3335
  }
3447
- if (token.$type === "duration" && this.isDurationObject(value)) {
3448
- return `${value.value}${value.unit}`;
3336
+ if (token.$type === "duration" && isDurationObject(value)) {
3337
+ return durationObjectToString(value);
3449
3338
  }
3450
3339
  if (token.$type === "fontFamily") {
3451
3340
  if (Array.isArray(value)) {
@@ -3500,9 +3389,6 @@ var TailwindRenderer = class {
3500
3389
  }
3501
3390
  return parts.join(" ");
3502
3391
  }
3503
- isDurationObject(value) {
3504
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
3505
- }
3506
3392
  async formatWithPrettier(css2) {
3507
3393
  try {
3508
3394
  return await prettier.format(css2, {
@@ -3519,7 +3405,7 @@ var TailwindRenderer = class {
3519
3405
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3520
3406
  tokens,
3521
3407
  modifierInputs,
3522
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3408
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3523
3409
  }));
3524
3410
  return await bundleAsTailwind(
3525
3411
  bundleData,
@@ -3529,12 +3415,12 @@ var TailwindRenderer = class {
3529
3415
  );
3530
3416
  }
3531
3417
  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
- }
3418
+ assertFileRequired(
3419
+ context.buildPath,
3420
+ context.output.file,
3421
+ context.output.name,
3422
+ "standalone Tailwind"
3423
+ );
3538
3424
  const files = {};
3539
3425
  for (const { tokens, modifierInputs } of context.permutations) {
3540
3426
  const processedTokens = stripInternalMetadata(tokens);
@@ -3550,11 +3436,6 @@ var TailwindRenderer = class {
3550
3436
  }
3551
3437
  return outputTree(files);
3552
3438
  }
3553
- isBasePermutation(modifierInputs, defaults) {
3554
- return Object.entries(defaults).every(
3555
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
3556
- );
3557
- }
3558
3439
  };
3559
3440
  function tailwindRenderer() {
3560
3441
  const rendererInstance = new TailwindRenderer();
@@ -3582,7 +3463,7 @@ function css(config) {
3582
3463
  file,
3583
3464
  renderer: cssRenderer(),
3584
3465
  options: { preset, ...rendererOptions },
3585
- transforms,
3466
+ transforms: [nameKebabCase(), ...transforms ?? []],
3586
3467
  filters,
3587
3468
  hooks
3588
3469
  };