dispersa 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -44,49 +44,8 @@ var __export = (target, all) => {
44
44
  __defProp(target, name, { get: all[name], enumerable: true });
45
45
  };
46
46
 
47
- // src/shared/utils/token-utils.ts
48
- function formatDeprecationMessage(token, description = "", format = "bracket") {
49
- if (token.$deprecated == null || token.$deprecated === false) {
50
- return description;
51
- }
52
- const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
53
- if (format === "comment") {
54
- const msg = deprecationMsg ? ` ${deprecationMsg}` : "";
55
- return `DEPRECATED${msg}`;
56
- } else {
57
- const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
58
- const prefix = `[DEPRECATED${msg}]`;
59
- return description ? `${prefix} ${description}` : prefix;
60
- }
61
- }
62
- function stripInternalTokenMetadata(tokens) {
63
- const cleaned = {};
64
- for (const [name, token] of Object.entries(tokens)) {
65
- const { _isAlias: _alias, _sourceModifier: _source, _sourceSet, ...rest } = token;
66
- cleaned[name] = rest;
67
- }
68
- return cleaned;
69
- }
70
- function getSortedTokenEntries(tokens) {
71
- return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
72
- }
73
- function isTokenLike(value) {
74
- return typeof value === "object" && value !== null && ("$value" in value || "$ref" in value);
75
- }
76
- function getPureAliasReferenceName(value) {
77
- if (typeof value !== "string") {
78
- return void 0;
79
- }
80
- const match = /^\{([^}]+)\}$/.exec(value);
81
- return match?.[1]?.trim();
82
- }
83
- var init_token_utils = __esm({
84
- "src/shared/utils/token-utils.ts"() {
85
- }
86
- });
87
-
88
47
  // src/shared/errors/index.ts
