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.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var changeCase = require('change-case');
3
4
  var culori = require('culori');
4
5
  var prettier = require('prettier');
5
6
 
@@ -52,13 +53,12 @@ function formatDeprecationMessage(token, description = "", format = "bracket") {
52
53
  }
53
54
  const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
54
55
  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;
56
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
57
+ return `DEPRECATED${msg2}`;
61
58
  }
59
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
60
+ const prefix = `[DEPRECATED${msg}]`;
61
+ return description ? `${prefix} ${description}` : prefix;
62
62
  }
63
63
  function stripInternalTokenMetadata(tokens) {
64
64
  const cleaned = {};
@@ -71,6 +71,30 @@ function stripInternalTokenMetadata(tokens) {
71
71
  function getSortedTokenEntries(tokens) {
72
72
  return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
73
73
  }
74
+ function buildNestedTokenObject(tokens, extractValue) {
75
+ const result = {};
76
+ for (const [, token] of getSortedTokenEntries(tokens)) {
77
+ setNestedValue(result, token.path, extractValue(token));
78
+ }
79
+ return result;
80
+ }
81
+ function setNestedValue(root, path, value) {
82
+ let current = root;
83
+ for (let i = 0; i < path.length - 1; i++) {
84
+ const part = path[i];
85
+ if (part == null) {
86
+ continue;
87
+ }
88
+ if (!(part in current)) {
89
+ current[part] = {};
90
+ }
91
+ current = current[part];
92
+ }
93
+ const lastPart = path[path.length - 1];
94
+ if (lastPart != null) {
95
+ current[lastPart] = value;
96
+ }
97
+ }
74
98
  function getPureAliasReferenceName(value) {
75
99
  if (typeof value !== "string") {
76
100
  return void 0;
@@ -90,6 +114,35 @@ function sanitizeDataAttributeName(value) {
90
114
  function escapeCssString(value) {
91
115
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
92
116
  }
117
+ function groupTokensByType(tokens, typeGroupMap) {
118
+ const groupMap = /* @__PURE__ */ new Map();
119
+ for (const [, token] of getSortedTokenEntries(tokens)) {
120
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
121
+ const existing = groupMap.get(groupName) ?? [];
122
+ existing.push(token);
123
+ groupMap.set(groupName, existing);
124
+ }
125
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
126
+ name,
127
+ tokens: groupTokens
128
+ }));
129
+ }
130
+ function indentStr(width, level) {
131
+ return " ".repeat(width * level);
132
+ }
133
+ function buildGeneratedFileHeader() {
134
+ return [
135
+ "// Generated by Dispersa - do not edit manually",
136
+ "// https://github.com/dispersa-core/dispersa"
137
+ ].join("\n");
138
+ }
139
+ function toSafeIdentifier(name, keywords, capitalize) {
140
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
141
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
142
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
143
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
144
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
145
+ }
93
146
  function normalizeModifierInputs(inputs) {
94
147
  const normalized = {};
95
148
  for (const [key, value] of Object.entries(inputs)) {
@@ -97,6 +150,14 @@ function normalizeModifierInputs(inputs) {
97
150
  }
98
151
  return normalized;
99
152
  }
153
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
154
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
155
+ if (!outputFile && requiresFile) {
156
+ throw new ConfigurationError(
157
+ `Output "${outputName}": file is required for ${presetLabel} output`
158
+ );
159
+ }
160
+ }
100
161
  function buildStablePermutationKey(modifierInputs, dimensions) {
101
162
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
102
163
  }
@@ -146,14 +207,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
146
207
  }
147
208
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
148
209
  }
149
- function buildInMemoryOutputKey(params) {
150
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
210
+ function isBasePermutation(modifierInputs, defaults) {
151
211
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
152
212
  const normalizedDefaults = normalizeModifierInputs(defaults);
153
- const isBase = Object.entries(normalizedDefaults).every(
154
- ([key, value]) => normalizedInputs[key] === value
213
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
214
+ }
215
+ function buildInMemoryOutputKey(params) {
216
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
217
+ const permutationKey = generatePermutationKey(
218
+ modifierInputs,
219
+ resolver,
220
+ isBasePermutation(modifierInputs, defaults)
155
221
  );
156
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
157
222
  return `${outputName}-${permutationKey}.${extension}`;
158
223
  }
159
224
  function buildMetadata(resolver) {
@@ -267,6 +332,7 @@ function resolveFileName(fileName, modifierInputs) {
267
332
  }
268
333
  var init_utils = __esm({
269
334
  "src/renderers/bundlers/utils.ts"() {
335
+ init_errors();
270
336
  init_token_utils();
271
337
  }
272
338
  });
@@ -276,36 +342,38 @@ var js_exports = {};
276
342
  __export(js_exports, {
277
343
  bundleAsJsModule: () => bundleAsJsModule
278
344
  });
345
+ function updateStringTracking(state, char) {
346
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
347
+ if (!state.inString) {
348
+ state.inString = true;
349
+ state.stringChar = char;
350
+ } else if (char === state.stringChar) {
351
+ state.inString = false;
352
+ state.stringChar = "";
353
+ }
354
+ }
355
+ state.escaped = !state.escaped && char === "\\";
356
+ }
279
357
  function extractObjectFromJsModule(formattedJs) {
280
358
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
281
359
  if (!assignmentMatch) {
282
360
  return "{}";
283
361
  }
284
362
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
363
+ const state = { inString: false, stringChar: "", escaped: false };
285
364
  let braceCount = 0;
286
- let inString = false;
287
- let stringChar = "";
288
- let escaped = false;
289
365
  for (let i = startIndex; i < formattedJs.length; i++) {
290
366
  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
- }
367
+ updateStringTracking(state, char);
368
+ if (state.inString) {
369
+ continue;
299
370
  }
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
- }
371
+ if (char === "{") {
372
+ braceCount++;
373
+ } else if (char === "}") {
374
+ braceCount--;
375
+ if (braceCount === 0) {
376
+ return formattedJs.substring(startIndex, i + 1);
309
377
  }
310
378
  }
311
379
  }
@@ -402,22 +470,19 @@ __export(json_exports, {
402
470
  bundleAsJson: () => bundleAsJson
403
471
  });
404
472
  async function bundleAsJson(bundleData, resolver, formatTokens) {
473
+ if (!formatTokens) {
474
+ throw new ConfigurationError("JSON formatter was not provided");
475
+ }
405
476
  const metadata = buildMetadata(resolver);
406
477
  const tokens = {};
407
478
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
408
479
  const cleanTokens = stripInternalMetadata(tokenSet);
409
- if (!formatTokens) {
410
- throw new ConfigurationError("JSON formatter was not provided");
411
- }
412
480
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
413
481
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
414
482
  const themeJson = await formatTokens(cleanTokens);
415
483
  tokens[key] = JSON.parse(themeJson);
416
484
  }
417
- const bundle = {
418
- _meta: metadata,
419
- tokens
420
- };
485
+ const bundle = { _meta: metadata, tokens };
421
486
  return JSON.stringify(bundle, null, 2);
422
487
  }