89
- exports.DispersaError = void 0; exports.TokenReferenceError = void 0; exports.CircularReferenceError = void 0; exports.ValidationError = void 0; exports.ColorParseError = void 0; exports.DimensionFormatError = void 0; exports.FileOperationError = void 0; exports.ConfigurationError = void 0; exports.BasePermutationError = void 0; exports.ModifierError = void 0;
48
+ exports.DispersaError = void 0; exports.TokenReferenceError = void 0; exports.CircularReferenceError = void 0; exports.ValidationError = void 0; exports.FileOperationError = void 0; exports.ConfigurationError = void 0; exports.BasePermutationError = void 0; exports.ModifierError = void 0;
90
49
  var init_errors = __esm({
91
50
  "src/shared/errors/index.ts"() {
92
51
  exports.DispersaError = class extends Error {
@@ -137,20 +96,6 @@ var init_errors = __esm({
137
96
  this.name = "ValidationError";
138
97
  }
139
98
  };
140
- exports.ColorParseError = class extends exports.DispersaError {
141
- constructor(colorValue) {
142
- super(`Color parsing failed: '${colorValue}'. Provide a valid CSS color or DTCG color object.`);
143
- this.colorValue = colorValue;
144
- this.name = "ColorParseError";
145
- }
146
- };
147
- exports.DimensionFormatError = class extends exports.DispersaError {
148
- constructor(dimensionValue) {
149
- super(`Dimension parsing failed: '${dimensionValue}'. Provide a valid DTCG dimension object.`);
150
- this.dimensionValue = dimensionValue;
151
- this.name = "DimensionFormatError";
152
- }
153
- };
154
99
  exports.FileOperationError = class extends exports.DispersaError {
155
100
  constructor(operation, filePath, originalError) {
156
101
  super(`Failed to ${operation} file: ${filePath}. ${originalError.message}`);
@@ -186,6 +131,70 @@ var init_errors = __esm({
186
131
  }
187
132
  });
188
133
 
134
+ // src/shared/utils/token-utils.ts
135
+ function formatDeprecationMessage(token, description = "", format = "bracket") {
136
+ if (token.$deprecated == null || token.$deprecated === false) {
137
+ return description;
138
+ }
139
+ const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
140
+ if (format === "comment") {
141
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
142
+ return `DEPRECATED${msg2}`;
143
+ }
144
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
145
+ const prefix = `[DEPRECATED${msg}]`;
146
+ return description ? `${prefix} ${description}` : prefix;
147
+ }
148
+ function stripInternalTokenMetadata(tokens) {
149
+ const cleaned = {};
150
+ for (const [name, token] of Object.entries(tokens)) {
151
+ const { _isAlias: _alias, _sourceModifier: _source, _sourceSet, ...rest } = token;
152
+ cleaned[name] = rest;
153
+ }
154
+ return cleaned;
155
+ }
156
+ function getSortedTokenEntries(tokens) {
157
+ return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
158
+ }
159
+ function buildNestedTokenObject(tokens, extractValue) {
160
+ const result = {};
161
+ for (const [, token] of getSortedTokenEntries(tokens)) {
162
+ setNestedValue(result, token.path, extractValue(token));
163
+ }
164
+ return result;
165
+ }
166
+ function setNestedValue(root, path7, value) {
167
+ let current = root;
168
+ for (let i = 0; i < path7.length - 1; i++) {
169
+ const part = path7[i];
170
+ if (part == null) {
171
+ continue;
172
+ }
173
+ if (!(part in current)) {
174
+ current[part] = {};
175
+ }
176
+ current = current[part];
177
+ }
178
+ const lastPart = path7[path7.length - 1];
179
+ if (lastPart != null) {
180
+ current[lastPart] = value;
181
+ }
182
+ }
183
+ function isTokenLike(value) {
184
+ return typeof value === "object" && value !== null && ("$value" in value || "$ref" in value);
185
+ }
186
+ function getPureAliasReferenceName(value) {
187
+ if (typeof value !== "string") {
188
+ return void 0;
189
+ }
190
+ const match = /^\{([^}]+)\}$/.exec(value);
191
+ return match?.[1]?.trim();
192
+ }
193
+ var init_token_utils = __esm({
194
+ "src/shared/utils/token-utils.ts"() {
195
+ }
196
+ });
197
+
189
198
  // src/shared/utils/validation-handler.ts
190
199
  var ValidationHandler;
191
200
  var init_validation_handler = __esm({
@@ -2925,30 +2934,29 @@ var init_validator = __esm({
2925
2934
  validateTokenOrGroup(obj) {
2926
2935
  const hasValue = isTokenLike(obj);
2927
2936
  if (hasValue) {
2928
- const tokenErrors = this.validateToken(obj);
2929
- if (tokenErrors.length === 0) {
2937
+ const tokenErrors2 = this.validateToken(obj);
2938
+ if (tokenErrors2.length === 0) {
2930
2939
  return { type: "token", errors: [] };
2931
2940
  }
2932
2941
  return {
2933
2942
  type: "invalid",
2934
- errors: tokenErrors,
2943
+ errors: tokenErrors2,
2935
2944
  message: "Object has $value/$ref but failed token validation"
2936
2945
  };
2937
- } else {
2938
- const groupErrors = this.validateGroup(obj);
2939
- if (groupErrors.length === 0) {
2940
- return { type: "group", errors: [] };
2941
- }
2942
- const tokenErrors = this.validateToken(obj);
2943
- if (tokenErrors.length === 0) {
2944
- return { type: "token", errors: [] };
2945
- }
2946
- return {
2947
- type: "invalid",
2948
- errors: groupErrors.length < tokenErrors.length ? groupErrors : tokenErrors,
2949
- message: groupErrors.length < tokenErrors.length ? "Object appears to be a group but failed validation" : "Object appears to be a token but failed validation"
2950
- };
2951
2946
  }
2947
+ const groupErrors = this.validateGroup(obj);
2948
+ if (groupErrors.length === 0) {
2949
+ return { type: "group", errors: [] };
2950
+ }
2951
+ const tokenErrors = this.validateToken(obj);
2952
+ if (tokenErrors.length === 0) {
2953
+ return { type: "token", errors: [] };
2954
+ }
2955
+ return {
2956
+ type: "invalid",
2957
+ errors: groupErrors.length < tokenErrors.length ? groupErrors : tokenErrors,
2958
+ message: groupErrors.length < tokenErrors.length ? "Object appears to be a group but failed validation" : "Object appears to be a token but failed validation"
2959
+ };
2952
2960
  }
2953
2961
  /**
2954
2962
  * Format AJV errors into readable ValidationError objects
@@ -3143,35 +3151,34 @@ var init_resolver_parser = __esm({
3143
3151
  this.validateSourceReferences(set.sources, `set "${setName}"`, contextMsg);
3144
3152
  }
3145
3153
  }
3146
- if (resolver.modifiers) {
3147
- for (const [modifierName, modifier] of Object.entries(resolver.modifiers)) {
3148
- for (const [contextName, sources] of Object.entries(modifier.contexts)) {
3149
- this.validateSourceReferences(
3150
- sources,
3151
- `modifier "${modifierName}" context "${contextName}"`,
3152
- contextMsg
3153
- );
3154
- }
3154
+ if (!resolver.modifiers) {
3155
+ return;
3156
+ }
3157
+ for (const [modifierName, modifier] of Object.entries(resolver.modifiers)) {
3158
+ for (const [contextName, sources] of Object.entries(modifier.contexts)) {
3159
+ this.validateSourceReferences(
3160
+ sources,
3161
+ `modifier "${modifierName}" context "${contextName}"`,
3162
+ contextMsg
3163
+ );
3155
3164
  }
3156
3165
  }
3157
3166
  }
3158
- /**
3159
- * Validate source references in an array
3160
- */
3161
3167
  validateSourceReferences(sources, location, contextMsg) {
3162
3168
  for (const source of sources) {
3163
- if (typeof source === "object" && source !== null && "$ref" in source) {
3164
- const ref = source.$ref;
3165
- if (ref.startsWith("#/modifiers/")) {
3166
- this.handleValidationIssue(
3167
- `Invalid reference in ${location}: "${ref}". Sets and modifier contexts MUST NOT reference modifiers${contextMsg}`
3168
- );
3169
- }
3170
- if (ref.startsWith("#/resolutionOrder/")) {
3171
- this.handleValidationIssue(
3172
- `Invalid reference in ${location}: "${ref}". References MUST NOT point to resolutionOrder array items${contextMsg}`
3173
- );
3174
- }
3169
+ if (typeof source !== "object" || source === null || !("$ref" in source)) {
3170
+ continue;
3171
+ }
3172
+ const ref = source.$ref;
3173
+ if (ref.startsWith("#/modifiers/")) {
3174
+ this.handleValidationIssue(
3175
+ `Invalid reference in ${location}: "${ref}". Sets and modifier contexts MUST NOT reference modifiers${contextMsg}`
3176
+ );
3177
+ }
3178
+ if (ref.startsWith("#/resolutionOrder/")) {
3179
+ this.handleValidationIssue(
3180
+ `Invalid reference in ${location}: "${ref}". References MUST NOT point to resolutionOrder array items${contextMsg}`
3181
+ );
3175
3182
  }
3176
3183
  }
3177
3184
  }
@@ -3251,15 +3258,14 @@ var init_resolver_loader = __esm({
3251
3258
  * ```
3252
3259
  */
3253
3260
  async load(resolver) {
3254
- if (typeof resolver === "string") {
3255
- const absolutePath = path__namespace.resolve(this.options.baseDir, resolver);
3256
- const resolverDoc = await this.parser.parseFile(absolutePath);
3257
- const baseDir = path__namespace.dirname(absolutePath);
3258
- return { resolverDoc, baseDir };
3259
- } else {
3260
- const resolverDoc = this.parser.parseInline(resolver);
3261
- return { resolverDoc, baseDir: this.options.baseDir };
3261
+ if (typeof resolver !== "string") {
3262
+ const resolverDoc2 = this.parser.parseInline(resolver);
3263
+ return { resolverDoc: resolverDoc2, baseDir: this.options.baseDir };
3262
3264
  }
3265
+ const absolutePath = path__namespace.resolve(this.options.baseDir, resolver);
3266
+ const resolverDoc = await this.parser.parseFile(absolutePath);
3267
+ const baseDir = path__namespace.dirname(absolutePath);
3268
+ return { resolverDoc, baseDir };
3263
3269
  }
3264
3270
  /**
3265
3271
  * Load only the resolver document (without base directory)
@@ -3292,6 +3298,35 @@ function sanitizeDataAttributeName(value) {
3292
3298
  function escapeCssString(value) {
3293
3299
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
3294
3300
  }
3301
+ function groupTokensByType(tokens, typeGroupMap) {
3302
+ const groupMap = /* @__PURE__ */ new Map();
3303
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3304
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
3305
+ const existing = groupMap.get(groupName) ?? [];
3306
+ existing.push(token);
3307
+ groupMap.set(groupName, existing);
3308
+ }
3309
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
3310
+ name,
3311
+ tokens: groupTokens
3312
+ }));
3313
+ }
3314
+ function indentStr(width, level) {
3315
+ return " ".repeat(width * level);
3316
+ }
3317
+ function buildGeneratedFileHeader() {
3318
+ return [
3319
+ "// Generated by Dispersa - do not edit manually",
3320
+ "// https://github.com/timges/dispersa"
3321
+ ].join("\n");
3322
+ }
3323
+ function toSafeIdentifier(name, keywords, capitalize) {
3324
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
3325
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
3326
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
3327
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
3328
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
3329
+ }
3295
3330
  function normalizeModifierInputs(inputs) {
3296
3331
  const normalized = {};
3297
3332
  for (const [key, value] of Object.entries(inputs)) {
@@ -3299,6 +3334,14 @@ function normalizeModifierInputs(inputs) {
3299
3334
  }
3300
3335
  return normalized;
3301
3336
  }
3337
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
3338
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
3339
+ if (!outputFile && requiresFile) {
3340
+ throw new exports.ConfigurationError(
3341
+ `Output "${outputName}": file is required for ${presetLabel} output`
3342
+ );
3343
+ }
3344
+ }
3302
3345
  function buildStablePermutationKey(modifierInputs, dimensions) {
3303
3346
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
3304
3347
  }
@@ -3348,14 +3391,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
3348
3391
  }
3349
3392
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
3350
3393
  }
3351
- function buildInMemoryOutputKey(params) {
3352
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
3394
+ function isBasePermutation(modifierInputs, defaults) {
3353
3395
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
3354
3396
  const normalizedDefaults = normalizeModifierInputs(defaults);
3355
- const isBase = Object.entries(normalizedDefaults).every(
3356
- ([key, value]) => normalizedInputs[key] === value
3397
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
3398
+ }
3399
+ function buildInMemoryOutputKey(params) {
3400
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
3401
+ const permutationKey = generatePermutationKey(
3402
+ modifierInputs,
3403
+ resolver,
3404
+ isBasePermutation(modifierInputs, defaults)
3357
3405
  );
3358
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
3359
3406
  return `${outputName}-${permutationKey}.${extension}`;
3360
3407
  }
3361
3408
  function buildMetadata(resolver) {
@@ -3477,6 +3524,7 @@ function resolveFileName(fileName, modifierInputs) {
3477
3524
  }
3478
3525
  var init_utils = __esm({
3479
3526
  "src/renderers/bundlers/utils.ts"() {
3527
+ init_errors();
3480
3528
  init_token_utils();
3481
3529
  }
3482
3530
  });
@@ -3486,36 +3534,38 @@ var js_exports = {};
3486
3534
  __export(js_exports, {
3487
3535
  bundleAsJsModule: () => bundleAsJsModule
3488
3536
  });
3537
+ function updateStringTracking(state, char) {
3538
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
3539
+ if (!state.inString) {
3540
+ state.inString = true;
3541
+ state.stringChar = char;
3542
+ } else if (char === state.stringChar) {
3543
+ state.inString = false;
3544
+ state.stringChar = "";
3545
+ }
3546
+ }
3547
+ state.escaped = !state.escaped && char === "\\";
3548
+ }
3489
3549
  function extractObjectFromJsModule(formattedJs) {
3490
3550
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
3491
3551
  if (!assignmentMatch) {
3492
3552
  return "{}";
3493
3553
  }
3494
3554
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
3555
+ const state = { inString: false, stringChar: "", escaped: false };
3495
3556
  let braceCount = 0;
3496
- let inString = false;
3497
- let stringChar = "";
3498
- let escaped = false;
3499
3557
  for (let i = startIndex; i < formattedJs.length; i++) {
3500
3558
  const char = formattedJs[i];
3501
- if (!escaped && (char === '"' || char === "'" || char === "`")) {
3502
- if (!inString) {
3503
- inString = true;
3504
- stringChar = char;
3505
- } else if (char === stringChar) {
3506
- inString = false;
3507
- stringChar = "";
3508
- }
3509
- }
3510
- escaped = !escaped && char === "\\";
3511
- if (!inString) {
3512
- if (char === "{") {
3513
- braceCount++;
3514
- } else if (char === "}") {
3515
- braceCount--;
3516
- if (braceCount === 0) {
3517
- return formattedJs.substring(startIndex, i + 1);
3518
- }
3559
+ updateStringTracking(state, char);
3560
+ if (state.inString) {
3561
+ continue;
3562
+ }
3563
+ if (char === "{") {
3564
+ braceCount++;
3565
+ } else if (char === "}") {
3566
+ braceCount--;
3567
+ if (braceCount === 0) {
3568
+ return formattedJs.substring(startIndex, i + 1);
3519
3569
  }
3520
3570
  }
3521
3571
  }
@@ -3612,22 +3662,19 @@ __export(json_exports, {
3612
3662
  bundleAsJson: () => bundleAsJson
3613
3663
  });
3614
3664
  async function bundleAsJson(bundleData, resolver, formatTokens) {
3665
+ if (!formatTokens) {
3666
+ throw new exports.ConfigurationError("JSON formatter was not provided");
3667
+ }
3615
3668
  const metadata = buildMetadata(resolver);
3616
3669
  const tokens = {};
3617
3670
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
3618
3671
  const cleanTokens = stripInternalMetadata(tokenSet);
3619
- if (!formatTokens) {
3620
- throw new exports.ConfigurationError("JSON formatter was not provided");
3621
- }
3622
3672
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
3623
3673
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
3624
3674
  const themeJson = await formatTokens(cleanTokens);
3625
3675
  tokens[key] = JSON.parse(themeJson);
3626
3676
  }
3627
- const bundle = {
3628
- _meta: metadata,
3629
- tokens
3630
- };
3677
+ const bundle = { _meta: metadata, tokens };
3631
3678
  return JSON.stringify(bundle, null, 2);
3632
3679
  }
3633
3680
  var init_json = __esm({
@@ -3693,15 +3740,15 @@ var TypeGenerator = class {
3693
3740
  const lines = [];
3694
3741
  if (names.length === 0) {
3695
3742
  lines.push(`export type ${typeName} = never`);
3696
- } else {
3697
- lines.push(`export type ${typeName} =`);
3698
- for (let i = 0; i < names.length; i++) {
3699
- const name = names[i];
3700
- if (name == null) {
3701
- continue;
3702
- }
3703
- lines.push(` | "${name}"`);
3743
+ return lines;
3744
+ }
3745
+ lines.push(`export type ${typeName} =`);
3746
+ for (let i = 0; i < names.length; i++) {
3747
+ const name = names[i];
3748
+ if (name == null) {
3749
+ continue;
3704
3750
  }
3751
+ lines.push(` | "${name}"`);
3705
3752
  }
3706
3753
  return lines;
3707
3754
  }
@@ -3727,15 +3774,10 @@ var TypeGenerator = class {
3727
3774
  generateStructureType(tokens, options) {
3728
3775
  const lines = [];
3729
3776
  const structure = this.buildNestedStructure(tokens);
3730
- if (options.exportType === "type") {
3731
- lines.push(`export type ${options.moduleName} = {`);
3732
- this.addStructureProperties(lines, structure, 1);
3733
- lines.push("}");
3734
- } else {
3735
- lines.push(`export interface ${options.moduleName} {`);
3736
- this.addStructureProperties(lines, structure, 1);
3737
- lines.push("}");
3738
- }
3777
+ const opener = options.exportType === "type" ? `export type ${options.moduleName} = {` : `export interface ${options.moduleName} {`;
3778
+ lines.push(opener);
3779
+ this.addStructureProperties(lines, structure, 1);
3780
+ lines.push("}");
3739
3781
  return lines;
3740
3782
  }
3741
3783
  /**
@@ -3769,20 +3811,20 @@ var TypeGenerator = class {
3769
3811
  /**
3770
3812
  * Add structure properties to lines
3771
3813
  */
3772
- addStructureProperties(lines, structure, indent2) {
3773
- const indentStr = " ".repeat(indent2);
3814
+ addStructureProperties(lines, structure, indent) {
3815
+ const indentStr2 = " ".repeat(indent);
3774
3816
  for (const [key, value] of Object.entries(structure)) {
3775
3817
  if (this.isToken(value)) {
3776
3818
  const token = value;
3777
3819
  if (token.$description) {
3778
- lines.push(`${indentStr}/** ${token.$description} */`);
3820
+ lines.push(`${indentStr2}/** ${token.$description} */`);
3779
3821
  }
3780
3822
  const valueType = this.inferValueType(token);
3781
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueType}`);
3823
+ lines.push(`${indentStr2}${this.quoteKey(key)}: ${valueType}`);
3782
3824
  } else {
3783
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
3784
- this.addStructureProperties(lines, value, indent2 + 1);
3785
- lines.push(`${indentStr}}`);
3825
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
3826
+ this.addStructureProperties(lines, value, indent + 1);
3827
+ lines.push(`${indentStr2}}`);
3786
3828
  }
3787
3829
  }
3788
3830
  }
@@ -3907,12 +3949,6 @@ function toBuildError(error, outputName) {
3907
3949
  if (error instanceof exports.ValidationError) {
3908
3950
  return { message, code: "VALIDATION", severity: "error" };
3909
3951
  }
3910
- if (error instanceof exports.ColorParseError) {
3911
- return { message, code: "COLOR_PARSE", severity: "error" };
3912
- }
3913
- if (error instanceof exports.DimensionFormatError) {
3914
- return { message, code: "DIMENSION_FORMAT", severity: "error" };
3915
- }
3916
3952
  if (error instanceof exports.FileOperationError) {
3917
3953
  return { message, code: "FILE_OPERATION", path: error.filePath, severity: "error" };
3918
3954
  }
@@ -3980,28 +4016,27 @@ var BuildOrchestrator = class {
3980
4016
  if (config.hooks?.onBuildStart) {
3981
4017
  await config.hooks.onBuildStart({ config, resolver });
3982
4018
  }
3983
- let permutations;
3984
- if (config.permutations && config.permutations.length > 0) {
3985
- permutations = await Promise.all(
3986
- config.permutations.map(async (modifierInputs) => {
3987
- const { tokens, modifierInputs: resolvedInputs } = await this.pipeline.resolve(
3988
- resolver,
3989
- modifierInputs,
3990
- config.transforms,
3991
- config.preprocessors,
3992
- config.filters
3993
- );
3994
- return { tokens, modifierInputs: resolvedInputs };
3995
- })
3996
- );
3997
- } else {
3998
- permutations = await this.pipeline.resolveAllPermutations(
4019
+ if (!config.permutations || config.permutations.length === 0) {
4020
+ const permutations2 = await this.pipeline.resolveAllPermutations(
3999
4021
  resolver,
4000
4022
  config.transforms,
4001
4023
  config.preprocessors,
4002
4024
  config.filters
4003
4025
  );
4026
+ return this.executeBuild(buildPath, config, permutations2, resolver);
4004
4027
  }
4028
+ const permutations = await Promise.all(
4029
+ config.permutations.map(async (modifierInputs) => {
4030
+ const { tokens, modifierInputs: resolvedInputs } = await this.pipeline.resolve(
4031
+ resolver,
4032
+ modifierInputs,
4033
+ config.transforms,
4034
+ config.preprocessors,
4035
+ config.filters
4036
+ );
4037
+ return { tokens, modifierInputs: resolvedInputs };
4038
+ })
4039
+ );
4005
4040
  return this.executeBuild(buildPath, config, permutations, resolver);
4006
4041
  }
4007
4042
  /**
@@ -4023,44 +4058,15 @@ var BuildOrchestrator = class {
4023
4058
  * @returns Build result with success status, outputs, and any errors
4024
4059
  */
4025
4060
  async executeBuild(buildPath, config, permutations, resolver) {
4026
- const outputs = [];
4027
- const errors = [];
4028
4061
  try {
4029
4062
  const resolverDoc = await resolveResolverDocument(resolver);
4030
4063
  const metadata = buildMetadata(resolverDoc);
4031
- const basePermutation = metadata.defaults;
4032
4064
  const settled = await Promise.allSettled(
4033
- config.outputs.map(async (output) => {
4034
- if (output.hooks?.onBuildStart) {
4035
- await output.hooks.onBuildStart({ config, resolver });
4036
- }
4037
- try {
4038
- const results = await this.processOutput(output, permutations, resolverDoc, metadata, basePermutation, buildPath);
4039
- if (output.hooks?.onBuildEnd) {
4040
- await output.hooks.onBuildEnd({ success: true, outputs: results });
4041
- }
4042
- return results;
4043
- } catch (error) {
4044
- if (output.hooks?.onBuildEnd) {
4045
- await output.hooks.onBuildEnd({ success: false, outputs: [], errors: [toBuildError(error, output.name)] });
4046
- }
4047
- throw error;
4048
- }
4049
- })
4065
+ config.outputs.map(
4066
+ (output) => this.buildSingleOutput(output, permutations, resolverDoc, metadata, buildPath, config, resolver)
4067
+ )
4050
4068
  );
4051
- for (let i = 0; i < settled.length; i++) {
4052
- const outcome = settled[i];
4053
- if (outcome.status === "fulfilled") {
4054
- outputs.push(...outcome.value);
4055
- } else {
4056
- errors.push(toBuildError(outcome.reason, config.outputs[i].name));
4057
- }
4058
- }
4059
- const result = {
4060
- success: errors.length === 0,
4061
- outputs,
4062
- errors: errors.length > 0 ? errors : void 0
4063
- };
4069
+ const result = this.collectSettledResults(settled, config);
4064
4070
  if (config.hooks?.onBuildEnd) {
4065
4071
  await config.hooks.onBuildEnd(result);
4066
4072
  }
@@ -4077,6 +4083,40 @@ var BuildOrchestrator = class {
4077
4083
  return result;
4078
4084
  }
4079
4085
  }
4086
+ async buildSingleOutput(output, permutations, resolverDoc, metadata, buildPath, config, resolver) {
4087
+ if (output.hooks?.onBuildStart) {
4088
+ await output.hooks.onBuildStart({ config, resolver });
4089
+ }
4090
+ try {
4091
+ const results = await this.processOutput(output, permutations, resolverDoc, metadata, metadata.defaults, buildPath);
4092
+ if (output.hooks?.onBuildEnd) {
4093
+ await output.hooks.onBuildEnd({ success: true, outputs: results });
4094
+ }
4095
+ return results;
4096
+ } catch (error) {
4097
+ if (output.hooks?.onBuildEnd) {
4098
+ await output.hooks.onBuildEnd({ success: false, outputs: [], errors: [toBuildError(error, output.name)] });
4099
+ }
4100
+ throw error;
4101
+ }
4102
+ }
4103
+ collectSettledResults(settled, config) {
4104
+ const outputs = [];
4105
+ const errors = [];
4106
+ for (let i = 0; i < settled.length; i++) {
4107
+ const outcome = settled[i];
4108
+ if (outcome.status === "fulfilled") {
4109
+ outputs.push(...outcome.value);
4110
+ } else {
4111
+ errors.push(toBuildError(outcome.reason, config.outputs[i].name));
4112
+ }
4113
+ }
4114
+ return {
4115
+ success: errors.length === 0,
4116
+ outputs,
4117
+ errors: errors.length > 0 ? errors : void 0
4118
+ };
4119
+ }
4080
4120
  async processOutput(output, permutations, resolverDoc, metadata, basePermutation, buildPath) {
4081
4121
  if (!output.renderer.format) {
4082
4122
  throw new exports.ConfigurationError("Renderer does not implement format()");
@@ -4126,7 +4166,7 @@ async function writeOutputFile(fileName, content, encoding = "utf-8") {
4126
4166
  }
4127
4167
  }
4128
4168
 
4129
- // src/processing/token-modifier.ts
4169
+ // src/processing/apply.ts
4130
4170
  function applyTransforms(tokens, transformList) {
4131
4171
  const result = {};
4132
4172
  for (const [name, token] of Object.entries(tokens)) {
@@ -5404,14 +5444,15 @@ var ResolutionEngine = class {
5404
5444
  mergeTokens(target, source) {
5405
5445
  const result = { ...target };
5406
5446
  for (const [key, value] of Object.entries(source)) {
5407
- if (typeof value === "object" && value !== null && !Array.isArray(value) && !("$value" in value)) {
5408
- result[key] = this.mergeTokens(
5409
- result[key] ?? {},
5410
- value
5411
- );
5412
- } else {
5447
+ const isGroup = typeof value === "object" && value !== null && !Array.isArray(value) && !("$value" in value);
5448
+ if (!isGroup) {
5413
5449
  result[key] = value;
5450
+ continue;
5414
5451
  }
5452
+ result[key] = this.mergeTokens(
5453
+ result[key] ?? {},
5454
+ value
5455
+ );
5415
5456
  }
5416
5457
  return result;
5417
5458
  }
@@ -6557,7 +6598,7 @@ function colorObjectToHex(color) {
6557
6598
  return culori.formatHex(culoriColor);
6558
6599
  }
6559
6600
 
6560
- // src/processing/processors/transforms/built-in/dimension-converter.ts
6601
+ // src/processing/transforms/built-in/dimension-converter.ts
6561
6602
  function isDimensionObject(value) {
6562
6603
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
6563
6604
  }
@@ -6565,6 +6606,14 @@ function dimensionObjectToString(dimension) {
6565
6606
  return `${dimension.value}${dimension.unit}`;
6566
6607
  }
6567
6608
 
6609
+ // src/processing/transforms/built-in/duration-converter.ts
6610
+ function isDurationObject(value) {
6611
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
6612
+ }
6613
+ function durationObjectToString(duration) {
6614
+ return `${duration.value}${duration.unit}`;
6615
+ }
6616
+
6568
6617
  // src/renderers/android.ts
6569
6618
  init_errors();
6570
6619
  init_token_utils();
@@ -6616,9 +6665,6 @@ function resolveColorFormat(format) {
6616
6665
  }
6617
6666
  return "argb_hex";
6618
6667
  }
6619
- function indent(width, level) {
6620
- return " ".repeat(width * level);
6621
- }
6622
6668
  function escapeKotlinString(str) {
6623
6669
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
6624
6670
  }
@@ -6634,22 +6680,6 @@ function roundComponent(value) {
6634
6680
  function toResourceName(family) {
6635
6681
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
6636
6682
  }
6637
- function toPascalCase(name) {
6638
- const pascal = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
6639
- const result = pascal.charAt(0).toUpperCase() + pascal.slice(1);
6640
- if (/^\d/.test(result)) {
6641
- return `_${result}`;
6642
- }
6643
- return KOTLIN_KEYWORDS.has(result.charAt(0).toLowerCase() + result.slice(1)) ? `\`${result}\`` : result;
6644
- }
6645
- function toKotlinIdentifier(name) {
6646
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
6647
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
6648
- if (/^\d/.test(identifier)) {
6649
- return `_${identifier}`;
6650
- }
6651
- return KOTLIN_KEYWORDS.has(identifier) ? `\`${identifier}\`` : identifier;
6652
- }
6653
6683
  var AndroidRenderer = class {
6654
6684
  async format(context, options) {
6655
6685
  if (!options?.packageName) {
@@ -6657,6 +6687,7 @@ var AndroidRenderer = class {
6657
6687
  `Output "${context.output.name}": packageName is required for Android output`
6658
6688
  );
6659
6689
  }
6690
+ const visibility = options?.visibility;
6660
6691
  const opts = {
6661
6692
  preset: options?.preset ?? "standalone",
6662
6693
  packageName: options.packageName,
@@ -6664,7 +6695,8 @@ var AndroidRenderer = class {
6664
6695
  colorFormat: resolveColorFormat(options?.colorFormat),
6665
6696
  colorSpace: options?.colorSpace ?? "sRGB",
6666
6697
  structure: options?.structure ?? "nested",
6667
- visibility: options?.visibility,
6698
+ visibility,
6699
+ visPrefix: visibility ? `${visibility} ` : "",
6668
6700
  indent: options?.indent ?? 4
6669
6701
  };
6670
6702
  if (opts.preset === "bundle") {
@@ -6697,19 +6729,6 @@ var AndroidRenderer = class {
6697
6729
  // -----------------------------------------------------------------------
6698
6730
  // Flat structure grouping
6699
6731
  // -----------------------------------------------------------------------
6700
- groupTokensByType(tokens) {
6701
- const groupMap = /* @__PURE__ */ new Map();
6702
- for (const [, token] of getSortedTokenEntries(tokens)) {
6703
- const groupName = KOTLIN_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
6704
- const existing = groupMap.get(groupName) ?? [];
6705
- existing.push(token);
6706
- groupMap.set(groupName, existing);
6707
- }
6708
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
6709
- name,
6710
- tokens: groupTokens
6711
- }));
6712
- }
6713
6732
  /**
6714
6733
  * Builds a flattened camelCase name from a token's path, stripping the
6715
6734
  * type prefix segment (which is already represented by the group object).
@@ -6718,7 +6737,7 @@ var AndroidRenderer = class {
6718
6737
  const path7 = token.path;
6719
6738
  const withoutTypePrefix = path7.length > 1 ? path7.slice(1) : path7;
6720
6739
  const joined = withoutTypePrefix.join("_");
6721
- return toKotlinIdentifier(joined);
6740
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
6722
6741
  }
6723
6742
  // -----------------------------------------------------------------------
6724
6743
  // Rendering
@@ -6730,22 +6749,21 @@ var AndroidRenderer = class {
6730
6749
  return this.formatAsNested(tokens, options);
6731
6750
  }
6732
6751
  formatAsNested(tokens, options) {
6752
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
6733
6753
  const tree = this.buildTokenTree(tokens);
6734
- const tokenTypes = /* @__PURE__ */ new Set();
6735
- this.collectTokenTypes(tree, tokenTypes);
6736
- return this.buildFile(tokenTypes, options, (lines, vis) => {
6754
+ return this.buildFile(tokenTypes, options, (lines) => {
6737
6755
  lines.push(`@Suppress("unused")`);
6738
- lines.push(`${vis}object ${options.objectName} {`);
6756
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
6739
6757
  this.renderTreeChildren(lines, tree, 1, options);
6740
6758
  lines.push("}");
6741
6759
  });
6742
6760
  }
6743
6761
  formatAsFlat(tokens, options) {
6744
- const groups = this.groupTokensByType(tokens);
6762
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
6745
6763
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
6746
- return this.buildFile(tokenTypes, options, (lines, vis) => {
6764
+ return this.buildFile(tokenTypes, options, (lines) => {
6747
6765
  lines.push(`@Suppress("unused")`);
6748
- lines.push(`${vis}object ${options.objectName} {`);
6766
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
6749
6767
  this.renderFlatGroups(lines, groups, 1, options);
6750
6768
  lines.push("}");
6751
6769
  });
@@ -6756,9 +6774,8 @@ var AndroidRenderer = class {
6756
6774
  */
6757
6775
  buildFile(tokenTypes, options, renderBody) {
6758
6776
  const imports = this.collectImports(tokenTypes, options);
6759
- const vis = options.visibility ? `${options.visibility} ` : "";
6760
6777
  const lines = [];
6761
- lines.push(this.buildFileHeader());
6778
+ lines.push(buildGeneratedFileHeader());
6762
6779
  lines.push("");
6763
6780
  lines.push(`package ${options.packageName}`);
6764
6781
  lines.push("");
@@ -6769,19 +6786,18 @@ var AndroidRenderer = class {
6769
6786
  lines.push("");
6770
6787
  }
6771
6788
  if (tokenTypes.has("shadow")) {
6772
- lines.push(...this.buildShadowTokenClass(vis, options));
6789
+ lines.push(...this.buildShadowTokenClass(options));
6773
6790
  lines.push("");
6774
6791
  }
6775
- renderBody(lines, vis);
6792
+ renderBody(lines);
6776
6793
  lines.push("");
6777
6794
  return lines.join("\n");
6778
6795
  }
6779
6796
  renderFlatGroups(lines, groups, baseDepth, options) {
6780
- const vis = options.visibility ? `${options.visibility} ` : "";
6781
- const groupIndent = indent(options.indent, baseDepth);
6782
- const valIndent = indent(options.indent, baseDepth + 1);
6797
+ const groupIndent = indentStr(options.indent, baseDepth);
6798
+ const valIndent = indentStr(options.indent, baseDepth + 1);
6783
6799
  for (const group of groups) {
6784
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
6800
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
6785
6801
  for (const token of group.tokens) {
6786
6802
  const kotlinName = this.buildFlatKotlinName(token);
6787
6803
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -6789,23 +6805,24 @@ var AndroidRenderer = class {
6789
6805
  if (token.$description) {
6790
6806
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
6791
6807
  }
6792
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
6808
+ lines.push(
6809
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
6810
+ );
6793
6811
  }
6794
6812
  lines.push(`${groupIndent}}`);
6795
6813
  lines.push("");
6796
6814
  }
6797
6815
  }
6798
6816
  renderTreeChildren(lines, node, depth, options) {
6799
- const vis = options.visibility ? `${options.visibility} ` : "";
6800
- const pad = indent(options.indent, depth);
6817
+ const pad = indentStr(options.indent, depth);
6801
6818
  const entries = Array.from(node.children.entries());
6802
6819
  for (let idx = 0; idx < entries.length; idx++) {
6803
6820
  const [key, child] = entries[idx];
6804
6821
  if (child.token && child.children.size === 0) {
6805
6822
  this.renderLeaf(lines, key, child.token, depth, options);
6806
6823
  } else if (child.children.size > 0 && !child.token) {
6807
- const objectName = toPascalCase(key);
6808
- lines.push(`${pad}${vis}object ${objectName} {`);
6824
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
6825
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
6809
6826
  this.renderTreeChildren(lines, child, depth + 1, options);
6810
6827
  lines.push(`${pad}}`);
6811
6828
  if (idx < entries.length - 1) {
@@ -6818,30 +6835,23 @@ var AndroidRenderer = class {
6818
6835
  }
6819
6836
  }
6820
6837
  renderLeaf(lines, key, token, depth, options) {
6821
- const vis = options.visibility ? `${options.visibility} ` : "";
6822
- const pad = indent(options.indent, depth);
6823
- const kotlinName = toKotlinIdentifier(key);
6838
+ const pad = indentStr(options.indent, depth);
6839
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
6824
6840
  const kotlinValue = this.formatKotlinValue(token, options, depth);
6825
6841
  const annotation = this.typeAnnotationSuffix(token);
6826
6842
  if (token.$description) {
6827
6843
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
6828
6844
  }
6829
- lines.push(`${pad}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
6830
- }
6831
- buildFileHeader() {
6832
- return [
6833
- "// Generated by Dispersa - do not edit manually",
6834
- "// https://github.com/timges/dispersa"
6835
- ].join("\n");
6845
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
6836
6846
  }
6837
6847
  // -----------------------------------------------------------------------
6838
6848
  // Shadow data class
6839
6849
  // -----------------------------------------------------------------------
6840
- buildShadowTokenClass(vis, options) {
6841
- const i1 = indent(options.indent, 1);
6850
+ buildShadowTokenClass(options) {
6851
+ const i1 = indentStr(options.indent, 1);
6842
6852
  return [
6843
6853
  "@Immutable",
6844
- `${vis}data class ShadowToken(`,
6854
+ `${options.visPrefix}data class ShadowToken(`,
6845
6855
  `${i1}val color: Color,`,
6846
6856
  `${i1}val elevation: Dp,`,
6847
6857
  `${i1}val offsetX: Dp,`,
@@ -6892,14 +6902,6 @@ var AndroidRenderer = class {
6892
6902
  }
6893
6903
  return Array.from(imports).sort();
6894
6904
  }
6895
- collectTokenTypes(node, types) {
6896
- if (node.token?.$type) {
6897
- types.add(node.token.$type);
6898
- }
6899
- for (const child of node.children.values()) {
6900
- this.collectTokenTypes(child, types);
6901
- }
6902
- }
6903
6905
  collectTokenTypesFromEntries(tokens) {
6904
6906
  const types = /* @__PURE__ */ new Set();
6905
6907
  for (const [, token] of Object.entries(tokens)) {
@@ -7126,9 +7128,8 @@ var AndroidRenderer = class {
7126
7128
  return map[name.toLowerCase()];
7127
7129
  }
7128
7130
  formatDurationValue(value) {
7129
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
7130
- const dur = value;
7131
- return dur.unit === "ms" ? `${dur.value}.milliseconds` : `${dur.value}.seconds`;
7131
+ if (isDurationObject(value)) {
7132
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
7132
7133
  }
7133
7134
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
7134
7135
  }
@@ -7146,8 +7147,8 @@ var AndroidRenderer = class {
7146
7147
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
7147
7148
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
7148
7149
  const offsetY = isDimensionObject(shadow.offsetY) ? this.formatDimensionValue(shadow.offsetY) : "0.dp";
7149
- const propIndent = indent(options.indent, depth + 1);
7150
- const closeIndent = indent(options.indent, depth);
7150
+ const propIndent = indentStr(options.indent, depth + 1);
7151
+ const closeIndent = indentStr(options.indent, depth);
7151
7152
  return [
7152
7153
  "ShadowToken(",
7153
7154
  `${propIndent}color = ${color},`,
@@ -7196,8 +7197,8 @@ var AndroidRenderer = class {
7196
7197
  if (parts.length === 0) {
7197
7198
  return "TextStyle()";
7198
7199
  }
7199
- const propIndent = indent(options.indent, depth + 1);
7200
- const closeIndent = indent(options.indent, depth);
7200
+ const propIndent = indentStr(options.indent, depth + 1);
7201
+ const closeIndent = indentStr(options.indent, depth);
7201
7202
  return `TextStyle(
7202
7203
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
7203
7204
  ${closeIndent})`;
@@ -7206,12 +7207,12 @@ ${closeIndent})`;
7206
7207
  // Output: standalone
7207
7208
  // -----------------------------------------------------------------------
7208
7209
  async formatStandalone(context, options) {
7209
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
7210
- if (!context.output.file && requiresFile) {
7211
- throw new exports.ConfigurationError(
7212
- `Output "${context.output.name}": file is required for standalone Android output`
7213
- );
7214
- }
7210
+ assertFileRequired(
7211
+ context.buildPath,
7212
+ context.output.file,
7213
+ context.output.name,
7214
+ "standalone Android"
7215
+ );
7215
7216
  const files = {};
7216
7217
  for (const { tokens, modifierInputs } of context.permutations) {
7217
7218
  const processedTokens = stripInternalMetadata(tokens);
@@ -7231,12 +7232,12 @@ ${closeIndent})`;
7231
7232
  // Output: bundle
7232
7233
  // -----------------------------------------------------------------------
7233
7234
  async formatBundle(context, options) {
7234
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
7235
- if (!context.output.file && requiresFile) {
7236
- throw new exports.ConfigurationError(
7237
- `Output "${context.output.name}": file is required for bundle Android output`
7238
- );
7239
- }
7235
+ assertFileRequired(
7236
+ context.buildPath,
7237
+ context.output.file,
7238
+ context.output.name,
7239
+ "bundle Android"
7240
+ );
7240
7241
  const content = this.formatBundleContent(context, options);
7241
7242
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
7242
7243
  outputName: context.output.name,
@@ -7249,15 +7250,15 @@ ${closeIndent})`;
7249
7250
  }
7250
7251
  formatBundleContent(context, options) {
7251
7252
  const allTokenTypes = this.collectAllPermutationTypes(context);
7252
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
7253
- const i1 = indent(options.indent, 1);
7253
+ return this.buildFile(allTokenTypes, options, (lines) => {
7254
+ const i1 = indentStr(options.indent, 1);
7254
7255
  lines.push(`@Suppress("unused")`);
7255
- lines.push(`${vis}object ${options.objectName} {`);
7256
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
7256
7257
  for (let idx = 0; idx < context.permutations.length; idx++) {
7257
7258
  const { tokens, modifierInputs } = context.permutations[idx];
7258
7259
  const processedTokens = stripInternalMetadata(tokens);
7259
7260
  const permName = this.buildPermutationName(modifierInputs);
7260
- lines.push(`${i1}${vis}object ${permName} {`);
7261
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
7261
7262
  this.renderBundleTokens(lines, processedTokens, options, 2);
7262
7263
  lines.push(`${i1}}`);
7263
7264
  if (idx < context.permutations.length - 1) {
@@ -7268,20 +7269,17 @@ ${closeIndent})`;
7268
7269
  });
7269
7270
  }
7270
7271
  collectAllPermutationTypes(context) {
7271
- const allTokenTypes = /* @__PURE__ */ new Set();
7272
+ const types = /* @__PURE__ */ new Set();
7272
7273
  for (const { tokens } of context.permutations) {
7273
- const processed = stripInternalMetadata(tokens);
7274
- for (const [, token] of Object.entries(processed)) {
7275
- if (token.$type) {
7276
- allTokenTypes.add(token.$type);
7277
- }
7274
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
7275
+ types.add(t);
7278
7276
  }
7279
7277
  }
7280
- return allTokenTypes;
7278
+ return types;
7281
7279
  }
7282
7280
  renderBundleTokens(lines, tokens, options, baseDepth) {
7283
7281
  if (options.structure === "flat") {
7284
- const groups = this.groupTokensByType(tokens);
7282
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
7285
7283
  this.renderFlatGroups(lines, groups, baseDepth, options);
7286
7284
  return;
7287
7285
  }
@@ -7293,7 +7291,7 @@ ${closeIndent})`;
7293
7291
  if (values.length === 0) {
7294
7292
  return "Default";
7295
7293
  }
7296
- return values.map((v) => toPascalCase(v)).join("");
7294
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
7297
7295
  }
7298
7296
  };
7299
7297
  function androidRenderer() {
@@ -7313,19 +7311,19 @@ init_token_utils();
7313
7311
  // src/renderers/bundlers/css.ts
7314
7312
  init_errors();
7315
7313
  init_utils();
7314
+ var REF_PREFIX_SETS = "#/sets/";
7315
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
7316
7316
  var getSourceSet = (token) => {
7317
7317
  if (typeof token !== "object" || token === null) {
7318
7318
  return void 0;
7319
7319
  }
7320
- const maybe = token;
7321
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
7320
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
7322
7321
  };
7323
7322
  var getSourceModifier = (token) => {
7324
7323
  if (typeof token !== "object" || token === null) {
7325
7324
  return void 0;
7326
7325
  }
7327
- const maybe = token;
7328
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
7326
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
7329
7327
  };
7330
7328
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
7331
7329
  const baseItem = bundleData.find((item) => item.isBase);
@@ -7410,6 +7408,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
7410
7408
  return `/* Modifier: ${modifier}=${context} */
7411
7409
  ${css2}`;
7412
7410
  }
7411
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
7412
+ if (Object.keys(blockTokens).length === 0) {
7413
+ return;
7414
+ }
7415
+ for (const k of Object.keys(blockTokens)) {
7416
+ included.add(k);
7417
+ }
7418
+ blocks.push({ key, description, tokens: blockTokens });
7419
+ }
7413
7420
  function collectSetTokens(tokens, setName, included) {
7414
7421
  const result = {};
7415
7422
  for (const [name, token] of Object.entries(tokens)) {
@@ -7440,75 +7447,67 @@ function collectRemainder(tokens, included) {
7440
7447
  function buildSetLayerBlocks(tokens, resolver) {
7441
7448
  const blocks = [];
7442
7449
  const included = /* @__PURE__ */ new Set();
7443
- const addBlock = (key, blockTokens, description) => {
7444
- if (Object.keys(blockTokens).length === 0) {
7445
- return;
7446
- }
7447
- for (const k of Object.keys(blockTokens)) {
7448
- included.add(k);
7449
- }
7450
- blocks.push({ key, description, tokens: blockTokens });
7451
- };
7452
7450
  for (const item of resolver.resolutionOrder) {
7453
7451
  const ref = item.$ref;
7454
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
7452
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
7455
7453
  continue;
7456
7454
  }
7457
- const setName = ref.slice("#/sets/".length);
7458
- addBlock(
7455
+ const setName = ref.slice(REF_PREFIX_SETS.length);
7456
+ addLayerBlock(
7457
+ blocks,
7458
+ included,
7459
7459
  `Set: ${setName}`,
7460
7460
  collectSetTokens(tokens, setName, included),
7461
7461
  resolver.sets?.[setName]?.description
7462
7462
  );
7463
7463
  }
7464
- addBlock("Unattributed", collectRemainder(tokens, included));
7464
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
7465
7465
  return blocks;
7466
7466
  }
7467
7467
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
7468
7468
  const blocks = [];
7469
7469
  const included = /* @__PURE__ */ new Set();
7470
7470
  const baseInputs = normalizeModifierInputs(baseModifierInputs);
7471
- const addBlock = (key, blockTokens, description) => {
7472
- if (Object.keys(blockTokens).length === 0) {
7473
- return;
7474
- }
7475
- for (const k of Object.keys(blockTokens)) {
7476
- included.add(k);
7477
- }
7478
- blocks.push({ key, description, tokens: blockTokens });
7479
- };
7480
7471
  for (const item of resolver.resolutionOrder) {
7481
7472
  const ref = item.$ref;
7482
7473
  if (typeof ref !== "string") {
7483
7474
  continue;
7484
7475
  }
7485
- if (ref.startsWith("#/sets/")) {
7486
- const setName = ref.slice("#/sets/".length);
7487
- addBlock(
7488
- `Set: ${setName}`,
7489
- collectSetTokens(tokens, setName, included),
7490
- resolver.sets?.[setName]?.description
7491
- );
7492
- continue;
7493
- }
7494
- if (ref.startsWith("#/modifiers/")) {
7495
- const modifierName = ref.slice("#/modifiers/".length);
7496
- const modifier = resolver.modifiers?.[modifierName];
7497
- const selectedContext = baseInputs[modifierName.toLowerCase()];
7498
- if (!modifier || !selectedContext) {
7499
- continue;
7500
- }
7501
- const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
7502
- addBlock(
7503
- `Modifier: ${modifierName}=${selectedContext} (default)`,
7504
- collectModifierTokens(tokens, expectedSource, included),
7505
- modifier.description
7506
- );
7507
- }
7476
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
7508
7477
  }
7509
- addBlock("Unattributed", collectRemainder(tokens, included));
7478
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
7510
7479
  return blocks;
7511
7480
  }
7481
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
7482
+ if (ref.startsWith(REF_PREFIX_SETS)) {
7483
+ const setName = ref.slice(REF_PREFIX_SETS.length);
7484
+ addLayerBlock(
7485
+ blocks,
7486
+ included,
7487
+ `Set: ${setName}`,
7488
+ collectSetTokens(tokens, setName, included),
7489
+ resolver.sets?.[setName]?.description
7490
+ );
7491
+ return;
7492
+ }
7493
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
7494
+ return;
7495
+ }
7496
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
7497
+ const modifier = resolver.modifiers?.[modifierName];
7498
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
7499
+ if (!modifier || !selectedContext) {
7500
+ return;
7501
+ }
7502
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
7503
+ addLayerBlock(
7504
+ blocks,
7505
+ included,
7506
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
7507
+ collectModifierTokens(tokens, expectedSource, included),
7508
+ modifier.description
7509
+ );
7510
+ }
7512
7511
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
7513
7512
  const normalizedModifier = modifierName.toLowerCase();
7514
7513
  const normalizedContext = context.toLowerCase();
@@ -7523,35 +7522,19 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
7523
7522
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
7524
7523
  });
7525
7524
  }
7526
- function orderBundleData(bundleData, resolver, baseItem) {
7527
- const modifiers = resolver.modifiers;
7528
- if (!modifiers) {
7529
- return bundleData;
7525
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
7526
+ if (!item) {
7527
+ return;
7530
7528
  }
7531
- const baseInputs = normalizeModifierInputs(baseItem.modifierInputs);
7532
- const orderedModifierNames = getOrderedModifierNames(resolver);
7533
- if (orderedModifierNames.length === 0) {
7534
- return bundleData;
7529
+ const key = stableInputsKey(item.modifierInputs);
7530
+ if (includedKeys.has(key)) {
7531
+ return;
7535
7532
  }
7536
- const firstModifierDef = modifiers[orderedModifierNames[0] ?? ""];
7537
- if (!firstModifierDef) {
7538
- return bundleData;
7539
- }
7540
- const includedKeys = /* @__PURE__ */ new Set();
7541
- const ordered = [];
7542
- const pushUnique = (item) => {
7543
- if (!item) {
7544
- return;
7545
- }
7546
- const key = stableInputsKey(item.modifierInputs);
7547
- if (includedKeys.has(key)) {
7548
- return;
7549
- }
7550
- includedKeys.add(key);
7551
- ordered.push(item);
7552
- };
7553
- pushUnique(baseItem);
7554
- for (const modifierName of orderedModifierNames) {
7533
+ includedKeys.add(key);
7534
+ ordered.push(item);
7535
+ }
7536
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
7537
+ for (const modifierName of orderedNames) {
7555
7538
  const modifierDef = modifiers[modifierName];
7556
7539
  if (!modifierDef) {
7557
7540
  continue;
@@ -7561,9 +7544,39 @@ function orderBundleData(bundleData, resolver, baseItem) {
7561
7544
  if (defaultValue === ctx.toLowerCase()) {
7562
7545
  continue;
7563
7546
  }
7564
- pushUnique(findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs));
7547
+ pushUniqueBundleItem(
7548
+ ordered,
7549
+ includedKeys,
7550
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
7551
+ );
7565
7552
  }
7566
7553
  }
7554
+ }
7555
+ function orderBundleData(bundleData, resolver, baseItem) {
7556
+ const modifiers = resolver.modifiers;
7557
+ if (!modifiers) {
7558
+ return bundleData;
7559
+ }
7560
+ const baseInputs = normalizeModifierInputs(baseItem.modifierInputs);
7561
+ const orderedModifierNames = getOrderedModifierNames(resolver);
7562
+ if (orderedModifierNames.length === 0) {
7563
+ return bundleData;
7564
+ }
7565
+ const firstModifierDef = modifiers[orderedModifierNames[0] ?? ""];
7566
+ if (!firstModifierDef) {
7567
+ return bundleData;
7568
+ }
7569
+ const includedKeys = /* @__PURE__ */ new Set();
7570
+ const ordered = [];
7571
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
7572
+ appendModifierPermutations(
7573
+ bundleData,
7574
+ modifiers,
7575
+ orderedModifierNames,
7576
+ baseInputs,
7577
+ ordered,
7578
+ includedKeys
7579
+ );
7567
7580
  return ordered.length > 0 ? ordered : bundleData;
7568
7581
  }
7569
7582
  function getOrderedModifierNames(resolver) {
@@ -7575,10 +7588,10 @@ function getOrderedModifierNames(resolver) {
7575
7588
  if (typeof ref !== "string") {
7576
7589
  continue;
7577
7590
  }
7578
- if (!ref.startsWith("#/modifiers/")) {
7591
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
7579
7592
  continue;
7580
7593
  }
7581
- const name = ref.slice("#/modifiers/".length);
7594
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
7582
7595
  if (seen.has(name)) {
7583
7596
  continue;
7584
7597
  }
@@ -7649,24 +7662,22 @@ var CssRenderer = class _CssRenderer {
7649
7662
  ...options,
7650
7663
  referenceTokens: options?.referenceTokens ?? tokens
7651
7664
  };
7652
- const groups = this.groupTokens(tokens, opts);
7665
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
7653
7666
  const referenceTokens = opts.referenceTokens;
7654
7667
  const lines = [];
7655
- for (const [selector, groupTokens] of Object.entries(groups)) {
7656
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
7657
- }
7668
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
7658
7669
  const cssString = lines.join("");
7659
7670
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
7660
7671
  }
7661
7672
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
7662
- const indent2 = opts.minify ? "" : " ";
7673
+ const indent = opts.minify ? "" : " ";
7663
7674
  const newline = opts.minify ? "" : "\n";
7664
7675
  const space = opts.minify ? "" : " ";
7665
7676
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
7666
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
7677
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
7667
7678
  if (hasMediaQuery) {
7668
7679
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
7669
- lines.push(`${indent2}${selector}${space}{${newline}`);
7680
+ lines.push(`${indent}${selector}${space}{${newline}`);
7670
7681
  } else {
7671
7682
  lines.push(`${selector}${space}{${newline}`);
7672
7683
  }
@@ -7683,21 +7694,21 @@ var CssRenderer = class _CssRenderer {
7683
7694
  );
7684
7695
  }
7685
7696
  if (hasMediaQuery) {
7686
- lines.push(`${indent2}}${newline}`);
7697
+ lines.push(`${indent}}${newline}`);
7687
7698
  }
7688
7699
  lines.push(`}${newline}${newline}`);
7689
7700
  }
7690
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
7701
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
7691
7702
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
7692
7703
  if (token.$deprecated != null && token.$deprecated !== false) {
7693
7704
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
7694
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
7705
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
7695
7706
  }
7696
7707
  if (token.$description && token.$description !== "") {
7697
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
7708
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
7698
7709
  }
7699
7710
  for (const entry of entries) {
7700
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
7711
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
7701
7712
  }
7702
7713
  }
7703
7714
  async formatWithPrettier(css2) {
@@ -7712,15 +7723,6 @@ var CssRenderer = class _CssRenderer {
7712
7723
  return css2;
7713
7724
  }
7714
7725
  }
7715
- /**
7716
- * Group tokens by selector (for theme support)
7717
- */
7718
- groupTokens(tokens, options) {
7719
- const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
7720
- return {
7721
- [options.selector]: sortedTokens
7722
- };
7723
- }
7724
7726
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
7725
7727
  if (preserveReferences) {
7726
7728
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -7862,7 +7864,7 @@ var CssRenderer = class _CssRenderer {
7862
7864
  leaves.push({ path: path7, value });
7863
7865
  return;
7864
7866
  }
7865
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
7867
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
7866
7868
  leaves.push({ path: path7, value });
7867
7869
  return;
7868
7870
  }
@@ -7905,8 +7907,8 @@ var CssRenderer = class _CssRenderer {
7905
7907
  if (isDimensionObject(value)) {
7906
7908
  return dimensionObjectToString(value);
7907
7909
  }
7908
- if (this.isDurationObject(value)) {
7909
- return this.formatDurationValue(value);
7910
+ if (isDurationObject(value)) {
7911
+ return durationObjectToString(value);
7910
7912
  }
7911
7913
  if (typeof value === "string") {
7912
7914
  return value;
@@ -7969,15 +7971,6 @@ var CssRenderer = class _CssRenderer {
7969
7971
  isPrimitiveValue(value) {
7970
7972
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
7971
7973
  }
7972
- isDurationObject(value) {
7973
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
7974
- }
7975
- formatDurationValue(value) {
7976
- if (typeof value === "string") {
7977
- return value;
7978
- }
7979
- return `${value.value}${value.unit}`;
7980
- }
7981
7974
  /**
7982
7975
  * Format token value for CSS
7983
7976
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -7998,8 +7991,8 @@ var CssRenderer = class _CssRenderer {
7998
7991
  return typeof value === "string" ? value : dimensionObjectToString(value);
7999
7992
  }
8000
7993
  if (type === "duration") {
8001
- if (this.isDurationObject(value)) {
8002
- return this.formatDurationValue(value);
7994
+ if (isDurationObject(value)) {
7995
+ return durationObjectToString(value);
8003
7996
  }
8004
7997
  if (typeof value === "string") {
8005
7998
  return value;
@@ -8089,16 +8082,16 @@ var CssRenderer = class _CssRenderer {
8089
8082
  */
8090
8083
  formatTransition(value) {
8091
8084
  const parts = [];
8092
- if (this.isDurationObject(value.duration)) {
8093
- parts.push(this.formatDurationValue(value.duration));
8085
+ if (isDurationObject(value.duration)) {
8086
+ parts.push(durationObjectToString(value.duration));
8094
8087
  } else if (value.duration != null) {
8095
8088
  parts.push(String(value.duration));
8096
8089
  }
8097
8090
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
8098
8091
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
8099
8092
  }
8100
- if (this.isDurationObject(value.delay)) {
8101
- parts.push(this.formatDurationValue(value.delay));
8093
+ if (isDurationObject(value.delay)) {
8094
+ parts.push(durationObjectToString(value.delay));
8102
8095
  } else if (value.delay != null) {
8103
8096
  parts.push(String(value.delay));
8104
8097
  }
@@ -8108,7 +8101,7 @@ var CssRenderer = class _CssRenderer {
8108
8101
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
8109
8102
  tokens,
8110
8103
  modifierInputs,
8111
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
8104
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
8112
8105
  }));
8113
8106
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
8114
8107
  return await this.formatTokens(tokens, {
@@ -8118,12 +8111,12 @@ var CssRenderer = class _CssRenderer {
8118
8111
  });
8119
8112
  }
8120
8113
  async formatStandalone(context, options) {
8121
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8122
- if (!context.output.file && requiresFile) {
8123
- throw new exports.ConfigurationError(
8124
- `Output "${context.output.name}": file is required for standalone CSS output`
8125
- );
8126
- }
8114
+ assertFileRequired(
8115
+ context.buildPath,
8116
+ context.output.file,
8117
+ context.output.name,
8118
+ "standalone CSS"
8119
+ );
8127
8120
  const files = {};
8128
8121
  for (const { tokens, modifierInputs } of context.permutations) {
8129
8122
  const { fileName, content } = await this.buildStandaloneFile(
@@ -8137,7 +8130,7 @@ var CssRenderer = class _CssRenderer {
8137
8130
  return { kind: "outputTree", files };
8138
8131
  }
8139
8132
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
8140
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
8133
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
8141
8134
  const { modifierName, modifierContext } = this.resolveModifierContext(
8142
8135
  modifierInputs,
8143
8136
  context,
@@ -8174,12 +8167,7 @@ var CssRenderer = class _CssRenderer {
8174
8167
  return { fileName, content };
8175
8168
  }
8176
8169
  async formatModifier(context, options) {
8177
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8178
- if (!context.output.file && requiresFile) {
8179
- throw new exports.ConfigurationError(
8180
- `Output "${context.output.name}": file is required for modifier CSS output`
8181
- );
8182
- }
8170
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
8183
8171
  if (!context.resolver.modifiers) {
8184
8172
  throw new exports.ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
8185
8173
  }
@@ -8205,7 +8193,7 @@ var CssRenderer = class _CssRenderer {
8205
8193
  }
8206
8194
  async buildModifierBaseFile(context, options) {
8207
8195
  const basePermutation = context.permutations.find(
8208
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
8196
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
8209
8197
  );
8210
8198
  if (!basePermutation) {
8211
8199
  return void 0;
@@ -8218,25 +8206,40 @@ var CssRenderer = class _CssRenderer {
8218
8206
  if (setBlocks.length === 0) {
8219
8207
  return void 0;
8220
8208
  }
8209
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
8210
+ const content = await this.formatSetBlocksCss(
8211
+ setBlocks,
8212
+ basePermutation.tokens,
8213
+ selector,
8214
+ mediaQuery,
8215
+ options
8216
+ );
8217
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
8218
+ return { fileName, content };
8219
+ }
8220
+ resolveBaseModifierContext(context, options) {
8221
8221
  const modifiers = context.resolver.modifiers;
8222
8222
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
8223
8223
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
8224
8224
  const baseModifierInputs = { ...context.meta.defaults };
8225
- const selector = resolveSelector(
8226
- options.selector,
8227
- firstModifierName,
8228
- firstModifierContext,
8229
- true,
8230
- baseModifierInputs
8231
- );
8232
- const mediaQuery = resolveMediaQuery(
8233
- options.mediaQuery,
8234
- firstModifierName,
8235
- firstModifierContext,
8236
- true,
8237
- baseModifierInputs
8238
- );
8239
- const referenceTokens = basePermutation.tokens;
8225
+ return {
8226
+ selector: resolveSelector(
8227
+ options.selector,
8228
+ firstModifierName,
8229
+ firstModifierContext,
8230
+ true,
8231
+ baseModifierInputs
8232
+ ),
8233
+ mediaQuery: resolveMediaQuery(
8234
+ options.mediaQuery,
8235
+ firstModifierName,
8236
+ firstModifierContext,
8237
+ true,
8238
+ baseModifierInputs
8239
+ )
8240
+ };
8241
+ }
8242
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
8240
8243
  const cssBlocks = [];
8241
8244
  for (const block of setBlocks) {
8242
8245
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -8252,9 +8255,7 @@ var CssRenderer = class _CssRenderer {
8252
8255
  cssBlocks.push(`${header}
8253
8256
  ${css2}`);
8254
8257
  }
8255
- const content = cssBlocks.join("\n");
8256
- const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
8257
- return { fileName, content };
8258
+ return cssBlocks.join("\n");
8258
8259
  }
8259
8260
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
8260
8261
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -8331,13 +8332,6 @@ ${css2}`);
8331
8332
  }
8332
8333
  return { modifierName: "", modifierContext: "" };
8333
8334
  }
8334
- isBasePermutation(modifierInputs, defaults) {
8335
- const normalizedInputs = normalizeModifierInputs(modifierInputs);
8336
- const normalizedDefaults = normalizeModifierInputs(defaults);
8337
- return Object.entries(normalizedDefaults).every(
8338
- ([key, value]) => normalizedInputs[key] === value
8339
- );
8340
- }
8341
8335
  };
8342
8336
  function cssRenderer() {
8343
8337
  const rendererInstance = new CssRenderer();
@@ -8350,8 +8344,6 @@ function cssRenderer() {
8350
8344
  }
8351
8345
 
8352
8346
  // src/renderers/ios.ts
8353
- init_errors();
8354
- init_token_utils();
8355
8347
  init_utils();
8356
8348
  var toSRGB2 = culori.converter("rgb");
8357
8349
  var toP32 = culori.converter("p3");
@@ -8440,94 +8432,68 @@ var IosRenderer = class {
8440
8432
  return await this.formatStandalone(context, opts);
8441
8433
  }
8442
8434
  formatTokens(tokens, options) {
8443
- if (options.structure === "grouped") {
8444
- return this.formatAsGrouped(tokens, options);
8445
- }
8446
- return this.formatAsEnum(tokens, options);
8447
- }
8448
- formatAsEnum(tokens, options) {
8449
8435
  const access3 = options.accessLevel;
8450
- const groups = this.groupTokensByType(tokens);
8436
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
8451
8437
  const imports = this.collectImports(tokens);
8452
- const i1 = this.indentStr(options.indent, 1);
8453
- const i2 = this.indentStr(options.indent, 2);
8454
8438
  const staticPrefix = this.staticLetPrefix(options);
8455
8439
  const frozen = this.frozenPrefix(options);
8456
8440
  const lines = [];
8457
- lines.push(this.buildFileHeader());
8441
+ lines.push(buildGeneratedFileHeader());
8458
8442
  lines.push("");
8459
8443
  for (const imp of imports) {
8460
8444
  lines.push(`import ${imp}`);
8461
8445
  }
8462
8446
  lines.push(...this.buildStructDefinitions(tokens, access3, options));
8447
+ this.pushTokenLayout(lines, groups, options, access3, staticPrefix, frozen);
8448
+ lines.push(...this.buildViewExtensions(tokens, access3, options));
8449
+ if (options.structure !== "grouped") {
8450
+ lines.push("");
8451
+ }
8452
+ return lines.join("\n");
8453
+ }
8454
+ pushTokenLayout(lines, groups, options, access3, staticPrefix, frozen) {
8455
+ const i1 = indentStr(options.indent, 1);
8456
+ const i2 = indentStr(options.indent, 2);
8457
+ if (options.structure === "grouped") {
8458
+ this.pushGroupedLayout(lines, groups, options, access3, i1, i2, staticPrefix, frozen);
8459
+ return;
8460
+ }
8463
8461
  lines.push("");
8464
8462
  lines.push(`${frozen}${access3} enum ${options.enumName} {`);
8465
8463
  for (const group of groups) {
8466
8464
  lines.push(`${i1}${frozen}${access3} enum ${group.name} {`);
8467
- for (const token of group.tokens) {
8468
- const swiftName = this.buildQualifiedSwiftName(token);
8469
- const swiftValue = this.formatSwiftValue(token, options);
8470
- const typeAnnotation = this.getTypeAnnotation(token);
8471
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8472
- const docComment = this.buildDocComment(token, i2);
8473
- if (docComment) {
8474
- lines.push(docComment);
8475
- }
8476
- lines.push(`${i2}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8477
- }
8465
+ this.pushTokenDeclarations(lines, group.tokens, options, access3, i2, staticPrefix);
8478
8466
  lines.push(`${i1}}`);
8479
8467
  lines.push("");
8480
8468
  }
8481
8469
  lines.push("}");
8482
- lines.push(...this.buildViewExtensions(tokens, access3, options));
8483
- lines.push("");
8484
- return lines.join("\n");
8485
8470
  }
8486
- formatAsGrouped(tokens, options) {
8487
- const access3 = options.accessLevel;
8471
+ pushGroupedLayout(lines, groups, options, access3, i1, i2, staticPrefix, frozen) {
8488
8472
  const namespace = options.extensionNamespace;
8489
- const groups = this.groupTokensByType(tokens);
8490
- const imports = this.collectImports(tokens);
8491
- const i1 = this.indentStr(options.indent, 1);
8492
- const i2 = this.indentStr(options.indent, 2);
8493
- const staticPrefix = this.staticLetPrefix(options);
8494
- const frozen = this.frozenPrefix(options);
8495
- const lines = [];
8496
- lines.push(this.buildFileHeader());
8497
- lines.push("");
8498
- for (const imp of imports) {
8499
- lines.push(`import ${imp}`);
8500
- }
8501
- lines.push(...this.buildStructDefinitions(tokens, access3, options));
8502
8473
  lines.push("");
8503
8474
  lines.push(`${frozen}${access3} enum ${namespace} {}`);
8504
8475
  lines.push("");
8505
8476
  for (const group of groups) {
8506
8477
  lines.push(`${access3} extension ${namespace} {`);
8507
8478
  lines.push(`${i1}${frozen}enum ${group.name} {`);
8508
- for (const token of group.tokens) {
8509
- const swiftName = this.buildQualifiedSwiftName(token);
8510
- const swiftValue = this.formatSwiftValue(token, options);
8511
- const typeAnnotation = this.getTypeAnnotation(token);
8512
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8513
- const docComment = this.buildDocComment(token, i2);
8514
- if (docComment) {
8515
- lines.push(docComment);
8516
- }
8517
- lines.push(`${i2}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8518
- }
8479
+ this.pushTokenDeclarations(lines, group.tokens, options, access3, i2, staticPrefix);
8519
8480
  lines.push(`${i1}}`);
8520
8481
  lines.push("}");
8521
8482
  lines.push("");
8522
8483
  }
8523
- lines.push(...this.buildViewExtensions(tokens, access3, options));
8524
- return lines.join("\n");
8525
8484
  }
8526
- buildFileHeader() {
8527
- return [
8528
- "// Generated by Dispersa - do not edit manually",
8529
- "// https://github.com/timges/dispersa"
8530
- ].join("\n");
8485
+ pushTokenDeclarations(lines, tokens, options, access3, indent, staticPrefix) {
8486
+ for (const token of tokens) {
8487
+ const swiftName = this.buildQualifiedSwiftName(token);
8488
+ const swiftValue = this.formatSwiftValue(token, options);
8489
+ const typeAnnotation = this.getTypeAnnotation(token);
8490
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8491
+ const docComment = this.buildDocComment(token, indent);
8492
+ if (docComment) {
8493
+ lines.push(docComment);
8494
+ }
8495
+ lines.push(`${indent}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8496
+ }
8531
8497
  }
8532
8498
  collectImports(tokens) {
8533
8499
  const imports = /* @__PURE__ */ new Set();
@@ -8542,24 +8508,11 @@ var IosRenderer = class {
8542
8508
  /**
8543
8509
  * Builds a `///` doc comment from a token's `$description`, if present.
8544
8510
  */
8545
- buildDocComment(token, indent2) {
8511
+ buildDocComment(token, indent) {
8546
8512
  if (!token.$description) {
8547
8513
  return void 0;
8548
8514
  }
8549
- return `${indent2}/// ${token.$description}`;
8550
- }
8551
- groupTokensByType(tokens) {
8552
- const groupMap = /* @__PURE__ */ new Map();
8553
- for (const [, token] of getSortedTokenEntries(tokens)) {
8554
- const groupName = SWIFT_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
8555
- const existing = groupMap.get(groupName) ?? [];
8556
- existing.push(token);
8557
- groupMap.set(groupName, existing);
8558
- }
8559
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
8560
- name,
8561
- tokens: groupTokens
8562
- }));
8515
+ return `${indent}/// ${token.$description}`;
8563
8516
  }
8564
8517
  /**
8565
8518
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -8572,43 +8525,40 @@ var IosRenderer = class {
8572
8525
  const path7 = token.path;
8573
8526
  const withoutTypePrefix = path7.length > 1 ? path7.slice(1) : path7;
8574
8527
  const joined = withoutTypePrefix.join("_");
8575
- return this.toSwiftIdentifier(joined);
8528
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
8576
8529
  }
8577
8530
  formatSwiftValue(token, options) {
8578
- const value = token.$value;
8579
- if (token.$type === "color") {
8580
- return this.formatColorValue(value, options);
8581
- }
8582
- if (token.$type === "dimension") {
8583
- return this.formatDimensionValue(value);
8584
- }
8585
- if (token.$type === "fontFamily") {
8586
- return this.formatFontFamilyValue(value);
8587
- }
8588
- if (token.$type === "fontWeight") {
8589
- return this.formatFontWeightValue(value);
8590
- }
8591
- if (token.$type === "duration") {
8592
- return this.formatDurationValue(value);
8593
- }
8594
- if (token.$type === "shadow") {
8595
- return this.formatShadowValue(value, options);
8596
- }
8597
- if (token.$type === "typography") {
8598
- return this.formatTypographyValue(value);
8599
- }
8600
- if (token.$type === "border") {
8601
- return this.formatBorderValue(value, options);
8602
- }
8603
- if (token.$type === "gradient") {
8604
- return this.formatGradientValue(value, options);
8605
- }
8606
- if (token.$type === "number") {
8607
- return String(value);
8608
- }
8609
- if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
8610
- return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
8531
+ const { $type, $value: value } = token;
8532
+ switch ($type) {
8533
+ case "color":
8534
+ return this.formatColorValue(value, options);
8535
+ case "dimension":
8536
+ return this.formatDimensionValue(value);
8537
+ case "fontFamily":
8538
+ return this.formatFontFamilyValue(value);
8539
+ case "fontWeight":
8540
+ return this.formatFontWeightValue(value);
8541
+ case "duration":
8542
+ return this.formatDurationValue(value);
8543
+ case "shadow":
8544
+ return this.formatShadowValue(value, options);
8545
+ case "typography":
8546
+ return this.formatTypographyValue(value);
8547
+ case "border":
8548
+ return this.formatBorderValue(value, options);
8549
+ case "gradient":
8550
+ return this.formatGradientValue(value, options);
8551
+ case "number":
8552
+ return String(value);
8553
+ case "cubicBezier":
8554
+ if (Array.isArray(value) && value.length === 4) {
8555
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
8556
+ }
8557
+ break;
8611
8558
  }
8559
+ return this.formatSwiftPrimitive(value);
8560
+ }
8561
+ formatSwiftPrimitive(value) {
8612
8562
  if (typeof value === "string") {
8613
8563
  return `"${this.escapeSwiftString(value)}"`;
8614
8564
  }
@@ -8641,9 +8591,7 @@ var IosRenderer = class {
8641
8591
  }
8642
8592
  formatDimensionValue(value) {
8643
8593
  if (isDimensionObject(value)) {
8644
- const dim = value;
8645
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8646
- return String(ptValue);
8594
+ return this.dimensionToPoints(value);
8647
8595
  }
8648
8596
  return String(value);
8649
8597
  }
@@ -8710,7 +8658,7 @@ var IosRenderer = class {
8710
8658
  return map[name.toLowerCase()];
8711
8659
  }
8712
8660
  formatDurationValue(value) {
8713
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
8661
+ if (isDurationObject(value)) {
8714
8662
  const dur = value;
8715
8663
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
8716
8664
  return String(seconds);
@@ -8759,9 +8707,7 @@ var IosRenderer = class {
8759
8707
  if (!isDimensionObject(typo.letterSpacing)) {
8760
8708
  return "0";
8761
8709
  }
8762
- const dim = typo.letterSpacing;
8763
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8764
- return String(ptValue);
8710
+ return this.dimensionToPoints(typo.letterSpacing);
8765
8711
  }
8766
8712
  extractLineSpacing(typo) {
8767
8713
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -8770,18 +8716,19 @@ var IosRenderer = class {
8770
8716
  if (!isDimensionObject(typo.fontSize)) {
8771
8717
  return "0";
8772
8718
  }
8773
- const dim = typo.fontSize;
8774
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
8719
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
8775
8720
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
8776
8721
  return String(lineHeightPt - basePt);
8777
8722
  }
8723
+ dimensionToNumericPoints(dim) {
8724
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
8725
+ }
8778
8726
  dimensionToPoints(dim) {
8779
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8780
- return String(ptValue);
8727
+ return String(this.dimensionToNumericPoints(dim));
8781
8728
  }
8782
8729
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
8783
8730
  dimensionToCGFloat(dim) {
8784
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8731
+ const ptValue = this.dimensionToNumericPoints(dim);
8785
8732
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
8786
8733
  }
8787
8734
  getTypeAnnotation(token) {
@@ -8800,21 +8747,12 @@ var IosRenderer = class {
8800
8747
  return void 0;
8801
8748
  }
8802
8749
  }
8803
- toSwiftIdentifier(name) {
8804
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
8805
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
8806
- const safe = /^\d/.test(identifier) ? `_${identifier}` : identifier;
8807
- return SWIFT_KEYWORDS.has(safe) ? `\`${safe}\`` : safe;
8808
- }
8809
8750
  escapeSwiftString(str) {
8810
8751
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
8811
8752
  }
8812
8753
  roundComponent(value) {
8813
8754
  return Math.round(value * 1e4) / 1e4;
8814
8755
  }
8815
- indentStr(width, level) {
8816
- return " ".repeat(width * level);
8817
- }
8818
8756
  /**
8819
8757
  * Returns the prefix for `static let` declarations.
8820
8758
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -8830,34 +8768,25 @@ var IosRenderer = class {
8830
8768
  structConformances(options) {
8831
8769
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
8832
8770
  }
8833
- hasShadowTokens(tokens) {
8834
- return Object.values(tokens).some((t) => t.$type === "shadow");
8835
- }
8836
- hasTypographyTokens(tokens) {
8837
- return Object.values(tokens).some((t) => t.$type === "typography");
8838
- }
8839
- hasBorderTokens(tokens) {
8840
- return Object.values(tokens).some((t) => t.$type === "border");
8841
- }
8842
8771
  /** Emits all struct definitions needed by the token set. */
8843
8772
  buildStructDefinitions(tokens, access3, options) {
8844
8773
  const lines = [];
8845
- if (this.hasShadowTokens(tokens)) {
8774
+ if (Object.values(tokens).some(isShadowToken)) {
8846
8775
  lines.push("");
8847
8776
  lines.push(...this.buildShadowStyleStruct(access3, options));
8848
8777
  }
8849
- if (this.hasTypographyTokens(tokens)) {
8778
+ if (Object.values(tokens).some(isTypographyToken)) {
8850
8779
  lines.push("");
8851
8780
  lines.push(...this.buildTypographyStyleStruct(access3, options));
8852
8781
  }
8853
- if (this.hasBorderTokens(tokens)) {
8782
+ if (Object.values(tokens).some(isBorderToken)) {
8854
8783
  lines.push("");
8855
8784
  lines.push(...this.buildBorderStyleStruct(access3, options));
8856
8785
  }
8857
8786
  return lines;
8858
8787
  }
8859
8788
  buildShadowStyleStruct(access3, options) {
8860
- const i1 = this.indentStr(options.indent, 1);
8789
+ const i1 = indentStr(options.indent, 1);
8861
8790
  const conformances = this.structConformances(options);
8862
8791
  const frozen = this.frozenPrefix(options);
8863
8792
  return [
@@ -8871,7 +8800,7 @@ var IosRenderer = class {
8871
8800
  ];
8872
8801
  }
8873
8802
  buildTypographyStyleStruct(access3, options) {
8874
- const i1 = this.indentStr(options.indent, 1);
8803
+ const i1 = indentStr(options.indent, 1);
8875
8804
  const conformances = this.structConformances(options);
8876
8805
  const frozen = this.frozenPrefix(options);
8877
8806
  return [
@@ -8883,7 +8812,7 @@ var IosRenderer = class {
8883
8812
  ];
8884
8813
  }
8885
8814
  buildBorderStyleStruct(access3, options) {
8886
- const i1 = this.indentStr(options.indent, 1);
8815
+ const i1 = indentStr(options.indent, 1);
8887
8816
  const conformances = this.structConformances(options);
8888
8817
  const frozen = this.frozenPrefix(options);
8889
8818
  return [
@@ -8896,9 +8825,9 @@ var IosRenderer = class {
8896
8825
  /** Emits convenience View extensions for shadow and typography application. */
8897
8826
  buildViewExtensions(tokens, access3, options) {
8898
8827
  const lines = [];
8899
- const i1 = this.indentStr(options.indent, 1);
8900
- const i2 = this.indentStr(options.indent, 2);
8901
- if (this.hasShadowTokens(tokens)) {
8828
+ const i1 = indentStr(options.indent, 1);
8829
+ const i2 = indentStr(options.indent, 2);
8830
+ if (Object.values(tokens).some(isShadowToken)) {
8902
8831
  lines.push("");
8903
8832
  lines.push(`${access3} extension View {`);
8904
8833
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -8908,7 +8837,7 @@ var IosRenderer = class {
8908
8837
  lines.push(`${i1}}`);
8909
8838
  lines.push("}");
8910
8839
  }
8911
- if (this.hasTypographyTokens(tokens)) {
8840
+ if (Object.values(tokens).some(isTypographyToken)) {
8912
8841
  lines.push("");
8913
8842
  lines.push(`${access3} extension View {`);
8914
8843
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -8940,12 +8869,12 @@ var IosRenderer = class {
8940
8869
  return `Gradient(stops: [${stops.join(", ")}])`;
8941
8870
  }
8942
8871
  async formatStandalone(context, options) {
8943
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8944
- if (!context.output.file && requiresFile) {
8945
- throw new exports.ConfigurationError(
8946
- `Output "${context.output.name}": file is required for standalone iOS output`
8947
- );
8948
- }
8872
+ assertFileRequired(
8873
+ context.buildPath,
8874
+ context.output.file,
8875
+ context.output.name,
8876
+ "standalone iOS"
8877
+ );
8949
8878
  const files = {};
8950
8879
  for (const { tokens, modifierInputs } of context.permutations) {
8951
8880
  const processedTokens = stripInternalMetadata(tokens);
@@ -8974,7 +8903,6 @@ function iosRenderer() {
8974
8903
 
8975
8904
  // src/renderers/js-module.ts
8976
8905
  init_utils();
8977
- init_errors();
8978
8906
  init_token_utils();
8979
8907
  var JsModuleRenderer = class {
8980
8908
  async format(context, options) {
@@ -8990,18 +8918,13 @@ var JsModuleRenderer = class {
8990
8918
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
8991
8919
  tokens: stripInternalMetadata(tokens),
8992
8920
  modifierInputs,
8993
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
8921
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
8994
8922
  }));
8995
8923
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
8996
8924
  return await this.formatTokens(tokens, opts);
8997
8925
  });
8998
8926
  }
8999
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9000
- if (!context.output.file && requiresFile) {
9001
- throw new exports.ConfigurationError(
9002
- `Output "${context.output.name}": file is required for JS module output`
9003
- );
9004
- }
8927
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
9005
8928
  const files = {};
9006
8929
  for (const { tokens, modifierInputs } of context.permutations) {
9007
8930
  const cleanTokens = stripInternalMetadata(tokens);
@@ -9055,42 +8978,18 @@ var JsModuleRenderer = class {
9055
8978
  lines.push(`export default ${varName}`);
9056
8979
  return lines;
9057
8980
  }
9058
- /**
9059
- * Convert tokens to plain object with flat or nested structure
9060
- */
9061
8981
  tokensToPlainObject(tokens, structure) {
8982
+ if (structure === "nested") {
8983
+ return buildNestedTokenObject(tokens, (token) => token.$value);
8984
+ }
9062
8985
  const result = {};
9063
- if (structure === "flat") {
9064
- for (const [name, token] of getSortedTokenEntries(tokens)) {
9065
- result[name] = token.$value;
9066
- }
9067
- } else {
9068
- for (const [, token] of getSortedTokenEntries(tokens)) {
9069
- const parts = token.path;
9070
- let current = result;
9071
- for (let i = 0; i < parts.length - 1; i++) {
9072
- const part = parts[i];
9073
- if (part == null) {
9074
- continue;
9075
- }
9076
- if (!(part in current)) {
9077
- current[part] = {};
9078
- }
9079
- current = current[part];
9080
- }
9081
- const lastPart = parts[parts.length - 1];
9082
- if (lastPart != null) {
9083
- current[lastPart] = token.$value;
9084
- }
9085
- }
8986
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
8987
+ result[name] = token.$value;
9086
8988
  }
9087
8989
  return result;
9088
8990
  }
9089
- /**
9090
- * Add object properties to lines
9091
- */
9092
- addObjectProperties(lines, obj, indent2) {
9093
- const indentStr = " ".repeat(indent2);
8991
+ addObjectProperties(lines, obj, indent) {
8992
+ const indentStr2 = " ".repeat(indent);
9094
8993
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
9095
8994
  for (let i = 0; i < entries.length; i++) {
9096
8995
  const entry = entries[i];
@@ -9099,14 +8998,16 @@ var JsModuleRenderer = class {
9099
8998
  }
9100
8999
  const [key, value] = entry;
9101
9000
  const isLast = i === entries.length - 1;
9102
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
9103
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
9104
- this.addObjectProperties(lines, value, indent2 + 1);
9105
- lines.push(`${indentStr}}${isLast ? "" : ","}`);
9106
- } else {
9107
- const valueStr = JSON.stringify(value);
9108
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueStr}${isLast ? "" : ","}`);
9001
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
9002
+ if (!isNestedObject) {
9003
+ lines.push(
9004
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
9005
+ );
9006
+ continue;
9109
9007
  }
9008
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
9009
+ this.addObjectProperties(lines, value, indent + 1);
9010
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
9110
9011
  }
9111
9012
  }
9112
9013
  /**
@@ -9118,9 +9019,6 @@ var JsModuleRenderer = class {
9118
9019
  }
9119
9020
  return `"${key}"`;
9120
9021
  }
9121
- isBasePermutation(modifierInputs, defaults) {
9122
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
9123
- }
9124
9022
  };
9125
9023
  function jsRenderer() {
9126
9024
  const rendererInstance = new JsModuleRenderer();
@@ -9134,7 +9032,6 @@ function jsRenderer() {
9134
9032
 
9135
9033
  // src/renderers/json.ts
9136
9034
  init_utils();
9137
- init_errors();
9138
9035
  init_token_utils();
9139
9036
  var JsonRenderer = class {
9140
9037
  async format(context, options) {
@@ -9149,18 +9046,13 @@ var JsonRenderer = class {
9149
9046
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
9150
9047
  tokens: stripInternalMetadata(tokens),
9151
9048
  modifierInputs,
9152
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
9049
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
9153
9050
  }));
9154
9051
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
9155
9052
  return await this.formatTokens(tokens, opts);
9156
9053
  });
9157
9054
  }
9158
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9159
- if (!context.output.file && requiresFile) {
9160
- throw new exports.ConfigurationError(
9161
- `Output "${context.output.name}": file is required for JSON output`
9162
- );
9163
- }
9055
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
9164
9056
  const files = {};
9165
9057
  for (const { tokens, modifierInputs } of context.permutations) {
9166
9058
  const processedTokens = stripInternalMetadata(tokens);
@@ -9220,55 +9112,11 @@ var JsonRenderer = class {
9220
9112
  }
9221
9113
  return result;
9222
9114
  }
9223
- /**
9224
- * Nest tokens by path (values only)
9225
- */
9226
9115
  nestValues(tokens) {
9227
- const result = {};
9228
- for (const [, token] of getSortedTokenEntries(tokens)) {
9229
- const parts = token.path;
9230
- let current = result;
9231
- for (let i = 0; i < parts.length - 1; i++) {
9232
- const part = parts[i];
9233
- if (part === null || part === void 0) {
9234
- continue;
9235
- }
9236
- if (!(part in current)) {
9237
- current[part] = {};
9238
- }
9239
- current = current[part];
9240
- }
9241
- const lastPart = parts[parts.length - 1];
9242
- if (lastPart !== null && lastPart !== void 0) {
9243
- current[lastPart] = token.$value;
9244
- }
9245
- }
9246
- return result;
9116
+ return buildNestedTokenObject(tokens, (token) => token.$value);
9247
9117
  }
9248
- /**
9249
- * Nest tokens by path (with metadata)
9250
- */
9251
9118
  nestTokens(tokens) {
9252
- const result = {};
9253
- for (const [, token] of getSortedTokenEntries(tokens)) {
9254
- const parts = token.path;
9255
- let current = result;
9256
- for (let i = 0; i < parts.length - 1; i++) {
9257
- const part = parts[i];
9258
- if (part === null || part === void 0) {
9259
- continue;
9260
- }
9261
- if (!(part in current)) {
9262
- current[part] = {};
9263
- }
9264
- current = current[part];
9265
- }
9266
- const lastPart = parts[parts.length - 1];
9267
- if (lastPart !== null && lastPart !== void 0) {
9268
- current[lastPart] = this.serializeToken(token);
9269
- }
9270
- }
9271
- return result;
9119
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
9272
9120
  }
9273
9121
  serializeToken(token) {
9274
9122
  return {
@@ -9279,9 +9127,6 @@ var JsonRenderer = class {
9279
9127
  ...token.$extensions != null && { $extensions: token.$extensions }
9280
9128
  };
9281
9129
  }
9282
- isBasePermutation(modifierInputs, defaults) {
9283
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
9284
- }
9285
9130
  };
9286
9131
  function jsonRenderer() {
9287
9132
  const rendererInstance = new JsonRenderer();
@@ -9294,7 +9139,6 @@ function jsonRenderer() {
9294
9139
  }
9295
9140
 
9296
9141
  // src/renderers/tailwind.ts
9297
- init_errors();
9298
9142
  init_token_utils();
9299
9143
 
9300
9144
  // src/renderers/bundlers/tailwind.ts
@@ -9323,6 +9167,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
9323
9167
  }
9324
9168
  return cssBlocks.join("\n");
9325
9169
  }
9170
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
9171
+ const normalized = normalizeModifierInputs(modifierInputs);
9172
+ return {
9173
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
9174
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
9175
+ };
9176
+ }
9326
9177
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
9327
9178
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
9328
9179
  if (differenceCount > 1) {
@@ -9335,19 +9186,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
9335
9186
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
9336
9187
  const [modifier, context] = parseModifierSource(expectedSource);
9337
9188
  const cleanTokens = stripInternalMetadata(tokensToInclude);
9338
- const selector = resolveSelector(
9339
- options.selector,
9189
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
9190
+ options,
9340
9191
  modifier,
9341
9192
  context,
9342
- false,
9343
- normalizeModifierInputs(modifierInputs)
9344
- );
9345
- const mediaQuery = resolveMediaQuery(
9346
- options.mediaQuery,
9347
- modifier,
9348
- context,
9349
- false,
9350
- normalizeModifierInputs(modifierInputs)
9193
+ modifierInputs
9351
9194
  );
9352
9195
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
9353
9196
  return `/* Modifier: ${modifier}=${context} */
@@ -9436,7 +9279,7 @@ var TailwindRenderer = class {
9436
9279
  */
9437
9280
  async formatTokens(tokens, options) {
9438
9281
  const lines = [];
9439
- const indent2 = options.minify ? "" : " ";
9282
+ const indent = options.minify ? "" : " ";
9440
9283
  const newline = options.minify ? "" : "\n";
9441
9284
  const space = options.minify ? "" : " ";
9442
9285
  if (options.includeImport) {
@@ -9458,7 +9301,7 @@ var TailwindRenderer = class {
9458
9301
  for (const [, token] of getSortedTokenEntries(tokens)) {
9459
9302
  const varName = this.buildVariableName(token);
9460
9303
  const varValue = this.formatValue(token);
9461
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
9304
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
9462
9305
  }
9463
9306
  lines.push(`}${newline}`);
9464
9307
  const cssString = lines.join("");
@@ -9469,15 +9312,15 @@ var TailwindRenderer = class {
9469
9312
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
9470
9313
  */
9471
9314
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
9472
- const indent2 = minify ? "" : " ";
9315
+ const indent = minify ? "" : " ";
9473
9316
  const newline = minify ? "" : "\n";
9474
9317
  const space = minify ? "" : " ";
9475
9318
  const hasMediaQuery = mediaQuery !== "";
9476
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
9319
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
9477
9320
  const lines = [];
9478
9321
  if (hasMediaQuery) {
9479
9322
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
9480
- lines.push(`${indent2}${selector}${space}{${newline}`);
9323
+ lines.push(`${indent}${selector}${space}{${newline}`);
9481
9324
  } else {
9482
9325
  lines.push(`${selector}${space}{${newline}`);
9483
9326
  }
@@ -9487,7 +9330,7 @@ var TailwindRenderer = class {
9487
9330
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
9488
9331
  }
9489
9332
  if (hasMediaQuery) {
9490
- lines.push(`${indent2}}${newline}`);
9333
+ lines.push(`${indent}}${newline}`);
9491
9334
  lines.push(`}${newline}`);
9492
9335
  } else {
9493
9336
  lines.push(`}${newline}`);
@@ -9514,8 +9357,8 @@ var TailwindRenderer = class {
9514
9357
  if (token.$type === "dimension" && isDimensionObject(value)) {
9515
9358
  return dimensionObjectToString(value);
9516
9359
  }
9517
- if (token.$type === "duration" && this.isDurationObject(value)) {
9518
- return `${value.value}${value.unit}`;
9360
+ if (token.$type === "duration" && isDurationObject(value)) {
9361
+ return durationObjectToString(value);
9519
9362
  }
9520
9363
  if (token.$type === "fontFamily") {
9521
9364
  if (Array.isArray(value)) {
@@ -9570,9 +9413,6 @@ var TailwindRenderer = class {
9570
9413
  }
9571
9414
  return parts.join(" ");
9572
9415
  }
9573
- isDurationObject(value) {
9574
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
9575
- }
9576
9416
  async formatWithPrettier(css2) {
9577
9417
  try {
9578
9418
  return await prettier__default.default.format(css2, {
@@ -9589,7 +9429,7 @@ var TailwindRenderer = class {
9589
9429
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
9590
9430
  tokens,
9591
9431
  modifierInputs,
9592
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
9432
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
9593
9433
  }));
9594
9434
  return await bundleAsTailwind(
9595
9435
  bundleData,
@@ -9599,12 +9439,12 @@ var TailwindRenderer = class {
9599
9439
  );
9600
9440
  }
9601
9441
  async formatStandalone(context, options) {
9602
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9603
- if (!context.output.file && requiresFile) {
9604
- throw new exports.ConfigurationError(
9605
- `Output "${context.output.name}": file is required for standalone Tailwind output`
9606
- );
9607
- }
9442
+ assertFileRequired(
9443
+ context.buildPath,
9444
+ context.output.file,
9445
+ context.output.name,
9446
+ "standalone Tailwind"
9447
+ );
9608
9448
  const files = {};
9609
9449
  for (const { tokens, modifierInputs } of context.permutations) {
9610
9450
  const processedTokens = stripInternalMetadata(tokens);
@@ -9620,11 +9460,6 @@ var TailwindRenderer = class {
9620
9460
  }
9621
9461
  return outputTree(files);
9622
9462
  }
9623
- isBasePermutation(modifierInputs, defaults) {
9624
- return Object.entries(defaults).every(
9625
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
9626
- );
9627
- }
9628
9463
  };
9629
9464
  function tailwindRenderer() {
9630
9465
  const rendererInstance = new TailwindRenderer();
@@ -9757,11 +9592,6 @@ function defineRenderer(renderer) {
9757
9592
 
9758
9593
  // src/index.ts
9759
9594
  init_errors();
9760
- /**
9761
- * @license
9762
- * Copyright (c) 2025 Dispersa Contributors
9763
- * SPDX-License-Identifier: MIT
9764
- */
9765
9595
  /**
9766
9596
  * @license MIT
9767
9597
  * Copyright (c) 2025-present Dispersa Contributors
@@ -9769,6 +9599,11 @@ init_errors();
9769
9599
  * This source code is licensed under the MIT license found in the
9770
9600
  * LICENSE file in the root directory of this source tree.
9771
9601
  */
9602
+ /**
9603
+ * @license
9604
+ * Copyright (c) 2025 Dispersa Contributors
9605
+ * SPDX-License-Identifier: MIT
9606
+ */
9772
9607
 
9773
9608
  exports.Dispersa = Dispersa;
9774
9609
  exports.android = android;