423
488
  var init_json = __esm({
@@ -426,6 +491,17 @@ var init_json = __esm({
426
491
  init_utils();
427
492
  }
428
493
  });
494
+ function nameKebabCase() {
495
+ return {
496
+ transform: (token) => {
497
+ const name = changeCase.kebabCase(token.path.join(" "));
498
+ return {
499
+ ...token,
500
+ name
501
+ };
502
+ }
503
+ };
504
+ }
429
505
  function isColorObject(value) {
430
506
  return typeof value === "object" && value !== null && "colorSpace" in value && "components" in value;
431
507
  }
@@ -483,7 +559,7 @@ function colorObjectToHex(color) {
483
559
  return culori.formatHex(culoriColor);
484
560
  }
485
561
 
486
- // src/processing/processors/transforms/built-in/dimension-converter.ts
562
+ // src/processing/transforms/built-in/dimension-converter.ts
487
563
  function isDimensionObject(value) {
488
564
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
489
565
  }
@@ -491,6 +567,14 @@ function dimensionObjectToString(dimension) {
491
567
  return `${dimension.value}${dimension.unit}`;
492
568
  }
493
569
 
570
+ // src/processing/transforms/built-in/duration-converter.ts
571
+ function isDurationObject(value) {
572
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
573
+ }
574
+ function durationObjectToString(duration) {
575
+ return `${duration.value}${duration.unit}`;
576
+ }
577
+
494
578
  // src/renderers/android.ts
495
579
  init_errors();
496
580
  init_token_utils();
@@ -552,9 +636,6 @@ function resolveColorFormat(format) {
552
636
  }
553
637
  return "argb_hex";
554
638
  }
555
- function indent(width, level) {
556
- return " ".repeat(width * level);
557
- }
558
639
  function escapeKotlinString(str) {
559
640
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
560
641
  }
@@ -570,22 +651,6 @@ function roundComponent(value) {
570
651
  function toResourceName(family) {
571
652
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
572
653
  }
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
654
  var AndroidRenderer = class {
590
655
  async format(context, options) {
591
656
  if (!options?.packageName) {
@@ -593,6 +658,7 @@ var AndroidRenderer = class {
593
658
  `Output "${context.output.name}": packageName is required for Android output`
594
659
  );
595
660
  }
661
+ const visibility = options?.visibility;
596
662
  const opts = {
597
663
  preset: options?.preset ?? "standalone",
598
664
  packageName: options.packageName,
@@ -600,7 +666,8 @@ var AndroidRenderer = class {
600
666
  colorFormat: resolveColorFormat(options?.colorFormat),
601
667
  colorSpace: options?.colorSpace ?? "sRGB",
602
668
  structure: options?.structure ?? "nested",
603
- visibility: options?.visibility,
669
+ visibility,
670
+ visPrefix: visibility ? `${visibility} ` : "",
604
671
  indent: options?.indent ?? 4
605
672
  };
606
673
  if (opts.preset === "bundle") {
@@ -633,19 +700,6 @@ var AndroidRenderer = class {
633
700
  // -----------------------------------------------------------------------
634
701
  // Flat structure grouping
635
702
  // -----------------------------------------------------------------------
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
703
  /**
650
704
  * Builds a flattened camelCase name from a token's path, stripping the
651
705
  * type prefix segment (which is already represented by the group object).
@@ -654,7 +708,7 @@ var AndroidRenderer = class {
654
708
  const path = token.path;
655
709
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
656
710
  const joined = withoutTypePrefix.join("_");
657
- return toKotlinIdentifier(joined);
711
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
658
712
  }
659
713
  // -----------------------------------------------------------------------
660
714
  // Rendering
@@ -666,22 +720,21 @@ var AndroidRenderer = class {
666
720
  return this.formatAsNested(tokens, options);
667
721
  }
668
722
  formatAsNested(tokens, options) {
723
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
669
724
  const tree = this.buildTokenTree(tokens);
670
- const tokenTypes = /* @__PURE__ */ new Set();
671
- this.collectTokenTypes(tree, tokenTypes);
672
- return this.buildFile(tokenTypes, options, (lines, vis) => {
725
+ return this.buildFile(tokenTypes, options, (lines) => {
673
726
  lines.push(`@Suppress("unused")`);
674
- lines.push(`${vis}object ${options.objectName} {`);
727
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
675
728
  this.renderTreeChildren(lines, tree, 1, options);
676
729
  lines.push("}");
677
730
  });
678
731
  }
679
732
  formatAsFlat(tokens, options) {
680
- const groups = this.groupTokensByType(tokens);
733
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
681
734
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
682
- return this.buildFile(tokenTypes, options, (lines, vis) => {
735
+ return this.buildFile(tokenTypes, options, (lines) => {
683
736
  lines.push(`@Suppress("unused")`);
684
- lines.push(`${vis}object ${options.objectName} {`);
737
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
685
738
  this.renderFlatGroups(lines, groups, 1, options);
686
739
  lines.push("}");
687
740
  });
@@ -692,9 +745,8 @@ var AndroidRenderer = class {
692
745
  */
693
746
  buildFile(tokenTypes, options, renderBody) {
694
747
  const imports = this.collectImports(tokenTypes, options);
695
- const vis = options.visibility ? `${options.visibility} ` : "";
696
748
  const lines = [];
697
- lines.push(this.buildFileHeader());
749
+ lines.push(buildGeneratedFileHeader());
698
750
  lines.push("");
699
751
  lines.push(`package ${options.packageName}`);
700
752
  lines.push("");
@@ -705,19 +757,18 @@ var AndroidRenderer = class {
705
757
  lines.push("");
706
758
  }
707
759
  if (tokenTypes.has("shadow")) {
708
- lines.push(...this.buildShadowTokenClass(vis, options));
760
+ lines.push(...this.buildShadowTokenClass(options));
709
761
  lines.push("");
710
762
  }
711
- renderBody(lines, vis);
763
+ renderBody(lines);
712
764
  lines.push("");
713
765
  return lines.join("\n");
714
766
  }
715
767
  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);
768
+ const groupIndent = indentStr(options.indent, baseDepth);
769
+ const valIndent = indentStr(options.indent, baseDepth + 1);
719
770
  for (const group of groups) {
720
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
771
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
721
772
  for (const token of group.tokens) {
722
773
  const kotlinName = this.buildFlatKotlinName(token);
723
774
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -725,23 +776,24 @@ var AndroidRenderer = class {
725
776
  if (token.$description) {
726
777
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
727
778
  }
728
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
779
+ lines.push(
780
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
781
+ );
729
782
  }
730
783
  lines.push(`${groupIndent}}`);
731
784
  lines.push("");
732
785
  }
733
786
  }
734
787
  renderTreeChildren(lines, node, depth, options) {
735
- const vis = options.visibility ? `${options.visibility} ` : "";
736
- const pad = indent(options.indent, depth);
788
+ const pad = indentStr(options.indent, depth);
737
789
  const entries = Array.from(node.children.entries());
738
790
  for (let idx = 0; idx < entries.length; idx++) {
739
791
  const [key, child] = entries[idx];
740
792
  if (child.token && child.children.size === 0) {
741
793
  this.renderLeaf(lines, key, child.token, depth, options);
742
794
  } else if (child.children.size > 0 && !child.token) {
743
- const objectName = toPascalCase(key);
744
- lines.push(`${pad}${vis}object ${objectName} {`);
795
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
796
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
745
797
  this.renderTreeChildren(lines, child, depth + 1, options);
746
798
  lines.push(`${pad}}`);
747
799
  if (idx < entries.length - 1) {
@@ -754,30 +806,23 @@ var AndroidRenderer = class {
754
806
  }
755
807
  }
756
808
  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);
809
+ const pad = indentStr(options.indent, depth);
810
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
760
811
  const kotlinValue = this.formatKotlinValue(token, options, depth);
761
812
  const annotation = this.typeAnnotationSuffix(token);
762
813
  if (token.$description) {
763
814
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
764
815
  }
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");
816
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
772
817
  }
773
818
  // -----------------------------------------------------------------------
774
819
  // Shadow data class
775
820
  // -----------------------------------------------------------------------
776
- buildShadowTokenClass(vis, options) {
777
- const i1 = indent(options.indent, 1);
821
+ buildShadowTokenClass(options) {
822
+ const i1 = indentStr(options.indent, 1);
778
823
  return [
779
824
  "@Immutable",
780
- `${vis}data class ShadowToken(`,
825
+ `${options.visPrefix}data class ShadowToken(`,
781
826
  `${i1}val color: Color,`,
782
827
  `${i1}val elevation: Dp,`,
783
828
  `${i1}val offsetX: Dp,`,
@@ -828,14 +873,6 @@ var AndroidRenderer = class {
828
873
  }
829
874
  return Array.from(imports).sort();
830
875
  }
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
876
  collectTokenTypesFromEntries(tokens) {
840
877
  const types = /* @__PURE__ */ new Set();
841
878
  for (const [, token] of Object.entries(tokens)) {
@@ -1062,9 +1099,8 @@ var AndroidRenderer = class {
1062
1099
  return map[name.toLowerCase()];
1063
1100
  }
1064
1101
  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`;
1102
+ if (isDurationObject(value)) {
1103
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
1068
1104
  }
1069
1105
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
1070
1106
  }
@@ -1082,8 +1118,8 @@ var AndroidRenderer = class {
1082
1118
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
1083
1119
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
1084
1120
  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);
1121
+ const propIndent = indentStr(options.indent, depth + 1);
1122
+ const closeIndent = indentStr(options.indent, depth);
1087
1123
  return [
1088
1124
  "ShadowToken(",
1089
1125
  `${propIndent}color = ${color},`,
@@ -1132,8 +1168,8 @@ var AndroidRenderer = class {
1132
1168
  if (parts.length === 0) {
1133
1169
  return "TextStyle()";
1134
1170
  }
1135
- const propIndent = indent(options.indent, depth + 1);
1136
- const closeIndent = indent(options.indent, depth);
1171
+ const propIndent = indentStr(options.indent, depth + 1);
1172
+ const closeIndent = indentStr(options.indent, depth);
1137
1173
  return `TextStyle(
1138
1174
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
1139
1175
  ${closeIndent})`;
@@ -1142,12 +1178,12 @@ ${closeIndent})`;
1142
1178
  // Output: standalone
1143
1179
  // -----------------------------------------------------------------------
1144
1180
  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
- }
1181
+ assertFileRequired(
1182
+ context.buildPath,
1183
+ context.output.file,
1184
+ context.output.name,
1185
+ "standalone Android"
1186
+ );
1151
1187
  const files = {};
1152
1188
  for (const { tokens, modifierInputs } of context.permutations) {
1153
1189
  const processedTokens = stripInternalMetadata(tokens);
@@ -1167,12 +1203,12 @@ ${closeIndent})`;
1167
1203
  // Output: bundle
1168
1204
  // -----------------------------------------------------------------------
1169
1205
  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
- }
1206
+ assertFileRequired(
1207
+ context.buildPath,
1208
+ context.output.file,
1209
+ context.output.name,
1210
+ "bundle Android"
1211
+ );
1176
1212
  const content = this.formatBundleContent(context, options);
1177
1213
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
1178
1214
  outputName: context.output.name,
@@ -1185,15 +1221,15 @@ ${closeIndent})`;
1185
1221
  }
1186
1222
  formatBundleContent(context, options) {
1187
1223
  const allTokenTypes = this.collectAllPermutationTypes(context);
1188
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
1189
- const i1 = indent(options.indent, 1);
1224
+ return this.buildFile(allTokenTypes, options, (lines) => {
1225
+ const i1 = indentStr(options.indent, 1);
1190
1226
  lines.push(`@Suppress("unused")`);
1191
- lines.push(`${vis}object ${options.objectName} {`);
1227
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
1192
1228
  for (let idx = 0; idx < context.permutations.length; idx++) {
1193
1229
  const { tokens, modifierInputs } = context.permutations[idx];
1194
1230
  const processedTokens = stripInternalMetadata(tokens);
1195
1231
  const permName = this.buildPermutationName(modifierInputs);
1196
- lines.push(`${i1}${vis}object ${permName} {`);
1232
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
1197
1233
  this.renderBundleTokens(lines, processedTokens, options, 2);
1198
1234
  lines.push(`${i1}}`);
1199
1235
  if (idx < context.permutations.length - 1) {
@@ -1204,20 +1240,17 @@ ${closeIndent})`;
1204
1240
  });
1205
1241
  }
1206
1242
  collectAllPermutationTypes(context) {
1207
- const allTokenTypes = /* @__PURE__ */ new Set();
1243
+ const types = /* @__PURE__ */ new Set();
1208
1244
  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
- }
1245
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
1246
+ types.add(t);
1214
1247
  }
1215
1248
  }
1216
- return allTokenTypes;
1249
+ return types;
1217
1250
  }
1218
1251
  renderBundleTokens(lines, tokens, options, baseDepth) {
1219
1252
  if (options.structure === "flat") {
1220
- const groups = this.groupTokensByType(tokens);
1253
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
1221
1254
  this.renderFlatGroups(lines, groups, baseDepth, options);
1222
1255
  return;
1223
1256
  }
@@ -1229,7 +1262,7 @@ ${closeIndent})`;
1229
1262
  if (values.length === 0) {
1230
1263
  return "Default";
1231
1264
  }
1232
- return values.map((v) => toPascalCase(v)).join("");
1265
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
1233
1266
  }
1234
1267
  };
1235
1268
  function androidRenderer() {
@@ -1249,19 +1282,19 @@ init_token_utils();
1249
1282
  // src/renderers/bundlers/css.ts
1250
1283
  init_errors();
1251
1284
  init_utils();
1285
+ var REF_PREFIX_SETS = "#/sets/";
1286
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
1252
1287
  var getSourceSet = (token) => {
1253
1288
  if (typeof token !== "object" || token === null) {
1254
1289
  return void 0;
1255
1290
  }
1256
- const maybe = token;
1257
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
1291
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
1258
1292
  };
1259
1293
  var getSourceModifier = (token) => {
1260
1294
  if (typeof token !== "object" || token === null) {
1261
1295
  return void 0;
1262
1296
  }
1263
- const maybe = token;
1264
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
1297
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
1265
1298
  };
1266
1299
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
1267
1300
  const baseItem = bundleData.find((item) => item.isBase);
@@ -1346,6 +1379,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
1346
1379
  return `/* Modifier: ${modifier}=${context} */
1347
1380
  ${css2}`;
1348
1381
  }
1382
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
1383
+ if (Object.keys(blockTokens).length === 0) {
1384
+ return;
1385
+ }
1386
+ for (const k of Object.keys(blockTokens)) {
1387
+ included.add(k);
1388
+ }
1389
+ blocks.push({ key, description, tokens: blockTokens });
1390
+ }
1349
1391
  function collectSetTokens(tokens, setName, included) {
1350
1392
  const result = {};
1351
1393
  for (const [name, token] of Object.entries(tokens)) {
@@ -1376,75 +1418,67 @@ function collectRemainder(tokens, included) {
1376
1418
  function buildSetLayerBlocks(tokens, resolver) {
1377
1419
  const blocks = [];
1378
1420
  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
1421
  for (const item of resolver.resolutionOrder) {
1389
1422
  const ref = item.$ref;
1390
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
1423
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
1391
1424
  continue;
1392
1425
  }
1393
- const setName = ref.slice("#/sets/".length);
1394
- addBlock(
1426
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1427
+ addLayerBlock(
1428
+ blocks,
1429
+ included,
1395
1430
  `Set: ${setName}`,
1396
1431
  collectSetTokens(tokens, setName, included),
1397
1432
  resolver.sets?.[setName]?.description
1398
1433
  );
1399
1434
  }
1400
- addBlock("Unattributed", collectRemainder(tokens, included));
1435
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1401
1436
  return blocks;
1402
1437
  }
1403
1438
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
1404
1439
  const blocks = [];
1405
1440
  const included = /* @__PURE__ */ new Set();
1406
1441
  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
1442
  for (const item of resolver.resolutionOrder) {
1417
1443
  const ref = item.$ref;
1418
1444
  if (typeof ref !== "string") {
1419
1445
  continue;
1420
1446
  }
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
- }
1447
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
1444
1448
  }
1445
- addBlock("Unattributed", collectRemainder(tokens, included));
1449
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
1446
1450
  return blocks;
1447
1451
  }
1452
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
1453
+ if (ref.startsWith(REF_PREFIX_SETS)) {
1454
+ const setName = ref.slice(REF_PREFIX_SETS.length);
1455
+ addLayerBlock(
1456
+ blocks,
1457
+ included,
1458
+ `Set: ${setName}`,
1459
+ collectSetTokens(tokens, setName, included),
1460
+ resolver.sets?.[setName]?.description
1461
+ );
1462
+ return;
1463
+ }
1464
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1465
+ return;
1466
+ }
1467
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
1468
+ const modifier = resolver.modifiers?.[modifierName];
1469
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
1470
+ if (!modifier || !selectedContext) {
1471
+ return;
1472
+ }
1473
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
1474
+ addLayerBlock(
1475
+ blocks,
1476
+ included,
1477
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
1478
+ collectModifierTokens(tokens, expectedSource, included),
1479
+ modifier.description
1480
+ );
1481
+ }
1448
1482
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
1449
1483
  const normalizedModifier = modifierName.toLowerCase();
1450
1484
  const normalizedContext = context.toLowerCase();
@@ -1459,6 +1493,36 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
1459
1493
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
1460
1494
  });
1461
1495
  }
1496
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
1497
+ if (!item) {
1498
+ return;
1499
+ }
1500
+ const key = stableInputsKey(item.modifierInputs);
1501
+ if (includedKeys.has(key)) {
1502
+ return;
1503
+ }
1504
+ includedKeys.add(key);
1505
+ ordered.push(item);
1506
+ }
1507
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
1508
+ for (const modifierName of orderedNames) {
1509
+ const modifierDef = modifiers[modifierName];
1510
+ if (!modifierDef) {
1511
+ continue;
1512
+ }
1513
+ const defaultValue = baseInputs[modifierName.toLowerCase()] ?? "";
1514
+ for (const ctx of Object.keys(modifierDef.contexts)) {
1515
+ if (defaultValue === ctx.toLowerCase()) {
1516
+ continue;
1517
+ }
1518
+ pushUniqueBundleItem(
1519
+ ordered,
1520
+ includedKeys,
1521
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
1522
+ );
1523
+ }
1524
+ }
1525
+ }
1462
1526
  function orderBundleData(bundleData, resolver, baseItem) {
1463
1527
  const modifiers = resolver.modifiers;
1464
1528
  if (!modifiers) {
@@ -1475,31 +1539,15 @@ function orderBundleData(bundleData, resolver, baseItem) {
1475
1539
  }
1476
1540
  const includedKeys = /* @__PURE__ */ new Set();
1477
1541
  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
- }
1542
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
1543
+ appendModifierPermutations(
1544
+ bundleData,
1545
+ modifiers,
1546
+ orderedModifierNames,
1547
+ baseInputs,
1548
+ ordered,
1549
+ includedKeys
1550
+ );
1503
1551
  return ordered.length > 0 ? ordered : bundleData;
1504
1552
  }
1505
1553
  function getOrderedModifierNames(resolver) {
@@ -1511,10 +1559,10 @@ function getOrderedModifierNames(resolver) {
1511
1559
  if (typeof ref !== "string") {
1512
1560
  continue;
1513
1561
  }
1514
- if (!ref.startsWith("#/modifiers/")) {
1562
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
1515
1563
  continue;
1516
1564
  }
1517
- const name = ref.slice("#/modifiers/".length);
1565
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
1518
1566
  if (seen.has(name)) {
1519
1567
  continue;
1520
1568
  }
@@ -1585,24 +1633,22 @@ var CssRenderer = class _CssRenderer {
1585
1633
  ...options,
1586
1634
  referenceTokens: options?.referenceTokens ?? tokens
1587
1635
  };
1588
- const groups = this.groupTokens(tokens, opts);
1636
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
1589
1637
  const referenceTokens = opts.referenceTokens;
1590
1638
  const lines = [];
1591
- for (const [selector, groupTokens] of Object.entries(groups)) {
1592
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
1593
- }
1639
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
1594
1640
  const cssString = lines.join("");
1595
1641
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
1596
1642
  }
1597
1643
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
1598
- const indent2 = opts.minify ? "" : " ";
1644
+ const indent = opts.minify ? "" : " ";
1599
1645
  const newline = opts.minify ? "" : "\n";
1600
1646
  const space = opts.minify ? "" : " ";
1601
1647
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
1602
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
1648
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
1603
1649
  if (hasMediaQuery) {
1604
1650
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
1605
- lines.push(`${indent2}${selector}${space}{${newline}`);
1651
+ lines.push(`${indent}${selector}${space}{${newline}`);
1606
1652
  } else {
1607
1653
  lines.push(`${selector}${space}{${newline}`);
1608
1654
  }
@@ -1619,21 +1665,21 @@ var CssRenderer = class _CssRenderer {
1619
1665
  );
1620
1666
  }
1621
1667
  if (hasMediaQuery) {
1622
- lines.push(`${indent2}}${newline}`);
1668
+ lines.push(`${indent}}${newline}`);
1623
1669
  }
1624
1670
  lines.push(`}${newline}${newline}`);
1625
1671
  }
1626
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
1672
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
1627
1673
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
1628
1674
  if (token.$deprecated != null && token.$deprecated !== false) {
1629
1675
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
1630
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1676
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
1631
1677
  }
1632
1678
  if (token.$description && token.$description !== "") {
1633
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1679
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
1634
1680
  }
1635
1681
  for (const entry of entries) {
1636
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
1682
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
1637
1683
  }
1638
1684
  }
1639
1685
  async formatWithPrettier(css2) {
@@ -1648,15 +1694,6 @@ var CssRenderer = class _CssRenderer {
1648
1694
  return css2;
1649
1695
  }
1650
1696
  }
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
1697
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
1661
1698
  if (preserveReferences) {
1662
1699
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -1798,7 +1835,7 @@ var CssRenderer = class _CssRenderer {
1798
1835
  leaves.push({ path, value });
1799
1836
  return;
1800
1837
  }
1801
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
1838
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
1802
1839
  leaves.push({ path, value });
1803
1840
  return;
1804
1841
  }
@@ -1841,8 +1878,8 @@ var CssRenderer = class _CssRenderer {
1841
1878
  if (isDimensionObject(value)) {
1842
1879
  return dimensionObjectToString(value);
1843
1880
  }
1844
- if (this.isDurationObject(value)) {
1845
- return this.formatDurationValue(value);
1881
+ if (isDurationObject(value)) {
1882
+ return durationObjectToString(value);
1846
1883
  }
1847
1884
  if (typeof value === "string") {
1848
1885
  return value;
@@ -1905,15 +1942,6 @@ var CssRenderer = class _CssRenderer {
1905
1942
  isPrimitiveValue(value) {
1906
1943
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1907
1944
  }
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
1945
  /**
1918
1946
  * Format token value for CSS
1919
1947
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -1934,8 +1962,8 @@ var CssRenderer = class _CssRenderer {
1934
1962
  return typeof value === "string" ? value : dimensionObjectToString(value);
1935
1963
  }
1936
1964
  if (type === "duration") {
1937
- if (this.isDurationObject(value)) {
1938
- return this.formatDurationValue(value);
1965
+ if (isDurationObject(value)) {
1966
+ return durationObjectToString(value);
1939
1967
  }
1940
1968
  if (typeof value === "string") {
1941
1969
  return value;
@@ -2025,16 +2053,16 @@ var CssRenderer = class _CssRenderer {
2025
2053
  */
2026
2054
  formatTransition(value) {
2027
2055
  const parts = [];
2028
- if (this.isDurationObject(value.duration)) {
2029
- parts.push(this.formatDurationValue(value.duration));
2056
+ if (isDurationObject(value.duration)) {
2057
+ parts.push(durationObjectToString(value.duration));
2030
2058
  } else if (value.duration != null) {
2031
2059
  parts.push(String(value.duration));
2032
2060
  }
2033
2061
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
2034
2062
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
2035
2063
  }
2036
- if (this.isDurationObject(value.delay)) {
2037
- parts.push(this.formatDurationValue(value.delay));
2064
+ if (isDurationObject(value.delay)) {
2065
+ parts.push(durationObjectToString(value.delay));
2038
2066
  } else if (value.delay != null) {
2039
2067
  parts.push(String(value.delay));
2040
2068
  }
@@ -2044,7 +2072,7 @@ var CssRenderer = class _CssRenderer {
2044
2072
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2045
2073
  tokens,
2046
2074
  modifierInputs,
2047
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2075
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2048
2076
  }));
2049
2077
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
2050
2078
  return await this.formatTokens(tokens, {
@@ -2054,12 +2082,12 @@ var CssRenderer = class _CssRenderer {
2054
2082
  });
2055
2083
  }
2056
2084
  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
- }
2085
+ assertFileRequired(
2086
+ context.buildPath,
2087
+ context.output.file,
2088
+ context.output.name,
2089
+ "standalone CSS"
2090
+ );
2063
2091
  const files = {};
2064
2092
  for (const { tokens, modifierInputs } of context.permutations) {
2065
2093
  const { fileName, content } = await this.buildStandaloneFile(
@@ -2073,7 +2101,7 @@ var CssRenderer = class _CssRenderer {
2073
2101
  return { kind: "outputTree", files };
2074
2102
  }
2075
2103
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
2076
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
2104
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
2077
2105
  const { modifierName, modifierContext } = this.resolveModifierContext(
2078
2106
  modifierInputs,
2079
2107
  context,
@@ -2110,12 +2138,7 @@ var CssRenderer = class _CssRenderer {
2110
2138
  return { fileName, content };
2111
2139
  }
2112
2140
  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
- }
2141
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
2119
2142
  if (!context.resolver.modifiers) {
2120
2143
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
2121
2144
  }
@@ -2141,7 +2164,7 @@ var CssRenderer = class _CssRenderer {
2141
2164
  }
2142
2165
  async buildModifierBaseFile(context, options) {
2143
2166
  const basePermutation = context.permutations.find(
2144
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
2167
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
2145
2168
  );
2146
2169
  if (!basePermutation) {
2147
2170
  return void 0;
@@ -2154,25 +2177,40 @@ var CssRenderer = class _CssRenderer {
2154
2177
  if (setBlocks.length === 0) {
2155
2178
  return void 0;
2156
2179
  }
2180
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
2181
+ const content = await this.formatSetBlocksCss(
2182
+ setBlocks,
2183
+ basePermutation.tokens,
2184
+ selector,
2185
+ mediaQuery,
2186
+ options
2187
+ );
2188
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
2189
+ return { fileName, content };
2190
+ }
2191
+ resolveBaseModifierContext(context, options) {
2157
2192
  const modifiers = context.resolver.modifiers;
2158
2193
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
2159
2194
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
2160
2195
  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;
2196
+ return {
2197
+ selector: resolveSelector(
2198
+ options.selector,
2199
+ firstModifierName,
2200
+ firstModifierContext,
2201
+ true,
2202
+ baseModifierInputs
2203
+ ),
2204
+ mediaQuery: resolveMediaQuery(
2205
+ options.mediaQuery,
2206
+ firstModifierName,
2207
+ firstModifierContext,
2208
+ true,
2209
+ baseModifierInputs
2210
+ )
2211
+ };
2212
+ }
2213
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
2176
2214
  const cssBlocks = [];
2177
2215
  for (const block of setBlocks) {
2178
2216
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -2188,9 +2226,7 @@ var CssRenderer = class _CssRenderer {
2188
2226
  cssBlocks.push(`${header}
2189
2227
  ${css2}`);
2190
2228
  }
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 };
2229
+ return cssBlocks.join("\n");
2194
2230
  }
2195
2231
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
2196
2232
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -2267,13 +2303,6 @@ ${css2}`);
2267
2303
  }
2268
2304
  return { modifierName: "", modifierContext: "" };
2269
2305
  }
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
2306
  };
2278
2307
  function cssRenderer() {
2279
2308
  const rendererInstance = new CssRenderer();
@@ -2285,9 +2314,18 @@ function cssRenderer() {
2285
2314
  };
2286
2315
  }
2287
2316
 
2317
+ // src/tokens/types.ts
2318
+ function isShadowToken(token) {
2319
+ return token.$type === "shadow";
2320
+ }
2321
+ function isTypographyToken(token) {
2322
+ return token.$type === "typography";
2323
+ }
2324
+ function isBorderToken(token) {
2325
+ return token.$type === "border";
2326
+ }
2327
+
2288
2328
  // src/renderers/ios.ts
2289
- init_errors();
2290
- init_token_utils();
2291
2329
  init_utils();
2292
2330
  var toSRGB2 = culori.converter("rgb");
2293
2331
  var toP32 = culori.converter("p3");
@@ -2376,94 +2414,68 @@ var IosRenderer = class {
2376
2414
  return await this.formatStandalone(context, opts);
2377
2415
  }
2378
2416
  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
2417
  const access = options.accessLevel;
2386
- const groups = this.groupTokensByType(tokens);
2418
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
2387
2419
  const imports = this.collectImports(tokens);
2388
- const i1 = this.indentStr(options.indent, 1);
2389
- const i2 = this.indentStr(options.indent, 2);
2390
2420
  const staticPrefix = this.staticLetPrefix(options);
2391
2421
  const frozen = this.frozenPrefix(options);
2392
2422
  const lines = [];
2393
- lines.push(this.buildFileHeader());
2423
+ lines.push(buildGeneratedFileHeader());
2394
2424
  lines.push("");
2395
2425
  for (const imp of imports) {
2396
2426
  lines.push(`import ${imp}`);
2397
2427
  }
2398
2428
  lines.push(...this.buildStructDefinitions(tokens, access, options));
2429
+ this.pushTokenLayout(lines, groups, options, access, staticPrefix, frozen);
2430
+ lines.push(...this.buildViewExtensions(tokens, access, options));
2431
+ if (options.structure !== "grouped") {
2432
+ lines.push("");
2433
+ }
2434
+ return lines.join("\n");
2435
+ }
2436
+ pushTokenLayout(lines, groups, options, access, staticPrefix, frozen) {
2437
+ const i1 = indentStr(options.indent, 1);
2438
+ const i2 = indentStr(options.indent, 2);
2439
+ if (options.structure === "grouped") {
2440
+ this.pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen);
2441
+ return;
2442
+ }
2399
2443
  lines.push("");
2400
2444
  lines.push(`${frozen}${access} enum ${options.enumName} {`);
2401
2445
  for (const group of groups) {
2402
2446
  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
- }
2447
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2414
2448
  lines.push(`${i1}}`);
2415
2449
  lines.push("");
2416
2450
  }
2417
2451
  lines.push("}");
2418
- lines.push(...this.buildViewExtensions(tokens, access, options));
2419
- lines.push("");
2420
- return lines.join("\n");
2421
2452
  }
2422
- formatAsGrouped(tokens, options) {
2423
- const access = options.accessLevel;
2453
+ pushGroupedLayout(lines, groups, options, access, i1, i2, staticPrefix, frozen) {
2424
2454
  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
2455
  lines.push("");
2439
2456
  lines.push(`${frozen}${access} enum ${namespace} {}`);
2440
2457
  lines.push("");
2441
2458
  for (const group of groups) {
2442
2459
  lines.push(`${access} extension ${namespace} {`);
2443
2460
  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
- }
2461
+ this.pushTokenDeclarations(lines, group.tokens, options, access, i2, staticPrefix);
2455
2462
  lines.push(`${i1}}`);
2456
2463
  lines.push("}");
2457
2464
  lines.push("");
2458
2465
  }
2459
- lines.push(...this.buildViewExtensions(tokens, access, options));
2460
- return lines.join("\n");
2461
2466
  }
2462
- buildFileHeader() {
2463
- return [
2464
- "// Generated by Dispersa - do not edit manually",
2465
- "// https://github.com/timges/dispersa"
2466
- ].join("\n");
2467
+ pushTokenDeclarations(lines, tokens, options, access, indent, staticPrefix) {
2468
+ for (const token of tokens) {
2469
+ const swiftName = this.buildQualifiedSwiftName(token);
2470
+ const swiftValue = this.formatSwiftValue(token, options);
2471
+ const typeAnnotation = this.getTypeAnnotation(token);
2472
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
2473
+ const docComment = this.buildDocComment(token, indent);
2474
+ if (docComment) {
2475
+ lines.push(docComment);
2476
+ }
2477
+ lines.push(`${indent}${access} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
2478
+ }
2467
2479
  }
2468
2480
  collectImports(tokens) {
2469
2481
  const imports = /* @__PURE__ */ new Set();
@@ -2478,24 +2490,11 @@ var IosRenderer = class {
2478
2490
  /**
2479
2491
  * Builds a `///` doc comment from a token's `$description`, if present.
2480
2492
  */
2481
- buildDocComment(token, indent2) {
2493
+ buildDocComment(token, indent) {
2482
2494
  if (!token.$description) {
2483
2495
  return void 0;
2484
2496
  }
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
- }));
2497
+ return `${indent}/// ${token.$description}`;
2499
2498
  }
2500
2499
  /**
2501
2500
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -2508,43 +2507,40 @@ var IosRenderer = class {
2508
2507
  const path = token.path;
2509
2508
  const withoutTypePrefix = path.length > 1 ? path.slice(1) : path;
2510
2509
  const joined = withoutTypePrefix.join("_");
2511
- return this.toSwiftIdentifier(joined);
2510
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
2512
2511
  }
2513
2512
  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]}))`;
2513
+ const { $type, $value: value } = token;
2514
+ switch ($type) {
2515
+ case "color":
2516
+ return this.formatColorValue(value, options);
2517
+ case "dimension":
2518
+ return this.formatDimensionValue(value);
2519
+ case "fontFamily":
2520
+ return this.formatFontFamilyValue(value);
2521
+ case "fontWeight":
2522
+ return this.formatFontWeightValue(value);
2523
+ case "duration":
2524
+ return this.formatDurationValue(value);
2525
+ case "shadow":
2526
+ return this.formatShadowValue(value, options);
2527
+ case "typography":
2528
+ return this.formatTypographyValue(value);
2529
+ case "border":
2530
+ return this.formatBorderValue(value, options);
2531
+ case "gradient":
2532
+ return this.formatGradientValue(value, options);
2533
+ case "number":
2534
+ return String(value);
2535
+ case "cubicBezier":
2536
+ if (Array.isArray(value) && value.length === 4) {
2537
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
2538
+ }
2539
+ break;
2547
2540
  }
2541
+ return this.formatSwiftPrimitive(value);
2542
+ }
2543
+ formatSwiftPrimitive(value) {
2548
2544
  if (typeof value === "string") {
2549
2545
  return `"${this.escapeSwiftString(value)}"`;
2550
2546
  }
@@ -2577,9 +2573,7 @@ var IosRenderer = class {
2577
2573
  }
2578
2574
  formatDimensionValue(value) {
2579
2575
  if (isDimensionObject(value)) {
2580
- const dim = value;
2581
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2582
- return String(ptValue);
2576
+ return this.dimensionToPoints(value);
2583
2577
  }
2584
2578
  return String(value);
2585
2579
  }
@@ -2646,7 +2640,7 @@ var IosRenderer = class {
2646
2640
  return map[name.toLowerCase()];
2647
2641
  }
2648
2642
  formatDurationValue(value) {
2649
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
2643
+ if (isDurationObject(value)) {
2650
2644
  const dur = value;
2651
2645
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
2652
2646
  return String(seconds);
@@ -2695,9 +2689,7 @@ var IosRenderer = class {
2695
2689
  if (!isDimensionObject(typo.letterSpacing)) {
2696
2690
  return "0";
2697
2691
  }
2698
- const dim = typo.letterSpacing;
2699
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2700
- return String(ptValue);
2692
+ return this.dimensionToPoints(typo.letterSpacing);
2701
2693
  }
2702
2694
  extractLineSpacing(typo) {
2703
2695
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -2706,18 +2698,19 @@ var IosRenderer = class {
2706
2698
  if (!isDimensionObject(typo.fontSize)) {
2707
2699
  return "0";
2708
2700
  }
2709
- const dim = typo.fontSize;
2710
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
2701
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
2711
2702
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
2712
2703
  return String(lineHeightPt - basePt);
2713
2704
  }
2705
+ dimensionToNumericPoints(dim) {
2706
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
2707
+ }
2714
2708
  dimensionToPoints(dim) {
2715
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2716
- return String(ptValue);
2709
+ return String(this.dimensionToNumericPoints(dim));
2717
2710
  }
2718
2711
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
2719
2712
  dimensionToCGFloat(dim) {
2720
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
2713
+ const ptValue = this.dimensionToNumericPoints(dim);
2721
2714
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
2722
2715
  }
2723
2716
  getTypeAnnotation(token) {
@@ -2736,21 +2729,12 @@ var IosRenderer = class {
2736
2729
  return void 0;
2737
2730
  }
2738
2731
  }
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
2732
  escapeSwiftString(str) {
2746
2733
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
2747
2734
  }
2748
2735
  roundComponent(value) {
2749
2736
  return Math.round(value * 1e4) / 1e4;
2750
2737
  }
2751
- indentStr(width, level) {
2752
- return " ".repeat(width * level);
2753
- }
2754
2738
  /**
2755
2739
  * Returns the prefix for `static let` declarations.
2756
2740
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -2766,34 +2750,25 @@ var IosRenderer = class {
2766
2750
  structConformances(options) {
2767
2751
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
2768
2752
  }
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
2753
  /** Emits all struct definitions needed by the token set. */
2779
2754
  buildStructDefinitions(tokens, access, options) {
2780
2755
  const lines = [];
2781
- if (this.hasShadowTokens(tokens)) {
2756
+ if (Object.values(tokens).some(isShadowToken)) {
2782
2757
  lines.push("");
2783
2758
  lines.push(...this.buildShadowStyleStruct(access, options));
2784
2759
  }
2785
- if (this.hasTypographyTokens(tokens)) {
2760
+ if (Object.values(tokens).some(isTypographyToken)) {
2786
2761
  lines.push("");
2787
2762
  lines.push(...this.buildTypographyStyleStruct(access, options));
2788
2763
  }
2789
- if (this.hasBorderTokens(tokens)) {
2764
+ if (Object.values(tokens).some(isBorderToken)) {
2790
2765
  lines.push("");
2791
2766
  lines.push(...this.buildBorderStyleStruct(access, options));
2792
2767
  }
2793
2768
  return lines;
2794
2769
  }
2795
2770
  buildShadowStyleStruct(access, options) {
2796
- const i1 = this.indentStr(options.indent, 1);
2771
+ const i1 = indentStr(options.indent, 1);
2797
2772
  const conformances = this.structConformances(options);
2798
2773
  const frozen = this.frozenPrefix(options);
2799
2774
  return [
@@ -2807,7 +2782,7 @@ var IosRenderer = class {
2807
2782
  ];
2808
2783
  }
2809
2784
  buildTypographyStyleStruct(access, options) {
2810
- const i1 = this.indentStr(options.indent, 1);
2785
+ const i1 = indentStr(options.indent, 1);
2811
2786
  const conformances = this.structConformances(options);
2812
2787
  const frozen = this.frozenPrefix(options);
2813
2788
  return [
@@ -2819,7 +2794,7 @@ var IosRenderer = class {
2819
2794
  ];
2820
2795
  }
2821
2796
  buildBorderStyleStruct(access, options) {
2822
- const i1 = this.indentStr(options.indent, 1);
2797
+ const i1 = indentStr(options.indent, 1);
2823
2798
  const conformances = this.structConformances(options);
2824
2799
  const frozen = this.frozenPrefix(options);
2825
2800
  return [
@@ -2832,9 +2807,9 @@ var IosRenderer = class {
2832
2807
  /** Emits convenience View extensions for shadow and typography application. */
2833
2808
  buildViewExtensions(tokens, access, options) {
2834
2809
  const lines = [];
2835
- const i1 = this.indentStr(options.indent, 1);
2836
- const i2 = this.indentStr(options.indent, 2);
2837
- if (this.hasShadowTokens(tokens)) {
2810
+ const i1 = indentStr(options.indent, 1);
2811
+ const i2 = indentStr(options.indent, 2);
2812
+ if (Object.values(tokens).some(isShadowToken)) {
2838
2813
  lines.push("");
2839
2814
  lines.push(`${access} extension View {`);
2840
2815
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -2844,7 +2819,7 @@ var IosRenderer = class {
2844
2819
  lines.push(`${i1}}`);
2845
2820
  lines.push("}");
2846
2821
  }
2847
- if (this.hasTypographyTokens(tokens)) {
2822
+ if (Object.values(tokens).some(isTypographyToken)) {
2848
2823
  lines.push("");
2849
2824
  lines.push(`${access} extension View {`);
2850
2825
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -2876,12 +2851,12 @@ var IosRenderer = class {
2876
2851
  return `Gradient(stops: [${stops.join(", ")}])`;
2877
2852
  }
2878
2853
  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
- }
2854
+ assertFileRequired(
2855
+ context.buildPath,
2856
+ context.output.file,
2857
+ context.output.name,
2858
+ "standalone iOS"
2859
+ );
2885
2860
  const files = {};
2886
2861
  for (const { tokens, modifierInputs } of context.permutations) {
2887
2862
  const processedTokens = stripInternalMetadata(tokens);
@@ -2910,7 +2885,6 @@ function iosRenderer() {
2910
2885
 
2911
2886
  // src/renderers/js-module.ts
2912
2887
  init_utils();
2913
- init_errors();
2914
2888
  init_token_utils();
2915
2889
  var JsModuleRenderer = class {
2916
2890
  async format(context, options) {
@@ -2926,18 +2900,13 @@ var JsModuleRenderer = class {
2926
2900
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
2927
2901
  tokens: stripInternalMetadata(tokens),
2928
2902
  modifierInputs,
2929
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
2903
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
2930
2904
  }));
2931
2905
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
2932
2906
  return await this.formatTokens(tokens, opts);
2933
2907
  });
2934
2908
  }
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
- }
2909
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
2941
2910
  const files = {};
2942
2911
  for (const { tokens, modifierInputs } of context.permutations) {
2943
2912
  const cleanTokens = stripInternalMetadata(tokens);
@@ -2991,42 +2960,18 @@ var JsModuleRenderer = class {
2991
2960
  lines.push(`export default ${varName}`);
2992
2961
  return lines;
2993
2962
  }
2994
- /**
2995
- * Convert tokens to plain object with flat or nested structure
2996
- */
2997
2963
  tokensToPlainObject(tokens, structure) {
2964
+ if (structure === "nested") {
2965
+ return buildNestedTokenObject(tokens, (token) => token.$value);
2966
+ }
2998
2967
  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
- }
2968
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
2969
+ result[name] = token.$value;
3022
2970
  }
3023
2971
  return result;
3024
2972
  }
3025
- /**
3026
- * Add object properties to lines
3027
- */
3028
- addObjectProperties(lines, obj, indent2) {
3029
- const indentStr = " ".repeat(indent2);
2973
+ addObjectProperties(lines, obj, indent) {
2974
+ const indentStr2 = " ".repeat(indent);
3030
2975
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
3031
2976
  for (let i = 0; i < entries.length; i++) {
3032
2977
  const entry = entries[i];
@@ -3035,14 +2980,16 @@ var JsModuleRenderer = class {
3035
2980
  }
3036
2981
  const [key, value] = entry;
3037
2982
  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 ? "" : ","}`);
2983
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
2984
+ if (!isNestedObject) {
2985
+ lines.push(
2986
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
2987
+ );
2988
+ continue;
3045
2989
  }
2990
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
2991
+ this.addObjectProperties(lines, value, indent + 1);
2992
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
3046
2993
  }
3047
2994
  }
3048
2995
  /**
@@ -3054,9 +3001,6 @@ var JsModuleRenderer = class {
3054
3001
  }
3055
3002
  return `"${key}"`;
3056
3003
  }
3057
- isBasePermutation(modifierInputs, defaults) {
3058
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3059
- }
3060
3004
  };
3061
3005
  function jsRenderer() {
3062
3006
  const rendererInstance = new JsModuleRenderer();
@@ -3070,7 +3014,6 @@ function jsRenderer() {
3070
3014
 
3071
3015
  // src/renderers/json.ts
3072
3016
  init_utils();
3073
- init_errors();
3074
3017
  init_token_utils();
3075
3018
  var JsonRenderer = class {
3076
3019
  async format(context, options) {
@@ -3085,18 +3028,13 @@ var JsonRenderer = class {
3085
3028
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3086
3029
  tokens: stripInternalMetadata(tokens),
3087
3030
  modifierInputs,
3088
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3031
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3089
3032
  }));
3090
3033
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
3091
3034
  return await this.formatTokens(tokens, opts);
3092
3035
  });
3093
3036
  }
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
- }
3037
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
3100
3038
  const files = {};
3101
3039
  for (const { tokens, modifierInputs } of context.permutations) {
3102
3040
  const processedTokens = stripInternalMetadata(tokens);
@@ -3156,55 +3094,11 @@ var JsonRenderer = class {
3156
3094
  }
3157
3095
  return result;
3158
3096
  }
3159
- /**
3160
- * Nest tokens by path (values only)
3161
- */
3162
3097
  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;
3098
+ return buildNestedTokenObject(tokens, (token) => token.$value);
3183
3099
  }
3184
- /**
3185
- * Nest tokens by path (with metadata)
3186
- */
3187
3100
  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;
3101
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
3208
3102
  }
3209
3103
  serializeToken(token) {
3210
3104
  return {
@@ -3215,9 +3109,6 @@ var JsonRenderer = class {
3215
3109
  ...token.$extensions != null && { $extensions: token.$extensions }
3216
3110
  };
3217
3111
  }
3218
- isBasePermutation(modifierInputs, defaults) {
3219
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
3220
- }
3221
3112
  };
3222
3113
  function jsonRenderer() {
3223
3114
  const rendererInstance = new JsonRenderer();
@@ -3230,7 +3121,6 @@ function jsonRenderer() {
3230
3121
  }
3231
3122
 
3232
3123
  // src/renderers/tailwind.ts
3233
- init_errors();
3234
3124
  init_token_utils();
3235
3125
 
3236
3126
  // src/renderers/bundlers/tailwind.ts
@@ -3259,6 +3149,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
3259
3149
  }
3260
3150
  return cssBlocks.join("\n");
3261
3151
  }
3152
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
3153
+ const normalized = normalizeModifierInputs(modifierInputs);
3154
+ return {
3155
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
3156
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
3157
+ };
3158
+ }
3262
3159
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
3263
3160
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
3264
3161
  if (differenceCount > 1) {
@@ -3271,19 +3168,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
3271
3168
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
3272
3169
  const [modifier, context] = parseModifierSource(expectedSource);
3273
3170
  const cleanTokens = stripInternalMetadata(tokensToInclude);
3274
- const selector = resolveSelector(
3275
- options.selector,
3276
- modifier,
3277
- context,
3278
- false,
3279
- normalizeModifierInputs(modifierInputs)
3280
- );
3281
- const mediaQuery = resolveMediaQuery(
3282
- options.mediaQuery,
3171
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
3172
+ options,
3283
3173
  modifier,
3284
3174
  context,
3285
- false,
3286
- normalizeModifierInputs(modifierInputs)
3175
+ modifierInputs
3287
3176
  );
3288
3177
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
3289
3178
  return `/* Modifier: ${modifier}=${context} */
@@ -3372,7 +3261,7 @@ var TailwindRenderer = class {
3372
3261
  */
3373
3262
  async formatTokens(tokens, options) {
3374
3263
  const lines = [];
3375
- const indent2 = options.minify ? "" : " ";
3264
+ const indent = options.minify ? "" : " ";
3376
3265
  const newline = options.minify ? "" : "\n";
3377
3266
  const space = options.minify ? "" : " ";
3378
3267
  if (options.includeImport) {
@@ -3394,7 +3283,7 @@ var TailwindRenderer = class {
3394
3283
  for (const [, token] of getSortedTokenEntries(tokens)) {
3395
3284
  const varName = this.buildVariableName(token);
3396
3285
  const varValue = this.formatValue(token);
3397
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
3286
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
3398
3287
  }
3399
3288
  lines.push(`}${newline}`);
3400
3289
  const cssString = lines.join("");
@@ -3405,15 +3294,15 @@ var TailwindRenderer = class {
3405
3294
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
3406
3295
  */
3407
3296
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
3408
- const indent2 = minify ? "" : " ";
3297
+ const indent = minify ? "" : " ";
3409
3298
  const newline = minify ? "" : "\n";
3410
3299
  const space = minify ? "" : " ";
3411
3300
  const hasMediaQuery = mediaQuery !== "";
3412
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
3301
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
3413
3302
  const lines = [];
3414
3303
  if (hasMediaQuery) {
3415
3304
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
3416
- lines.push(`${indent2}${selector}${space}{${newline}`);
3305
+ lines.push(`${indent}${selector}${space}{${newline}`);
3417
3306
  } else {
3418
3307
  lines.push(`${selector}${space}{${newline}`);
3419
3308
  }
@@ -3423,7 +3312,7 @@ var TailwindRenderer = class {
3423
3312
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
3424
3313
  }
3425
3314
  if (hasMediaQuery) {
3426
- lines.push(`${indent2}}${newline}`);
3315
+ lines.push(`${indent}}${newline}`);
3427
3316
  lines.push(`}${newline}`);
3428
3317
  } else {
3429
3318
  lines.push(`}${newline}`);
@@ -3450,8 +3339,8 @@ var TailwindRenderer = class {
3450
3339
  if (token.$type === "dimension" && isDimensionObject(value)) {
3451
3340
  return dimensionObjectToString(value);
3452
3341
  }
3453
- if (token.$type === "duration" && this.isDurationObject(value)) {
3454
- return `${value.value}${value.unit}`;
3342
+ if (token.$type === "duration" && isDurationObject(value)) {
3343
+ return durationObjectToString(value);
3455
3344
  }
3456
3345
  if (token.$type === "fontFamily") {
3457
3346
  if (Array.isArray(value)) {
@@ -3506,9 +3395,6 @@ var TailwindRenderer = class {
3506
3395
  }
3507
3396
  return parts.join(" ");
3508
3397
  }
3509
- isDurationObject(value) {
3510
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
3511
- }
3512
3398
  async formatWithPrettier(css2) {
3513
3399
  try {
3514
3400
  return await prettier__default.default.format(css2, {
@@ -3525,7 +3411,7 @@ var TailwindRenderer = class {
3525
3411
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
3526
3412
  tokens,
3527
3413
  modifierInputs,
3528
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
3414
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
3529
3415
  }));
3530
3416
  return await bundleAsTailwind(
3531
3417
  bundleData,
@@ -3535,12 +3421,12 @@ var TailwindRenderer = class {
3535
3421
  );
3536
3422
  }
3537
3423
  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
- }
3424
+ assertFileRequired(
3425
+ context.buildPath,
3426
+ context.output.file,
3427
+ context.output.name,
3428
+ "standalone Tailwind"
3429
+ );
3544
3430
  const files = {};
3545
3431
  for (const { tokens, modifierInputs } of context.permutations) {
3546
3432
  const processedTokens = stripInternalMetadata(tokens);
@@ -3556,11 +3442,6 @@ var TailwindRenderer = class {
3556
3442
  }
3557
3443
  return outputTree(files);
3558
3444
  }
3559
- isBasePermutation(modifierInputs, defaults) {
3560
- return Object.entries(defaults).every(
3561
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
3562
- );
3563
- }
3564
3445
  };
3565
3446
  function tailwindRenderer() {
3566
3447
  const rendererInstance = new TailwindRenderer();
@@ -3588,7 +3469,7 @@ function css(config) {
3588
3469
  file,
3589
3470
  renderer: cssRenderer(),
3590
3471
  options: { preset, ...rendererOptions },
3591
- transforms,
3472
+ transforms: [nameKebabCase(), ...transforms ?? []],
3592
3473
  filters,
3593
3474
  hooks
3594
3475
  };